From bc6366402885fd43613a30d21b3c8929fb6d4d58 Mon Sep 17 00:00:00 2001 From: buchtioof <48603083+buchtioof@users.noreply.github.com> Date: Wed, 18 Feb 2026 22:07:15 +0100 Subject: [PATCH] v0.1 alfred commit init, should work normally ig idk fr fr --- alfred.sh | 104 + bin/inxi | 36684 ++++++++++++++++++++++++++++++++++++++++++++++++++++ bin/jq | Bin 0 -> 2255816 bytes 3 files changed, 36788 insertions(+) create mode 100755 alfred.sh create mode 100755 bin/inxi create mode 100755 bin/jq diff --git a/alfred.sh b/alfred.sh new file mode 100755 index 0000000..4160aa8 --- /dev/null +++ b/alfred.sh @@ -0,0 +1,104 @@ +########## MAIN VARIABLES ########## +DATE=$(date +'%Y-%m-%d_%H:%M:%S') +TARGET=${1:-localhost:8000} + +########## TEMPORARY WORKING REPERTORY ########## +export PATH="$(pwd)/bin:$PATH" + +############ HARDWARE FETCHER ################# +# --- CPU --- +CPU_MODEL=$(lscpu | grep "Model name:" | cut -d: -f2 | sed 's/^ *//') +_VENDOR=$(lscpu | grep "Vendor ID:" | cut -d: -f2 | xargs) +_FAMILY=$(lscpu | grep "CPU family:" | cut -d: -f2 | xargs) +_MODEL=$(lscpu | grep "Model:" | cut -d: -f2 | xargs) +CPU_ID="${_VENDOR} Fam ${_FAMILY} Mod ${_MODEL}" +CPU_FREQUENCY_MIN=$(lscpu | grep "CPU min MHz" | cut -d: -f2 | xargs | cut -d. -f1) +CPU_FREQUENCY_MAX=$(lscpu | grep "CPU max MHz" | cut -d: -f2 | xargs | cut -d. -f1) +CPU_FREQUENCY_CUR=$(grep "cpu MHz" /proc/cpuinfo | head -n1 | cut -d: -f2 | cut -d. -f1 | xargs) +CPU_CORES=$(lscpu | grep "^CPU(s):" | cut -d: -f2 | xargs) +CPU_THREADS=$(nproc) + +# --- RAM --- +# On récupère la RAM totale via free -h +RAM_TOTAL=$(free -h | awk '/^Mem:/ {print $2}') +# On récupère les slots via inxi +RAM_SLOTS=$(inxi -m -c 0 | grep "slots:" | head -n1 | sed -E 's/.*slots: ([0-9]+).*/\1/') +if [ -z "$RAM_SLOTS" ]; then RAM_SLOTS="N/A"; fi + +# --- MOTHERBOARD / GPU --- +MB_SERIAL=$(inxi -M -c 0 | grep "Mobo:" | sed -E 's/.*Mobo: (.*) model: (.*) serial: .*/\1 \2/' | xargs) +[ -z "$MB_SERIAL" ] && MB_SERIAL=$(inxi -M -c 0 | grep "Mobo:" | cut -d: -f2 | cut -d',' -f1 | xargs) +GPU_MODEL=$(inxi -G -c 0 | grep "Device-1:" | cut -d: -f2 | xargs) + +# --- STORAGE --- +# Calcul du stockage total +SIZES=$(lsblk -dnb | grep -v loop | grep -v boot | tr -s " " | cut -d \ -f4) +TOTAL_STORAGE=0 +for SIZE in ${SIZES[@]}; do TOTAL_STORAGE=$((TOTAL_STORAGE + SIZE)); done +TOTAL_STORAGE=$(numfmt --to iec $TOTAL_STORAGE) + +# --- SOFTWARE --- +OS=$(lsb_release -d 2>/dev/null | cut -f2 || grep PRETTY_NAME /etc/os-release | cut -d= -f2 | tr -d '"') +ARCH=$(uname -m) +KERNEL=$(uname -r) +HOSTNAME=$(hostname) +DEFAULT_IFACE=$(ls /sys/class/net | grep -vE '^(lo|docker|veth|br)' | head -n 1) +MAC_ADDRESS=$(cat "/sys/class/net/$DEFAULT_IFACE/address" 2>/dev/null || echo "Unknown-MAC") + +##### JSON PART ##### +json_pkg() { + json_data=$(jq -n \ + --arg motherboard "$MB_SERIAL" \ + --arg cpu_model "$CPU_MODEL" \ + --arg cpu_id "$CPU_ID" \ + --arg cpu_cores "$CPU_CORES" \ + --arg cpu_threads "$CPU_THREADS" \ + --arg cpu_frequency_min "$CPU_FREQUENCY_MIN" \ + --arg cpu_frequency_cur "$CPU_FREQUENCY_CUR" \ + --arg cpu_frequency_max "$CPU_FREQUENCY_MAX" \ + --arg gpu_model "$GPU_MODEL" \ + --arg ram_slots "$RAM_SLOTS" \ + --arg ram_total "$RAM_TOTAL" \ + --arg total_storage "$TOTAL_STORAGE" \ + --arg hostname "$HOSTNAME" \ + --arg mac_address "$MAC_ADDRESS" \ + --arg os "$OS" \ + --arg arch "$ARCH" \ + --arg desktop_env "${XDG_CURRENT_DESKTOP:-N/A}" \ + --arg window_manager "${XDG_SESSION_TYPE:-N/A}" \ + --arg kernel "$KERNEL" \ + '{ + HARDWARE: { + motherboard: $motherboard, + cpu_model: $cpu_model, + cpu_id: $cpu_id, + cpu_cores: $cpu_cores, + cpu_threads: $cpu_threads, + cpu_frequency_min: $cpu_frequency_min, + cpu_frequency_cur: $cpu_frequency_cur, + cpu_frequency_max: $cpu_frequency_max, + gpu_model: $gpu_model, + ram_slots: $ram_slots, + ram_total: $ram_total, + total_storage: $total_storage + }, + SOFTWARE: { + hostname: $hostname, + mac_address:$mac_address, + os: $os, + arch: $arch, + desktop_env: $desktop_env, + window_manager: $window_manager, + kernel: $kernel + } + }' + ) + + curl -X POST "http://$TARGET/endpoint" \ + -H "Content-Type: application/json" \ + -d "$json_data" \ + --connect-timeout 5 || echo "Erreur: Serveur $TARGET injoignable." + echo "" +} + +json_pkg \ No newline at end of file diff --git a/bin/inxi b/bin/inxi new file mode 100755 index 0000000..cb30e44 --- /dev/null +++ b/bin/inxi @@ -0,0 +1,36684 @@ +#!/usr/bin/env perl +## infobash: Copyright (C) 2005-2007 Michiel de Boer aka locsmif +## inxi: Copyright (C) 2008-2023 Harald Hope +## Additional features (C) Scott Rogers - kde, cpu info +## Parse::EDID (C): 2005-2010 by Mandriva SA, Pascal Rigaux, Anssi Hannula +## Further fixes (listed as known): Horst Tritremmel +## Steven Barrett (aka: damentz) - usb audio patch; swap percent used patch +## Jarett.Stevens - dmidecode -M patch for older systems without /sys machine +## +## License: GNU GPL v3 or greater +## +## You should have received a copy of the GNU General Public License +## along with this program. If not, see . +## +## If you don't understand what Free Software is, please read (or reread) +## this page: http://www.gnu.org/philosophy/free-sw.html +## +## DEVS: NOTE: geany/scite folding is picky. Leave 1 space after # or it breaks! + +use strict; +use warnings; +# use diagnostics; +use 5.008; + +## Perl 7 things for testing: depend on Perl 5.032 +# use 5.034; +# use compat::perl5; # act like Perl 5's defaults +# no feature qw(indirect); +# no multidimensional; +# no bareword::filehandles; + +use Cwd qw(abs_path); # #abs_path realpath getcwd +use Data::Dumper qw(Dumper); # print_r +$Data::Dumper::Sortkeys = 1; +# NOTE: load in SystemDebugger unless encounter issues with require/import +# use File::Find; +use File::stat; # needed for Xorg.0.log file mtime comparisons +use Getopt::Long qw(GetOptions); +# Note: default auto_abbrev is enabled +Getopt::Long::Configure ('bundling', 'no_ignore_case', +'no_getopt_compat', 'no_auto_abbrev','pass_through'); +use POSIX qw(ceil uname strftime ttyname); +# use bigint qw/hex/; # to handle large hex number warnings, but Perl 5.010 and later. +# use Benchmark qw(:all);_ +# use Devel::Size qw(size total_size); +# use feature qw(say state); # 5.10 or newer Perl + +### INITIALIZE VARIABLES ### + +## INXI INFO ## +my $self_name='inxi'; +my $self_version='3.3.31'; +my $self_date='2023-11-02'; +my $self_patch='00'; +## END INXI INFO ## + +my ($b_pledge,@pledges); +if (eval {require OpenBSD::Pledge}){ + OpenBSD::Pledge->import(); + $b_pledge = 1; + # cpath/wpath: dir/files .inxi, --debug > 9, -c 9x, -w/W; + # dns/inet: ftp upload --debug > 20; exec/proc/rpath: critical; + # prot_exec: Perl import; getpw: perl getpwuid() -c 9x, Net::FTP --debug > 20; + # stdio: default; error: debugging pledge/perl + # tested. not required: mcast pf ps recvfd sendfd tmppath tty unix vminfo; + # Pledge removal: OptionsHandler::post_process() [dns,inet,cpath,getpw,wpath]; + # SelectColors::set_selection() [getpw] + @pledges = qw(cpath dns exec getpw inet proc prot_exec rpath wpath); + pledge(@pledges); +} + +## Self data +my ($fake_data_dir,$self_path,$user_config_dir,$user_config_file,$user_data_dir); + +## Hashes +my (%alerts,%build_prop,%client,%colors,,%cpuinfo_machine,%disks_bsd, +%dboot,%devices,%dl,%dmmapper,%force,%loaded,%mapper,%program_values,%risc, +%service_tool,%show,%sysctl,%system_files,%usb,%windows); + +## System Arrays +my (@app,@cpuinfo,@dmi,@ifs,@ifs_bsd,@paths,@ps_aux,@ps_cmd,@ps_gui, +@sensors_exclude,@sensors_use,@uname); + +## Disk/Logical/Partition/RAID arrays +my (@btrfs_raid,@glabel,@labels,@lsblk,@lvm,@lvm_raid,@md_raid,@partitions, +@proc_partitions,@raw_logical,@soft_raid,@swaps,@uuids,@zfs_raid); + +## Debuggers +my %debugger = ('level' => 0); +my (@dbg,%fake,@t0); +my ($b_hires,$b_log,$b_log_colors,$b_log_full); +my ($end,$start,$fh_l,$log_file); # log file handle, file +my ($t1,$t2,$t3) = (0,0,0); # timers +## debug / temp tools +$debugger{'sys'} = 1; +$client{'test-konvi'} = 0; + +# NOTE: redhat removed HiRes from Perl Core Modules. +if (eval {require Time::HiRes}){ + Time::HiRes->import('gettimeofday','tv_interval','usleep'); + $b_hires = 1; +} +@t0 = eval 'Time::HiRes::gettimeofday()' if $b_hires; # let's start it right away + +## Booleans [busybox_ps not used actively] +my ($b_admin,$b_android,$b_busybox_ps,$b_display,$b_irc,$b_root); + +## System +my ($bsd_type,$device_vm,$language,$os,$pci_tool) = ('','','','',''); +my ($wan_url,$wl_compositors) = ('',''); +my ($bits_sys,$cpu_arch,$ppid); +my ($cpu_sleep,$dl_timeout,$limit,$ps_cols,$ps_count) = (0.35,4,10,0,5); +my $sensors_cpu_nu = 0; +my ($weather_source,$weather_unit) = (100,'mi'); + +## Tools +my ($display,$ftp_alt); +my ($display_opt,$sudoas) = ('',''); + +## Output +my $extra = 0;# supported values: 0-3 +my $filter_string = ''; +my $line1 = "----------------------------------------------------------------------\n"; +my $line2 = "======================================================================\n"; +my $line3 = "----------------------------------------\n"; +my ($output_file,$output_type) = ('','screen'); +my $prefix = 0; # for the primary row hash key prefix + +## Initialize internal hashes +# these assign a separator to non irc states. Important! Using ':' can +# trigger stupid emoticon. Note: SEP1/SEP2 from short form not used anymore. +# behaviors in output on IRC, so do not use those. +my %sep = ( +'s1-irc' => ':', +'s1-console' => ':', +'s2-irc' => '', +'s2-console' => ':', +); +#$show{'host'} = 1; +my %size = ( +'console' => 80, # In display, orig: 115 +# Default indentation level. NOTE: actual indent is 1 greater to allow for +# spacing +'indent' => 11, +'indents' => 2, +'irc' => 100, # shorter because IRC clients have nick lists etc +'lines' => 1, # for active output line counter for -Y +'max-cols' => 0, +'max-lines' => 0, +'max-wrap' => 110, +'no-display' => 100, # No Display, orig: 130 +# this will be set dynamically in set_display_size() +'term-cols' => 80, # orig: 80 +'term-lines' => 40, # orig: 100 +); +my %use = ( +'update' => 1, # switched off/on with maintainer config ALLOW_UPDATE +'weather' => 1, # switched off/on with maintainer config ALLOW_WEATHER +); + +######################################################################## +#### STARTUP +######################################################################## + +#### ------------------------------------------------------------------- +#### MAIN +#### ------------------------------------------------------------------- + +sub main { + # print Dumper \@ARGV; + eval $start if $b_log; + initialize(); + ## Uncomment these two values for start client debugging + # $debugger{'level'} = 3; # 3 prints timers / 10 prints to log file + # set_debugger(); # for debugging of konvi and other start client issues + ## legacy method + # my $ob_start = StartClient->new(); + #$ob_start->get_client_data(); + StartClient::set(); + # print_line(Dumper \%client); + OptionsHandler::get(); + set_debugger(); # right after so it's set + CheckTools::set(); + set_colors(); + set_sep(); + # print download_file('stdout','https://') . "\n"; + OutputGenerator::generate(); + eval $end if $b_log; + cleanup(); + # weechat's executor plugin forced me to do this, and rightfully so, + # because else the exit code from the last command is taken.. + exit 0; +} + +#### ------------------------------------------------------------------- +#### INITIALIZE +#### ------------------------------------------------------------------- + +sub initialize { + set_path(); + set_user_paths(); + set_basics(); + set_system_files(); + set_os(); + Configs::set(); + # set_downloader(); + set_display_size(); +} + +## CheckTools +{ +package CheckTools; +my (%commands); + +sub set { + eval $start if $b_log; + set_commands(); + my ($action,$program,$message,@data); + foreach my $test (keys %commands){ + ($action,$program) = ('use',''); + $message = main::message('tool-present'); + if ($commands{$test}->[1] && ( + ($commands{$test}->[1] eq 'linux' && $os ne 'linux') || + ($commands{$test}->[1] eq 'bsd' && $os eq 'linux'))){ + $action = 'platform'; + } + elsif ($program = main::check_program($test)){ + # > 0 means error in shell + # my $cmd = "$program $commands{$test} >/dev/null"; + # print "$cmd\n"; + $pci_tool = $test if $test =~ /pci/; + # this test is not ideal because other errors can make program fail, but + # we can't test for root since could be say, wheel permissions needed + if ($commands{$test}->[0] eq 'exec-sys'){ + $action = 'permissions' if system("$program $commands{$test}->[2] >/dev/null 2>&1"); + } + elsif ($commands{$test}->[0] eq 'exec-string'){ + @data = main::grabber("$program $commands{$test}->[2] 2>&1"); + # dmidecode errors are so specific it gets its own section + # also sets custom dmidecode error messages + if ($test eq 'dmidecode'){ + $action = set_dmidecode(\@data) if scalar @data < 15; + } + elsif (grep { $_ =~ /$commands{$test}->[3]/i } @data){ + $action = 'permissions'; + } + } + } + else { + $action = 'missing'; + } + $alerts{$test}->{'action'} = $action; + $alerts{$test}->{'path'} = $program; + if ($action eq 'missing'){ + $alerts{$test}->{'message'} = main::message('tool-missing-recommends',"$test"); + } + elsif ($action eq 'permissions'){ + $alerts{$test}->{'message'} = main::message('tool-permissions',"$test"); + } + elsif ($action eq 'platform'){ + $alerts{$test}->{'message'} = main::message('tool-missing-os', $uname[0] . " $test"); + } + } + print Data::Dumper::Dumper \%alerts if $dbg[25]; + set_fake_bsd_tools() if $fake{'bsd'}; + eval $end if $b_log; +} + +sub set_dmidecode { + my ($data) = @_; + my $action = 'use'; + if ($b_root){ + foreach (@$data){ + # don't need first line or scanning /dev/mem lines + if (/^(# dmi|Scanning)/){ + next; + } + elsif ($_ =~ /No SMBIOS/i){ + $action = 'smbios'; + last; + } + elsif ($_ =~ /^\/dev\/mem: Operation/i){ + $action = 'no-data'; + last; + } + else { + $action = 'unknown-error'; + last; + } + } + } + else { + if (grep {$_ =~ /(^\/dev\/mem: Permission|Permission denied)/i } @$data){ + $action = 'permissions'; + } + else { + $action = 'unknown-error'; + } + } + if ($action ne 'use' && $action ne 'permissions'){ + if ($action eq 'smbios'){ + $alerts{'dmidecode'}->{'message'} = main::message('dmidecode-smbios'); + } + elsif ($action eq 'no-data'){ + $alerts{'dmidecode'}->{'message'} = main::message('dmidecode-dev-mem'); + } + elsif ($action eq 'unknown-error'){ + $alerts{'dmidecode'}->{'message'} = main::message('tool-unknown-error','dmidecode'); + } + } + return $action; +} + +sub set_commands { + # note: gnu/linux has sysctl so it may be used that for something if present + # there is lspci for bsds so doesn't hurt to check it + if (!$bsd_type){ + if ($use{'pci'}){ + $commands{'lspci'} = ['exec-sys','','-n']; + } + if ($use{'logical'}){ + $commands{'lvs'} = ['exec-sys','','']; + } + } + else { + if ($use{'pci'}){ + $commands{'pciconf'} = ['exec-sys','','-l']; + $commands{'pcictl'} = ['exec-sys','',' pci0 list']; + $commands{'pcidump'} = ['exec-sys','','']; + } + if ($use{'sysctl'}){ + # note: there is a case of kernel.osrelease but it's a linux distro + $commands{'sysctl'} = ['exec-sys','','kern.osrelease']; + } + if ($use{'bsd-partition'}){ + $commands{'bioctl'} = ['missing','','']; + $commands{'disklabel'} = ['missing','','']; + $commands{'fdisk'} = ['missing','','']; + $commands{'gpart'} = ['missing','','']; + } + } + if ($use{'dmidecode'}){ + $commands{'dmidecode'} = ['exec-string','','-t chassis -t baseboard -t processor','']; + } + if ($use{'usb'}){ + # note: lsusb ships in FreeBSD ports sysutils/usbutils + $commands{'lsusb'} = ['missing','','','']; + # we want these set for various null bsd data tests + $commands{'usbconfig'} = ['exec-string','bsd','list','permissions']; + $commands{'usbdevs'} = ['missing','bsd','','']; + } + if ($show{'bluetooth'}){ + $commands{'bluetoothctl'} = ['missing','linux','','']; + # bt-adapter hangs when bluetooth service is disabled + $commands{'bt-adapter'} = ['missing','linux','','']; + # btmgmt enters its own shell with no options given + $commands{'btmgmt'} = ['missing','linux','','']; + $commands{'hciconfig'} = ['missing','linux','','']; + } + if ($show{'sensor'}){ + $commands{'sensors'} = ['missing','linux','','']; + } + if ($show{'ip'} || ($bsd_type && $show{'network-advanced'})){ + $commands{'ip'} = ['missing','linux','','']; + $commands{'ifconfig'} = ['missing','','','']; + } + # can't check permissions since we need to know the partition/disc + if ($use{'block-tool'}){ + $commands{'blockdev'} = ['missing','linux','','']; + $commands{'lsblk'} = ['missing','linux','','']; + } + if ($use{'btrfs'}){ + $commands{'btrfs'} = ['missing','linux','','']; + } + if ($use{'mdadm'}){ + $commands{'mdadm'} = ['missing','linux','','']; + } + if ($use{'smartctl'}){ + $commands{'smartctl'} = ['missing','','','']; + } + if ($show{'unmounted'}){ + $commands{'disklabel'} = ['missing','bsd','xx']; + } +} + +# only for dev/debugging BSD +sub set_fake_bsd_tools { + $system_files{'dmesg-boot'} = '/var/run/dmesg.boot' if $fake{'dboot'}; + $alerts{'sysctl'}->{'action'} = 'use' if $fake{'sysctl'}; + if ($fake{'pciconf'} || $fake{'pcictl'} || $fake{'pcidump'}){ + $alerts{'pciconf'}->{'action'} = 'use' if $fake{'pciconf'}; + $alerts{'pcictl'}->{'action'} = 'use' if $fake{'pcictl'}; + $alerts{'pcidump'}->{'action'} = 'use' if $fake{'pcidump'}; + $alerts{'lspci'} = { + 'action' => 'missing', + 'message' => 'Required program lspci not available', + }; + } + if ($fake{'usbconfig'} || $fake{'usbdevs'}){ + $alerts{'usbconfig'}->{'action'} = 'use' if $fake{'usbconfig'}; + $alerts{'usbdevs'}->{'action'} = 'use' if $fake{'usbdevs'}; + $alerts{'lsusb'} = { + 'action' => 'missing', + 'message' => 'Required program lsusb not available', + }; + } + if ($fake{'disklabel'}){ + $alerts{'disklabel'}->{'action'} = 'use'; + } +} +} + +sub set_basics { + ### LOCALIZATION - DO NOT CHANGE! ### + # set to default LANG to avoid locales errors with , or . + # Make sure every program speaks English. + $ENV{'LANG'}='C'; + $ENV{'LC_ALL'}='C'; + # remember, perl uses the opposite t/f return as shell!!! + # some versions of busybox do not have tty, like openwrt + $b_irc = (check_program('tty') && system('tty >/dev/null')) ? 1 : 0; + # print "birc: $b_irc\n"; + $b_display = ($ENV{'DISPLAY'}) ? 1 : 0; + $b_root = $< == 0; # root UID 0, all others > 0 + $dl{'dl'} = 'curl'; + $dl{'curl'} = 1; + $dl{'fetch'} = 1; + $dl{'tiny'} = 1; # note: two modules needed, tested for in set_downloader + $dl{'wget'} = 1; + $client{'console-irc'} = 0; + $client{'dcop'} = (check_program('dcop')) ? 1 : 0; + $client{'qdbus'} = (check_program('qdbus')) ? 1 : 0; + $client{'konvi'} = 0; + $client{'name'} = ''; + $client{'name-print'} = ''; + $client{'su-start'} = ''; # shows sudo/su + $client{'version'} = ''; + $client{'whoami'} = getpwuid($<) || ''; + $colors{'default'} = 2; + $show{'partition-sort'} = 'id'; # sort order for partitions + @raw_logical = (0,0,0); + $ppid = getppid(); +} + +sub set_display_size { + ## sometimes tput will trigger an error (mageia) if irc client + if (!$b_irc){ + if (my $program = check_program('tput')){ + # Arch urxvt: 'tput: unknown terminal "rxvt-unicode-256color"' + # trips error if use qx(); in FreeBSD, if you use 2>/dev/null + # it makes default value 80x24, who knows why? + chomp($size{'term-cols'} = qx{$program cols}); + chomp($size{'term-lines'} = qx{$program lines}); + } + # print "tc: $size{'term-cols'} cmc: $size{'console'}\n"; + # double check, just in case it's missing functionality or whatever + if (!is_int($size{'term-cols'} || $size{'term-cols'} == 0)){ + $size{'term-cols'} = 80; + } + if (!is_int($size{'term-lines'} || $size{'term-lines'} == 0)){ + $size{'term-lines'} = 24; + } + } + # this lets you set different size for in or out of display server + if (!$b_display && $size{'no-display'}){ + $size{'console'} = $size{'no-display'}; + } + # term_cols is set in top globals, using tput cols + # print "tc: $size{'term-cols'} cmc: $size{'console'}\n"; + if ($size{'term-cols'} < $size{'console'}){ + $size{'console'} = $size{'term-cols'}; + } + # adjust, some terminals will wrap if output cols == term cols + $size{'console'} = ($size{'console'} - 1); + # echo cmc: $size{'console'} + # comes after source for user set stuff + if (!$b_irc){ + $size{'max-cols'} = $size{'console'}; + } + else { + $size{'max-cols'} = $size{'irc'}; + } + # for -V/-h overrides + $size{'max-cols-basic'} = $size{'max-cols'}; + # print "tc: $size{'term-cols'} cmc: $size{'console'} cm: $size{'max-cols'}\n"; +} + +sub set_os { + @uname = uname(); + $os = lc($uname[0]); + $cpu_arch = lc($uname[-1]); + if ($cpu_arch =~ /arm|aarch/){ + $risc{'arm'} = 1; + $risc{'id'} = 'arm';} + elsif ($cpu_arch =~ /mips/){ + $risc{'mips'} = 1; + $risc{'id'} = 'mips';} + elsif ($cpu_arch =~ /power|ppc/){ + $risc{'ppc'} = 1; + $risc{'id'} = 'ppc';} + elsif ($cpu_arch =~ /riscv/){ + $risc{'riscv'} = 1; + $risc{'id'} = 'riscv';} + elsif ($cpu_arch =~ /(sparc|sun4[uv])/){ + $risc{'sparc'} = 1; + $risc{'id'} = 'sparc';} + # aarch32 mips32, i386. centaur/via/intel/amd handled in cpu + if ($cpu_arch =~ /(armv[1-7]|32|[23456]86)/){ + $bits_sys = 32; + } + elsif ($cpu_arch =~ /(alpha|64|e2k|sparc_v9|sun4[uv]|ultrasparc)/){ + $bits_sys = 64; + # force to string e2k, and also in case we need that ID changed + $cpu_arch = 'elbrus' if $cpu_arch =~ /e2k|elbrus/; + } + # set some less common scenarios + if ($os =~ /cygwin/){ + $windows{'cygwin'} = 1; + } + elsif (-e '/usr/lib/wsl/drivers'){ + $windows{'wsl'} = 1; + } + elsif (-e '/system/build.prop'){ + $b_android = 1; + } + if ($os =~ /(aix|bsd|cosix|dragonfly|darwin|hp-?ux|indiana|illumos|irix|sunos|solaris|ultrix|unix)/){ + if ($os =~ /openbsd/){ + $os = 'openbsd'; + } + elsif ($os =~ /darwin/){ + $os = 'darwin'; + } + # NOTE: most tests internally are against !$bsd_type + if ($os =~ /kfreebsd/){ + $bsd_type = 'debian-bsd'; + } + else { + $bsd_type = $os; + } + } +} + +# Sometimes users will have more PATHs local to their setup, so we want those +# too. +sub set_path { + # Extra path variable to make execute failures less likely, merged below + my (@path); + # NOTE: recent Xorg's show error if you try /usr/bin/Xorg -version but work + # if you use the /usr/lib/xorg-server/Xorg path. + my @test = qw(/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin + /usr/X11R6/bin); + foreach (@test){ + push(@paths,$_) if -d $_; + } + @path = split(':', $ENV{'PATH'}) if $ENV{'PATH'}; + # print "paths: @paths\nPATH: $ENV{'PATH'}\n"; + # Create a difference of $PATH and $extra_paths and add that to $PATH: + foreach my $id (@path){ + if (-d $id && !(grep {/^$id$/} @paths) && $id !~ /(game)/){ + push(@paths, $id); + } + } + # print "paths: \n", join("\n", @paths),"\n"; +} + +sub set_sep { + if ($b_irc){ + # too hard to read if no colors, so force that for users on irc + if ($colors{'scheme'} == 0){ + $sep{'s1'} = $sep{'s1-console'}; + $sep{'s2'} = $sep{'s2-console'}; + } + else { + $sep{'s1'} = $sep{'s1-irc'}; + $sep{'s2'} = $sep{'s2-irc'}; + } + } + else { + $sep{'s1'} = $sep{'s1-console'}; + $sep{'s2'} = $sep{'s2-console'}; + } +} + +# Important: -n makes it non interactive, no prompt for password +# only use doas/sudo if not root, -n option requires sudo -V 1.7 or greater. +# for some reason sudo -n with < 1.7 in Perl does not print to stderr +# sudo will just error out which is the safest course here for now, +# otherwise that interactive sudo password thing is too annoying +sub set_sudo { + if (!$b_root){ + my ($path); + if (!$force{'no-doas'} && ($path = check_program('doas'))){ + $sudoas = "$path -n "; + } + elsif (!$force{'no-sudo'} && ($path = check_program('sudo'))){ + my @data = program_data('sudo'); + $data[1] =~ s/^([0-9]+\.[0-9]+).*/$1/; + # print "sudo v: $data[1]\n"; + $sudoas = "$path -n " if is_numeric($data[1]) && $data[1] >= 1.7; + } + } +} + +sub set_system_files { + my %files = ( + 'asound-cards' => '/proc/asound/cards', + 'asound-modules' => '/proc/asound/modules', + 'asound-version' => '/proc/asound/version', + 'dmesg-boot' => '/var/run/dmesg.boot', + 'proc-cmdline' => '/proc/cmdline', + 'proc-cpuinfo' => '/proc/cpuinfo', + 'proc-mdstat' => '/proc/mdstat', + 'proc-meminfo' => '/proc/meminfo', + 'proc-modules' => '/proc/modules', # not used + 'proc-mounts' => '/proc/mounts',# not used + 'proc-partitions' => '/proc/partitions', + 'proc-scsi' => '/proc/scsi/scsi', + 'proc-version' => '/proc/version', + # note: 'xorg-log' is set in set_xorg_log() only if -G is triggered + ); + foreach (keys %files){ + $system_files{$_} = (-e $files{$_}) ? $files{$_} : ''; + } +} + +sub set_user_paths { + my ($b_conf,$b_data); + # this needs to be set here because various options call the parent + # initialize function directly. + $self_path = $0; + $self_path =~ s/[^\/]+$//; + # print "0: $0 sp: $self_path\n"; + if (defined $ENV{'XDG_CONFIG_HOME'} && $ENV{'XDG_CONFIG_HOME'}){ + $user_config_dir=$ENV{'XDG_CONFIG_HOME'}; + $b_conf=1; + } + elsif (-d "$ENV{'HOME'}/.config"){ + $user_config_dir="$ENV{'HOME'}/.config"; + $b_conf=1; + } + else { + $user_config_dir="$ENV{'HOME'}/.$self_name"; + } + if (defined $ENV{'XDG_DATA_HOME'} && $ENV{'XDG_DATA_HOME'}){ + $user_data_dir="$ENV{'XDG_DATA_HOME'}/$self_name"; + $b_data=1; + } + elsif (-d "$ENV{'HOME'}/.local/share"){ + $user_data_dir="$ENV{'HOME'}/.local/share/$self_name"; + $b_data=1; + } + else { + $user_data_dir="$ENV{'HOME'}/.$self_name"; + } + # note, this used to be created/checked in specific instance, but we'll just + # do it universally so it's done at script start. + if (! -d $user_data_dir){ + mkdir $user_data_dir; + # system "echo", "Made: $user_data_dir"; + } + if ($b_conf && -f "$ENV{'HOME'}/.$self_name/$self_name.conf"){ + # system 'mv', "-f $ENV{'HOME'}/.$self_name/$self_name.conf", $user_config_dir; + # print "WOULD: Moved $self_name.conf from $ENV{'HOME'}/.$self_name to $user_config_dir\n"; + } + if ($b_data && -d "$ENV{'HOME'}/.$self_name"){ + # system 'mv', '-f', "$ENV{'HOME'}/.$self_name/*", $user_data_dir; + # system 'rm', '-Rf', "$ENV{'HOME'}/.$self_name"; + # print "WOULD: Moved data dir $ENV{'HOME'}/.$self_name to $user_data_dir\n"; + } + $fake_data_dir = "$ENV{'HOME'}/bin/scripts/inxi/data"; + $log_file="$user_data_dir/$self_name.log"; + # system 'echo', "$ENV{'HOME'}/.$self_name/* $user_data_dir"; + # print "scd: $user_config_dir sdd: $user_data_dir \n"; +} + +sub set_xorg_log { + eval $start if $b_log; + my (@temp,@x_logs); + my ($file_holder,$time_holder,$x_mtime) = ('',0,0); + # NOTE: other variations may be /var/run/gdm3/... but not confirmed + # worry about we are just going to get all the Xorg logs we can find, + # and not which is 'right'. Xorg was XFree86 earlier, only in /var/log. + @temp = globber('/var/log/{Xorg,XFree86}.*.log'); + push(@x_logs, @temp) if @temp; + @temp = globber('/var/lib/gdm/.local/share/xorg/Xorg.*.log'); + push(@x_logs, @temp) if @temp; + @temp = globber($ENV{'HOME'} . '/.local/share/xorg/Xorg.*.log',); + push(@x_logs, @temp) if @temp; + # root will not have a /root/.local/share/xorg directory so need to use a + # user one if we can find one. + if ($b_root){ + @temp = globber('/home/*/.local/share/xorg/Xorg.*.log'); + push(@x_logs, @temp) if @temp; + } + foreach (@x_logs){ + if (-r $_){ + my $src_info = File::stat::stat("$_"); + # print "$_\n"; + if ($src_info){ + $x_mtime = $src_info->mtime; + # print $_ . ": $x_time" . "\n"; + if ($x_mtime > $time_holder){ + $time_holder = $x_mtime; + $file_holder = $_; + } + } + } + } + if (!$file_holder && check_program('xset')){ + my $data = qx(xset q 2>/dev/null); + foreach (split('\n', $data)){ + if ($_ =~ /Log file/i){ + $file_holder = get_piece($_,3); + last; + } + } + } + print "Xorg log file: $file_holder\nLast modified: $time_holder\n" if $dbg[14]; + log_data('data',"Xorg log file: $file_holder") if $b_log; + $system_files{'xorg-log'} = $file_holder; + eval $end if $b_log; +} + +######################################################################## +#### UTILITIES +######################################################################## + +#### ------------------------------------------------------------------- +#### COLORS +#### ------------------------------------------------------------------- + +## args: 0: the type of action, either integer, count, or full +sub get_color_scheme { + eval $start if $b_log; + my ($type) = @_; + my $color_schemes = [ + [qw(EMPTY EMPTY EMPTY)], + [qw(NORMAL NORMAL NORMAL)], + # for dark OR light backgrounds + [qw(BLUE NORMAL NORMAL)], + [qw(BLUE RED NORMAL)], + [qw(CYAN BLUE NORMAL)], + [qw(DCYAN NORMAL NORMAL)], + [qw(DCYAN BLUE NORMAL)], + [qw(DGREEN NORMAL NORMAL)], + [qw(DYELLOW NORMAL NORMAL)], + [qw(GREEN DGREEN NORMAL)], + [qw(GREEN NORMAL NORMAL)], + [qw(MAGENTA NORMAL NORMAL)], + [qw(RED NORMAL NORMAL)], + # for light backgrounds + [qw(BLACK DGREY NORMAL)], + [qw(DBLUE DGREY NORMAL)], + [qw(DBLUE DMAGENTA NORMAL)], + [qw(DBLUE DRED NORMAL)], + [qw(DBLUE BLACK NORMAL)], + [qw(DGREEN DYELLOW NORMAL)], + [qw(DYELLOW BLACK NORMAL)], + [qw(DMAGENTA BLACK NORMAL)], + [qw(DCYAN DBLUE NORMAL)], + # for dark backgrounds + [qw(WHITE GREY NORMAL)], + [qw(GREY WHITE NORMAL)], + [qw(CYAN GREY NORMAL)], + [qw(GREEN WHITE NORMAL)], + [qw(GREEN YELLOW NORMAL)], + [qw(YELLOW WHITE NORMAL)], + [qw(MAGENTA CYAN NORMAL)], + [qw(MAGENTA YELLOW NORMAL)], + [qw(RED CYAN NORMAL)], + [qw(RED WHITE NORMAL)], + [qw(BLUE WHITE NORMAL)], + # miscellaneous + [qw(RED BLUE NORMAL)], + [qw(RED DBLUE NORMAL)], + [qw(BLACK BLUE NORMAL)], + [qw(BLACK DBLUE NORMAL)], + [qw(NORMAL BLUE NORMAL)], + [qw(BLUE MAGENTA NORMAL)], + [qw(DBLUE MAGENTA NORMAL)], + [qw(BLACK MAGENTA NORMAL)], + [qw(MAGENTA BLUE NORMAL)], + [qw(MAGENTA DBLUE NORMAL)], + ]; + eval $end if $b_log; + if ($type eq 'count'){ + return scalar @$color_schemes; + } + if ($type eq 'full'){ + return $color_schemes; + } + else { + # print Dumper $color_schemes->[$type]; + return $color_schemes->[$type]; + } +} + +sub set_color_scheme { + eval $start if $b_log; + my ($scheme) = @_; + $colors{'scheme'} = $scheme; + my $index = ($b_irc) ? 1 : 0; # defaults to non irc + # NOTE: qw(...) kills the escape, it is NOT the same as using + # Literal "..", ".." despite docs saying it is. + my %color_palette = ( + 'EMPTY' => [ '', '' ], + 'DGREY' => [ "\e[1;30m", "\x0314" ], + 'BLACK' => [ "\e[0;30m", "\x0301" ], + 'RED' => [ "\e[1;31m", "\x0304" ], + 'DRED' => [ "\e[0;31m", "\x0305" ], + 'GREEN' => [ "\e[1;32m", "\x0309" ], + 'DGREEN' => [ "\e[0;32m", "\x0303" ], + 'YELLOW' => [ "\e[1;33m", "\x0308" ], + 'DYELLOW' => [ "\e[0;33m", "\x0307" ], + 'BLUE' => [ "\e[1;34m", "\x0312" ], + 'DBLUE' => [ "\e[0;34m", "\x0302" ], + 'MAGENTA' => [ "\e[1;35m", "\x0313" ], + 'DMAGENTA' => [ "\e[0;35m", "\x0306" ], + 'CYAN' => [ "\e[1;36m", "\x0311" ], + 'DCYAN' => [ "\e[0;36m", "\x0310" ], + 'WHITE' => [ "\e[1;37m", "\x0300" ], + 'GREY' => [ "\e[0;37m", "\x0315" ], + 'NORMAL' => [ "\e[0m", "\x03" ], + ); + my $color_scheme = get_color_scheme($colors{'scheme'}); + $colors{'c1'} = $color_palette{$color_scheme->[0]}[$index]; + $colors{'c2'} = $color_palette{$color_scheme->[1]}[$index]; + $colors{'cn'} = $color_palette{$color_scheme->[2]}[$index]; + # print Dumper \@scheme; + # print "$colors{'c1'}here$colors{'c2'} we are!$colors{'cn'}\n"; + eval $end if $b_log; +} + +sub set_colors { + eval $start if $b_log; + # it's already been set with -c 0-43 + if (exists $colors{'c1'}){ + return 1; + } + # This let's user pick their color scheme. For IRC, only shows the color + # schemes, no interactive. The override value only will be placed in user + # config files. /etc/inxi.conf can also override + if (exists $colors{'selector'}){ + my $ob_selector = SelectColors->new($colors{'selector'}); + $ob_selector->select_schema(); + return 1; + } + # set the default, then override as required + my $color_scheme = $colors{'default'}; + # these are set in user configs + if (defined $colors{'global'}){ + $color_scheme = $colors{'global'}; + } + else { + if ($b_irc){ + if (defined $colors{'irc-virt-term'} && $b_display && $client{'console-irc'}){ + $color_scheme = $colors{'irc-virt-term'}; + } + elsif (defined $colors{'irc-console'} && !$b_display){ + $color_scheme = $colors{'irc-console'}; + } + elsif (defined $colors{'irc-gui'}){ + $color_scheme = $colors{'irc-gui'}; + } + } + else { + if (defined $colors{'console'} && !$b_display){ + $color_scheme = $colors{'console'}; + } + elsif (defined $colors{'virt-term'}){ + $color_scheme = $colors{'virt-term'}; + } + } + } + # force 0 for | or > output, all others prints to irc or screen + if (!$b_irc && !$force{'colors'} && ! -t STDOUT){ + $color_scheme = 0; + } + set_color_scheme($color_scheme); + eval $end if $b_log; +} + +## SelectColors +{ +package SelectColors; +my (@data,%configs,%status); +my ($type,$w_fh); +my $safe_color_count = 12; # null/normal + default color group +my $count = 0; + +# args: 0: type +sub new { + my $class = shift; + ($type) = @_; + my $self = {}; + return bless $self, $class; +} + +sub select_schema { + eval $start if $b_log; + assign_selectors(); + main::set_color_scheme(0); + set_status(); + start_selector(); + create_color_selections(); + if (!$b_irc){ + Configs::check_file(); + get_selection(); + } + else { + print_irc_message(); + } + eval $end if $b_log; +} + +sub set_status { + $status{'console'} = (defined $colors{'console'}) ? "Set: $colors{'console'}" : 'Not Set'; + $status{'virt-term'} = (defined $colors{'virt-term'}) ? "Set: $colors{'virt-term'}" : 'Not Set'; + $status{'irc-console'} = (defined $colors{'irc-console'}) ? "Set: $colors{'irc-console'}" : 'Not Set'; + $status{'irc-gui'} = (defined $colors{'irc-gui'}) ? "Set: $colors{'irc-gui'}" : 'Not Set'; + $status{'irc-virt-term'} = (defined $colors{'irc-virt-term'}) ? "Set: $colors{'irc-virt-term'}" : 'Not Set'; + $status{'global'} = (defined $colors{'global'}) ? "Set: $colors{'global'}" : 'Not Set'; +} + +sub assign_selectors { + if ($type == 94){ + $configs{'variable'} = 'CONSOLE_COLOR_SCHEME'; + $configs{'selection'} = 'console'; + } + elsif ($type == 95){ + $configs{'variable'} = 'VIRT_TERM_COLOR_SCHEME'; + $configs{'selection'} = 'virt-term'; + } + elsif ($type == 96){ + $configs{'variable'} = 'IRC_COLOR_SCHEME'; + $configs{'selection'} = 'irc-gui'; + } + elsif ($type == 97){ + $configs{'variable'} = 'IRC_X_TERM_COLOR_SCHEME'; + $configs{'selection'} = 'irc-virt-term'; + } + elsif ($type == 98){ + $configs{'variable'} = 'IRC_CONS_COLOR_SCHEME'; + $configs{'selection'} = 'irc-console'; + } + elsif ($type == 99){ + $configs{'variable'} = 'GLOBAL_COLOR_SCHEME'; + $configs{'selection'} = 'global'; + } +} + +sub start_selector { + my $whoami = getpwuid($<) || "unknown???"; + if (!$b_irc){ + @data = ( + [ 0, '', '', "Welcome to $self_name! Please select the default + $configs{'selection'} color scheme."], + ); + } + push(@data, + [ 0, '', '', "Because there is no way to know your $configs{'selection'} + foreground/background colors, you can set your color preferences from + color scheme option list below:"], + [ 0, '', '', "0 is no colors; 1 is neutral."], + [ 0, '', '', "After these, there are 4 sets:"], + [ 0, '', '', "1-dark^or^light^backgrounds; 2-light^backgrounds; + 3-dark^backgrounds; 4-miscellaneous"], + [ 0, '', '', ""], + ); + if (!$b_irc){ + push(@data, + [ 0, '', '', "Please note that this will set the $configs{'selection'} + preferences only for user: $whoami"], + ); + } + push(@data, + [ 0, '', '', "$line1"], + ); + main::print_basic(\@data); + @data = (); +} + +sub create_color_selections { + my $spacer = '^^'; # printer removes double spaces, but replaces ^ with ' ' + $count = (main::get_color_scheme('count') - 1); + foreach my $i (0 .. $count){ + if ($i > 9){ + $spacer = '^'; + } + if ($configs{'selection'} =~ /^(global|irc-gui|irc-console|irc-virt-term)$/ && $i > $safe_color_count){ + last; + } + main::set_color_scheme($i); + push(@data, + [0, '', '', "$i)$spacer$colors{'c1'}Card:$colors{'c2'}^nVidia^GT218 + $colors{'c1'}Display^Server$colors{'c2'}^x11^(X.Org^1.7.7)$colors{'cn'}"], + ); + } + main::print_basic(\@data); + @data = (); + main::set_color_scheme(0); +} + +sub get_selection { + my $number = $count + 1; + @data = ( + [0, '', '', ($number++) . ")^Remove all color settings. Restore $self_name default."], + [0, '', '', ($number++) . ")^Continue, no changes or config file setting."], + [0, '', '', ($number++) . ")^Exit, use another terminal, or set manually."], + [0, '', '', "$line1"], + [0, '', '', "Simply type the number for the color scheme that looks best to your + eyes for your $configs{'selection'} settings and hit . NOTE: You can bring this + option list up by starting $self_name with option: -c plus one of these numbers:"], + [0, '', '', "94^-^console,^not^in^desktop^-^$status{'console'}"], + [0, '', '', "95^-^terminal,^desktop^-^$status{'virt-term'}"], + [0, '', '', "96^-^irc,^gui,^desktop^-^$status{'irc-gui'}"], + [0, '', '', "97^-^irc,^desktop,^in^terminal^-^$status{'irc-virt-term'}"], + [0, '', '', "98^-^irc,^not^in^desktop^-^$status{'irc-console'}"], + [0, '', '', "99^-^global^-^$status{'global'}"], + [0, '', '', ""], + [0, '', '', "Your selection(s) will be stored here: $user_config_file"], + [0, '', '', "Global overrides all individual color schemes. Individual + schemes remove the global setting."], + [0, '', '', "$line1"], + ); + main::print_basic(\@data); + @data = (); + chomp(my $response = ); + if (!main::is_int($response) || $response > ($count + 3)){ + @data = ( + [0, '', '', "Error - Invalid Selection. You entered this: $response. Hit to continue."], + [0, '', '', "$line1"], + ); + main::print_basic(\@data); + my $response = ; + start_selector(); + create_color_selections(); + get_selection(); + } + else { + process_selection($response); + } + if ($b_pledge){ + @pledges = grep {$_ ne 'getpw'} @pledges; + OpenBSD::Pledge::pledge(@pledges); + } +} + +sub process_selection { + my $response = shift; + if ($response == ($count + 3)){ + @data = ( + [0, '', '', "Ok, exiting $self_name now. You can set the colors later."], + ); + main::print_basic(\@data); + exit 0; + } + elsif ($response == ($count + 2)){ + @data = ( + [0, '', '', "Ok, continuing $self_name unchanged."], + [0, '', '', "$line1"], + ); + main::print_basic(\@data); + if (defined $colors{'console'} && !$b_display){ + main::set_color_scheme($colors{'console'}); + } + if (defined $colors{'virt-term'}){ + main::set_color_scheme($colors{'virt-term'}); + } + else { + main::set_color_scheme($colors{'default'}); + } + } + elsif ($response == ($count + 1)){ + @data = ( + [0, '', '', "Removing all color settings from config file now..."], + [0, '', '', "$line1"], + ); + main::print_basic(\@data); + delete_all_config_colors(); + main::set_color_scheme($colors{'default'}); + } + else { + main::set_color_scheme($response); + @data = ( + [0, '', '', "Updating config file for $configs{'selection'} color scheme now..."], + [0, '', '', "$line1"], + ); + main::print_basic(\@data); + if ($configs{'selection'} eq 'global'){ + delete_all_colors(); + } + else { + delete_global_color(); + } + set_config_color_scheme($response); + } +} + +sub delete_all_colors { + my @file_lines = main::reader($user_config_file); + open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!); + foreach (@file_lines){ + if ($_ !~ /^(CONSOLE_COLOR_SCHEME|GLOBAL_COLOR_SCHEME|IRC_COLOR_SCHEME|IRC_CONS_COLOR_SCHEME|IRC_X_TERM_COLOR_SCHEME|VIRT_TERM_COLOR_SCHEME)/){ + print {$w_fh} "$_"; + } + } + close $w_fh; +} + +sub delete_global_color { + my @file_lines = main::reader($user_config_file); + open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!); + foreach (@file_lines){ + if ($_ !~ /^GLOBAL_COLOR_SCHEME/){ + print {$w_fh} "$_"; + } + } + close $w_fh; +} + +sub set_config_color_scheme { + my $value = shift; + my @file_lines = main::reader($user_config_file); + my $b_found = 0; + open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!); + foreach (@file_lines){ + if ($_ =~ /^$configs{'variable'}/){ + $_ = "$configs{'variable'}=$value"; + $b_found = 1; + } + print $w_fh "$_\n"; + } + if (!$b_found){ + print $w_fh "$configs{'variable'}=$value\n"; + } + close $w_fh; +} + +sub print_irc_message { + @data = ( + [ 0, '', '', "$line1"], + [ 0, '', '', "After finding the scheme number you like, simply run this again + in a terminal to set the configuration data file for your irc client. You can + set color schemes for the following: start inxi with -c plus:"], + [ 0, '', '', "94 (console,^not^in^desktop^-^$status{'console'})"], + [ 0, '', '', "95 (terminal, desktop^-^$status{'virt-term'})"], + [ 0, '', '', "96 (irc,^gui,^desktop^-^$status{'irc-gui'})"], + [ 0, '', '', "97 (irc,^desktop,^in terminal^-^$status{'irc-virt-term'})"], + [ 0, '', '', "98 (irc,^not^in^desktop^-^$status{'irc-console'})"], + [ 0, '', '', "99 (global^-^$status{'global'})"] + ); + main::print_basic(\@data); + exit 0; +} +} + +#### ------------------------------------------------------------------- +#### CONFIGS +#### ------------------------------------------------------------------- + +## Configs +# public: set() check_file() +{ +package Configs; + +sub set { + my ($b_show) = @_; + my ($b_files,$key, $val,@config_files); + # removed legacy kde @$configs test which never worked + @config_files = ( + qq(/etc/$self_name.conf), + qq(/etc/$self_name.d/$self_name.conf), + qq($user_config_dir/$self_name.conf) + ); + # Config files should be passed in an array as a param to this function. + # Default intended use: global @CONFIGS; + foreach (@config_files){ + next unless open(my $fh, '<', "$_"); + my $b_configs; + $b_files = 1; + print "${line1}Configuration file: $_\n" if $b_show; + while (<$fh>){ + chomp; + s/#.*//; + s/^\s+//; + s/\s+$//; + s/'|"//g; + s/true/1/i; # switch to 1/0 perl boolean + s/false/0/i; # switch to 1/0 perl boolean + next unless length; + ($key, $val) = split(/\s*=\s*/, $_, 2); + next unless length($val); + if (!$b_show){ + process_item($key,$val); + } + else { + print $line3 if !$b_configs; + print "$key=$val\n"; + $b_configs = 1; + } + # print "f: $file key: $key val: $val\n"; + } + close $fh; + if ($b_show && !$b_configs){ + print "No configuration items found in file.\n"; + } + } + return $b_files if $b_show; +} + +sub show { + print "Showing current active/set configurations, by file. Last overrides previous.\n"; + my $b_files = set(1); + print $line1; + if ($b_files){ + print "All done! Everything look good? If not, fix it.\n"; + } + else { + print "No configuration files found. Is that what you expected?\n"; + } + exit 0; +} + +# note: someone managed to make a config file with corrupted values, so check +# int explicitly, don't assume it was done correctly. +# args: 0: key; 1: value +sub process_item { + my ($key,$val) = @_; + + ## UTILITIES ## + if ($key eq 'ALLOW_UPDATE' || $key eq 'B_ALLOW_UPDATE'){ + $use{'update'} = $val if main::is_int($val)} + elsif ($key eq 'ALLOW_WEATHER' || $key eq 'B_ALLOW_WEATHER'){ + $use{'weather'} = $val if main::is_int($val)} + elsif ($key eq 'CPU_SLEEP'){ + $cpu_sleep = $val if main::is_numeric($val)} + elsif ($key eq 'DL_TIMEOUT'){ + $dl_timeout = $val if main::is_int($val)} + elsif ($key eq 'DOWNLOADER'){ + if ($val =~ /^(curl|fetch|ftp|perl|wget)$/){ + # this dumps all the other data and resets %dl for only the + # desired downloader. + $val = main::set_perl_downloader($val); + %dl = ('dl' => $val, $val => 1); + }} + elsif ($key eq 'FAKE_DATA_DIR'){ + $fake_data_dir = $val} + elsif ($key eq 'FILTER_STRING'){ + $filter_string = $val} + elsif ($key eq 'LANGUAGE'){ + $language = $val if $val =~ /^(en)$/} + elsif ($key eq 'LIMIT'){ + $limit = $val if main::is_int($val)} + elsif ($key eq 'OUTPUT_TYPE'){ + $output_type = $val if $val =~ /^(json|screen|xml)$/} + elsif ($key eq 'NO_DIG'){ + $force{'no-dig'} = $val if main::is_int($val)} + elsif ($key eq 'NO_DOAS'){ + $force{'no-doas'} = $val if main::is_int($val)} + elsif ($key eq 'NO_HTML_WAN'){ + $force{'no-html-wan'} = $val if main::is_int($val)} + elsif ($key eq 'NO_SUDO'){ + $force{'no-sudo'} = $val if main::is_int($val)} + elsif ($key eq 'PARTITION_SORT'){ + if ($val =~ /^(dev-base|fs|id|label|percent-used|size|uuid|used)$/){ + $show{'partition-sort'} = $val; + }} + elsif ($key eq 'PS_COUNT'){ + $ps_count = $val if main::is_int($val) } + elsif ($key eq 'SENSORS_CPU_NO'){ + $sensors_cpu_nu = $val if main::is_int($val)} + elsif ($key eq 'SENSORS_EXCLUDE'){ + @sensors_exclude = split(/\s*,\s*/, $val) if $val} + elsif ($key eq 'SENSORS_USE'){ + @sensors_use = split(/\s*,\s*/, $val) if $val} + elsif ($key eq 'SHOW_HOST' || $key eq 'B_SHOW_HOST'){ + if (main::is_int($val)){ + $show{'host'} = $val; + $show{'no-host'} = 1 if !$show{'host'}; + } + } + elsif ($key eq 'USB_SYS'){ + $force{'usb-sys'} = $val if main::is_int($val)} + elsif ($key eq 'WAN_IP_URL'){ + if ($val =~ /^(ht|f)tp[s]?:\//i){ + $wan_url = $val; + $force{'no-dig'} = 1; + } + } + elsif ($key eq 'WEATHER_SOURCE'){ + $weather_source = $val if main::is_int($val)} + elsif ($key eq 'WEATHER_UNIT'){ + $val = lc($val) if $val; + if ($val && $val =~ /^(c|f|cf|fc|i|m|im|mi)$/){ + my %units = ('c'=>'m','f'=>'i','cf'=>'mi','fc'=>'im'); + $val = $units{$val} if defined $units{$val}; + $weather_unit = $val; + } + } + + ## COLORS/SEP ## + elsif ($key eq 'CONSOLE_COLOR_SCHEME'){ + $colors{'console'} = $val if main::is_int($val)} + elsif ($key eq 'GLOBAL_COLOR_SCHEME'){ + $colors{'global'} = $val if main::is_int($val)} + elsif ($key eq 'IRC_COLOR_SCHEME'){ + $colors{'irc-gui'} = $val if main::is_int($val)} + elsif ($key eq 'IRC_CONS_COLOR_SCHEME'){ + $colors{'irc-console'} = $val if main::is_int($val)} + elsif ($key eq 'IRC_X_TERM_COLOR_SCHEME'){ + $colors{'irc-virt-term'} = $val if main::is_int($val)} + elsif ($key eq 'VIRT_TERM_COLOR_SCHEME'){ + $colors{'virt-term'} = $val if main::is_int($val)} + # note: not using the old short SEP1/SEP2 + elsif ($key eq 'SEP1_IRC'){ + $sep{'s1-irc'} = $val} + elsif ($key eq 'SEP1_CONSOLE'){ + $sep{'s1-console'} = $val} + elsif ($key eq 'SEP2_IRC'){ + $sep{'s2-irc'} = $val} + elsif ($key eq 'SEP2_CONSOLE'){ + $sep{'s2-console'} = $val} + + ## SIZES ## + elsif ($key eq 'COLS_MAX_CONSOLE'){ + $size{'console'} = $val if main::is_int($val)} + elsif ($key eq 'COLS_MAX_IRC'){ + $size{'irc'} = $val if main::is_int($val)} + elsif ($key eq 'COLS_MAX_NO_DISPLAY'){ + $size{'no-display'} = $val if main::is_int($val)} + elsif ($key eq 'INDENT'){ + $size{'indent'} = $val if main::is_int($val)} + elsif ($key eq 'INDENTS'){ + $filter_string = $val if main::is_int($val)} + elsif ($key eq 'LINES_MAX'){ + if ($val =~ /^-?\d+$/ && $val >= -1){ + if ($val == 0){ + $size{'max-lines'} = $size{'term-lines'};} + elsif ($val == -1){ + $use{'output-block'} = 1;} + else { + $size{'max-lines'} = $val;} + }} + elsif ($key eq 'MAX_WRAP' || $key eq 'WRAP_MAX' || $key eq 'INDENT_MIN'){ + $size{'max-wrap'} = $val if main::is_int($val)} + # print "mc: key: $key val: $val\n"; + # print Dumper (keys %size) . "\n"; +} + +sub check_file { + $user_config_file = "$user_config_dir/$self_name.conf"; + if (! -f $user_config_file){ + open(my $fh, '>', $user_config_file) or + main::error_handler('create', $user_config_file, $!); + } +} +} + +#### ------------------------------------------------------------------- +#### DEBUGGERS +#### ------------------------------------------------------------------- + +# called in the initial -@ 10 program args setting so we can get logging +# as soon as possible # will have max 3 files, inxi.log, inxi.1.log, +# inxi.2.log +sub begin_logging { + return 1 if $fh_l; # if we want to start logging for testing before options + my $log_file_2="$user_data_dir/$self_name.1.log"; + my $log_file_3="$user_data_dir/$self_name.2.log"; + my $data = ''; + $end='main::log_data("fe", (caller(1))[3], "");'; + $start='main::log_data("fs", (caller(1))[3], \@_);'; + #$t3 = tv_interval ($t0, [gettimeofday]); + $t3 = eval 'Time::HiRes::tv_interval (\@t0, [Time::HiRes::gettimeofday()]);' if $b_hires; + # print Dumper $@; + my $now = strftime "%Y-%m-%d %H:%M:%S", localtime; + return if $debugger{'timers'}; + # do the rotation if logfile exists + if (-f $log_file){ + # copy if present second to third + if (-f $log_file_2){ + rename $log_file_2, $log_file_3 or error_handler('rename', "$log_file_2 -> $log_file_3", "$!"); + } + # then copy initial to second + rename $log_file, $log_file_2 or error_handler('rename', "$log_file -> $log_file_2", "$!"); + } + # now create the logfile + # print "Opening log file for reading: $log_file\n"; + open($fh_l, '>', $log_file) or error_handler(4, $log_file, "$!"); + # and echo the start data + $data = $line2; + $data .= "START $self_name LOGGING:\n"; + $data .= "NOTE: HiRes timer not available.\n" if !$b_hires; + $data .= "$now\n"; + $data .= "Elapsed since start: $t3\n"; + $data .= "n: $self_name v: $self_version p: $self_patch d: $self_date\n"; + $data .= '@paths:' . joiner(\@paths, '::', 'unset') . "\n"; + $data .= $line2; + + print $fh_l $data; +} + +# NOTE: no logging available until get_parameters is run, since that's what +# sets logging # in order to trigger earlier logging manually set $b_log +# to true in top variables. +# args: 0: type [fs|fe|cat|dump|raw]; 1: function name OR data to log; +# [2: function args OR hash/array ref] +sub log_data { + return if !$b_log; + my ($one, $two, $three) = @_; + my ($args,$data,$timer) = ('','',''); + my $spacer = ' '; + # print "1: $one 2: $two 3: $three\n"; + if ($one eq 'fs'){ + if (ref $three eq 'ARRAY'){ + # print Data::Dumper::Dumper $three; + $args = "\n${spacer}Args: " . joiner($three, '; ', 'unset'); + } + else { + $args = "\n${spacer}Args: None"; + } + # $t1 = [gettimeofday]; + #$t3 = tv_interval ($t0, [gettimeofday]); + $t3 = eval 'Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()])' if $b_hires; + # print Dumper $@; + $data = "Start: Function: $two$args\n${spacer}Elapsed: $t3\n"; + $spacer=''; + $timer = $data if $debugger{'timers'}; + } + elsif ($one eq 'fe'){ + # print 'timer:', Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()]),"\n"; + #$t3 = tv_interval ($t0, [gettimeofday]); + eval '$t3 = Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()])' if $b_hires; + # print Dumper $t3; + $data = "${spacer}Elapsed: $t3\nEnd: Function: $two\n"; + $spacer=''; + $timer = $data if $debugger{'timers'}; + } + elsif ($one eq 'cat'){ + if ($b_log_full){ + foreach my $file ($two){ + my $contents = do { local(@ARGV, $/) = $file; <> }; # or: qx(cat $file) + $data = "$data${line3}Full file data: $file\n\n$contents\n$line3\n"; + } + $spacer=''; + } + } + elsif ($one eq 'cmd'){ + $data = "Command: $two\n"; + $data .= qx($two); + } + elsif ($one eq 'data'){ + $data = "$two\n"; + } + elsif ($one eq 'dump'){ + $data = "$two:\n"; + if (ref $three eq 'HASH'){ + $data .= Data::Dumper::Dumper $three; + } + elsif (ref $three eq 'ARRAY'){ + # print Data::Dumper::Dumper $three; + $data .= Data::Dumper::Dumper $three; + } + else { + $data .= Data::Dumper::Dumper $three; + } + $data .= "\n"; + # print $data; + } + elsif ($one eq 'raw'){ + if ($b_log_full){ + $data = "\n${line3}Raw System Data:\n\n$two\n$line3"; + $spacer=''; + } + } + else { + $data = "$two\n"; + } + if ($debugger{'timers'}){ + print $timer if $timer; + } + # print "d: $data"; + elsif ($data){ + print $fh_l "$spacer$data"; + } +} + +sub set_debugger { + user_debug_test_1() if $debugger{'test-1'}; + if ($debugger{'level'} >= 20){ + error_handler('not-in-irc', 'debug data generator') if $b_irc; + my $option = ($debugger{'level'} > 22) ? 'main-full' : 'main'; + $debugger{'gz'} = 1 if ($debugger{'level'} == 22 || $debugger{'level'} == 24); + my $ob_sys = SystemDebugger->new($option); + $ob_sys->run_debugger(); + $ob_sys->upload_file($ftp_alt) if $debugger{'level'} > 20; + exit 0; + } + elsif ($debugger{'level'} >= 10 && $debugger{'level'} <= 12){ + $b_log = 1; + if ($debugger{'level'} == 11){ + $b_log_full = 1; + } + elsif ($debugger{'level'} == 12){ + $b_log_colors = 1; + } + begin_logging(); + } + elsif ($debugger{'level'} <= 3){ + if ($debugger{'level'} == 3){ + $b_log = 1; + $debugger{'timers'} = 1; + begin_logging(); + } + else { + $end = ''; + $start = ''; + } + } +} + +## SystemDebugger +{ +package SystemDebugger; +my $option = 'main'; +my ($data_dir,$debug_dir,$debug_gz,$parse_src,$upload) = ('','','','',''); +my @content; +my $b_debug = 0; +my $b_delete_dir = 1; + +# args: 0: type; 1: upload +sub new { + my $class = shift; + ($option) = @_; + my $self = {}; + # print "$f\n"; + # print "$option\n"; + return bless $self, $class; +} + +sub run_debugger { + print "Starting $self_name debugging data collector...\n"; + check_required_items(); + create_debug_directory(); + print "Note: for dmidecode, smartctl, lvm data you must be root.\n" if !$b_root; + print $line3; + if (!$b_debug){ + audio_data(); + bluetooth_data(); + disk_data(); + display_data(); + network_data(); + perl_modules(); + system_data(); + } + system_files(); + print $line3; + if (!$b_debug){ + # note: android has unreadable /sys, but -x and -r tests pass + # main::globber('/sys/*') && + if ($debugger{'sys'} && main::count_dir_files('/sys')){ + build_tree('sys'); + # kernel crash, not sure what creates it, for ppc, as root + if ($debugger{'sys'} && ($debugger{'sys-force'} || !$b_root || !$risc{'ppc'})){ + sys_traverse_data(); + } + } + else { + print "Skipping /sys data collection.\n"; + } + print $line3; + # note: proc has some files that are apparently kernel processes, I've tried + # filtering them out but more keep appearing, so only run proc debugger if not root + if (!$debugger{'no-proc'} && (!$b_root || $debugger{'proc'}) && -d '/proc' && main::count_dir_files('/proc')){ + build_tree('proc'); + proc_traverse_data(); + } + else { + print "Skipping /proc data collection.\n"; + } + print $line3; + } + run_self(); + print $line3; + compress_dir(); +} + +sub check_required_items { + print "Loading required debugger Perl File:: modules... \n"; + # Fedora/Redhat doesn't include File::Find File::Copy in + # core modules. why? Or rather, they deliberately removed them. + if (main::check_perl_module('File::Find')){ + File::Find->import; + } + else { + main::error_handler('required-module', 'File', 'File::Find'); + } + if (main::check_perl_module('File::Copy')){ + File::Copy->import; + } + else { + main::error_handler('required-module', 'File', 'File::Copy'); + } + if (main::check_perl_module('File::Spec::Functions')){ + File::Spec::Functions->import; + } + else { + main::error_handler('required-module', 'File', 'File::Spec::Functions'); + } + if ($debugger{'level'} > 20){ + if (main::check_perl_module('Net::FTP')){ + Net::FTP->import; + } + else { + main::error_handler('required-module', 'Net', 'Net::FTP'); + } + } + print "Checking basic core system programs exist... \n"; + if ($debugger{'level'} > 19){ + # astoundingly, rhel 9 and variants are shipping without tar in minimal install + if (!main::check_program('tar')){ + main::error_handler('required-program', 'tar', 'debugger'); + } + } +} + +sub create_debug_directory { + my $host = main::get_hostname(); + $host =~ s/ /-/g; + $host = 'no-host' if !$host || $host eq 'N/A'; + my ($alt_string,$root_string) = ('',''); + # note: Time::Piece was introduced in perl 5.9.5 + my ($sec,$min,$hour,$mday,$mon,$year) = localtime; + $year = $year+1900; + $mon += 1; + if (length($sec) == 1){$sec = "0$sec";} + if (length($min) == 1){$min = "0$min";} + if (length($hour) == 1){$hour = "0$hour";} + if (length($mon) == 1){$mon = "0$mon";} + if (length($mday) == 1){$mday = "0$mday";} + my $today = "$year-$mon-${mday}_$hour$min$sec"; + # my $date = strftime "-%Y-%m-%d_", localtime; + if ($b_root){ + $root_string = '-root'; + } + my $id = ($debugger{'id'}) ? '-' . $debugger{'id'}: ''; + $alt_string = '-' . uc($risc{'id'}) if %risc; + $alt_string .= "-BSD-$bsd_type" if $bsd_type; + $alt_string .= '-ANDROID' if $b_android; + $alt_string .= '-CYGWIN' if $windows{'cygwin'}; # could be windows arm? + $alt_string .= '-WSL' if $windows{'wsl'}; # could be windows arm? + $debug_dir = "$self_name$alt_string-$host$id-$today$root_string-$self_version-$self_patch"; + $debug_gz = "$debug_dir.tar.gz"; + $data_dir = "$user_data_dir/$debug_dir"; + if (-d $data_dir){ + unlink $data_dir or main::error_handler('remove', "$data_dir", "$!"); + } + mkdir $data_dir or main::error_handler('mkdir', "$data_dir", "$!"); + if (-e "$user_data_dir/$debug_gz"){ + #rmdir "$user_data_dir$debug_gz" or main::error_handler('remove', "$user_data_dir/$debug_gz", "$!"); + print "Failed removing leftover directory:\n$user_data_dir$debug_gz error: $?" if system('rm','-rf',"$user_data_dir$debug_gz"); + } + print "Debugger data going into:\n$data_dir\n"; +} + +sub compress_dir { + print "Creating tar.gz compressed file of this material...\n"; + print "File: $debug_gz\n"; + system("cd $user_data_dir; tar -czf $debug_gz $debug_dir"); + print "Removing $data_dir...\n"; + #rmdir $data_dir or print "failed removing: $data_dir error: $!\n"; + return 1 if !$b_delete_dir; + if (system('rm','-rf',$data_dir)){ + print "Failed removing: $data_dir\nError: $?\n"; + } + else { + print "Directory removed.\n"; + } +} + +# NOTE: incomplete, don't know how to ever find out +# what sound server is actually running, and is in control +sub audio_data { + my (%data,@files,@files2); + print "Collecting audio data...\n"; + my @cmds = ( + ['aplay', '--version'], # alsa + ['aplay', '-l'], # alsa devices + ['aplay', '-L'], # alsa list of features, can detect active sound server + ['artsd', '-v'], # aRts + ['esd', '-v'], # EsounD, to stderr + ['nasd', '-V'], # NAS + ['jackd', '--version'], # JACK + ['pactl', '--version'], # pulseaudio + ['pactl', 'info'], # pulseaudio, check if running as server: Server Name: + ['pactl', 'list'], # pulseaudio + ['pipewire', '--version'], # pipewire + ['pipewire-alsa', '--version'], # pipewire-alsa - just config files + ['pipewire-pulse', '--version'], # pipewire-pulse + ['pulseaudio', '--version'], # PulseAudio + ['pw-jack', '--version'], # pipewire-jack + ['pw-cli', 'ls'], # pipewire, check if running as server + ['pw-cli', 'info all'], + ); + run_commands(\@cmds,'audio'); + @files = main::globber('/proc/asound/card*/codec*'); + if (@files){ + my $asound = qx(head -n 1 /proc/asound/card*/codec* 2>&1); + $data{'proc-asound-codecs'} = $asound; + } + else { + $data{'proc-asound-codecs'} = undef; + } + write_data(\%data,'audio'); + @files = ( + '/proc/asound/cards', + '/proc/asound/version', + ); + @files2 = main::globber('/proc/asound/*/usbid'); + push(@files,@files2) if @files2; + copy_files(\@files,'audio'); +} + +sub bluetooth_data { + print "Collecting bluetooth data...\n"; + # no warnings 'uninitialized'; + my @cmds = ( + ['btmgmt','info'], + ['hciconfig','-a'], # no version + #['hcidump',''], # hangs sometimes + ['hcitool','dev'], + ['rfkill','--output-all'], + ); + # these hang if bluetoothd not enabled + if (@ps_cmd && (grep {m|/bluetoothd|} @ps_cmd)){ + push(@cmds, + ['bt-adapter','--list'], # no version + ['bt-adapter','--info'], + ['bluetoothctl','--version'], + ['bluetoothctl','--list'], + ['bluetoothctl','--show'] + ); + } + run_commands(\@cmds,'bluetooth'); +} + +## NOTE: >/dev/null 2>&1 is sh, and &>/dev/null is bash, fix this +# ls -w 1 /sysrs > tester 2>&1 +sub disk_data { + my (%data,@files,@files2); + print "Collecting dev, label, disk, uuid data, df...\n"; + @files = ( + '/etc/fstab', + '/etc/mtab', + '/proc/devices', + '/proc/mdstat', + '/proc/mounts', + '/proc/partitions', + '/proc/scsi/scsi', + '/proc/sys/dev/cdrom/info', + ); + # very old systems + if (-d '/proc/ide/'){ + my @ides = main::globber('/proc/ide/*/*'); + push(@files, @ides) if @ides; + } + else { + push(@files, '/proc-ide-directory'); + } + copy_files(\@files, 'disk'); + my @cmds = ( + ['blockdev', '--version'], + ['blockdev', '--report'], + ['btrfs', 'fi show'], # no version + ['btrfs', 'filesystem show'], + ['btrfs', 'filesystem show --mounted'], + # ['btrfs', 'filesystem show --all-devices'], + ['df', '-h -T'], # no need for version, and bsd doesn't have its + ['df', '-h'], + ['df', '-k'], + ['df', '-k -P'], + ['df', '-k -T'], + ['df', '-k -T -P'], + ['df', '-k -T -P -a'], + ['df', '-P'], + ['dmsetup', 'ls --tree'], + ['findmnt', ''], + ['findmnt', '--df --no-truncate'], + ['findmnt', '--list --no-truncate'], + ['gpart', 'list'], # no version + ['gpart', 'show'], + ['gpart', 'status'], + ['ls', '-l /dev'],# core util, don't need version + # block is for mmcblk / arm devices + ['ls', '-l /dev/block'], + ['ls', '-l /dev/block/bootdevice'], + ['ls', '-l /dev/block/bootdevice/by-name'], + ['ls', '-l /dev/disk'], + ['ls', '-l /dev/disk/by-id'], + ['ls', '-l /dev/disk/by-label'], + ['ls', '-l /dev/disk/by-partlabel'], + ['ls', '-l /dev/disk/by-partuuid'], + ['ls', '-l /dev/disk/by-path'], + ['ls', '-l /dev/disk/by-uuid'], + # http://comments.gmane.org/gmane.linux.file-systems.zfs.user/2032 + ['ls', '-l /dev/disk/by-wwn'], + ['ls', '-l /dev/mapper'], + ['lsblk', '--version'], # important since lsblk has been changing output + ['lsblk', '-fs'], + ['lsblk', '-fsr'], + ['lsblk', '-fsP'], + ['lsblk', '-a'], + ['lsblk', '-aP'], + ['lsblk', '-ar'], + ['lsblk', '-p'], + ['lsblk', '-pr'], + ['lsblk', '-pP'], + ['lsblk', '-r'], + ['lsblk', '-r --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'], + ['lsblk', '-rb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'], + ['lsblk', '-rb --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,MAJ:MIN,PKNAME'], + ['lsblk', '-Pb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE'], + ['lsblk', '-Pb --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'], + # this should always be the live command used internally: + ['lsblk', '-bP --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,MAJ:MIN,PKNAME'], + ['lvdisplay', '--version'], + ['lvdisplay', '-c'], + ['lvdisplay', '-cv'], + ['lvdisplay', '-cv --segments'], + ['lvdisplay', '-m --segments'], + ['lvdisplay', '-ma --segments'], + ['lvs', '--version'], + ['lvs', '--separator :'], + ['lvs', '--separator : --segments'], + ['lvs', '-o +devices --separator : --segments'], + ['lvs', '-o +devices -v --separator : --segments'], + ['lvs', '-o +devices -av --separator : --segments'], + ['lvs', '-o +devices -aPv --separator : --segments'], + # LSI raid https://hwraid.le-vert.net/wiki/LSIMegaRAIDSAS + ['megacli', '-AdpAllInfo -aAll'], # no version + ['megacli', '-LDInfo -L0 -a0'], + ['megacli', '-PDList -a0'], + ['megaclisas-status', ''], # no version + ['megaraidsas-status', ''], + ['megasasctl', ''], + ['mount', ''], + ['nvme', 'present'], # no version + ['pvdisplay', '--version'], + ['pvdisplay', '-c'], + ['pvdisplay', '-cv'], + ['pvdisplay', '-m'], + ['pvdisplay', '-ma'], + ['pvs', '--version'], + ['pvs', '--separator :'], + ['pvs', '--separator : --segments'], + ['pvs', '-a --separator : --segments'], + ['pvs', '-av --separator : --segments'], + ['pvs', '-aPv --separator : --segments -o +pv_major,pv_minor'], + ['pvs', '-v --separator : --segments'], + ['pvs', '-Pv --separator : --segments'], + ['pvs', '--segments -o pv_name,pv_size,seg_size,vg_name,lv_name,lv_size,seg_pe_ranges'], + ['readlink', '/dev/root'], # coreutils, don't need version + ['swapon', '-s'], # coreutils, don't need version + # 3ware-raid + ['tw-cli', 'info'], + ['vgdisplay', ''], + ['vgdisplay', '-v'], + ['vgdisplay', '-c'], + ['vgdisplay', '-vc'], + ['vgs', '--separator :'], # part of lvm, don't need version + ['vgs', '-av --separator :'], + ['vgs', '-aPv --separator :'], + ['vgs', '-v --separator :'], + ['vgs', '-o +pv_name --separator :'], + ['zfs', 'list'], + ['zpool', 'list'], # don't use version, might not be supported in linux + ['zpool', 'list -v'], + ); + run_commands(\@cmds,'disk'); + @cmds = ( + ['atacontrol', 'list'], + ['camcontrol', 'devlist'], + ['camcontrol', 'devlist -v'], + ['geom', 'part list'], + ['glabel', 'status'], + ['gpart', 'list'], # gpart in linux/bsd but do it here again + ['gpart', 'show'], + ['gpart', 'status'], + ['swapctl', '-l -k'], + ['swapctl', '-l -k'], + ['vmstat', ''], + ['vmstat', '-H'], + ); + run_commands(\@cmds,'disk-bsd'); +} + +sub display_data { + my (%data,@files,@files2); + my $working = ''; + if (!$b_display){ + print "Warning: only some of the data collection can occur if you are not in X\n"; + main::toucher("$data_dir/display-data-warning-user-not-in-x"); + } + if ($b_root){ + print "Warning: only some of the data collection can occur if you are running as Root user\n"; + main::toucher("$data_dir/display-data-warning-root-user"); + } + print "Collecting Xorg log and xorg.conf files...\n"; + if (-d "/etc/X11/xorg.conf.d/"){ + @files = main::globber("/etc/X11/xorg.conf.d/*"); + } + else { + @files = ('/xorg-conf-d'); + } + # keep this updated to handle all possible locations we know about for Xorg.0.log + # not using $system_files{'xorg-log'} for now though it would be best to know what file is used + main::set_xorg_log(); + push(@files, '/var/log/Xorg.0.log'); + push(@files, '/var/lib/gdm/.local/share/xorg/Xorg.0.log'); + push(@files, $ENV{'HOME'} . '/.local/share/xorg/Xorg.0.log'); + push(@files, $system_files{'xorg-log'}) if $system_files{'xorg-log'}; + push(@files, '/etc/X11/XFCconfig-4'); # very old format for xorg.conf + push(@files, '/etc/X11/xorg.conf'); + copy_files(\@files,'display-xorg'); + print "Collecting X, xprop, glxinfo, xrandr, xdpyinfo data, Wayland info...\n"; + %data = ( + 'desktop-session' => $ENV{'DESKTOP_SESSION'}, + 'display' => $ENV{'DISPLAY'}, + 'gdmsession' => $ENV{'GDMSESSION'}, + 'gnome-desktop-session-id' => $ENV{'GNOME_DESKTOP_SESSION_ID'}, + 'kde-full-session' => $ENV{'KDE_FULL_SESSION'}, + 'kde-session-version' => $ENV{'KDE_SESSION_VERSION'}, + 'vdpau-driver' => $ENV{'VDPAU_DRIVER'}, + 'xdg-current-desktop' => $ENV{'XDG_CURRENT_DESKTOP'}, + 'xdg-session-desktop' => $ENV{'XDG_SESSION_DESKTOP'}, + 'xdg-vtnr' => $ENV{'XDG_VTNR'}, + # wayland data collectors: + 'wayland-display' => $ENV{'WAYLAND_DISPLAY'}, + 'xdg-session-type' => $ENV{'XDG_SESSION_TYPE'}, + 'gdk-backend' => $ENV{'GDK_BACKEND'}, + 'qt-qpa-platform' => $ENV{'QT_QPA_PLATFORM'}, + 'clutter-backend' => $ENV{'CLUTTER_BACKEND'}, + 'sdl-videodriver' => $ENV{'SDL_VIDEODRIVER'}, + # program display values + 'size-cols-max' => $size{'max-cols'}, + 'size-indent' => $size{'indent'}, + 'size-lines-max' => $size{'max-lines'}, + 'size-wrap-width' => $size{'max-wrap'}, + ); + write_data(\%data,'display'); + my @cmds = ( + # kde 5/plasma desktop 5, this is maybe an extra package and won't be used + ['about-distro',''], + ['aticonfig','--adapter=all --od-gettemperature'], + ['clinfo',''], + ['clinfo','--list'], + ['clinfo','--raw'], # machine friendly + ['eglinfo',''], + ['eglinfo','-B'], + ['es2_info',''], + ['glxinfo',''], + ['glxinfo','-B'], + ['kded','--version'], + ['kded1','--version'], + ['kded2','--version'], + ['kded3','--version'], + ['kded4','--version'], + ['kded5','--version'], + ['kded6','--version'], + ['kded7','--version'], + ['kf-config','--version'], + ['kf4-config','--version'], + ['kf5-config','--version'], + ['kf6-config','--version'], + ['kf7-config','--version'], + ['kwin_x11','--version'], + # ['locate','/Xorg'], # for Xorg.wrap problem + ['loginctl','--no-pager list-sessions'], + ['ls','/sys/class/drm'], + ['nvidia-settings','-q screens'], + ['nvidia-settings','-c :0.0 -q all'], + ['nvidia-smi','-q'], + ['nvidia-smi','-q -x'], + ['plasmashell','--version'], + ['swaymsg','-t get_inputs -p'], + ['swaymsg','-t get_inputs -r'], + ['swaymsg','-t get_outputs -p'], + ['swaymsg','-t get_outputs -r'], + ['swaymsg','-t get_tree'], + ['swaymsg','-t get_workspaces -p'], + ['swaymsg','-t get_workspaces -r'], + ['vainfo',''], + ['vdpauinfo',''], + ['vulkaninfo',''], + ['vulkaninfo','--summary'], + # ['vulkaninfo','--json'], # outputs to file, not sure how to output to stdout + ['wayland-info',''], # not packaged as far as I know yet + ['weston-info',''], + ['wmctrl','-m'], + ['weston','--version'], + ['wlr-randr',''], + ['xdpyinfo',''], + ['xdriinfo',''], + ['Xorg','-version'], + ['xprop','-root'], + ['xrandr',''], + ['Xvesa','-version'], + ['Xvesa','-listmodes'], + ['Xwayland','-version'], + ); + run_commands(\@cmds,'display'); +} + +sub network_data { + print "Collecting networking data...\n"; + # no warnings 'uninitialized'; + my @cmds = ( + ['ifconfig',''], # no version maybe in bsd, --version in linux + ['ip','-Version'], + ['ip','addr'], + ['ip','-s link'], + ); + run_commands(\@cmds,'network'); +} + +sub perl_modules { + print "Collecting Perl module data (this can take a while)...\n"; + my @modules; + my ($dirname,$holder,$mods,$value) = ('','','',''); + my $filename = 'perl-modules.txt'; + my @inc; + foreach (sort @INC){ + # some BSD installs have '.' n @INC path + if (-d $_ && $_ ne '.'){ + $_ =~ s/\/$//; # just in case, trim off trailing slash + $value .= "EXISTS: $_\n"; + push(@inc, $_); + } + else { + $value .= "ABSENT: $_\n"; + } + } + main::writer("$data_dir/perl-inc-data.txt",$value); + File::Find::find({ wanted => sub { + push(@modules, File::Spec->canonpath($_)) if /\.pm\z/ + }, no_chdir => 1 }, @inc); + @modules = sort @modules; + foreach (@modules){ + my $dir = $_; + $dir =~ s/[^\/]+$//; + if (!$holder || $holder ne $dir){ + $holder = $dir; + $value = "DIR: $dir\n"; + $_ =~ s/^$dir//; + $value .= " $_\n"; + } + else { + $value = $_; + $value =~ s/^$dir//; + $value = " $value\n"; + } + $mods .= $value; + } + open(my $fh, '>', "$data_dir/$filename"); + print $fh $mods; + close $fh; +} + +sub system_data { + print "Collecting system data...\n"; + # has to run here because if null, error, list constructor throws fatal error + my $ksh = qx(ksh -c 'printf \%s "\$KSH_VERSION"' 2>/dev/null); + my %data = ( + 'cc' => $ENV{'CC'}, + # @(#)MIRBSD KSH R56 2018/03/09: ksh and mksh + 'ksh-version' => $ksh, # shell, not env, variable + 'manpath' => $ENV{'MANPATH'}, + 'path' => $ENV{'PATH'}, + 'shell' => $ENV{'SHELL'}, + 'xdg-config-home' => $ENV{'XDG_CONFIG_HOME'}, + 'xdg-config-dirs' => $ENV{'XDG_CONFIG_DIRS'}, + 'xdg-data-home' => $ENV{'XDG_DATA_HOME'}, + 'xdg-data-dirs' => $ENV{'XDG_DATA_DIRS'}, + ); + my @files = main::globber('/usr/bin/gcc*'); + if (@files){ + $data{'gcc-versions'} = join("\n", @files); + } + else { + $data{'gcc-versions'} = undef; + } + @files = main::globber('/sys/*'); + if (@files){ + $data{'sys-tree-ls-1-basic'} = join("\n", @files); + } + else { + $data{'sys-tree-ls-1-basic'} = undef; + } + write_data(\%data,'system'); + # bsd tools http://cb.vu/unixtoolbox.xhtml + my @cmds = ( + # general + ['sysctl', '-a'], + ['sysctl', '-b kern.geom.conftxt'], + ['sysctl', '-b kern.geom.confxml'], + ['usbdevs','-v'], + # freebsd + ['ofwdump','-a'], # arm / soc + ['ofwdump','-ar'], # arm / soc + ['pciconf','-l -cv'], + ['pciconf','-vl'], + ['pciconf','-l'], + ['usbconfig','dump_device_desc'], + ['usbconfig','list'], # needs root, sigh... why? + # openbsd + ['ofctl',''], # arm / soc, need to see data sample of this + ['pcidump',''], + ['pcidump','-v'], + # netbsd + ['kldstat',''], + ['pcictl','pci0 list'], + ['pcictl','pci0 list -N'], + ['pcictl','pci0 list -n'], + # sunos + ['prtdiag',''], + ['prtdiag','-v'], + ); + run_commands(\@cmds,'system-bsd'); + # diskinfo -v + # fdisk + @cmds = ( + ['clang','--version'], + # only for prospective ram feature data collection: requires i2c-tools and module eeprom loaded + ['decode-dimms',''], + ['dmidecode','--version'], + ['dmidecode',''], + ['dmesg',''], + ['gcc','--version'], + ['getconf','-a'], + ['getconf','-l'], # openbsd + ['initctl','list'], + ['ipmi-sensors','-V'], # version + ['ipmi-sensors',''], + ['ipmi-sensors','--output-sensor-thresholds'], + ['ipmitool','-V'],# version + ['ipmitool','sensor'], + ['lscpu',''],# part of util-linux + ['lsmem',''], + ['lsmem','--all'], + ['lspci','--version'], + ['lspci',''], + ['lspci','-k'], + ['lspci','-n'], + ['lspci','-nn'], + ['lspci','-nnk'], + ['lspci','-nnkv'],# returns ports + ['lspci','-nnv'], + ['lspci','-mm'], + ['lspci','-mmk'], + ['lspci','-mmkv'], + ['lspci','-mmv'], + ['lspci','-mmnn'], + ['lspci','-v'], + ['lsusb','--version'], + ['lsusb',''], + ['lsusb','-t'], + ['lsusb','-v'], + ['ps','aux'], + ['ps','-e'], + ['ps','-p 1'], + ['runlevel',''], + ['rc-status','-a'], + ['rc-status','-l'], + ['rc-status','-r'], + ['sensors','--version'], + ['sensors',''], + ['sensors','-j'], + ['sensors','-u'], + # leaving this commented out to remind that some systems do not + # support strings --version, but will just simply hang at that command + # which you can duplicate by simply typing: strings then hitting enter. + # ['strings','--version'], + ['strings','present'], + ['sysctl','-a'], + ['systemctl','--version'], + ['systemctl','get-default'], + ['systemctl','list-units'], + ['systemctl','list-units --type=target'], + ['systemd-detect-virt',''], + ['uname','-a'], + ['upower','-e'], + ['uptime',''], + ['vcgencmd','get_mem arm'], + ['vcgencmd','get_mem gpu'], + ); + run_commands(\@cmds,'system'); + my $glob = '/sys/devices/system/cpu/'; + $glob .= '{cpufreq,cpu*/topology,cpu*/cpufreq,cpu*/cache/index*,smt,'; + $glob .= 'vulnerabilities}/*'; + get_glob('sys','cpu',$glob); + @files = main::globber('/dev/bus/usb/*/*'); + copy_files(\@files, 'system'); +} + +sub system_files { + print "Collecting system files data...\n"; + my (%data,@files,@files2); + @files = RepoItem::get($data_dir); + copy_files(\@files, 'repo'); + # chdir "/etc"; + @files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*'); + push(@files, '/etc/issue',' + /etc/lsb-release', + '/etc/os-release', + '/system/build.prop', # android data file, requires rooted + '/var/log/installer/oem-id'); # ubuntu only for oem installs? + copy_files(\@files,'system-distro'); + @files = main::globber('/etc/upstream[-_]{[rR]elease,[vV]ersion}/*'); + copy_files(\@files,'system-distro'); + @files = main::globber('/etc/calamares/branding/*/branding.desc'); + copy_files(\@files,'system-distro'); + @files = ( + '/etc/systemd/system/default.target', + '/proc/1/comm', + '/proc/cmdline', + '/proc/cpuinfo', + '/proc/iomem', + '/proc/meminfo', + '/proc/modules', + '/proc/net/arp', + '/proc/version', + ); + @files2=main::globber('/sys/class/power_supply/*/uevent'); + if (@files2){ + push(@files,@files2); + } + else { + push(@files, '/sys-class-power-supply-empty'); + } + copy_files(\@files, 'system'); + @files = ( + '/etc/make.conf', + '/etc/src.conf', + '/var/run/dmesg.boot', + ); + copy_files(\@files,'system-bsd'); + @files = main::globber('/sys/devices/system/cpu/vulnerabilities/*'); + copy_files(\@files,'security'); +} + +## SELF EXECUTE FOR LOG/OUTPUT +sub run_self { + print "Creating $self_name output file now. This can take a few seconds...\n"; + print "Starting $self_name from: $self_path\n"; + my $args = '-FERfJLrploudma --slots --pkg --edid'; + my $a = ($debugger{'arg'}) ? ' ' . $debugger{'arg'} : ''; + my $i = ($option eq 'main-full')? ' -i' : ''; + my $z = ($debugger{'filter'}) ? ' -z' : ''; + my $w = ($debugger{'width'}) ? $debugger{'width'} : 120; + $args = $debugger{'arg-use'} if $debugger{'arg-use'}; + $args = "$args$a$i$z --debug 10 -y $w"; + my $arg_string = $args; + $arg_string =~ s/\s//g; + my $self_file = "$data_dir/$self_name$arg_string.txt"; + my $cmd = "$self_path/$self_name $args > $self_file 2>&1"; + # print "Args: $args\nArg String: $arg_string\n";exit; + system($cmd); + copy($log_file, "$data_dir") or main::error_handler('copy-failed', "$log_file", "$!"); + system("$self_path/$self_name --recommends -y 120 > $data_dir/$self_name-recommends-120.txt 2>&1"); +} + +## UTILITIES COPY/CMD/WRITE +sub copy_files { + my ($files_ref,$type,$alt_dir) = @_; + my ($absent,$error,$good,$name,$unreadable); + my $directory = ($alt_dir) ? $alt_dir : $data_dir; + my $working = ($type ne 'proc') ? "$type-file-": ''; + foreach (@$files_ref){ + $name = $_; + $name =~ s/^\///; + $name =~ s/\//~/g; + # print "$name\n" if $type eq 'proc'; + $name = "$directory/$working$name"; + $good = $name . '.txt'; + $absent = $name . '-absent'; + $error = $name . '-error'; + $unreadable = $name . '-unreadable'; + # proc have already been tested for readable/exists + if ($type eq 'proc' || -e $_){ + print "F:$_\n" if $type eq 'proc' && $debugger{'proc-print'}; + if ($type eq 'proc' || -r $_){ + copy($_,"$good") or main::toucher($error); + } + else { + main::toucher($unreadable); + } + } + else { + main::toucher($absent); + } + } +} + +sub run_commands { + my ($cmds,$type) = @_; + my $holder = ''; + my ($name,$cmd,$args); + foreach my $rows (@$cmds){ + if (my $program = main::check_program($rows->[0])){ + if ($rows->[1] eq 'present'){ + $name = "$data_dir/$type-cmd-$rows->[0]-present"; + main::toucher($name); + } + else { + $args = $rows->[1]; + $args =~ s/\s|--|\/|=/-/g; # for: + $args =~ s/--/-/g;# strip out -- that result from the above + $args =~ s/^-//g; + $args = "-$args" if $args; + $name = "$data_dir/$type-cmd-$rows->[0]$args.txt"; + $cmd = "$program $rows->[1] >$name 2>&1"; + system($cmd); + } + } + else { + if ($holder ne $rows->[0]){ + $name = "$data_dir/$type-cmd-$rows->[0]-absent"; + main::toucher($name); + $holder = $rows->[0]; + } + } + } +} + +sub get_glob { + my ($type,$id,$glob) = @_; + my @files = main::globber($glob); + return if !@files; + my ($item,@result); + foreach (sort @files){ + next if -d $_; + if (-r $_) { + $item = main::reader($_,'strip',0); + } + else { + $item = main::message('root-required'); + } + $item = main::message('undefined') if !defined $item; + push(@result,$_ . '::' . $item); + } + # print Data::Dumper::Dumper \@result; + main::writer("$data_dir/$type-data-$id-glob.txt",\@result); +} + +sub write_data { + my ($data_ref, $type) = @_; + my ($empty,$error,$fh,$good,$name,$undefined,$value); + foreach (keys %$data_ref){ + $value = $data_ref->{$_}; + $name = "$data_dir/$type-data-$_"; + $good = $name . '.txt'; + $empty = $name . '-empty'; + $error = $name . '-error'; + $undefined = $name . '-undefined'; + if (defined $value){ + if ($value || $value eq '0'){ + open($fh, '>', $good) or main::toucher($error); + print $fh "$value"; + } + else { + main::toucher($empty); + } + } + else { + main::toucher($undefined); + } + } +} + +## TOOLS FOR DIRECTORY TREE/LS/TRAVERSE; UPLOADER +sub build_tree { + my ($which) = @_; + if ($which eq 'sys' && main::check_program('tree')){ + print "Constructing /$which tree data...\n"; + my $dirname = '/sys'; + my $cmd; + system("tree -a -L 10 /sys > $data_dir/sys-data-tree-full-10.txt"); + opendir(my $dh, $dirname) or main::error_handler('open-dir',"$dirname", "$!"); + my @files = readdir($dh); + closedir $dh; + foreach (@files){ + next if /^\./; + $cmd = "tree -a -L 10 $dirname/$_ > $data_dir/sys-data-tree-$_-10.txt"; + # print "$cmd\n"; + system($cmd); + } + } + print "Constructing /$which ls data...\n"; + if ($which eq 'sys'){ + directory_ls($which,1); + directory_ls($which,2); + directory_ls($which,3); + directory_ls($which,4); + } + elsif ($which eq 'proc'){ + directory_ls('proc',1); + directory_ls('proc',2,'[a-z]'); + # don't want the /proc/self or /proc/thread-self directories, those are + # too invasive + #directory_ls('proc',3,'[a-z]'); + #directory_ls('proc',4,'[a-z]'); + } +} + +# include is basic regex for ls path syntax, like [a-z] +sub directory_ls { + my ($dir,$depth,$include) = @_; + $include ||= ''; + my ($exclude) = (''); + # we do NOT want to see anything in self or thread-self!! + # $exclude = 'I self -I thread-self' if $dir eq 'proc'; + my $cmd = do { + if ($depth == 1){ "ls -l $exclude /$dir/$include 2>/dev/null" } + elsif ($depth == 2){ "ls -l $exclude /$dir/$include*/ 2>/dev/null" } + elsif ($depth == 3){ "ls -l $exclude /$dir/$include*/*/ 2>/dev/null" } + elsif ($depth == 4){ "ls -l $exclude /$dir/$include*/*/*/ 2>/dev/null" } + elsif ($depth == 5){ "ls -l $exclude /$dir/$include*/*/*/*/ 2>/dev/null" } + elsif ($depth == 6){ "ls -l $exclude /$dir/$include*/*/*/*/*/ 2>/dev/null" } + }; + my @working; + my $output = ''; + my ($type); + my $result = qx($cmd); + open(my $ch, '<', \$result) or main::error_handler('open-data',"$cmd", "$!"); + while (my $line = <$ch>){ + chomp($line); + $line =~ s/^\s+|\s+$//g; + @working = split(/\s+/, $line); + $working[0] ||= ''; + if (scalar @working > 7){ + if ($working[0] =~ /^d/){ + $type = "d - "; + } + elsif ($working[0] =~ /^l/){ + $type = "l - "; + } + elsif ($working[0] =~ /^c/){ + $type = "c - "; + } + else { + $type = "f - "; + } + $working[9] ||= ''; + $working[10] ||= ''; + $output = $output . " $type$working[8] $working[9] $working[10]\n"; + } + elsif ($working[0] !~ /^total/){ + $output = $output . $line . "\n"; + } + } + close $ch; + my $file = "$data_dir/$dir-data-ls-$depth.txt"; + open(my $fh, '>', $file) or main::error_handler('create',"$file", "$!"); + print $fh $output; + close $fh; + # print "$output\n"; +} + +sub proc_traverse_data { + print "Building /proc file list...\n"; + # get rid pointless error:Can't cd to (/sys/kernel/) debug: Permission denied + #no warnings 'File::Find'; + no warnings; + $parse_src = 'proc'; + File::Find::find(\&wanted, "/proc"); + process_proc_traverse(); + @content = (); +} + +sub process_proc_traverse { + my ($data,$fh,$result,$row,$sep); + my $proc_dir = "$data_dir/proc"; + print "Adding /proc files...\n"; + mkdir $proc_dir or main::error_handler('mkdir', "$proc_dir", "$!"); + # @content = sort @content; + copy_files(\@content,'proc',$proc_dir); + # foreach (@content){print "$_\n";} +} + +sub sys_traverse_data { + print "Building /sys file list...\n"; + # get rid pointless error:Can't cd to (/sys/kernel/) debug: Permission denied + #no warnings 'File::Find'; + no warnings; + $parse_src = 'sys'; + File::Find::find(\&wanted, "/sys"); + process_sys_traverse(); + @content = (); +} + +sub process_sys_traverse { + my ($data,$fh,$result,$row,$sep); + my $filename = "sys-data-parse.txt"; + print "Parsing /sys files...\n"; + # no sorts, we want the order it comes in + # @content = sort @content; + foreach (@content){ + $data=''; + $sep=''; + my $b_fh = 1; + print "F:$_\n" if $debugger{'sys-print'}; + open($fh, '<', $_) or $b_fh = 0; + # needed for removing -T test and root + if ($b_fh){ + while ($row = <$fh>){ + chomp($row); + $data .= $sep . '"' . $row . '"'; + $sep=', '; + } + } + else { + $data = ''; + } + $result .= "$_:[$data]\n"; + # print "$_:[$data]\n" + } + # print scalar @content . "\n"; + open($fh, '>', "$data_dir/$filename"); + print $fh $result; + close $fh; + # print $fh "$result"; +} + +# perl compiler complains on start if prune = 1 used only once, so either +# do $File::Find::prune = 1 if !$File::Find::prune; OR use no warnings 'once' +sub wanted { + # note: we want these directories pruned before the -d test so find + # doesn't try to read files inside of the directories + if ($parse_src eq 'proc'){ + if ($File::Find::name =~ m!^/proc/[0-9]+! || + # /proc/registry is from cygwin, we never want to see that + $File::Find::name =~ m!^/proc/(irq|spl|sys|reg)! || + # these choke on sudo/root: kmsg kcore kpage and we don't want keys or kallsyms + $File::Find::name =~ m!^/proc/k! || + $File::Find::name =~ m!^/proc/bus/pci!){ + $File::Find::prune = 1; + return; + } + } + elsif ($parse_src eq 'sys'){ + # note: a new file in 4.11 /sys can hang this, it is /parameter/ then + # a few variables. Since inxi does not need to see that file, we will + # not use it. + if ($File::Find::name =~ m!/(kernel/|trace/|parameters|debug)!){ + $File::Find::prune = 1; + } + } + return if -d; # not directory + return unless -e; # Must exist + return unless -f; # Must be file + return unless -r; # Must be readable + if ($parse_src eq 'sys'){ + # print $File::Find::name . "\n"; + # block maybe: cfgroup\/ + # picdec\/|, wait_for_fb_sleep/wake is an odroid thing caused hang + # wakeup_count also fails for android, but works fine on regular systems + return if $risc{'arm'} && $File::Find::name =~ m!^/sys/power/(wait_for_fb_|wakeup_count$)!; + # do not need . files or __ starting files + return if $File::Find::name =~ m!/\.[a-z]!; + # pp_num_states: amdgpu driver bug; android: wakeup_count + return if $File::Find::name =~ m!/pp_num_states$!; + # comment this one out if you experience hangs or if + # we discover syntax of foreign language characters + # Must be ascii like. This is questionable and might require further + # investigation, it is removing some characters that we might want + # NOTE: this made a bunch of files on arm systems unreadable so we handle + # the readable tests in copy_files() + # return unless -T; + } + elsif ($parse_src eq 'proc'){ + return if $File::Find::name =~ m!(/mb_groups|debug)$!; + } + # print $File::Find::name . "\n"; + push(@content, $File::Find::name); + return; +} + +# args: 0: path to file to be uploaded; 1: optional: alternate ftp upload url +# NOTE: must be in format: ftp.site.com/incoming +sub upload_file { + my ($self, $ftp_url) = @_; + my ($ftp, $domain, $host, $user, $pass, $dir, $error); + $ftp_url ||= main::get_defaults('ftp-upload'); + $ftp_url =~ s/\/$//g; # trim off trailing slash if present + my @url = split('/', $ftp_url); + my $file_path = "$user_data_dir/$debug_gz"; + $host = $url[0]; + $dir = $url[1]; + $domain = $host; + $domain =~ s/^ftp\.//; + $user = "anonymous"; + $pass = "anonymous\@$domain"; + print $line3; + print "Uploading to: $ftp_url\n"; + # print "$host $domain $dir $user $pass\n"; + print "File to be uploaded:\n$file_path\n"; + if ($host && ($file_path && -e $file_path)){ + # NOTE: important: must explicitly set to passive true/1 + $ftp = Net::FTP->new($host, Debug => 0, Passive => 1) || main::error_handler('ftp-connect', $ftp->message); + $ftp->login($user, $pass) || main::error_handler('ftp-login', $ftp->message); + $ftp->binary(); + $ftp->cwd($dir); + print "Connected to FTP server.\n"; + $ftp->put($file_path) || main::error_handler('ftp-upload', $ftp->message); + $ftp->quit; + print "Uploaded file successfully!\n"; + print $ftp->message; + if ($debugger{'gz'}){ + print "Removing debugger gz file:\n$file_path\n"; + unlink $file_path or main::error_handler('remove',"$file_path", "$!"); + print "File removed.\n"; + } + print "Debugger data generation and upload completed. Thank you for your help.\n"; + } + else { + main::error_handler('ftp-bad-path', "$file_path"); + } +} +} + +# random tests for various issues +sub user_debug_test_1 { +# open(my $duped, '>&', STDOUT); +# local *STDOUT = $duped; +# my $item = POSIX::strftime("%c", localtime); +# print "Testing character encoding handling. Perl IO data:\n"; +# print(join(', ', PerlIO::get_layers(STDOUT)), "\n"); +# print "Without binmode: ", $item,"\n"; +# binmode STDOUT,":utf8"; +# print "With binmode: ", $item,"\n"; +# print "Perl IO data:\n"; +# print(join(', ', PerlIO::get_layers(STDOUT)), "\n"); +# close $duped; +} + +# see docs/optimization.txt +sub ram_use { + my ($name, $ref) = @_; + printf "%-25s %5d %5d\n", $name, size($ref), total_size($ref); +} + +#### ------------------------------------------------------------------- +#### DOWNLOADER +#### ------------------------------------------------------------------- + +# args: 0: download type; 1: url; 2: file; 3: [ua type string] +sub download_file { + my ($type, $url, $file,$ua) = @_; + my ($cmd,$args,$timeout) = ('','',''); + my $debug_data = ''; + my $result = 1; + $ua = ($ua && $dl{'ua'}) ? $dl{'ua'} . $ua : ''; + $dl{'no-ssl'} ||= ''; + $dl{'spider'} ||= ''; + $file ||= 'N/A'; # to avoid debug error + if (!$dl{'dl'}){ + return 0; + } + if ($dl{'timeout'}){ + $timeout = "$dl{'timeout'}$dl_timeout"; + } + # print "$dl{'no-ssl'}\n"; + # print "$dl{'dl'}\n"; + # tiny supports spider sort of + ## NOTE: 1 is success, 0 false for Perl + if ($dl{'dl'} eq 'tiny'){ + $cmd = "Using tiny: type: $type \nurl: $url \nfile: $file"; + $result = get_file_http_tiny($type,$url,$file,$ua); + $debug_data = ($type ne 'stdout') ? $result : 'Success: stdout data not null.'; + } + # But: 0 is success, and 1 is false for these + # when strings are returned, they will be taken as true + # urls must be " quoted in case special characters present + else { + if ($type eq 'stdout'){ + $args = $dl{'stdout'}; + $cmd = "$dl{'dl'} $dl{'no-ssl'} $ua $timeout $args \"$url\" $dl{'null'}"; + $result = qx($cmd); + $debug_data = ($result) ? 'Success: stdout data not null.' : 'Download resulted in null data!'; + } + elsif ($type eq 'file'){ + $args = $dl{'file'}; + $cmd = "$dl{'dl'} $dl{'no-ssl'} $ua $timeout $args $file \"$url\" $dl{'null'}"; + system($cmd); + $result = ($?) ? 0 : 1; # reverse these into Perl t/f + $debug_data = $result; + } + elsif ($dl{'dl'} eq 'wget' && $type eq 'spider'){ + $cmd = "$dl{'dl'} $dl{'no-ssl'} $ua $timeout $dl{'spider'} \"$url\""; + system($cmd); + $result = ($?) ? 0 : 1; # reverse these into Perl t/f + $debug_data = $result; + } + } + print "-------\nDownloader Data:\n$cmd\nResult: $debug_data\n" if $dbg[1]; + log_data('data',"$cmd\nResult: $result") if $b_log; + return $result; +} + +sub get_file_http_tiny { + my ($type,$url,$file,$ua) = @_; + $ua = ($ua && $dl{'ua'}) ? $dl{'ua'} . $ua: ''; + my %headers = ($ua) ? ('agent' => $ua) : (); + my $tiny = HTTP::Tiny->new(%headers); + # note: default is no verify, so default here actually is to verify unless overridden + $tiny->verify_SSL => 1 if !$use{'no-ssl'}; + my $response = $tiny->get($url); + my $return = 1; + my $debug = 0; + my $fh; + $file ||= 'N/A'; + log_data('dump','%{$response}',$response) if $b_log; + # print Dumper $response; + if (!$response->{'success'}){ + my $content = $response->{'content'}; + $content ||= "N/A\n"; + my $msg = "Failed to connect to server/file!\n"; + $msg .= "Response: ${content}Downloader: HTTP::Tiny URL: $url\nFile: $file"; + log_data('data',$msg) if $b_log; + print error_defaults('download-error',$msg) if $dbg[1]; + $return = 0; + } + else { + if ($debug){ + print "$response->{success}\n"; + print "$response->{status} $response->{reason}\n"; + while (my ($key, $value) = each %{$response->{'headers'}}){ + for (ref $value eq "ARRAY" ? @$value : $value){ + print "$key: $_\n"; + } + } + } + if ($type eq "stdout" || $type eq "ua-stdout"){ + $return = $response->{'content'}; + } + elsif ($type eq "spider"){ + # do nothing, just use the return value + } + elsif ($type eq "file"){ + open($fh, ">", $file); + print $fh $response->{'content'}; # or die "can't write to file!\n"; + close $fh; + } + } + return $return; +} + +sub set_downloader { + eval $start if $b_log; + my $quiet = ''; + my $ua_raw = 's-tools/' . $self_name . '-'; + $dl{'no-ssl'} = ''; + $dl{'null'} = ''; + $dl{'spider'} = ''; + # we only want to use HTTP::Tiny if it's present in user system. + # It is NOT part of core modules. IO::Socket::SSL is also required + # For some https connections so only use tiny as option if both present + if ($dl{'tiny'}){ + # this only for -U 4, grab file with ftp to avoid unsupported SSL issues + if ($use{'ftp-download'}){ + $dl{'tiny'} = 0; + } + elsif (check_perl_module('HTTP::Tiny') && check_perl_module('IO::Socket::SSL')){ + HTTP::Tiny->import; + IO::Socket::SSL->import; + $dl{'tiny'} = 1; + } + else { + $dl{'tiny'} = 0; + } + } + # print $dl{'tiny'} . "\n"; + if ($dl{'tiny'}){ + $dl{'dl'} = 'tiny'; + $dl{'file'} = ''; + $dl{'stdout'} = ''; + $dl{'timeout'} = ''; + $dl{'ua'} = $ua_raw; + } + elsif ($dl{'curl'} && check_program('curl')){ + $quiet = '-s ' if !$dbg[1]; + $dl{'dl'} = 'curl'; + $dl{'file'} = " -L ${quiet}-o "; + $dl{'no-ssl'} = ' --insecure'; + $dl{'stdout'} = " -L ${quiet}"; + $dl{'timeout'} = ' -y '; + $dl{'ua'} = ' -A ' . $ua_raw; + } + elsif ($dl{'wget'} && check_program('wget')){ + $quiet = '-q ' if !$dbg[1]; + $dl{'dl'} = 'wget'; + $dl{'file'} = " ${quiet}-O "; + $dl{'no-ssl'} = ' --no-check-certificate'; + $dl{'spider'} = " ${quiet}--spider"; + $dl{'stdout'} = " $quiet -O -"; + $dl{'timeout'} = ' -T '; + $dl{'ua'} = ' -U ' . $ua_raw; + } + elsif ($dl{'fetch'} && check_program('fetch')){ + $quiet = '-q ' if !$dbg[1]; + $dl{'dl'} = 'fetch'; + $dl{'file'} = " ${quiet}-o "; + $dl{'no-ssl'} = ' --no-verify-peer'; + $dl{'stdout'} = " ${quiet}-o -"; + $dl{'timeout'} = ' -T '; + $dl{'ua'} = ' --user-agent=' . $ua_raw; + } + # at least openbsd/netbsd + elsif ($bsd_type && check_program('ftp')){ + $dl{'dl'} = 'ftp'; + $dl{'file'} = ' -o '; + $dl{'null'} = ' 2>/dev/null'; + $dl{'stdout'} = ' -o - '; + $dl{'timeout'} = ''; + $dl{'ua'} = ' -U ' . $ua_raw; + } + else { + $dl{'dl'} = ''; + } + # $use{'no-ssl' is set to 1 with --no-ssl, when false, unset to '' + $dl{'no-ssl'} = '' if !$use{'no-ssl'}; + eval $end if $b_log; +} + +sub set_perl_downloader { + my ($downloader) = @_; + $downloader =~ s/perl/tiny/; + return $downloader; +} + +#### ------------------------------------------------------------------- +#### ERROR HANDLER +#### ------------------------------------------------------------------- + +sub error_handler { + eval $start if $b_log; + my ($err,$one,$two) = @_; + my ($b_help,$b_recommends); + my ($b_exit,$errno) = (1,0); + my $message = do { + if ($err eq 'empty'){ 'empty value' } + ## Basic rules + elsif ($err eq 'not-in-irc'){ + $errno=1; "You can't run option $one in an IRC client!" } + ## Internal/external options + elsif ($err eq 'bad-arg'){ + $errno=10; $b_help=1; "Unsupported value: $two for option: $one" } + elsif ($err eq 'bad-arg-int'){ + $errno=11; "Bad internal argument: $one" } + elsif ($err eq 'distro-block'){ + $errno=20; "Option: $one has been disabled by the $self_name distribution maintainer." } + elsif ($err eq 'option-feature-incomplete'){ + $errno=21; "Option: '$one' feature: '$two' has not been implemented yet." } + elsif ($err eq 'unknown-option'){ + $errno=22; $b_help=1; "Unsupported option: $one" } + ## Data + elsif ($err eq 'open-data'){ + $errno=32; "Error opening data for reading: $one \nError: $two" } + elsif ($err eq 'download-error'){ + $errno=33; "Error downloading file with $dl{'dl'}: $one \nError: $two" } + ## Files: + elsif ($err eq 'copy-failed'){ + $errno=40; "Error copying file: $one \nError: $two" } + elsif ($err eq 'create'){ + $errno=41; "Error creating file: $one \nError: $two" } + elsif ($err eq 'downloader-error'){ + $errno=42; "Error downloading file: $one \nfor download source: $two" } + elsif ($err eq 'file-corrupt'){ + $errno=43; "Downloaded file is corrupted: $one" } + elsif ($err eq 'mkdir'){ + $errno=44; "Error creating directory: $one \nError: $two" } + elsif ($err eq 'open'){ + $errno=45; $b_exit=0; "Error opening file: $one \nError: $two" } + elsif ($err eq 'open-dir'){ + $errno=46; "Error opening directory: $one \nError: $two" } + elsif ($err eq 'output-file-bad'){ + $errno=47; "Value for --output-file must be full path, a writable directory, \nand include file name. Path: $two" } + elsif ($err eq 'not-writable'){ + $errno=48; "The file: $one is not writable!" } + elsif ($err eq 'open-dir-failed'){ + $errno=49; "The directory: $one failed to open with error: $two" } + elsif ($err eq 'remove'){ + $errno=50; "Failed to remove file: $one Error: $two" } + elsif ($err eq 'rename'){ + $errno=51; "There was an error moving files: $one\nError: $two" } + elsif ($err eq 'write'){ + $errno=52; "Failed writing file: $one - Error: $two!" } + elsif ($err eq 'dir-missing'){ + $errno=53; "Directory supplied for option $one does not exist:\n $two" } + ## Downloaders + elsif ($err eq 'missing-downloader'){ + $errno=60; "Downloader program $two could not be located on your system." } + elsif ($err eq 'missing-perl-downloader'){ + $errno=61; $b_recommends=1; "Perl downloader missing required module." } + ## FTP + elsif ($err eq 'ftp-bad-path'){ + $errno=70; "Unable to locate for FTP upload file:\n$one" } + elsif ($err eq 'ftp-connect'){ + $errno=71; "There was an error with connection to ftp server: $one" } + elsif ($err eq 'ftp-login'){ + $errno=72; "There was an error with login to ftp server: $one" } + elsif ($err eq 'ftp-upload'){ + $errno=73; "There was an error with upload to ftp server: $one" } + ## Modules + elsif ($err eq 'required-module'){ + $errno=80; $b_recommends=1; "The required $one Perl module is not installed:\n$two" } + ## Programs + elsif ($err eq 'required-program'){ + $errno=90; "Required program '$one' could not be located on your system.\nNeeded for: $two" } + ## DEFAULT + else { + $errno=255; "Error handler ERROR!! Unsupported options: $err!"} + }; + print_line("Error $errno: $message\n"); + if ($b_help){ + print_line("Check -h for correct parameters.\n"); + } + if ($b_recommends){ + print_line("See --recommends for more information.\n"); + } + eval $end if $b_log; + exit $errno if $b_exit && !$debugger{'no-exit'}; +} + +sub error_defaults { + my ($type,$one) = @_; + $one ||= ''; + my %errors = ( + 'download-error' => "Download Failure:\n$one\n", + ); + return $errors{$type}; +} + +#### ------------------------------------------------------------------- +#### RECOMMENDS +#### ------------------------------------------------------------------- + +## CheckRecommends +{ +package CheckRecommends; +my ($item_data,@modules,@pms); + +sub run { + main::error_handler('not-in-irc', 'recommends') if $b_irc; + my (@data,@rows); + my $rows = []; + my $line = main::make_line(); + @pms = get_pms(); + set_item_data(); + basic_data($rows,$line); + if (!$bsd_type){ + check_items($rows,'required system directories',$line); + } + check_items($rows,'recommended system programs',$line); + check_items($rows,'recommended display information programs',$line); + check_items($rows,'recommended downloader programs',$line); + if (!$bsd_type){ + check_items($rows,'recommended kernel modules',$line); + } + check_items($rows,'recommended Perl modules',$line); + check_items($rows,'recommended directories',$line); + check_items($rows,'recommended files',$line); + push(@$rows, + ['0', '', '', "$line"], + ['0', '', '', "Ok, all done with the checks. Have a nice day."], + ['0', '', '', ''], + ); + # print Data::Dumper::Dumper $rows; + main::print_basic($rows); + exit 0; # shell true +} + +sub basic_data { + my ($rows,$line) = @_; + my (@data,@rows); + $extra = 1; # needed for shell version + ShellData::set(); + my $client = $client{'name-print'}; + $client .= ' ' . $client{'version'} if $client{'version'}; + my $default_shell = 'N/A'; + if ($ENV{'SHELL'}){ + $default_shell = $ENV{'SHELL'}; + $default_shell =~ s/.*\///; + } + my $sh = main::check_program('sh'); + my $sh_real = Cwd::abs_path($sh); + push(@$rows, + ['0', '', '', "$self_name will now begin checking for the programs it needs + to operate."], + ['0', '', '', ""], + ['0', '', '', "Check $self_name --help or the man page (man $self_name) + to see what options are available."], + ['0', '', '', "$line"], + ['0', '', '', "Test: core tools:"], + ['0', '', '', ""], + ['0', '', '', "Perl version: ^$]"], + ['0', '', '', "Current shell: " . $client], + ['0', '', '', "Default shell: " . $default_shell], + ['0', '', '', "sh links to: $sh_real"], + ); + if (scalar @pms == 0){ + push(@$rows,['0', '', '', "Package manager(s): No supported PM(s) detected"]); + } + elsif (scalar @pms == 1){ + push(@$rows,['0', '', '', "Package manager: $pms[0]"]); + } + else { + push(@$rows,['0', '', '', "Package managers detected:"]); + foreach my $pm (@pms){ + push(@$rows,['0', '', '', " pm: $pm"]); + } + } +} + +sub check_items { + my ($rows,$type,$line) = @_; + my (@data,@missing,$row,$result,@unreadable); + my ($b_dir,$b_file,$b_kernel_module,$b_perl_module,$b_program,$item); + my ($about,$extra,$extra2,$extra3,$extra4,$info_os) = ('','','','','','info'); + if ($type eq 'required system directories'){ + @data = qw(/proc /sys); + $b_dir = 1; + $item = 'Directory'; + } + elsif ($type eq 'recommended system programs'){ + if ($bsd_type){ + @data = qw(camcontrol dig disklabel dmidecode doas fdisk file glabel gpart + ifconfig ipmi-sensors ipmitool pciconfig pcidump pcictl smartctl sudo + sysctl tree upower uptime usbconfig usbdevs); + $info_os = 'info-bsd'; + } + else { + @data = qw(blockdev bt-adapter btmgmt dig dmidecode doas fdisk file + fruid_print hciconfig hddtemp ifconfig ip ipmitool ipmi-sensors lsblk + lsusb lvs mdadm modinfo runlevel sensors smartctl strings sudo tree upower + uptime); + } + $b_program = 1; + $item = 'Program'; + $extra2 = "Note: IPMI sensors are generally only found on servers. To access + that data, you only need one of the ipmi items."; + } + elsif ($type eq 'recommended display information programs'){ + if ($bsd_type){ + @data = qw(eglinfo glxinfo vulkaninfo wmctrl xdpyinfo xprop xdriinfo + xrandr); + $info_os = 'info-bsd'; + } + else { + @data = qw(eglinfo glxinfo vulkaninfo wmctrl xdpyinfo xprop xdriinfo + xrandr); + } + $b_program = 1; + $item = 'Program'; + } + elsif ($type eq 'recommended downloader programs'){ + if ($bsd_type){ + @data = qw(curl dig fetch ftp wget); + $info_os = 'info-bsd'; + } + else { + @data = qw(curl dig wget); + } + $b_program = 1; + $extra = ' (You only need one of these)'; + $extra2 = "Perl HTTP::Tiny is the default downloader tool if IO::Socket::SSL is present. + See --help --alt 40-44 options for how to override default downloader(s) in case of issues. "; + $extra3 = "If dig is installed, it is the default for WAN IP data. + Strongly recommended. Dig is fast and accurate."; + $extra4 = ". However, you really only need dig in most cases. All systems should have "; + $extra4 .= "at least one of the downloader options present."; + $item = 'Program'; + } + elsif ($type eq 'recommended Perl modules'){ + @data = qw(File::Copy File::Find File::Spec::Functions HTTP::Tiny IO::Socket::SSL + Time::HiRes JSON::PP Cpanel::JSON::XS JSON::XS XML::Dumper Net::FTP); + if ($bsd_type && $bsd_type eq 'openbsd'){ + push(@data, qw(OpenBSD::Pledge OpenBSD::Unveil)); + } + $b_perl_module = 1; + $item = 'Perl Module'; + $extra = ' (Optional)'; + $extra2 = "None of these are strictly required, but if you have them all, + you can eliminate some recommended non Perl programs from the install. "; + $extra3 = "HTTP::Tiny and IO::Socket::SSL must both be present to use as a + downloader option. For json export Cpanel::JSON::XS is preferred over + JSON::XS, but JSON::PP is in core modules. To run --debug 20-22 File::Copy, + File::Find, and File::Spec::Functions must be present (most distros have + these in Core Modules). + "; + } + elsif ($type eq 'recommended kernel modules'){ + @data = qw(amdgpu drivetemp nouveau radeon); + @modules = main::lister('/sys/module/'); + $b_kernel_module = 1; + $extra2 = "GPU modules are only needed if applicable. NVMe drives do not need drivetemp + but other types do."; + $extra3 = "To load a module: modprobe - To permanently load + add to /etc/modules or /etc/modules-load.d/modules.conf (check your system + paths for exact file/directory names)."; + $item = 'Kernel Module'; + } + elsif ($type eq 'recommended directories'){ + if ($bsd_type){ + @data = qw(/dev); + } + else { + @data = qw(/dev /dev/disk/by-id /dev/disk/by-label /dev/disk/by-path + /dev/disk/by-uuid /sys/class/dmi/id /sys/class/hwmon); + } + $b_dir = 1; + $item = 'Directory'; + } + elsif ($type eq 'recommended files'){ + if ($bsd_type){ + @data = qw(/var/run/dmesg.boot /var/log/Xorg.0.log); + } + else { + @data = qw(/etc/lsb-release /etc/os-release /proc/asound/cards + /proc/asound/version /proc/cpuinfo /proc/mdstat /proc/meminfo /proc/modules + /proc/mounts /proc/scsi/scsi /var/log/Xorg.0.log); + } + $b_file = 1; + $item = 'File'; + $extra2 = "Note that not all of these are used by every system, + so if one is missing it's usually not a big deal."; + } + push(@$rows, + ['0', '', '', "$line" ], + ['0', '', '', "Test: $type$extra:" ], + ['0', '', '', ''], + ); + if ($extra2){ + push(@$rows, + ['0', '', '', $extra2], + ['0', '', '', '']); + } + if ($extra3){ + push(@$rows, + ['0', '', '', $extra3], + ['0', '', '', '']); + } + foreach my $item (@data){ + undef $about; + my $info = $item_data->{$item}; + $about = $info->{$info_os}; + if (($b_dir && -d $item) || ($b_file && -r $item) || + ($b_program && main::check_program($item)) || + ($b_perl_module && main::check_perl_module($item)) || + ($b_kernel_module && @modules && (grep {/^$item$/} @modules))){ + $result = 'Present'; + } + elsif ($b_file && -f $item){ + $result = 'Unreadable'; + push(@unreadable, "$item"); + } + else { + $result = 'Missing'; + push(@missing,"$item"); + if (($b_program || $b_perl_module) && @pms){ + my @install; + foreach my $pm (@pms){ + $info->{$pm} ||= 'N/A'; + push(@install," $pm: $info->{$pm}"); + } + push(@missing,@install); + } + } + $row = make_row($item,$about,$result); + push(@$rows, ['0', '', '', $row]); + } + push(@$rows, ['0', '', '', '']); + if (@missing){ + push(@$rows, ['0', '', '', "The following $type are missing$extra4:"]); + foreach (@missing){ + push(@$rows, ['0', '', '', $_]); + } + } + if (@unreadable){ + push(@$rows, ['0', '', '', "The following $type are not readable: "]); + foreach (@unreadable){ + push(@$rows, ['0', '', '', "$item: $_"]); + } + } + if (!@missing && !@unreadable){ + push(@$rows, ['0', '', '', "All $type are present"]); + } +} + +sub set_item_data { + $item_data = { + ## Directory Data ## + '/dev' => { + 'info' => '-l,-u,-o,-p,-P,-D disk partition data', + }, + '/dev/disk/by-id' => { + 'info' => '-D serial numbers', + }, + '/dev/disk/by-path' => { + 'info' => '-D extra data', + }, + '/dev/disk/by-label' => { + 'info' => '-l,-o,-p,-P partition labels', + }, + '/dev/disk/by-uuid' => { + 'info' => '-u,-o,-p,-P partition uuid', + }, + '/proc' => { + 'info' => '', + }, + '/sys' => { + 'info' => '', + }, + '/sys/class/dmi/id' => { + 'info' => '-M system, motherboard, bios', + }, + '/sys/class/hwmon' => { + 'info' => '-s sensor data (fallback if no lm-sensors)', + }, + ## File Data ## + '/etc/lsb-release' => { + 'info' => '-S distro version data (older version)', + }, + '/etc/os-release' => { + 'info' => '-S distro version data (newer version)', + }, + '/proc/asound/cards' => { + 'info' => '-A sound card data', + }, + '/proc/asound/version' => { + 'info' => '-A ALSA data', + }, + '/proc/cpuinfo' => { + 'info' => '-C cpu data', + }, + '/proc/mdstat' => { + 'info' => '-R mdraid data (if you use dm-raid)', + }, + '/proc/meminfo' => { + 'info' => '-I,-tm, -m memory data', + }, + '/proc/modules' => { + 'info' => '-G module data (sometimes)', + }, + '/proc/mounts' => { + 'info' => '-P,-p partition advanced data', + }, + '/proc/scsi/scsi' => { + 'info' => '-D Advanced hard disk data (used rarely)', + }, + '/var/log/Xorg.0.log' => { + 'info' => '-G graphics driver load status', + }, + '/var/run/dmesg.boot' => { + 'info' => '-D,-d disk data', + }, + ## Kernel Module Data ## + 'amdgpu' => { + 'info' => '-s, -G AMD GPU sensor data (newer GPUs)', + 'info-bsd' => '', + }, + 'drivetemp' => { + 'info' => '-Dx drive temperature (kernel >= 5.6)', + 'info-bsd' => '', + }, + 'nouveau' => { + 'info' => '-s, -G Nvidia GPU sensor data (if using free driver)', + 'info-bsd' => '', + }, + 'radeon' => { + 'info' => '-s, -G AMD GPU sensor data (older GPUs)', + 'info-bsd' => '', + }, + ## START PACKAGE MANAGER BLOCK ## + # BSD only tools do not list package manager install names + ## Programs-System ## + # Note: see inxi-perl branch for details: docs/inxi-custom-recommends.txt + # System Tools + 'blockdev' => { + 'info' => '--admin -p/-P (filesystem blocksize)', + 'info-bsd' => '', + 'apt' => 'util-linux', + 'pacman' => 'util-linux', + 'pkgtool' => 'util-linux', + 'rpm' => 'util-linux', + }, + 'bt-adapter' => { + 'info' => '-E bluetooth data (if no hciconfig, btmgmt)', + 'info-bsd' => '', + 'apt' => 'bluez-tools', + 'pacman' => 'bluez-tools', + 'pkgtool' => '', # needs to be built by user + 'rpm' => 'bluez-tools', + }, + 'btmgmt' => { + 'info' => '-E bluetooth data (if no hciconfig)', + 'info-bsd' => '', + 'apt' => 'bluez', + 'pacman' => 'bluez-utils', + 'pkgtool' => '', # needs to be built by user + 'rpm' => 'bluez', + }, + 'curl' => { + 'info' => '-i (if no dig); -w,-W; -U', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => 'curl', + 'pacman' => 'curl', + 'pkgtool' => 'curl', + 'rpm' => 'curl', + }, + 'camcontrol' => { + 'info' => '', + 'info-bsd' => '-R; -D; -P. Get actual gptid /dev path', + }, + 'dig' => { + 'info' => '-i wlan IP', + 'info-bsd' => '-i wlan IP', + 'apt' => 'dnsutils', + 'pacman' => 'dnsutils', + 'pkgtool' => 'bind', + 'rpm' => 'bind-utils', + }, + 'disklabel' => { + 'info' => '', + 'info-bsd' => '-j, -p, -P; -R; -o (Open/NetBSD+derived)', + }, + 'dmidecode' => { + 'info' => '-M if no sys machine data; -m', + 'info-bsd' => '-M if null sysctl; -m; -B if null sysctl', + 'apt' => 'dmidecode', + 'pacman' => 'dmidecode', + 'pkgtool' => 'dmidecode', + 'rpm' => 'dmidecode', + }, + 'doas' => { + 'info' => '-Dx hddtemp-user; -o file-user (alt for sudo)', + 'info-bsd' => '-Dx hddtemp-user; -o file-user', + 'apt' => 'doas', + 'pacman' => 'doas', + 'pkgtool' => ' opendoas', + 'rpm' => 'doas', + }, + 'fdisk' => { + 'info' => '-D partition scheme (fallback)', + 'info-bsd' => '-D partition scheme', + 'apt' => 'fdisk', + 'pacman' => 'util-linux', + 'pkgtool' => 'util-linux', + 'rpm' => 'util-linux', + }, + 'fetch' => { + 'info' => '', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + }, + 'file' => { + 'info' => '-o unmounted file system (if no lsblk)', + 'info-bsd' => '-o unmounted file system', + 'apt' => 'file', + 'pacman' => 'file', + 'pkgtool' => 'file', + 'rpm' => 'file', + }, + 'ftp' => { + 'info' => '', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + }, + 'fruid_print' => { + 'info' => '-M machine data, Elbrus only', + 'info-bsd' => '', + 'apt' => '', + 'pacman' => '', + 'pkgtool' => '', + 'rpm' => '', + }, + 'glabel' => { + 'info' => '', + 'info-bsd' => '-R; -D; -P. Get actual gptid /dev path', + }, + 'gpart' => { + 'info' => '', + 'info-bsd' => '-p,-P; -R; -o (FreeBSD+derived)', + }, + 'hciconfig' => { + 'info' => '-E bluetooth data (deprecated, good report)', + 'info-bsd' => '', + 'apt' => 'bluez', + 'pacman' => 'bluez-utils-compat (frugalware: bluez-utils)', + 'pkgtool' => 'bluez', + 'rpm' => 'bluez-utils', + }, + 'hddtemp' => { + 'info' => '-Dx show hdd temp, if no drivetemp module', + 'info-bsd' => '-Dx show hdd temp', + 'apt' => 'hddtemp', + 'pacman' => 'hddtemp', + 'pkgtool' => 'hddtemp', + 'rpm' => 'hddtemp', + }, + 'ifconfig' => { + 'info' => '-i ip LAN (deprecated)', + 'info-bsd' => '-i ip LAN', + 'apt' => 'net-tools', + 'pacman' => 'net-tools', + 'pkgtool' => 'net-tools', + 'rpm' => 'net-tools', + }, + 'ip' => { + 'info' => '-i ip LAN', + 'info-bsd' => '', + 'apt' => 'iproute', + 'pacman' => 'iproute2', + 'pkgtool' => 'iproute2', + 'rpm' => 'iproute', + }, + 'ipmi-sensors' => { + 'info' => '-s IPMI sensors (servers)', + 'info-bsd' => '', + 'apt' => 'freeipmi-tools', + 'pacman' => 'freeipmi', + 'pkgtool' => 'freeipmi', + 'rpm' => 'freeipmi', + }, + 'ipmitool' => { + 'info' => '-s IPMI sensors (servers)', + 'info-bsd' => '-s IPMI sensors (servers)', + 'apt' => 'ipmitool', + 'pacman' => 'ipmitool', + 'pkgtool' => 'ipmitool', + 'rpm' => 'ipmitool', + }, + 'lsblk' => { + 'info' => '-L LUKS/bcache; -o unmounted file system (best option)', + 'info-bsd' => '-o unmounted file system', + 'apt' => 'util-linux', + 'pacman' => 'util-linux', + 'pkgtool' => 'util-linux', + 'rpm' => 'util-linux-ng', + }, + 'lvs' => { + 'info' => '-L LVM data', + 'info-bsd' => '', + 'apt' => 'lvm2', + 'pacman' => 'lvm2', + 'pkgtool' => 'lvm2', + 'rpm' => 'lvm2', + }, + 'lsusb' => { + 'info' => '-A usb audio; -J (optional); -N usb networking', + 'info-bsd' => '', + 'apt' => 'usbutils', + 'pacman' => 'usbutils', + 'pkgtool' => 'usbutils', + 'rpm' => 'usbutils', + }, + 'mdadm' => { + 'info' => '-Ra advanced mdraid data', + 'info-bsd' => '', + 'apt' => 'mdadm', + 'pacman' => 'mdadm', + 'pkgtool' => 'mdadm', + 'rpm' => 'mdadm', + }, + 'modinfo' => { + 'info' => 'Ax; -Nx module version', + 'info-bsd' => '', + 'apt' => 'module-init-tools', + 'pacman' => 'module-init-tools', + 'pkgtool' => 'kmod (earlier: module-init-tools)', + 'rpm' => 'module-init-tools', + }, + 'pciconfig' => { + 'info' => '', + 'info-bsd' => '-A,-E,-G,-N pci devices (FreeBSD+derived)', + }, + 'pcictl' => { + 'info' => '', + 'info-bsd' => '-A,-E,-G,-N pci devices (NetBSD+derived)', + }, + 'pcidump' => { + 'info' => '', + 'info-bsd' => '-A,-E,-G,-N pci devices (OpenBSD+derived, doas/su)', + }, + 'runlevel' => { + 'info' => '-I fallback to Perl', + 'info-bsd' => '', + 'apt' => 'systemd or sysvinit', + 'pacman' => 'systemd', + 'pkgtool' => 'sysvinit', + 'rpm' => 'systemd or sysvinit', + }, + 'sensors' => { + 'info' => '-s sensors output (optional, /sys supplies most)', + 'info-bsd' => '', + 'apt' => 'lm-sensors', + 'pacman' => 'lm-sensors', + 'pkgtool' => 'lm_sensors', + 'rpm' => 'lm-sensors', + }, + 'smartctl' => { + 'info' => '-Da advanced data', + 'info-bsd' => '-Da advanced data', + 'apt' => 'smartmontools', + 'pacman' => 'smartmontools', + 'pkgtool' => 'smartmontools', + 'rpm' => 'smartmontools', + }, + 'strings' => { + 'info' => '-I sysvinit version', + 'info-bsd' => '', + 'apt' => 'binutils', + 'pacman' => 'binutils', + 'pkgtool' => 'binutils', + 'rpm' => 'binutils', + }, + 'sudo' => { + 'info' => '-Dx hddtemp-user; -o file-user (try doas!)', + 'info-bsd' => '-Dx hddtemp-user; -o file-user (alt for doas)', + 'apt' => 'sudo', + 'pacman' => 'sudo', + 'pkgtool' => 'sudo', + 'rpm' => 'sudo', + }, + 'sysctl' => { + 'info' => '', + 'info-bsd' => '-C; -I; -m; -tm', + }, + 'tree' => { + 'info' => '--debugger 20,21 /sys tree', + 'info-bsd' => '--debugger 20,21 /sys tree', + 'apt' => 'tree', + 'pacman' => 'tree', + 'pkgtool' => 'tree', + 'rpm' => 'tree', + }, + 'upower' => { + 'info' => '-sx attached device battery info', + 'info-bsd' => '-sx attached device battery info', + 'apt' => 'upower', + 'pacman' => 'upower', + 'pkgtool' => 'upower', + 'rpm' => 'upower', + }, + 'uptime' => { + 'info' => '-I uptime', + 'info-bsd' => '-I uptime', + 'apt' => 'procps', + 'pacman' => 'procps', + 'pkgtool' => 'procps', + 'rpm' => 'procps', + }, + 'usbconfig' => { + 'info' => '', + 'info-bsd' => '-A; -E; -G; -J; -N; (FreeBSD+derived, doas/su)', + }, + 'usbdevs' => { + 'info' => '', + 'info-bsd' => '-A; -E; -G; -J; -N; (Open/NetBSD+derived)', + }, + 'wget' => { + 'info' => '-i (if no dig); -w,-W; -U', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => 'wget', + 'pacman' => 'wget', + 'pkgtool' => 'wget', + 'rpm' => 'wget', + }, + ## Programs-Display ## + 'eglinfo' => { + 'info' => '-G X11/Wayland EGL info', + 'info-bsd' => '-G X11/Wayland EGL info', + 'apt' => 'mesa-utils (or: mesa-utils-extra)', + 'pacman' => 'mesa-demos', + 'pkgtool' => 'mesa', + 'rpm' => 'egl-utils (SUSE: Mesa-demo-egl)', + }, + 'glxinfo' => { + 'info' => '-G X11 GLX info', + 'info-bsd' => '-G X11 GLX info', + 'apt' => 'mesa-utils', + 'pacman' => 'mesa-demos', + 'pkgtool' => 'mesa', + 'rpm' => 'glx-utils (Fedora: glx-utils; SUSE: Mesa-demo-x)', + }, + 'vulkaninfo' => { + 'info' => '-G Vulkan API info', + 'info-bsd' => '-G Vulkan API info', + 'apt' => 'vulkan-tools', + 'pacman' => 'vulkan-tools', + 'pkgtool' => 'vulkan-tools', + 'rpm' => 'vulkan-demos (Fedora: vulkan-tools; SUSE: vulkan-demos)', + }, + 'wmctrl' => { + 'info' => '-S active window manager (fallback)', + 'info-bsd' => '-S active window manager (fallback)', + 'apt' => 'wmctrl', + 'pacman' => 'wmctrl', + 'pkgtool' => 'wmctrl', + 'rpm' => 'wmctrl', + }, + 'xdpyinfo' => { + 'info' => '-G (X) Screen resolution, dpi; -Ga Screen size', + 'info-bsd' => '-G (X) Screen resolution, dpi; -Ga Screen size', + 'apt' => 'X11-utils', + 'pacman' => 'xorg-xdpyinfo', + 'pkgtool' => 'xdpyinfo', + 'rpm' => 'xorg-x11-utils (SUSE/Fedora: xdpyinfo)', + }, + 'xdriinfo' => { + 'info' => '-G (X) DRI driver (if missing, fallback to Xorg log)', + 'info-bsd' => '-G (X) DRI driver (if missing, fallback to Xorg log', + 'apt' => 'X11-utils', + 'pacman' => 'xorg-xdriinfo', + 'pkgtool' => 'xdriinfo', + 'rpm' => 'xorg-x11-utils (SUSE/Fedora: xdriinfo)', + }, + 'xprop' => { + 'info' => '-S (X) desktop data', + 'info-bsd' => '-S (X) desktop data', + 'apt' => 'X11-utils', + 'pacman' => 'xorg-xprop', + 'pkgtool' => 'xprop', + 'rpm' => 'x11-utils (Fedora/SUSE: xprop)', + }, + 'xrandr' => { + 'info' => '-G (X) monitors(s) resolution; -Ga monitor data', + 'info-bsd' => '-G (X) monitors(s) resolution; -Ga monitor data', + 'apt' => 'x11-xserver-utils', + 'pacman' => 'xrandr', + 'pkgtool' => 'xrandr', + 'rpm' => 'x11-server-utils (SUSE/Fedora: xrandr)', + }, + ## Perl Modules ## + 'Cpanel::JSON::XS' => { + 'info' => '-G wayland, --output json (faster).', + 'info-bsd' => '-G wayland, --output json (faster).', + 'apt' => 'libcpanel-json-xs-perl', + 'pacman' => 'perl-cpanel-json-xs', + 'pkgtool' => 'perl-Cpanel-JSON-XS', + 'rpm' => 'perl-Cpanel-JSON-XS', + }, + 'File::Copy' => { + 'info' => '--debug 20-22 - required for debugger.', + 'info-bsd' => '--debug 20-22 - required for debugger.', + 'apt' => 'Core Modules', + 'pacman' => 'Core Modules', + 'pkgtool' => 'Core Modules', + 'rpm' => 'perl-File-Copy', + }, + 'File::Find' => { + 'info' => '--debug 20-22 - required for debugger.', + 'info-bsd' => '--debug 20-22 - required for debugger.', + 'apt' => 'Core Modules', + 'pacman' => 'Core Modules', + 'pkgtool' => 'Core Modules', + 'rpm' => 'perl-File-Find', + }, + 'File::Spec::Functions' => { + 'info' => '--debug 20-22 - required for debugger.', + 'info-bsd' => '--debug 20-22 - required for debugger.', + 'apt' => 'Core Modules', + 'pacman' => 'Core Modules', + 'pkgtool' => 'Core Modules', + 'rpm' => 'Core Modules', + }, + 'HTTP::Tiny' => { + 'info' => '-U; -w,-W; -i (if dig not installed).', + 'info-bsd' => '-U; -w,-W; -i (if dig not installed)', + 'apt' => 'libhttp-tiny-perl (Core Modules >= 5.014)', + 'pacman' => 'Core Modules', + 'pkgtool' => 'perl-http-tiny (Core Modules >= 5.014)', + 'rpm' => 'Perl-http-tiny', + }, + 'IO::Socket::SSL' => { + 'info' => '-U; -w,-W; -i (if dig not installed).', + 'info-bsd' => '-U; -w,-W; -i (if dig not installed)', + 'apt' => 'libio-socket-ssl-perl', + 'pacman' => 'perl-io-socket-ssl', + 'pkgtool' => 'perl-IO-Socket-SSL', # maybe in core modules + 'rpm' => 'perl-IO-Socket-SSL', + }, + 'JSON::PP' => { + 'info' => '-G wayland, --output json (in CoreModules, slower).', + 'info-bsd' => '-G wayland, --output json (in CoreModules, slower).', + 'apt' => 'libjson-pp-perl (Core Modules >= 5.014)', + 'pacman' => 'perl-json-pp (Core Modules >= 5.014)', + 'pkgtool' => 'Core Modules >= 5.014', + 'rpm' => 'perl-JSON-PP', + }, + 'JSON::XS' => { + 'info' => '-G wayland, --output json (legacy).', + 'info-bsd' => '-G wayland, --output json (legacy).', + 'apt' => 'libjson-xs-perl', + 'pacman' => 'perl-json-xs', + 'pkgtool' => 'perl-JSON-XS', + 'rpm' => 'perl-JSON-XS', + }, + 'Net::FTP' => { + 'info' => '--debug 21,22', + 'info-bsd' => '--debug 21,22', + 'apt' => 'Core Modules', + 'pacman' => 'Core Modules', + 'pkgtool' => 'Core Modules', + 'rpm' => 'Core Modules', + }, + 'OpenBSD::Pledge' => { + 'info' => "$self_name Perl pledge support.", + 'info-bsd' => "$self_name Perl pledge support.", + }, + 'OpenBSD::Unveil' => { + 'info' => "Experimental: $self_name Perl unveil support.", + 'info-bsd' => "Experimental: $self_name Perl unveil support.", + }, + 'Time::HiRes' => { + 'info' => '-C cpu sleep (not required); --debug timers', + 'info-bsd' => '-C cpu sleep (not required); --debug timers', + 'apt' => 'Core Modules', + 'pacman' => 'Core Modules', + 'pkgtool' => 'Core Modules', + 'rpm' => 'perl-Time-HiRes', + }, + 'XML::Dumper' => { + 'info' => '--output xml - Crude and raw.', + 'info-bsd' => '--output xml - Crude and raw.', + 'apt' => 'libxml-dumper-perl', + 'pacman' => 'perl-xml-dumper', + 'pkgtool' => '', # package does not appear to exist + 'rpm' => 'perl-XML-Dumper', + }, + ## END PACKAGE MANAGER BLOCK ## + }; +} + +sub get_pms { + my @pms = (); + # support maintainers of other pm types using custom lists + if (main::check_program('dpkg')){ + push(@pms,'apt'); + } + if (main::check_program('pacman')){ + push(@pms,'pacman'); + } + # assuming netpkg uses installpkg as backend + if (main::check_program('installpkg')){ + push(@pms,'pkgtool'); + } + # rpm needs to go last because it's sometimes available on other pm systems + if (main::check_program('rpm')){ + push(@pms,'rpm'); + } + return @pms; +} + +# note: end will vary, but should always be treated as longest value possible. +# expected values: Present/Missing +sub make_row { + my ($start,$middle,$end) = @_; + my ($dots,$line,$sep) = ('','',': '); + foreach (0 .. ($size{'max-cols'} - 16 - length("$start$middle"))){ + $dots .= '.'; + } + $line = "$start$sep$middle$dots $end"; + return $line; +} +} + +#### ------------------------------------------------------------------- +#### TOOLS +#### ------------------------------------------------------------------- + +# Duplicates the functionality of awk to allow for one liner +# type data parsing. note: -1 corresponds to awk NF +# args: 0: array of data; 1: search term; 2: field result; 3: separator +# correpsonds to: awk -F='separator' '/search/ {print $2}' <<< @data +# array is sent by reference so it must be dereferenced +# NOTE: if you just want the first row, pass it \S as search string +# NOTE: if $num is undefined, it will skip the second step +sub awk { + eval $start if $b_log; + my ($ref,$search,$num,$sep) = @_; + my ($result); + # print "search: $search\n"; + return if !@$ref || !$search; + foreach (@$ref){ + next if !defined $_; + if (/$search/i){ + $result = $_; + $result =~ s/^\s+|\s+$//g; + last; + } + } + if ($result && defined $num){ + $sep ||= '\s+'; + $num-- if $num > 0; # retain the negative values as is + $result = (split(/$sep/, $result))[$num]; + $result =~ s/^\s+|,|\s+$//g if $result; + } + eval $end if $b_log; + return $result; +} + +# 0: Perl module to check +sub check_perl_module { + my ($module) = @_; + my $b_present = 0; + eval "require $module"; + $b_present = 1 if !$@; + return $b_present; +} + +# args: 0: string or path to search gneerated @paths data for. +# note: a few nano seconds are saved by using raw $_[0] for program +sub check_program { + (grep { return "$_/$_[0]" if -e "$_/$_[0]"} @paths)[0]; +} + +sub cleanup { + # maybe add in future: , $fh_c, $fh_j, $fh_x + foreach my $fh ($fh_l){ + if ($fh){ + close $fh; + } + } +} + +# args: 0,1: version numbers to compare by turning them to strings +# note that the structure of the two numbers is expected to be fairly +# similar, otherwise it may not work perfectly. +sub compare_versions { + my ($one,$two) = @_; + if ($one && !$two){return $one;} + elsif ($two && !$one){return $two;} + elsif (!$one && !$two){return} + my ($pad1,$pad2) = ('',''); + $pad1 = join('', map {$_ = sprintf("%04s", $_);$_ } split(/[._-]/, $one)); + $pad2 = join('', map {$_ = sprintf("%04s", $_);$_ } split(/[._-]/, $two)); + # print "p1:$pad1 p2:$pad2\n"; + if ($pad1 ge $pad2){return $one} + elsif ($pad2 gt $pad1){return $two} +} + +# some things randomly use hex with 0x starter, return always integer +# warning: perl will generate a 32 bit too big number warning if you pass it +# random values that exceed 2^32 in hex, even if the base system is 64 bit. +# sample: convert_hex(0x000b0000000b); +sub convert_hex { + return (defined $_[0] && $_[0] =~ /^0x/) ? hex($_[0]) : $_[0]; +} + +# returns count of files in directory, if 0, dir is empty +sub count_dir_files { + return unless -d $_[0]; + opendir(my $dh, $_[0]) or error_handler('open-dir-failed', "$_[0]", $!); + my $count = grep { ! /^\.{1,2}/ } readdir($dh); # strips out . and .. + closedir $dh; + return $count; +} + +# args: 0: the string to get piece of +# 1: the position in string, starting at 1 for 0 index. +# 2: the separator, default is ' ' +sub get_piece { + eval $start if $b_log; + my ($string, $num, $sep) = @_; + $num--; + $sep ||= '\s+'; + $string =~ s/^\s+|\s+$//g; + my @temp = split(/$sep/, $string); + eval $end if $b_log; + if (exists $temp[$num]){ + $temp[$num] =~ s/,//g; + return $temp[$num]; + } +} + +# args: 0: command to turn into an array; 1: optional: splitter; +# 2: optionsl, strip and clean data +# similar to reader() except this creates an array of data +# by lines from the command arg +sub grabber { + eval $start if $b_log; + my ($cmd,$split,$strip,$type) = @_; + $type ||= 'arr'; + $split ||= "\n"; + my @rows; + if ($strip){ + for (split(/$split/, qx($cmd))){ + next if /^\s*(#|$)/; + $_ =~ s/^\s+|\s+$//g; + push(@rows,$_); + } + } + else { + @rows = split(/$split/, qx($cmd)); + } + eval $end if $b_log; + return ($type eq 'arr') ? @rows : \@rows; +} + +# args: 0: string value to glob +sub globber { + eval $start if $b_log; + my @files = <$_[0]>; + eval $end if $b_log; + return @files; +} + +# arg MUST be quoted when inserted, otherwise perl takes it for a hex number +sub is_hex { + return (defined $_[0] && $_[0] =~ /^0x/) ? 1 : 0; +} + +## NOTE: for perl pre 5.012 length(undef) returns warning +# receives string, returns boolean 1 if integer +sub is_int { + return 1 if (defined $_[0] && length($_[0]) && + length($_[0]) == ($_[0] =~ tr/0123456789//)); +} + +# receives string, returns true/1 if >= 0 numeric. tr/// 4x faster than regex +sub is_numeric { + return 1 if (defined $_[0] && ($_[0] =~ tr/0123456789//) >= 1 && + length($_[0]) == ($_[0] =~ tr/0123456789.//) && ($_[0] =~ tr/.//) <= 1); +} + +# gets array ref, which may be undefined, plus join string +# this helps avoid debugger print errors when we are printing arrays +# which we don't know are defined or not null. +# args: 0: array ref; 1: join string; 2: default value, optional +sub joiner { + my ($arr,$join,$default) = @_; + $default ||= ''; + my $string = ''; + foreach (@$arr){ + if (defined $_){ + $string .= $_ . $join; + } + else { + $string .= $default . $join; + } + } + return $string; +} + +# gets directory file list +sub lister { + return if ! -d $_[0]; + opendir my $dir, $_[0] or return; + my @list = readdir $dir; + @list = grep {!/^(\.|\.\.)$/} @list if @list; + closedir $dir; + return @list; +} +# checks for 1 of 3 perl json modules. All three have same encode_json, +# decode_json() methods. +sub load_json { + eval $start if $b_log; + $loaded{'json'} = 1; + # recommended, but not in core modules + if (check_perl_module('Cpanel::JSON::XS')){ + Cpanel::JSON::XS->import(qw(encode_json decode_json)); + # my $new = Cpanel::JSON::XS->new; + $use{'json'} = {'type' => 'cpanel-json-xs', + 'encode' => \&Cpanel::JSON::XS::encode_json, + 'decode' => \&Cpanel::JSON::XS::decode_json,}; + # $use{'json'} = {'type' => 'cpanel-json-xs', + # 'new-json' => \Cpanel::JSON::XS->new()}; + } + # somewhat legacy, not in perl modules + elsif (check_perl_module('JSON::XS')){ + JSON::XS->import; + $use{'json'} = {'type' => 'json-xs', + 'encode' => \&JSON::XS::encode_json, + 'decode' => \&JSON::XS::decode_json}; + } + # perl, in core modules as of 5.14 + elsif (check_perl_module('JSON::PP')){ + JSON::PP->import; + $use{'json'} = {'type' => 'json-pp', + 'encode' => \&JSON::PP::encode_json, + 'decode' => \&JSON::PP::decode_json}; + } + eval $end if $b_log; +} + +# returns array of: 0: program print name 1: program version +# args: 0: program values id; 1: program version string; +# 2: $extra level. Note that StartClient runs BEFORE -x levels are set! +# Only use this function when you only need the name/version data returned +sub program_data { + eval $start if $b_log; + my ($values_id,$version_id,$level) = @_; + my (@data,$path,@program_data); + $level = 0 if !$level; + # print "val_id: $values_id ver_id:$version_id lev:$level ex:$extra\n"; + $version_id = $values_id if !$version_id; + @data = program_values($values_id); + if ($data[3]){ + $program_data[0] = $data[3]; + # programs that have no version method return 0 0 for index 1 and 2 + if ($extra >= $level && $data[1] && $data[2]){ + $program_data[1] = program_version($version_id,$data[0], + $data[1],$data[2],$data[5],$data[6],$data[7],$data[8]); + } + } + $program_data[0] ||= ''; + $program_data[1] ||= ''; + eval $end if $b_log; + return @program_data; +} + +# It's almost 1000 times slower to load these each time program_values is called!! +sub set_program_values { + %program_values = ( + ## Clients ## + 'bitchx' => ['bitchx',2,'','BitchX',1,0,0,'',''],# special + 'finch' => ['finch',2,'-v','Finch',1,1,0,'',''], + 'gaim' => ['[0-9.]+',2,'-v','Gaim',0,1,0,'',''], + 'ircii' => ['[0-9.]+',3,'-v','ircII',1,1,0,'',''], + 'irssi' => ['irssi',2,'-v','Irssi',1,1,0,'',''], + 'irssi-text' => ['irssi',2,'-v','Irssi',1,1,0,'',''], + 'konversation' => ['konversation',2,'-v','Konversation',0,0,0,'',''], + 'kopete' => ['Kopete',2,'-v','Kopete',0,0,0,'',''], + 'kvirc' => ['[0-9.]+',2,'-v','KVIrc',0,0,1,'',''], # special + 'pidgin' => ['[0-9.]+',2,'-v','Pidgin',0,1,0,'',''], + 'quassel' => ['',1,'-v','Quassel [M]',0,0,0,'',''], # special + 'quasselclient' => ['',1,'-v','Quassel',0,0,0,'',''],# special + 'quasselcore' => ['',1,'-v','Quassel (core)',0,0,0,'',''],# special + 'gribble' => ['^Supybot',2,'--version','Gribble',1,0,0,'',''],# special + 'limnoria' => ['^Supybot',2,'--version','Limnoria',1,0,0,'',''],# special + 'supybot' => ['^Supybot',2,'--version','Supybot',1,0,0,'',''],# special + 'weechat' => ['[0-9.]+',1,'-v','WeeChat',1,0,0,'',''], + 'weechat-curses' => ['[0-9.]+',1,'-v','WeeChat',1,0,0,'',''], + 'xchat-gnome' => ['[0-9.]+',2,'-v','X-Chat-Gnome',1,1,0,'',''], + 'xchat' => ['[0-9.]+',2,'-v','X-Chat',1,1,0,'',''], + ## Desktops / wm / compositors ## + '2bwm' => ['^2bwm',0,'0','2bWM',0,1,0,'',''], # unverified/based on mcwm + '3dwm' => ['^3dwm',0,'0','3Dwm',0,1,0,'',''], # unverified + '5dwm' => ['^5dwm',0,'0','5Dwm',0,1,0,'',''], # unverified + '9wm' => ['^9wm',3,'-version','9wm',0,1,0,'',''], + 'aewm' => ['^aewm',3,'--version','aewm',0,1,0,'',''], + 'aewm++' => ['^Version:',2,'-version','aewm++',0,1,0,'',''], + 'afterstep' => ['^afterstep',3,'--version','AfterStep',0,1,0,'',''], + 'amiwm' => ['^amiwm',0,'0','AmiWM',0,1,0,'',''], # no version + 'antiwm' => ['^antiwm',0,'0','AntiWM',0,1,0,'',''], # no version known + 'asc' => ['^asc',0,'0','asc',0,1,0,'',''], + 'awc' => ['^awc',0,'0','awc',0,1,0,'',''], # unverified + 'awesome' => ['^awesome',2,'--version','awesome',0,1,0,'',''], + 'beryl' => ['^beryl',0,'0','Beryl',0,1,0,'',''], # unverified; legacy + 'blackbox' => ['^Blackbox',2,'--version','Blackbox',0,1,0,'',''], + 'bspwm' => ['^\S',1,'-v','bspwm',0,1,0,'',''], + 'budgie-desktop' => ['^budgie-desktop',2,'--version','Budgie',0,1,0,'',''], + 'budgie-wm' => ['^budgie',0,'0','budgie-wm',0,1,0,'',''], + 'cage' => ['^cage',0,'0','Cage',0,1,0,'',''], # unverified + 'cagebreak' => ['^Cagebreak',3,'-v','Cagebreak',0,1,0,'',''], + 'calmwm' => ['^calmwm',0,'0','CalmWM',0,1,0,'',''], # unverified + 'cardboard' => ['^cardboard',0,'0','Cardboard',0,1,0,'',''], # unverified + 'catwm' => ['^catwm',0,'0','catwm',0,1,0,'',''], # unverified + 'cde' => ['^cde',0,'0','CDE',0,1,0,'',''], # unverified + 'chameleonwm' => ['^chameleon',0,'0','ChameleonWM',0,1,0,'',''], # unverified + 'cinnamon' => ['^cinnamon',2,'--version','Cinnamon',0,1,0,'',''], + 'clfswm' => ['^clsfwm',0,'0','clfswm',0,1,0,'',''], # no version + 'comfc' => ['^comfc',0,'0','comfc',0,1,0,'',''], # unverified + 'compiz' => ['^compiz',2,'--version','Compiz',0,1,0,'',''], + 'compton' => ['^\d',1,'--version','Compton',0,1,0,'',''], + 'cosmic-comp' => ['^cosmic-comp',0,'0','cosmic-comp',0,1,0,'',''], # unverified + 'ctwm' => ['^\S',1,'-version','ctwm',0,1,0,'',''], + 'cwm' => ['^cwm',0,'0','CWM',0,1,0,'',''], # no version + 'dcompmgr' => ['^dcompmgr',0,'0','dcompmgr',0,1,0,'',''], # unverified + 'deepin' => ['^Version',2,'file','Deepin',0,100,'=','','/etc/deepin-version'], # special + 'deepin-metacity' => ['^metacity',2,'--version','Deepin-Metacity',0,1,0,'',''], + 'deepin-mutter' => ['^mutter',2,'--version','Deepin-Mutter',0,1,0,'',''], + 'deepin-wm' => ['^gala',0,'0','DeepinWM',0,1,0,'',''], # no version + 'dwc' => ['^dwc',0,'0','dwc',0,1,0,'',''], # unverified + 'dwl' => ['^dwl',0,'0','dwl',0,1,0,'',''], # unverified + 'dwm' => ['^dwm',1,'-v','dwm',0,1,1,'^dwm-',''], + 'echinus' => ['^echinus',1,'-v','echinus',0,1,1,'',''], # echinus-0.4.9 (c)... + # only listed here for compositor values, version data comes from xprop + 'enlightenment' => ['^enlightenment',0,'0','enlightenment',0,1,0,'',''], # no version, yet? + 'epd-wm' => ['^epd-wm',0,'0','epd-wm',0,1,0,'',''], # unverified + 'evilwm' => ['evilwm',3,'-V','evilwm',0,1,0,'',''],# might use full path in match + 'feathers' => ['^feathers',0,'0','feathers',0,1,0,'',''], # unverified + 'fenestra' => ['^fenestra',0,'0','fenestra',0,1,0,'',''], # unverified + 'fireplace' => ['^fireplace',0,'0','fireplace',0,1,0,'',''], # unverified + 'fluxbox' => ['^fluxbox',2,'-v','Fluxbox',0,1,0,'',''], + 'flwm' => ['^flwm',0,'0','FLWM',0,0,1,'',''], # no version + # openbsd changed: version string: [FVWM[[main] Fvwm.. sigh, and outputs to stderr. Why? + 'fvwm' => ['^fvwm',2,'-version','FVWM',0,1,0,'',''], + 'fvwm1' => ['^Fvwm',3,'-version','FVWM1',0,1,1,'',''], + 'fvwm2' => ['^fvwm',2,'--version','FVWM2',0,1,0,'',''], + 'fvwm3' => ['^fvwm',2,'--version','FVWM3',0,1,0,'',''], + 'fvwm95' => ['^fvwm',2,'--version','FVWM95',0,1,1,'',''], + 'fvwm-crystal' => ['^fvwm',2,'--version','FVWM-Crystal',0,0,0,'',''], # for print name fvwm + 'gala' => ['^gala',0,'0','gala',0,1,0,'',''], # pantheon wm: super slow result, 2, '--version' works? + 'gamescope' => ['^gamescope',0,'0','Gamescope',0,1,0,'',''], # unverified + 'glass' => ['^glass',3,'-v','Glass',0,1,0,'',''], + 'gnome' => ['^gnome',3,'--version','GNOME',0,1,0,'',''], # no version, print name + 'gnome-about' => ['^gnome',3,'--version','GNOME',0,1,0,'',''], + 'gnome-shell' => ['^gnome',3,'--version','gnome-shell',0,1,0,'',''], + 'greenfield' => ['^greenfield',0,'0','Greenfield',0,1,0,'',''], # unverified + 'grefson' => ['^grefson',0,'0','Grefson',0,1,0,'',''], # unverified + 'hackedbox' => ['^hackedbox',2,'-version','HackedBox',0,1,0,'',''], # unverified, assume blackbox + # note, herbstluftwm when launched with full path returns full path in version string + 'herbstluftwm' => ['herbstluftwm',2,'--version','herbstluftwm',0,1,0,'',''], + 'hikari' => ['^hikari',0,'0','hikari',0,1,0,'',''], # unverified + 'hopalong' => ['^hopalong',0,'0','Hopalong',0,1,0,'',''], # unverified + 'hyprland' => ['^hyprland',0,'0','Hyprland',0,1,0,'',''], # unverified + 'i3' => ['^i3',3,'--version','i3',0,1,0,'',''], + 'icewm' => ['^icewm',2,'--version','IceWM',0,1,0,'',''], + 'inaban' => ['^inaban',0,'0','inaban',0,1,0,'',''], # unverified + 'instantwm' => ['^instantwm',1,'-v','instantWM',0,1,1,'^instantwm-?(instantos-?)?',''], + 'ion3' => ['^ion3',0,'--version','Ion3',0,1,0,'',''], # unverified; also shell called ion + 'japokwm' => ['^japokwm',0,'0','japokwm',0,1,0,'',''], # unverified + 'jbwm' => ['jbwm',3,'-v','JBWM',0,1,0,'',''], # might use full path in match + 'jwm' => ['^jwm',2,'--version','JWM',0,1,0,'',''], + 'kded' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''], + 'kded1' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''], + 'kded2' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''], + 'kded3' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''], + 'kded4' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''], + 'kiwmi' => ['^kwimi',0,'0','kiwmi',0,1,0,'',''], # unverified + 'ksmcon' => ['^ksmcon',0,'0','ksmcon',0,1,0,'',''],# no version + 'kwin' => ['^kwin',0,'0','kwin',0,1,0,'',''],# no version + 'kwin_wayland' => ['^kwin_wayland',0,'0','kwin_wayland',0,1,0,'',''],# no version + 'kwin_x11' => ['^kwin_x11',0,'0','kwin_x11',0,1,0,'',''],# no version + 'kwinft' => ['^kwinft',0,'0','KWinFT',0,1,0,'',''], # unverified + 'labwc' => ['^labwc',0,'0','LabWC',0,1,0,'',''], # unverified + 'laikawm' => ['^laikawm',0,'0','LaikaWM',0,1,0,'',''], # unverified + 'larswm' => ['^larswm',2,'-v','larswm',0,1,1,'',''], + 'leftwm' => ['^leftwm',0,'0','LeftWM',0,1,0,'',''],# no version, in CHANGELOG + 'liri' => ['^liri',0,'0','liri',0,1,0,'',''], + 'lipstick' => ['^lipstick',0,'0','Lipstick',0,1,0,'',''], # unverified + 'liri' => ['^liri',0,'0','liri',0,1,0,'',''], # unverified + 'lumina-desktop' => ['^\S',1,'--version','Lumina',0,1,1,'',''], + 'lwm' => ['^lwm',0,'0','lwm',0,1,0,'',''], # no version + 'lxpanel' => ['^lxpanel',2,'--version','LXDE',0,1,0,'',''], + # command: lxqt-panel + 'lxqt-panel' => ['^lxqt-panel',2,'--version','LXQt',0,1,0,'',''], + 'lxqt-variant' => ['^lxqt-panel',0,'0','LXQt-Variant',0,1,0,'',''], + 'lxsession' => ['^lxsession',0,'0','lxsession',0,1,0,'',''], + 'mahogany' => ['^mahogany',0,'0','Mahogany',0,1,0,'',''], # unverified + 'manokwari' => ['^manokwari',0,'0','Manokwari',0,1,0,'',''], + 'marina' => ['^marina',0,'0','Marina',0,1,0,'',''], # unverified + 'marco' => ['^marco',2,'--version','marco',0,1,0,'',''], + 'matchbox' => ['^matchbox',0,'0','Matchbox',0,1,0,'',''], + 'matchbox-window-manager' => ['^matchbox',2,'--help','Matchbox',0,0,0,'',''], + 'mate-about' => ['^MATE[[:space:]]DESKTOP',-1,'--version','MATE',0,1,0,'',''], + # note, mate-session when launched with full path returns full path in version string + 'mate-session' => ['mate-session',-1,'--version','MATE',0,1,0,'',''], + 'maze' => ['^maze',0,'0','Maze',0,1,0,'',''], # unverified + 'mcwm' => ['^mcwm',0,'0','mcwm',0,1,0,'',''], # unverified/see 2bwm + 'metacity' => ['^metacity',2,'--version','Metacity',0,1,0,'',''], + 'metisse' => ['^metisse',0,'0','metisse',0,1,0,'',''], + 'mini' => ['^Mini',5,'--version','Mini',0,1,0,'',''], + 'mir' => ['^mir',0,'0','mir',0,1,0,'',''],# unverified + 'moblin' => ['^moblin',0,'0','moblin',0,1,0,'',''],# unverified + 'monsterwm' => ['^monsterwm',0,'0','monsterwm',0,1,0,'',''],# unverified + 'motorcar' => ['^motorcar',0,'0','motorcar',0,1,0,'',''],# unverified + 'muffin' => ['^muffin',2,'--version','Muffin',0,1,0,'',''], + 'musca' => ['^musca',0,'-v','Musca',0,1,0,'',''], # unverified + 'mutter' => ['^mutter',2,'--version','Mutter',0,1,0,'',''], + 'mwm' => ['^mwm',0,'0','MWM',0,1,0,'',''],# no version + 'nawm' => ['^nawm',0,'0','nawm',0,1,0,'',''],# unverified + 'newm' => ['^newm',0,'0','newm',0,1,0,'',''], # unverified + 'notion' => ['^.',1,'--version','Notion',0,1,0,'',''], + 'nscde' => ['^nscde',0,'0','NsCDE',0,1,0,'',''], # unverified + 'nucleus' => ['^nucleus',0,'0','Nucleus',0,1,0,'',''], # unverified + 'openbox' => ['^openbox',2,'--version','Openbox',0,1,0,'',''], + 'orbital' => ['^orbital',0,'0','Orbital',0,1,0,'',''],# unverified + 'pantheon' => ['^pantheon',0,'0','Pantheon',0,1,0,'',''],# no version + 'papyros' => ['^papyros',0,'0','papyros',0,1,0,'',''],# no version + 'pekwm' => ['^pekwm',3,'--version','PekWM',0,1,0,'',''], + 'penrose' => ['^penrose',0,'0','Penrose',0,1,0,'',''],# no version? + 'perceptia' => ['^perceptia',0,'0','perceptia',0,1,0,'',''], + 'phoc' => ['^phoc',0,'0','phoc',0,1,0,'',''], # unverified + 'picom' => ['^\S',1,'--version','Picom',0,1,0,'^v',''], + 'plasmashell' => ['^plasmashell',2,'--version','KDE Plasma',0,1,0,'',''], + 'pywm' => ['^pywm',0,'0','pywm',0,1,0,'',''], # unverified + 'qtile' => ['^',1,'--version','Qtile',0,1,0,'',''], + 'qvwm' => ['^qvwm',0,'0','qvwm',0,1,0,'',''], # unverified + 'razor-session' => ['^razor',0,'0','Razor-Qt',0,1,0,'',''], + 'ratpoison' => ['^ratpoison',2,'--version','Ratpoison',0,1,0,'',''], + 'river' => ['^river',0,'0','River',0,1,0,'',''], # unverified + 'rootston' => ['^rootston',0,'0','rootston',0,1,0,'',''], # unverified, wlroot ref + 'rustland' => ['^rustland',0,'0','rustland',0,1,0,'',''], # unverified + 'sawfish' => ['^sawfish',3,'--version','Sawfish',0,1,0,'',''], + 'scrotwm' => ['^scrotwm.*welcome.*',5,'-v','scrotwm',0,1,1,'',''], + 'simulavr' => ['simulavr^',0,'0','SimulaVR',0,1,0,'',''], # unverified + 'skylight' => ['^skylight',0,'0','Skylight',0,1,0,'',''], # unverified + 'smithay' => ['^smithay',0,'0','Smithay',0,1,0,'',''], # unverified + 'sommelier' => ['^sommelier',0,'0','sommelier',0,1,0,'',''], # unverified + 'snapwm' => ['^snapwm',0,'0','snapwm',0,1,0,'',''], # unverified + 'spectrwm' => ['^spectrwm.*welcome.*wm',5,'-v','spectrwm',0,1,1,'',''], + # out of stump, 2 --version, but in tries to start new wm instance endless hang + 'stumpwm' => ['^SBCL',0,'--version','StumpWM',0,1,0,'',''], # hangs when run in wm + 'sway' => ['^sway',3,'-v','sway',0,1,0,'',''], + 'swc' => ['^swc',0,'0','swc',0,1,0,'',''], # unverified + 'swvkc' => ['^swvkc',0,'0','swvkc',0,1,0,'',''], # unverified + 'tabby' => ['^tabby',0,'0','Tabby',0,1,0,'',''], # unverified + 'taiwins' => ['^taiwins',0,'0','taiwins',0,1,0,'',''], # unverified + 'tinybox' => ['^tinybox',0,'0','tinybox',0,1,0,'',''], # unverified + 'tinywl' => ['^tinywl',0,'0','TinyWL',0,1,0,'',''], # unverified + 'tinywm' => ['^tinywm',0,'0','TinyWM',0,1,0,'',''], # no version + 'trinkster' => ['^trinkster',0,'0','Trinkster',0,1,0,'',''], # unverified + 'tvtwm' => ['^tvtwm',0,'0','tvtwm',0,1,0,'',''], # unverified + 'twin' => ['^Twin:',2,'--version','Twin',0,0,0,'',''], + 'twm' => ['^twm',0,'0','TWM',0,1,0,'',''], # no version + 'ukui' => ['^ukui-session',2,'--version','UKUI',0,1,0,'',''], + 'ukwm' => ['^ukwm',2,'--version','ukwm',0,1,0,'',''], + 'unagi' => ['^\S',1,'--version','unagi',0,1,0,'',''], + 'unity' => ['^unity',2,'--version','Unity',0,1,0,'',''], + 'unity-system-compositor' => ['^unity-system-compositor',2,'--version', + 'unity-system-compositor (mir)',0,0,0,'',''], + 'uwm' => ['^uwm',0,'0','UWM',0,1,0,'',''], # unverified + 'velox' => ['^velox',0,'0','Velox',0,1,0,'',''], # unverified + 'vimway' => ['^vimway',0,'0','vimway',0,1,0,'',''], # unverified + 'vivarium' => ['^vivarium',0,'0','Vivarium',0,1,0,'',''], # unverified + 'wavy' => ['^wavy',0,'0','wavy',0,1,0,'',''], # unverified + 'waybox' => ['^way',0,'0','waybox',0,1,0,'',''], # unverified + 'waycooler' => ['^way',3,'--version','way-cooler',0,1,0,'',''], + 'way-cooler' => ['^way',3,'--version','way-cooler',0,1,0,'',''], + 'wayfire' => ['^\d',1,'--version','wayfire',0,1,0,'',''], # -version/--version + 'wayhouse' => ['^wayhouse',0,'0','wayhouse',0,1,0,'',''], # unverified + 'waymonad' => ['^waymonad',0,'0','waymonad',0,1,0,'',''], # unverified + 'westeros' => ['^westeros',0,'0','westeros',0,1,0,'',''], # unverified + 'westford' => ['^westford',0,'0','westford',0,1,0,'',''], # unverified + 'weston' => ['^weston',0,'0','Weston',0,1,0,'',''], # unverified + 'windowlab' => ['^windowlab',2,'-about','WindowLab',0,1,0,'',''], + 'wingo' => ['^wingo',0,'0','Wingo',0,1,0,'',''], # unverified + 'wio' => ['^wio',0,'0','Wio',0,1,0,'',''], # unverified + 'wio' => ['^wio\+',0,'0','wio+',0,1,0,'',''], # unverified + 'wm2' => ['^wm2',0,'0','wm2',0,1,0,'',''], # no version + 'wmaker' => ['^Window[[:space:]]*Maker',-1,'--version','WindowMaker',0,1,0,'',''], + 'wmfs' => ['^wmfs',0,'0','WMFS',0,1,0,'',''], # unverified + 'wmfs2' => ['^wmfs',0,'0','WMFS',0,1,0,'',''], # unverified + 'wmii' => ['^wmii',1,'-v','wmii',0,1,0,'^wmii[234]?-',''], # wmii is wmii3 + 'wmii2' => ['^wmii2',1,'--version','wmii2',0,1,0,'^wmii[234]?-',''], + 'wmx' => ['^wmx',0,'0','wmx',0,1,0,'',''], # no version + 'wxrc' => ['^wx',0,'0','',0,1,0,'WXRC',''], # unverified + 'wxrd' => ['^wx',0,'0','',0,1,0,'WXRD',''], # unverified + 'xcompmgr' => ['^xcompmgr',0,'0','xcompmgr',0,1,0,'',''], # no version + 'xfce-panel' => ['^xfce-panel',2,'--version','Xfce',0,1,0,'',''], + 'xfce4-panel' => ['^xfce4-panel',2,'--version','Xfce',0,1,0,'',''], + 'xfce5-panel' => ['^xfce5-panel',2,'--version','Xfce',0,1,0,'',''], + 'xfdesktop' => ['xfdesktop[[:space:]]version',5,'--version','Xfce',0,1,0,'',''], + # command: xfdesktop + 'xfdesktop-toolkit' => ['Built[[:space:]]with[[:space:]]GTK',4,'--version','Gtk',0,1,0,'',''], + # ' This is xfwm4 version 4.16.1 (revision 5f61a84ad) for Xfce 4.16' + 'xfwm' => ['xfwm[3-8]? version',5,'--version','xfwm',0,1,0,'^^\s+',''],# unverified + 'xfwm4' => ['xfwm4? version',5,'--version','xfwm',0,1,0,'^^\s+',''], + 'xfwm5' => ['xfwm5? version',5,'--version','xfwm',0,1,0,'^^\s+',''], # unverified + 'xmonad' => ['^xmonad',2,'--version','XMonad',0,1,0,'',''], + 'xuake' => ['^xuake',0,'0','xuake',0,1,0,'',''], # unverified + 'yeahwm' => ['^yeahwm',0,'--version','YeahWM',0,1,0,'',''], # unverified + ## Toolkits ## + 'gtk-launch' => ['^\S',1,'--version','GTK',0,1,0,'',''], + 'qmake' => ['^^Using Qt version',4,'--version','Qt',0,0,0,'',''], + 'qtdiag' => ['^qt',2,'--version','Qt',0,1,0,'',''], + ## Display Managers (dm) ## + 'brzdm' => ['^brzdm version',3,'-v','brzdm',0,1,0,'',''], # unverified, slim fork + 'cdm' => ['^cdm',0,'0','CDM',0,1,0,'',''], + # might be xlogin, unknown output for -V + 'clogin' => ['^clogin',0,'-V','clogin',0,1,0,'',''], # unverified, maybe xlogin + 'emptty' => ['^emptty',0,'0','EMPTTY',0,1,0,'',''], # unverified + 'entrance' => ['^entrance',0,'0','Entrance',0,1,0,'',''], + 'gdm' => ['^gdm',2,'--version','GDM',0,1,0,'',''], + 'gdm3' => ['^gdm',2,'--version','GDM3',0,1,0,'',''], + 'greetd' => ['^greetd',0,'0','greetd',0,1,0,'',''], # no version + 'kdm' => ['^kdm',0,'0','KDM',0,1,0,'',''], + 'kdm3' => ['^kdm',0,'0','KDM',0,1,0,'',''], + 'kdmctl' => ['^kdm',0,'0','KDM',0,1,0,'',''], + 'ldm' => ['^ldm',0,'0','LDM',0,1,0,'',''], + 'lightdm' => ['^lightdm',2,'--version','LightDM',0,1,1,'',''], + 'lxdm' => ['^lxdm',0,'0','LXDM',0,1,0,'',''], + 'ly' => ['^ly',3,'--version','Ly',0,1,0,'',''], + 'mdm' => ['^mdm',0,'0','MDM',0,1,0,'',''], + 'mlogin' => ['^mlogin',0,'0','mlogin',0,1,0,'',''], # unverified + 'nodm' => ['^nodm',0,'0','nodm',0,1,0,'',''], + 'pcdm' => ['^pcdm',0,'0','PCDM',0,1,0,'',''], + 'qingy' => ['^qingy',0,'0','qingy',0,1,0,'',''], # unverified + 'sddm' => ['^sddm',0,'0','SDDM',0,1,0,'',''], + 'slim' => ['slim version',3,'-v','SLiM',0,1,0,'',''], + 'slimski' => ['slimski version',3,'-v','slimski',0,1,0,'',''], # slim fork + 'tbsm' => ['^tbsm',0,'0','tbsm',0,1,0,'',''], # unverified + 'tdm' => ['^tdm',0,'0','TDM',0,1,0,'',''], + 'udm' => ['^udm',0,'0','udm',0,1,0,'',''], + 'wdm' => ['^wdm',0,'0','WINGs DM',0,1,0,'',''], + 'xdm' => ['^xdm',0,'0','XDM',0,1,0,'',''], + 'xdmctl' => ['^xdm',0,'0','XDM',0,1,0,'',''],# opensuse/redhat may use this to start real dm + 'xenodm' => ['^xenodm',0,'0','xenodm',0,1,0,'',''], + 'xlogin' => ['^xlogin',0,'-V','xlogin',0,1,0,'',''], # unverified, probably clogin + ## Shells - not checked: ion, eshell ## + ## See ShellData::shell_test() for unhandled but known shells + 'ash' => ['',3,'pkg','ash',1,0,0,'',''], # special; dash precursor + 'bash' => ['^GNU[[:space:]]bash',4,'--version','Bash',1,1,0,'',''], + 'busybox' => ['^busybox',0,'0','BusyBox',1,0,0,'',''], # unverified, hush/ash likely + 'cicada' => ['^\s*version',2,'cmd','cicada',1,1,0,'',''], # special + 'csh' => ['^tcsh',2,'--version','csh',1,1,0,'',''], # mapped to tcsh often + 'dash' => ['',3,'pkg','DASH',1,0,0,'',''], # no version, pkg query + 'elvish' => ['^\S',1,'--version','Elvish',1,0,0,'',''], + 'fish' => ['^fish',3,'--version','fish',1,0,0,'',''], + 'fizsh' => ['^fizsh',3,'--version','FIZSH',1,0,0,'',''], + # ksh/lksh/loksh/mksh/posh//pdksh need to print their own $VERSION info + 'ksh' => ['^\S',1,'cmd','ksh',1,0,0,'^(Version|.*KSH)\s*',''], # special + 'ksh93' => ['^\S',1,'cmd','ksh93',1,0,0,'^(Version|.*KSH)\s*',''], # special + 'lksh' => ['^\S',1,'cmd','lksh',1,0,0,'^.*KSH\s*',''], # special + 'loksh' => ['^\S',1,'cmd','loksh',1,0,0,'^.*KSH\s*',''], # special + 'mksh' => ['^\S',1,'cmd','mksh',1,0,0,'^.*KSH\s*',''], # special + 'nash' => ['^nash',0,'0','Nash',1,0,0,'',''], # unverified; rc based [no version] + 'oh' => ['^oh',0,'0','Oh',1,0,0,'',''], # no version yet + 'oil' => ['^Oil',3,'--version','Oil',1,1,0,'',''], # could use cmd $OIL_SHELL + 'osh' => ['^osh',3,'--version','OSH',1,1,0,'',''], # precursor of oil + 'pdksh' => ['^\S',1,'cmd','pdksh',1,0,0,'^.*KSH\s*',''], # special, in ksh family + 'posh' => ['^\S',1,'cmd','posh',1,0,0,'',''], # special, in ksh family + 'tcsh' => ['^tcsh',2,'--version','tcsh',1,1,0,'',''], # enhanced csh + 'xonsh' => ['^xonsh',1,'--version','xonsh',1,0,0,'^xonsh[\/-]',''], + 'yash' => ['^Y',5,'--version','yash',1,0,0,'',''], + 'zsh' => ['^zsh',2,'--version','Zsh',1,0,0,'',''], + ## Tools ## + 'clang' => ['clang',3,'--version','Clang',1,0,0,'',''], + 'gcc' => ['^gcc',3,'--version','GCC',1,0,0,'',''], + 'gcc-apple' => ['Apple[[:space:]]LLVM',2,'--version','LLVM',1,0,0,'',''], + 'sudo' => ['^Sudo',3,'-V','Sudo',1,1,0,'',''], # sudo pre 1.7 does not have --version + ); +} + +# returns array of: +# 0: match string; 1: search number; 2: version string [alt: file]; +# 3: Print name; 4: console 0/1; +# 5: 0/1 exit version loop at 1 [alt: if version=file replace value with \s]; +# 6: 0/1 write to stderr [alt: if version=file, path for file]; +# 7: replace regex for further cleanup; 8: extra data +# note: setting index 1 or 2 to 0 will trip flags to not do version +# args: 0: program lower case name +sub program_values { + my ($app) = @_; + my (@program_data); + set_program_values() if !%program_values; + if (defined $program_values{$app}){ + @program_data = @{$program_values{$app}}; + } + # my $debug = Dumper \@program_data; + log_data('dump',"Program Data",\@program_data) if $b_log; + return @program_data; +} + +# args: 0: desktop/app command for --version; 1: search string; +# 2: space print number; 3: [optional] version arg: -v, version, etc; +# 4: [optional] exit first find 0/1; 5: [optional] 0/1 stderr output; +# 6: replace regex; 7: extra data +sub program_version { + eval $start if $b_log; + my ($app,$search,$num,$version,$exit,$stderr,$replace,$extra) = @_; + my ($b_no_space,$cmd,$line,$output); + my $version_nu = ''; + my $count = 0; + my $app_name = $app; + $app_name =~ s%^.*/%%; + # print "app: $app :: appname: $app_name\n"; + $exit ||= 100; # basically don't exit ever + $version ||= '--version'; + # adjust to array index, not human readable + $num-- if (defined $num && $num > 0); + # konvi in particular doesn't like using $ENV{'PATH'} as set, so we need + # to always assign the full path if it hasn't already been done + if ($version ne 'file' && $app !~ /^\//){ + if (my $program = check_program($app)){ + $app = $program; + } + else { + log_data('data',"$app not found in path.") if $b_log; + return 0; + } + } + if ($version eq 'file'){ + return 0 unless $extra && -r $extra; + my @data = reader($extra,'strip'); + @data = map {s/$stderr/ /;$_} @data if $stderr; # $stderr is the splitter + $output = join("\n", @data); + $cmd = ''; + } + # These will mostly be shells that require running the shell command -c to get info data + elsif ($version eq 'cmd'){ + ($cmd,$b_no_space) = program_version_cmd($app,$app_name,$extra); + return 0 if !$cmd; + } + # slow: use pkg manager to get version, avoid unless you really want version + elsif ($version eq 'pkg'){ + ($cmd,$search) = program_version_pkg($app_name); + return 0 if !$cmd; + } + # note, some wm/apps send version info to stderr instead of stdout + elsif ($stderr){ + $cmd = "$app $version 2>&1"; + } + else { + $cmd = "$app $version 2>/dev/null"; + } + log_data('data',"version: $version num: $num search: $search command: $cmd") if $b_log; + # special case, in rare instances version comes from file + if ($version ne 'file'){ + $output = qx($cmd); + log_data('data',"output: $output") if $b_log; + } + # print "cmd: $cmd\noutput:\n$output\n"; + # sample: dwm-5.8.2, ©.. etc, why no space? who knows. Also get rid of v in number string + # xfce, and other, output has , in it, so dump all commas and parentheses + if ($output){ + open(my $ch, '<', \$output) or error_handler('open-data',"$cmd", "$!"); + while (<$ch>){ + #chomp; + last if $count > $exit; + if ($_ =~ /$search/i){ + $_ = trimmer($_); + # print "loop: $_ :: num: $num\n"; + $_ =~ s/$replace//i if $replace; + $_ =~ s/\s/_/g if $b_no_space; # needed for some items with version > 1 word + my @data = split(/\s+/, $_); + $version_nu = $data[$num]; + last if ! defined $version_nu; + # some distros add their distro name before the version data, which + # breaks version detection. A quick fix attempt is to just add 1 to $num + # to get the next value. + $version_nu = $data[$num+1] if $data[$num+1] && $version_nu =~ /version/i; + $version_nu =~ s/(\([^)]+\)|,|"|\||\(|\))//g if $version_nu; + # trim off leading v but only when followed by a number + $version_nu =~ s/^v([0-9])/$1/i if $version_nu; + # print "$version_nu\n"; + last; + } + $count++; + } + close $ch if $ch; + } + log_data('data',"Program version: $version_nu") if $b_log; + eval $end if $b_log; + return $version_nu; +} +# print program_version('bash', 'bash', 4) . "\n"; + +# returns ($cmdd, $b_no_space) +# ksh: Version JM 93t+ 2010-03-05 [OR] Version A 2020.0.0 +# mksh: @(#)MIRBSD KSH R56 2018/03/09; lksh/pdksh: @(#)LEGACY KSH R56 2018/03/09 +# loksh: @(#)PD KSH v5.2.14 99/07/13.2; posh: 0.13.2 +sub program_version_cmd { + eval $start if $b_log; + my ($app,$app_name,$extra) = @_; + my @data = ('',0); + if ($app_name eq 'cicada'){ + $data[0] = $app . ' -c "' . $extra . '" 2>/dev/null';} + elsif ($app_name =~ /^(|l|lo|m|pd)ksh(93)?$/){ + $data[0] = $app . ' -c \'printf %s "$KSH_VERSION"\' 2>/dev/null'; + $data[1] = 1;} + elsif ($app_name eq 'posh'){ + $data[0] = $app . ' -c \'printf %s "$POSH_VERSION"\' 2>/dev/null'} + # print "$data[0] :: $data[1]\n"; + eval $end if $b_log; + return @data; +} + +# returns $cmd, $search +sub program_version_pkg { + eval $start if $b_log; + my ($app) = @_; + my ($program,@data); + # note: version $num is 3 in dpkg-query/pacman/rpm, which is convenient + if ($program = check_program('dpkg-query')){ + $data[0] = "$program -W -f='\${Package}\tversion\t\${Version}\n' $app 2>/dev/null"; + $data[1] = "^$app\\b"; + } + elsif ($program = check_program('pacman')){ + $data[0] = "$program -Q --info $app 2>/dev/null"; + $data[1] = '^Version'; + } + elsif ($program = check_program('rpm')){ + $data[0] = "$program -qi --nodigest --nosignature $app 2>/dev/null"; + $data[1] = '^Version'; + } + # print "$data[0] :: $data[1]\n"; + eval $end if $b_log; + return @data; +} + +# args: 0: full file path, returns array of file lines; +# 1: optionsl, strip and clean data; +# 2: optional: undef|arr|ref|index return specific index, if it exists, else undef +# note: chomp has to chomp the entire action, not just <$fh> +sub reader { + eval $start if $b_log; + my ($file,$strip,$type) = @_; + return if !$file || ! -r $file; # not all OS respect -r tests!! + $type = 'arr' if !defined $type; + my ($error,@rows); + open(my $fh, '<', $file) or $error = $!; # $fh always non null, even on error + if ($error){ + error_handler('open', $file, $error); + } + else { + chomp(@rows = <$fh>); + close $fh; + if (@rows && $strip){ + my @temp; + for (@rows){ + next if /^\s*(#|$)/; + $_ =~ s/^\s+|\s+$//g; + push(@temp,$_); + } + @rows = @temp; + } + } + eval $end if $b_log; + return @rows if $type eq 'arr'; + return \@rows if $type eq 'ref'; + # note: returns undef scalar value if $rows[index] does not exist + return $rows[$type]; +} + +# args: 0: the file to create if not exists +sub toucher { + my $file = shift; + if (! -e $file){ + open(my $fh, '>', $file) or error_handler('create', $file, $!); + } +} + +# calling it trimmer to avoid conflicts with existing trim stuff +# args: 0: string to be right left trimmed. Also slices off \n so no chomp needed +# this thing is super fast, no need to log its times etc, 0.0001 seconds or less +sub trimmer { + # eval $start if $b_log; + my ($str) = @_; + $str =~ s/^\s+|\s+$|\n$//g; + # eval $end if $b_log; + return $str; +} + +# args: 0: array, by ref, modifying by ref +# send array, assign to hash, changed array by reference, uniq values only. +sub uniq { + my %seen; + @{$_[0]} = grep !$seen{$_}++, @{$_[0]}; +} + +# args: 0: file full path to write to; 1: array ref or scalar of data to write. +# note: turning off strict refs so we can pass it a scalar or an array reference. +sub writer { + my ($path, $content) = @_; + my ($contents); + no strict 'refs'; + # print Dumper $content, "\n"; + if (ref $content eq 'ARRAY'){ + $contents = join("\n", @$content); # or die "failed with error $!"; + } + else { + $contents = $content; + } + open(my $fh, ">", $path) or error_handler('open',"$path", "$!"); + print $fh $contents; + close $fh; +} + +#### ------------------------------------------------------------------- +#### UPDATER +#### ------------------------------------------------------------------- + +# args: 0: type to return +sub get_defaults { + my ($type) = @_; + my %defaults = ( + 'ftp-upload' => 'ftp.smxi.org/incoming', + 'inxi-branch-1' => 'https://codeberg.org/smxi/inxi/raw/one/', + 'inxi-branch-2' => 'https://codeberg.org/smxi/inxi/raw/two/', + "$self_name-dev" => 'https://smxi.org/in/', + "$self_name-dev-ftp" => 'ftp://ftp.smxi.org/outgoing/', + "inxi-main" => 'https://codeberg.org/smxi/inxi/raw/master/', + 'pinxi-main' => 'https://codeberg.org/smxi/pinxi/raw/master/', + ); + if ($defaults{$type}){ + return $defaults{$type}; + } + else { + error_handler('bad-arg-int', $type); + } +} + +# args: 0: download url, not including file name; 1: string to print out +# 2: update type option +# note that 0 must end in / to properly construct the url path +sub update_me { + eval $start if $b_log; + my ($self_download,$download_id) = @_; + my $downloader_error=1; + my $file_contents=''; + my $output = ''; + $self_path =~ s/\/$//; # dirname sometimes ends with /, sometimes not + $self_download =~ s/\/$//; # dirname sometimes ends with /, sometimes not + my $full_self_path = "$self_path/$self_name"; + if ($b_irc){ + error_handler('not-in-irc', "-U/--update") + } + if (! -w $full_self_path){ + error_handler('not-writable', "$self_name", ''); + } + $output .= "Starting $self_name self updater.\n"; + $output .= "Using $dl{'dl'} as downloader.\n"; + $output .= "Currently running $self_name version number: $self_version\n"; + $output .= "Current version patch number: $self_patch\n"; + $output .= "Current version release date: $self_date\n"; + $output .= "Updating $self_name in $self_path using $download_id as download source...\n"; + print $output; + $output = ''; + $self_download = "$self_download/$self_name"; + $file_contents = download_file('stdout', $self_download); + # then do the actual download + if ($file_contents){ + # make sure the whole file got downloaded and is in the variable + print "Validating downloaded data...\n"; + if ($file_contents =~ /###\*\*EOF\*\*###/){ + open(my $fh, '>', $full_self_path); + print $fh $file_contents or error_handler('write', $full_self_path, "$!"); + close $fh; + qx(chmod +x '$self_path/$self_name'); + set_version_data(); + $output .= "Successfully updated to $download_id version: $self_version\n"; + $output .= "New $download_id version patch number: $self_patch\n"; + $output .= "New $download_id version release date: $self_date\n"; + $output .= "To run the new version, just start $self_name again.\n"; + $output .= "$line3\n"; + print $output; + $output = ''; + if ($use{'man'}){ + update_man($self_download,$download_id); + } + else { + print "Skipping man download because branch version is being used.\n"; + } + exit 0; + } + else { + error_handler('file-corrupt', "$self_name"); + } + } + # now run the error handlers on any downloader failure + else { + error_handler('download-error', $self_download, $download_id); + } + eval $end if $b_log; +} + +sub update_man { + eval $start if $b_log; + my ($self_download,$download_id) = @_; + my $man_file_location = set_man_location(); + my $man_file_path = "$man_file_location/$self_name.1" ; + my ($file_contents,$man_file_url,$output,$program) = ('','','',''); + print "Starting download of man page file now.\n"; + if (! -d $man_file_location){ + print "The required man directory was not detected on your system.\n"; + print "Unable to continue: $man_file_location\n"; + return 0; + } + if (! -w $man_file_location){ + print "Cannot write to $man_file_location! Root privileges required.\n"; + print "Unable to continue: $man_file_location\n"; + return 0; + } + if (-f "/usr/share/man/man8/inxi.8.gz"){ + print "Updating man page location to man1.\n"; + rename "/usr/share/man/man8/inxi.8.gz", "$man_file_location/inxi.1.gz"; + if (check_program('mandb')){ + system('mandb'); + } + } + if (!($program = check_program('gzip'))){ + print "Required program gzip not found. Unable to install man page.\n"; + return 0; + } + # first choice is inxi.1/pinxi.1 from gh, second from smxi.org + $man_file_url = $self_download . '.1'; + print "Updating $self_name.1 in $man_file_location\n"; + print "using $download_id branch as download source\n"; + print "Downloading man page file...\n"; + print "Download URL: $man_file_url\n" if $dbg[1]; + $file_contents = download_file('stdout', $man_file_url); + if ($file_contents){ + # make sure the whole file got downloaded and is in the variable + print "Download successful. Validating downloaded man file data...\n"; + if ($file_contents =~ m|\.\\" EOF|){ + print "Contents validated. Writing to man location...\n"; + open(my $fh, '>', $man_file_path); + print $fh $file_contents or error_handler('write', $man_file_path, "$!"); + close $fh; + print "Writing successful. Compressing file...\n"; + system("$program -9 -f $man_file_path > $man_file_path.gz"); + my $err = $?; + if ($err > 0){ + print "Oh no! Something went wrong compressing the man file!\n"; + print "Error: $err\n"; + } + else { + print "Download, install, and compression of man page successful.\n"; + print "Check to make sure it works: man $self_name\n"; + } + } + else { + error_handler('file-corrupt', "$self_name.1"); + } + } + # now run the error handlers on any downloader failure + else { + error_handler('download-error', $man_file_url, $download_id); + } + eval $end if $b_log; +} + +sub set_man_location { + my $location=''; + my $default_location='/usr/share/man/man1'; + my $man_paths=qx(man --path 2>/dev/null); + my $man_local='/usr/local/share/man'; + my $b_use_local=0; + if ($man_paths && $man_paths =~ /$man_local/){ + $b_use_local=1; + } + # for distro installs + if (-f "$default_location/inxi.1.gz"){ + $location=$default_location; + } + else { + if ($b_use_local){ + if (! -d "$man_local/man1"){ + mkdir "$man_local/man1"; + } + $location="$man_local/man1"; + } + } + if (!$location){ + $location=$default_location; + } + return $location; +} + +# update for updater output version info +# note, this is only now used for self updater function so it can get +# the values from the UPDATED file, NOT the running program! +sub set_version_data { + open(my $fh, '<', "$self_path/$self_name"); + while (my $row = <$fh>){ + chomp($row); + $row =~ s/'|;//g; + if ($row =~ /^my \$self_name/){ + $self_name = (split('=', $row))[1]; + } + elsif ($row =~ /^my \$self_version/){ + $self_version = (split('=', $row))[1]; + } + elsif ($row =~ /^my \$self_date/){ + $self_date = (split('=', $row))[1]; + } + elsif ($row =~ /^my \$self_patch/){ + $self_patch = (split('=', $row))[1]; + } + elsif ($row =~ /^## END INXI INFO/){ + last; + } + } + close $fh; +} + +######################################################################## +#### OPTIONS HANDLER / VERSION +######################################################################## + +## OptionsHandler +{ +package OptionsHandler; +# note: had %trigger local but tripped odd perl 5.008 failures unless global +# so moved to %use and %show globals. +my ($self_download,$download_id); + +sub get { + eval $start if $b_log; + $show{'short'} = 1; + Getopt::Long::GetOptions ( + 'a|admin' => sub { + $b_admin = 1;}, + 'A|audio' => sub { + $show{'short'} = 0; + $show{'audio'} = 1;}, + 'b|basic' => sub { + $show{'short'} = 0; + $show{'battery'} = 1; + $show{'cpu-basic'} = 1; + $show{'raid-basic'} = 1; + $show{'disk-total'} = 1; + $show{'graphic'} = 1; + $show{'graphic-basic'} = 1; + $show{'info'} = 1; + $show{'machine'} = 1; + $show{'network'} = 1; + $show{'system'} = 1;}, + 'B|battery' => sub { + $show{'short'} = 0; + $show{'battery'} = 1; + $show{'battery-forced'} = 1;}, + 'c|color:i' => sub { + my ($opt,$arg) = @_; + if ($arg >= 0 && $arg < main::get_color_scheme('count')){ + main::set_color_scheme($arg); + } + elsif ($arg >= 94 && $arg <= 99){ + $colors{'selector'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'C|cpu' => sub { + $show{'short'} = 0; + $show{'cpu'} = 1;}, + 'config|configs|configuration|configurations' => sub { + $show{'configs'} = 1;}, + 'd|disk-full|optical' => sub { + $show{'short'} = 0; + $show{'disk'} = 1; + $show{'optical'} = 1;}, + 'D|disk' => sub { + $show{'short'} = 0; + $show{'disk'} = 1;}, + 'E|bluetooth' => sub { + $show{'short'} = 0; + $show{'bluetooth'} = 1; + $show{'bluetooth-forced'} = 1;}, + 'edid' => sub { + $b_admin = 1; + $show{'short'} = 0; + $show{'edid'} = 1; + $show{'graphic'} = 1; + $show{'graphic-full'} = 1;}, + 'f|flags|flag' => sub { + $show{'short'} = 0; + $show{'cpu'} = 1; + $show{'cpu-flag'} = 1;}, + 'F|full' => sub { + $show{'short'} = 0; + $show{'audio'} = 1; + $show{'battery'} = 1; + $show{'bluetooth'} = 1; + $show{'cpu'} = 1; + $show{'disk'} = 1; + $show{'graphic'} = 1; + $show{'graphic-basic'} = 1; + $show{'graphic-full'} = 1; + $show{'info'} = 1; + $show{'machine'} = 1; + $show{'network'} = 1; + $show{'network-advanced'} = 1; + $show{'partition'} = 1; + $show{'raid'} = 1; + $show{'sensor'} = 1; + $show{'swap'} = 1; + $show{'system'} = 1;}, + 'gpu|nvidia|nv' => sub { + $b_admin = 1; + $show{'short'} = 0; + $show{'graphic'} = 1; + $show{'graphic-full'} = 1;}, + 'G|graphics|graphic' => sub { + $show{'short'} = 0; + $show{'graphic'} = 1; + $show{'graphic-basic'} = 1; + $show{'graphic-full'} = 1;}, + 'h|help|?' => sub { + $show{'help'} = 1;}, + 'i|ip' => sub { + $show{'short'} = 0; + $show{'ip'} = 1; + $show{'network'} = 1; + $show{'network-advanced'} = 1; + $use{'downloader'} = 1 if ! main::check_program('dig');}, + 'I|info' => sub { + $show{'short'} = 0; + $show{'info'} = 1;}, + 'j|swap|swaps' => sub { + $show{'short'} = 0; + $show{'swap'} = 1;}, + 'J|usb' => sub { + $show{'short'} = 0; + $show{'usb'} = 1;}, + 'l|labels|label' => sub { + $show{'label'} = 1;}, + 'limit:i' => sub { + my ($opt,$arg) = @_; + if ($arg != 0){ + $limit = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'L|logical|lvm' => sub { + $show{'short'} = 0; + $show{'logical'} = 1;}, + 'm|memory' => sub { + $show{'short'} = 0; + $show{'ram'} = 1;}, + 'memory-modules|mm' => sub { + $show{'short'} = 0; + $show{'ram'} = 1; + $show{'ram-modules'} = 1;}, + 'memory-short|ms' => sub { + $show{'short'} = 0; + $show{'ram'} = 1; + $show{'ram-short'} = 1;}, + 'M|machine' => sub { + $show{'short'} = 0; + $show{'machine'} = 1;}, + 'n|network-advanced' => sub { + $show{'short'} = 0; + $show{'network'} = 1; + $show{'network-advanced'} = 1;}, + 'N|network' => sub { + $show{'short'} = 0; + $show{'network'} = 1;}, + 'o|unmounted' => sub { + $show{'short'} = 0; + $show{'unmounted'} = 1;}, + 'p|partition-full|partitions-full' => sub { + $show{'short'} = 0; + $show{'partition'} = 0; + $show{'partition-full'} = 1;}, + 'P|partitions|partition' => sub { + $show{'short'} = 0; + $show{'partition'} = 1;}, + 'partition-sort:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(dev-base|fs|id|label|percent-used|size|uuid|used)$/){ + $show{'partition-sort'} = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'r|repos|repo' => sub { + $show{'short'} = 0; + $show{'repo'} = 1;}, + 'R|raid' => sub { + $show{'short'} = 0; + $show{'raid'} = 1; + $show{'raid-forced'} = 1;}, + 's|sensors|sensor' => sub { + $show{'short'} = 0; + $show{'sensor'} = 1;}, + 'separator|sep:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + $sep{'s1-console'} = $arg; + $sep{'s2-console'} = $arg; + $sep{'s1-irc'} = $arg; + $sep{'s2-irc'} = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'sleep:s' => sub { + my ($opt,$arg) = @_; + $arg ||= 0; + if ($arg >= 0){ + $cpu_sleep = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'slots|slot' => sub { + $show{'short'} = 0; + $show{'slot'} = 1;}, + 'S|system' => sub { + $show{'short'} = 0; + $show{'system'} = 1;}, + 't|processes|process:s' => sub { + my ($opt,$arg) = @_; + $show{'short'} = 0; + $arg ||= 'cm'; + my $num = $arg; + $num =~ s/^[cm]+// if $num; + if ($arg =~ /^([cm]+)([0-9]+)?$/ && (!$num || $num =~ /^\d+/)){ + $show{'process'} = 1; + if ($arg =~ /c/){ + $show{'ps-cpu'} = 1; + } + if ($arg =~ /m/){ + $show{'ps-mem'} = 1; + } + $ps_count = $num if $num; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'u|uuid' => sub { + $show{'uuid'} = 1;}, + 'v|verbosity:i' => sub { + my ($opt,$arg) = @_; + $show{'short'} = 0; + if ($arg =~ /^[0-8]$/){ + if ($arg == 0){ + $show{'short'} = 1; + } + if ($arg >= 1){ + $show{'cpu-basic'} = 1; + $show{'disk-total'} = 1; + $show{'graphic'} = 1; + $show{'graphic-basic'} = 1; + $show{'info'} = 1; + $show{'system'} = 1; + } + if ($arg >= 2){ + $show{'battery'} = 1; + $show{'disk-basic'} = 1; + $show{'raid-basic'} = 1; + $show{'machine'} = 1; + $show{'network'} = 1; + } + if ($arg >= 3){ + $show{'network-advanced'} = 1; + $show{'cpu'} = 1; + $extra = 1; + } + if ($arg >= 4){ + $show{'disk'} = 1; + $show{'partition'} = 1; + } + if ($arg >= 5){ + $show{'audio'} = 1; + $show{'bluetooth'} = 1; + $show{'graphic-full'} = 1; + $show{'label'} = 1; + $show{'optical-basic'} = 1; + $show{'raid'} = 1; + $show{'ram'} = 1; + $show{'sensor'} = 1; + $show{'swap'} = 1; + $show{'uuid'} = 1; + } + if ($arg >= 6){ + $show{'optical'} = 1; + $show{'partition-full'} = 1; + $show{'unmounted'} = 1; + $show{'usb'} = 1; + $extra = 2; + } + if ($arg >= 7){ + $use{'downloader'} = 1 if !main::check_program('dig'); + $show{'battery-forced'} = 1; + $show{'bluetooth-forced'} = 1; + $show{'cpu-flag'} = 1; + $show{'ip'} = 1; + $show{'logical'} = 1; + $show{'raid-forced'} = 1; + $extra = 3; + } + if ($arg >= 8){ + $b_admin = 1; + # $use{'downloader'} = 1; # only if weather + $force{'pkg'} = 1; + $show{'edid'} = 1; + $show{'process'} = 1; + $show{'ps-cpu'} = 1; + $show{'ps-mem'} = 1; + $show{'repo'} = 1; + $show{'slot'} = 1; + # $show{'weather'} = 1; + } + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'V|version' => sub { + $show{'version'} = 1;}, + 'version-short|vs' => sub { + $show{'version-short'} = 1;}, + 'w|weather' => sub { + my ($opt) = @_; + $show{'short'} = 0; + $use{'downloader'} = 1; + if ($use{'weather'}){ + $show{'weather'} = 1; + } + else { + main::error_handler('distro-block', $opt); + }}, + 'W|weather-location:s' => sub { + my ($opt,$arg) = @_; + $arg ||= ''; + $arg =~ s/\s//g; + $show{'short'} = 0; + $use{'downloader'} = 1; + if ($use{'weather'}){ + if ($arg){ + $show{'weather'} = 1; + $show{'weather-location'} = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + } + } + else { + main::error_handler('distro-block', $opt); + }}, + 'ws|weather-source:s' => sub { + my ($opt,$arg) = @_; + # let api processor handle checks if valid, this + # future proofs this + if ($arg =~ /^[1-9]$/){ + $weather_source = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'weather-unit:s' => sub { + my ($opt,$arg) = @_; + $arg ||= ''; + $arg =~ s/\s//g; + $arg = lc($arg) if $arg; + if ($arg && $arg =~ /^(c|f|cf|fc|i|m|im|mi)$/){ + my %units = ('c'=>'m','f'=>'i','cf'=>'mi','fc'=>'im'); + $arg = $units{$arg} if defined $units{$arg}; + $weather_unit = $arg; + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'x|extra:i' => sub { + my ($opt,$arg) = @_; + if ($arg > 0){ + $extra = $arg; + } + else { + $extra++; + }}, + 'y|width:i' => sub { + my ($opt, $arg) = @_; + if (defined $arg && $arg == -1){ + $arg = 2000; + } + # note: :i creates 0 value if not supplied even though means optional + elsif (!$arg){ + $arg = 80; + } + if ($arg =~ /\d/ && ($arg == 1 || $arg >= 60)){ + $size{'max-cols-basic'} = $arg if $arg != 1; + $size{'max-cols'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'Y|height|less:i' => sub { + my ($opt, $arg) = @_; + main::error_handler('not-in-irc', '-Y/--height') if $b_irc; + if ($arg >= -3){ + if ($arg >= 0){ + $size{'max-lines'} = ($arg) ? $arg: $size{'term-lines'}; + } + elsif ($arg == -1) { + $use{'output-block'} = 1; + } + elsif ($arg == -2) { + $force{'colors'} = 1; + } + # unset conifiguration set max height + else { + $size{'max-lines'} = 0; + } + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'z|filter' => sub { + $use{'filter'} = 1;}, + 'filter-all|za' => sub { + $use{'filter'} = 1; + $use{'filter-label'} = 1; + $use{'filter-uuid'} = 1; + $use{'filter-vulnerabilities'} = 1;}, + 'filter-label|zl' => sub { + $use{'filter-label'} = 1;}, + 'Z|filter-override|no-filter' => sub { + $use{'filter-override'} = 1;}, + 'filter-uuid|zu' => sub { + $use{'filter-uuid'} = 1;}, + 'filter-v|filter-vulnerabilities|zv' => sub { + $use{'filter-vulnerabilities'} = 1;}, + ## Start non data options + 'alt:i' => sub { + my ($opt,$arg) = @_; + if ($arg == 40){ + $dl{'tiny'} = 0; + $use{'downloader'} = 1;} + elsif ($arg == 41){ + $dl{'curl'} = 0; + $use{'downloader'} = 1;} + elsif ($arg == 42){ + $dl{'fetch'} = 0; + $use{'downloader'} = 1;} + elsif ($arg == 43){ + $dl{'wget'} = 0; + $use{'downloader'} = 1;} + elsif ($arg == 44){ + $dl{'curl'} = 0; + $dl{'fetch'} = 0; + $dl{'wget'} = 0; + $use{'downloader'} = 1;} + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + # set --arm flag separately since android can be on different platforms + 'android' => sub { + $b_android = 1;}, + 'arm' => sub { + undef %risc; + $risc{'id'} = 'arm'; + $risc{'arm'} = 1;}, + 'bsd:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(darwin|dragonfly|freebsd|openbsd|netbsd)$/i){ + $bsd_type = lc($arg); + $fake{'bsd'} = 1; + } + else { + main::error_handler('bad-arg', $opt, $arg); + } + }, + 'bt-tool:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(bluetoothctl|bt-adapter|btmgmt|hciconfig|rfkill)$/i){ + $force{lc($arg)} = 1; + } + else { + main::error_handler('bad-arg', $opt, $arg); + } + }, + 'cygwin' => sub { + $windows{'cygwin'} = 1;}, + 'dbg:s' => sub { + my ($opt,$arg) = @_; + if ($arg !~ /^\d+(,\d+)*$/){ + main::error_handler('bad-arg', $opt, $arg); + } + for (split(',',$arg)){ + $dbg[$_] = 1; + }}, + 'debug:i' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^[1-3]|1[0-3]|2[0-4]$/){ + $debugger{'level'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'debug-arg:s' => sub { + my ($opt,$arg) = @_; + if ($arg && $arg =~ /^--?[a-z]/ig){ + $debugger{'arg'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'debug-arg-use:s' => sub { + my ($opt,$arg) = @_; + print "$arg\n"; + if ($arg && $arg =~ /^--?[a-z]/ig){ + $debugger{'arg-use'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'debug-filter|debug-z' => sub { + $debugger{'filter'} = 1 }, + 'debug-id:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + $debugger{'id'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'debug-no-eps' => sub { + $debugger{'no-exit'} = 1; + $debugger{'no-proc'} = 1; + $debugger{'sys'} = 0; + }, + 'debug-no-exit' => sub { + $debugger{'no-exit'} = 1 }, + 'debug-no-proc' => sub { + $debugger{'no-proc'} = 1;}, + 'debug-no-sys' => sub { + $debugger{'sys'} = 0;}, + 'debug-proc' => sub { + $debugger{'proc'} = 1;}, + 'debug-proc-print' => sub { + $debugger{'proc-print'} = 1;}, + 'debug-sys-print' => sub { + $debugger{'sys-print'} = 1;}, + 'debug-test-1' => sub { + $debugger{'test-1'} = 1;}, + 'debug-width|debug-y:i' => sub { + my ($opt,$arg) = @_; + $arg ||= 80; + if ($arg =~ /^\d+$/ && ($arg == 1 || $arg >= 80)){ + $debugger{'width'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'debug-zy|debug-yz:i' => sub { + my ($opt,$arg) = @_; + $arg ||= 80; + if ($arg =~ /^\d+$/ && ($arg == 1 || $arg >= 80)){ + $debugger{'width'} = $arg; + $debugger{'filter'} = 1; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'dig' => sub { + $force{'no-dig'} = 0;}, + 'display:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^:?([0-9\.]+)?$/){ + $display=$arg; + $display ||= ':0'; + $display = ":$display" if $display !~ /^:/; + $b_display = ($b_root) ? 0 : 1; + $force{'display'} = 1; + $display_opt = "-display $display"; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'dmi|dmidecode' => sub { + $force{'dmidecode'} = 1;}, + 'downloader:s' => sub { + my ($opt,$arg) = @_; + $arg = lc($arg); + if ($arg =~ /^(curl|fetch|ftp|perl|wget)$/){ + if ($arg eq 'perl' && (!main::check_perl_module('HTTP::Tiny') || + !main::check_perl_module('IO::Socket::SSL'))){ + main::error_handler('missing-perl-downloader', $opt, $arg); + } + elsif (!main::check_program($arg)){ + main::error_handler('missing-downloader', $opt, $arg); + } + else { + # this dumps all the other data and resets %dl for only the + # desired downloader. + $arg = main::set_perl_downloader($arg); + %dl = ('dl' => $arg, $arg => 1); + $use{'downloader'} = 1; + } + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'fake:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + my $wl = 'bluetooth|compiler|cpu|dboot|dmidecode|egl|elbrus|glx|'; + $wl .= 'iomem|ip-if|ipmi|logical|lspci|partitions|pciconf|pcictl|pcidump|'; + $wl .= 'raid-btrfs|raid-hw|raid-lvm|raid-md|raid-soft|raid-zfs|'; + $wl .= 'sensors|sensors-sys|swaymsg|sys-mem|sysctl|uptime|usbconfig|'; + $wl .= 'usbdevs|vmstat|vulkan|wl-info|wlr-randr|xdpyinfo|xorg-log|xrandr'; + for (split(',',$arg)){ + if ($_ =~ /\b($wl)\b/){ + $fake{lc($1)} = 1; + } + else { + main::error_handler('bad-arg', $opt, $_); + } + } + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'fake-data-dir:s' => sub { + my ($opt,$arg) = @_; + if ($arg && -d $arg){ + $fake_data_dir = $arg; + } + else { + main::error_handler('dir-not-exist', $opt, $arg); + }}, + 'force:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + my $wl = 'bluetoothctl|bt-adapter|btmgmt|colors|cpuinfo|display|dmidecode|'; + $wl .= 'hciconfig|hddtemp|ip|ifconfig|lsusb|man|meminfo|'; + $wl .= 'no-dig|no-doas|no-html-wan|no-sudo|pkg|rfkill|rpm|sensors-sys|'; + $wl .= 'usb-sys|vmstat|wayland|wmctrl'; + for (split(',',$arg)){ + if ($_ =~ /\b($wl)\b/){ + $force{lc($1)} = 1; + } + else { + main::error_handler('bad-arg', $opt, $_); + } + } + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'ftp:s' => sub { + my ($opt,$arg) = @_; + # pattern: ftp.x.x/x + if ($arg =~ /^ftp\..+\..+\/[^\/]+$/){ + $ftp_alt = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'hddtemp' => sub { + $force{'hddtemp'} = 1;}, + 'host|hostname' => sub { + $show{'host'} = 1; + $show{'no-host'} = 0;}, + 'html-wan' => sub { + $force{'no-html-wan'} = 0;}, + 'ifconfig' => sub { + $force{'ifconfig'} = 1;}, + 'indent:i' => sub { + my ($opt,$arg) = @_; + if ($arg >= 11){ + $size{'indent'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'indents:i' => sub { + my ($opt,$arg) = @_; + if ($arg >= 0 && $arg < 11){ + $size{'indents'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'irc' => sub { + $b_irc = 1;}, + 'man' => sub { + $use{'yes-man'} = 1;}, + 'max-wrap|wrap-max|indent-min:i' => sub { + my ($opt,$arg) = @_; + if ($arg >= 0){ + $size{'max-wrap'} = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'mips' => sub { + undef %risc; + $risc{'id'} = 'mips'; + $risc{'mips'} = 1;}, + 'no-dig' => sub { + $force{'no-dig'} = 1;}, + 'no-doas' => sub { + $force{'no-doas'} = 1;}, + 'no-host|no-hostname' => sub { + $show{'host'} = 0; + $show{'no-host'} = 1;}, + 'no-html-wan' => sub { + $force{'no-html-wan'}= 1;}, + 'no-man' => sub { + $use{'no-man'} = 0;}, + 'no-ssl' => sub { + $use{'no-ssl'} = 1;}, + 'no-sudo' => sub { + $force{'no-sudo'} = 1;}, + 'output|export:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(json|screen|xml)$/){ + $output_type = $arg; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'output-file|export-file:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + if ($arg eq 'print' || main::check_output_path($arg)){ + $output_file = $arg; + } + else { + main::error_handler('output-file-bad', $opt, $arg); + } + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'pkg|rpm' => sub { + $force{'pkg'} = 1;}, + 'ppc' => sub { + undef %risc; + $risc{'id'} = 'ppc'; + $risc{'ppc'} = 1;}, + 'recommends' => sub { + $show{'recommends'} = 1;}, + 'riscv' => sub { + undef %risc; + $risc{'id'} = 'riscv'; + $risc{'riscv'} = 1;}, + 'sensors-default' => sub { + $use{'sensors-default'} = 1;}, + 'sensors-exclude:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + @sensors_exclude = split(/\s*,\s*/, $arg); + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'sensors-sys' => sub { + $force{'sensors-sys'} = 1;}, + 'sensors-use:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + @sensors_use = split(/\s*,\s*/, $arg); + } + else { + main::error_handler('bad-arg',$opt,$arg); + }}, + 'sparc' => sub { + undef %risc; + $risc{'id'} = 'sparc'; + $risc{'sparc'} = 1;}, + 'sys-debug' => sub { + $debugger{'sys-force'} = 1;}, + 'tty' => sub { # workaround for ansible/scripts running this + $b_irc = 0;}, + 'U|update:s' => sub { # 1,2,3,4 OR http://myserver/path/inxi + my ($opt,$arg) = @_; + process_updater($opt,$arg);}, + 'usb-sys' => sub { + $force{'usb-sys'} = 1;}, + 'usb-tool' => sub { + $force{'lsusb'} = 1;}, + 'wan-ip-url:s' => sub { + my ($opt,$arg) = @_; + if ($arg && $arg =~ /^(f|ht)tp[s]?:\/\//){ + $wan_url = $arg; + $force{'no-dig'} = 1; + } + else { + main::error_handler('bad-arg', $opt, $arg); + }}, + 'wayland|wl' => sub { + $force{'wayland'} = 1;}, + 'wm|wmctrl' => sub { + $force{'wmctrl'} = 1;}, + 'wsl' => sub { + $windows{'wsl'} = 1;}, + '<>' => sub { + my ($opt) = @_; + main::error_handler('unknown-option', "$opt", "");} + ); # or error_handler('unknown-option', "@ARGV", ''); + # run all these after so that we can change widths, downloaders, etc + # print Data::Dumper::Dumper \%trigger; + post_process(); + eval $end if $b_log; +} + +sub post_process { + # first run all the stuff that exits after running + CheckRecommends::run() if $show{'recommends'}; + Configs::show() if $show{'configs'}; + main::show_options() if $show{'help'}; + main::show_version() if ($show{'version'} || $show{'version-short'}); + # sets for either config or arg here + if ($use{'downloader'} || $wan_url || ($force{'no-dig'} && $show{'ip'})){ + main::set_downloader(); + } + $use{'man'} = 0 if (!$use{'yes-man'} || $use{'no-man'}); + main::update_me($self_download,$download_id) if $use{'update-trigger'}; + main::set_xorg_log() if $show{'graphic'}; + if ($b_pledge){ + my $b_update; + # if -c 9x, remove in SelectColors::set_selection(), else remove here + if (!$colors{'selector'} && $debugger{'level'} < 21){ + @pledges = grep {$_ ne 'getpw'} @pledges; + $b_update = 1; + } + if ($debugger{'level'} < 21){ # remove ftp upload + @pledges = grep {!/(dns|inet)/} @pledges; + $b_update = 1; + } + # not writing/creating .inxi data dirs colors selector launches set_color() + if (!$show{'weather'} && !$colors{'selector'} && $debugger{'level'} < 10 && + $output_type eq 'screen'){ + @pledges = grep {!/(cpath|wpath)/} @pledges; + $b_update = 1; + } + OpenBSD::Pledge::pledge(@pledges) if $b_update; + } + if ($output_type){ + if ($output_type ne 'screen' && !$output_file){ + main::error_handler('bad-arg', '--output', '--output-file not provided'); + } + } + if (($show{'label'} || $show{'uuid'}) && !$show{'partition'} && + !$show{'partition-full'} && !$show{'swap'} && !$show{'unmounted'}){ + main::error_handler('bad-arg', '-l/-u', 'missing required option(s) -j, -o, -p, -P'); + } + $extra = 3 if $b_admin; + # this turns off basic for F/v graphic output levels. + if ($show{'graphic-basic'} && $show{'graphic-full'} && $extra > 1){ + $show{'graphic-basic'} = 0; + } + if ($force{'rpm'}){ + $force{'pkg'} = 1; + delete $force{'rpm'}; + } + if ($use{'sensors-default'}){ + @sensors_exclude = (); + @sensors_use = (); + } + if ($show{'short'} || $show{'disk'} || $show{'disk-basic'} || $show{'disk-total'} || + $show{'logical'} || $show{'partition'} || $show{'partition-full'} || $show{'raid'} || + $show{'unmounted'}){ + $use{'block-tool'} = 1; + } + if ($show{'short'} || $show{'raid'} || $show{'disk'} || $show{'disk-total'} || + $show{'disk-basic'} || $show{'unmounted'}){ + $use{'btrfs'} = 1; + $use{'mdadm'} = 1; + } + if ($b_admin && $show{'disk'}){ + $use{'smartctl'} = 1; + } + # triggers may extend to -D, -pP + if ($show{'short'} || $show{'logical'} || $show{'raid'} || $show{'disk'} || + $show{'disk-total'} || $show{'disk-basic'} || $show{'unmounted'}){ + $use{'logical'} = 1; + } + main::set_sudo() if ($show{'unmounted'} || ($extra > 0 && $show{'disk'})); + if ($use{'filter-override'}){ + $use{'filter'} = 0; + $use{'filter-label'} = 0; + $use{'filter-uuid'} = 0; + $use{'filter-vulnerabilities'} = 0; + } + # override for things like -b or -v2 to -v3 + $show{'cpu-basic'} = 0 if $show{'cpu'}; + $show{'optical-basic'} = 0 if $show{'optical'}; + $show{'partition'} = 0 if $show{'partition-full'}; + $show{'host'} = 0 if $show{'no-host'}; + $show{'host'} = 1 if ($show{'host'} || (!$use{'filter'} && !$show{'no-host'})); + if ($show{'disk'} || $show{'optical'}){ + $show{'disk-basic'} = 0; + $show{'disk-total'} = 0; + } + if ($show{'ram'} || $show{'slot'} || + ($show{'cpu'} && ($extra > 1 || $bsd_type)) || + (($bsd_type || $force{'dmidecode'}) && ($show{'machine'} || $show{'battery'}))){ + $use{'dmidecode'} = 1; + } + if ($show{'audio'} || $show{'bluetooth'} || $show{'graphic'} || + $show{'network'} || $show{'raid'}){ + $use{'pci'} = 1; + } + if ($show{'usb'} || $show{'audio'} || $show{'bluetooth'} || $show{'disk'} || + $show{'graphic'} || $show{'network'}){ + $use{'usb'} = 1; + } + if ($bsd_type){ + if ($show{'audio'}){ + $use{'bsd-audio'} = 1;} + if ($show{'battery'}){ + $use{'bsd-battery'} = 1;} + if ($show{'short'} || $show{'cpu-basic'} || $show{'cpu'}){ + $use{'bsd-cpu'} = 1; + $use{'bsd-sleep'} = 1;} + if ($show{'short'} || $show{'disk-basic'} || $show{'disk-total'} || + $show{'disk'} || $show{'partition'} || $show{'partition-full'} || + $show{'raid'} || $show{'swap'} || $show{'unmounted'}){ + $use{'bsd-disk'} = 1; + $use{'bsd-partition'} = 1; + $use{'bsd-raid'} = 1;} + if ($show{'system'}){ + $use{'bsd-kernel'} = 1;} + if ($show{'machine'}){ + $use{'bsd-machine'} = 1;} + if ($show{'short'} || $show{'info'} || $show{'ps-mem'} || $show{'ram'}){ + $use{'bsd-memory'} = 1;} + if ($show{'optical-basic'} || $show{'optical'}){ + $use{'bsd-optical'} = 1;} + # strictly only used to fill in pci drivers if tool doesn't support that + if ($use{'pci'}){ + $use{'bsd-pci'} = 1;} + if ($show{'raid'}){ + $use{'bsd-raid'} = 1;} + if ($show{'ram'}){ + $use{'bsd-ram'} = 1;} + if ($show{'sensor'}){ + $use{'bsd-sensor'} = 1;} + # always use this, it's too core + $use{'sysctl'} = 1; + } +} + +sub process_updater { + my ($opt,$arg) = @_; + $use{'downloader'} = 1; + if ($use{'update'}){ + $use{'update-trigger'} = 1; + if (!$arg){ + $use{'man'} = 1; + $download_id = "$self_name main branch"; + $self_download = main::get_defaults("$self_name-main"); + } + elsif ($arg && $arg eq '3'){ + $use{'man'} = 1; + $download_id = 'dev server'; + $self_download = main::get_defaults("$self_name-dev"); + } + elsif ($arg && $arg eq '4'){ + $use{'man'} = 1; + $use{'ftp-download'} = 1; + $download_id = 'dev server ftp'; + $self_download = main::get_defaults("$self_name-dev-ftp"); + } + elsif ($arg =~ /^[12]$/){ + if ($self_name eq 'inxi'){ + $download_id = "branch $arg"; + $self_download = main::get_defaults("inxi-branch-$arg"); + } + else { + main::error_handler('bad-arg', $opt, $arg); + } + } + elsif ($arg =~ /^(ftp|https?):/){ + $download_id = 'alt server'; + $self_download = $arg; + } + if ($self_download && $self_name eq 'inxi'){ + $use{'man'} = 1; + $use{'yes-man'} = 1; + } + if (!$self_download){ + main::error_handler('bad-arg', $opt, $arg); + } + } + else { + main::error_handler('distro-block', $opt); + } +} +} + +sub show_options { + error_handler('not-in-irc', 'help') if $b_irc; + my $rows = []; + my $line = make_line(); + my $color_scheme_count = get_color_scheme('count') - 1; + my $partition_string='partition'; + my $partition_string_u='Partition'; + my $flags = (%risc || $bsd_type) ? 'features' : 'flags' ; + if ($bsd_type){ + $partition_string='slice'; + $partition_string_u='Slice'; + } + # fit the line to the screen! + push(@$rows, + ['0', '', '', "$self_name supports the following options. For more detailed + information, see man^$self_name. If you start $self_name with no arguments, + it will display a short system summary."], + ['0', '', '', ''], + ['0', '', '', "You can use these options alone or together, + to show or add the item(s) you want to see: A, B, C, D, E, G, I, J, L, M, N, + P, R, S, W, d, f, i, j, l, m, n, o, p, r, s, t, u, w, --edid, --slots. + If you use them with -v [level], -b or -F, $self_name will add the requested + lines to the output."], + ['0', '', '', '' ], + ['0', '', '', "Examples:^$self_name^-v4^-c6 OR $self_name^-bDc^6 OR + $self_name^-FzjJxy^80"], + ['0', '', '', $line ], + ['0', '', '', "See Filter Options for output filtering, Output Control Options + for colors, sizing, output changes, Extra Data Options to extend Main output, + Additional Options and Advanced Options for less common situations."], + ['0', '', '', $line ], + ['0', '', '', "Main Feature Options:"], + ['1', '-A', '--audio', "Audio/sound devices(s), driver; active sound APIs and + servers."], + ['1', '-b', '--basic', "Basic output, short form. Same as $self_name^-v^2."], + ['1', '-B', '--battery', "System battery info, including charge, condition + voltage (if critical), plus extra info (if battery present/detected)."], + ['1', '-C', '--cpu', "CPU output (if each item available): basic topology, + model, type (see man for types), cache, average CPU speed, min/max speeds, + per core clock speeds."], + ['1', '-d', '--disk-full, --optical', "Optical drive data (and floppy disks, + if present). Triggers -D."], + ['1', '-D', '--disk', "Hard Disk info, including total storage and details + for each disk. Disk total used percentage includes swap ${partition_string} + size(s)."], + ['1', '-E', '--bluetooth', "Show bluetooth device data and report, if + available. Shows state, address, IDs, version info."], + ['1', '', '--edid', "Full graphics data, triggers -a, -G. Add monitor chroma, + full modelines (if > 2), EDID errors and warnings, if present."], + ['1', '-f', '--flags', "All CPU $flags. Triggers -C. Not shown with -F to + avoid spamming."], + ['1', '-F', '--full', "Full output. Includes all Upper Case line letters + (except -J, -W) plus --swap, -s and -n. Does not show extra verbose options + such as -d -f -i -J -l -m -o -p -r -t -u -x, unless specified."], + ['1', '', '--gpu', "Deprecated. Triggers -Ga."], + ['1', '-G', '--graphics', "Graphics info (devices(s), drivers, display + protocol (if available), display server/Wayland compositor, resolution, X.org: + renderer, basic EGL, OpenGL, Vulkan API data; Xvesa API: VBE info."], + ['1', '-i', '--ip', "WAN IP address and local interfaces (requires ifconfig + or ip network tool). Triggers -n. Not shown with -F for user security reasons. + You shouldn't paste your local/WAN IP."], + ['1', '-I', '--info', "General info, including processes, uptime, memory (if + -m/-tm not used), IRC client or shell type, $self_name version."], + ['1', '-j', '--swap', "Swap in use. Includes ${partition_string}s, zram, + file."], + ['1', '-J', '--usb', "Show USB data: Hubs and Devices."], + ['1', '-l', '--label', "$partition_string_u labels. Use with -j, -o, -p, -P."], + ['1', '-L', '--logical', "Logical devices, LVM (VG, LV), + LUKS, Crypto, bcache, etc. Shows components/devices, sizes, etc."], + ['1', '-m', '--memory', "Memory (RAM) data. Requires root. Numbers of + devices (slots) supported and individual memory devices (sticks of memory etc). + For devices, shows device locator, type (e.g. DDR3), size, speed. Also shows + System RAM report, and removes Memory report from -I or -tm."], + ['1', '', '--memory-modules,--mm', "Memory (RAM) data. Exclude empty module slots."], + ['1', '', '--memory-short,--ms', "Memory (RAM) data. Show only short Memory RAM + report, number of arrays, slots, modules, and RAM type."], + ['1', '-M', '--machine', "Machine data. Device type (desktop, server, laptop, + VM etc.), motherboard, BIOS and, if present, system builder (e.g. Lenovo). + Shows UEFI/BIOS/UEFI [Legacy]. Older systems/kernels without the required /sys + data can use dmidecode instead, run as root. Dmidecode can be forced with + --dmidecode"], + ['1', '-n', '--network-advanced', "Advanced Network device info. Triggers -N. + Shows interface, speed, MAC id, state, etc. "], + ['1', '-N', '--network', "Network device(s), driver."], + ['1', '-o', '--unmounted', "Unmounted $partition_string info (includes UUID + and Label if available). Shows file system type if you have lsblk installed + (Linux) or, for BSD/GNU Linux, if 'file' installed and you are root or if + you have added to /etc/sudoers (sudo v. 1.7 or newer)(or try doas)."], + ['1', '', '', "Example: ^^ALL^=^NOPASSWD:^/usr/bin/file^"], + ['1', '-p', '--partitions-full', "Full $partition_string information (-P plus + all other detected ${partition_string}s)."], + ['1', '-P', '--partitions', "Basic $partition_string info. Shows, if detected: + / /boot /home /opt /tmp /usr /usr/home /var /var/log /var/tmp. Swap + ${partition_string}s show if --swap is not used. Use -p to see all + mounted ${partition_string}s."], + ['1', '-r', '--repos', "Distro repository data. Supported repo types: APK, + APT, CARDS, EOPKG, NETPKG, NIX, PACMAN, PACMAN-G2, PISI, PKG (BSDs), PORTAGE, + PORTS (BSDs), SBOPKG, SBOUI, SCRATCHPKG, SLACKPKG, SLAPT_GET, SLPKG, TCE, + URPMQ, XBPS, YUM/ZYPP."], + ['1', '-R', '--raid', "RAID data. Shows RAID devices, states, levels, array + sizes, and components. md-raid: If device is resyncing, also shows resync + progress line."], + ['1', '-s', '--sensors', "Sensors output (if sensors installed/configured): + mobo/CPU/GPU temp; detected fan speeds. Nvidia shows screen number for > 1 + screen. IPMI sensors if present."], + ['1', '', '--slots', "PCI slots: type, speed, status. Requires root."], + ['1', '-S', '--system', "System info: host name, kernel, desktop environment + (if in X/Wayland), distro."], + ['1', '-t', '--processes', "Processes. Requires extra options: c (CPU), m + (memory), cm (CPU+memory). If followed by numbers 1-x, shows that number + of processes for each type (default: 5; if in IRC, max: 5). "], + ['1', '', '', "Make sure that there is no space between letters and + numbers (e.g.^-t^cm10)."], + ['1', '-u', '--uuid', "$partition_string_u UUIDs. Use with -j, -o, -p, -P."], + ['1', '-v', '--verbosity', "Set $self_name verbosity level (0-8). + Should not be used with -b or -F. Example: $self_name^-v^4"], + ['2', '0', '', "Same as: $self_name"], + ['2', '1', '', "Basic verbose, -S + basic CPU + -G + basic Disk + -I."], + ['2', '2', '', "Networking device (-N), Machine (-M), Battery (-B; if + present), and, if present, basic RAID (devices only; notes if inactive). Same + as $self_name^-b"], + ['2', '3', '', "Advanced CPU (-C), battery (-B), network (-n); + triggers -x. "], + ['2', '4', '', "$partition_string_u size/used data (-P) for + (if present) /, /home, /var/, /boot. Shows full disk data (-D). "], + ['2', '5', '', "Audio device (-A), sensors (-s), memory/RAM (-m), + bluetooth (if present), $partition_string label^(-l), full swap (-j), + UUID^(-u), short form of optical drives, RAID data (if present)."], + ['2', '6', '', "Full $partition_string (-p), + unmounted $partition_string (-o), optical drive (-d), USB (-J), + full RAID; triggers -xx."], + ['2', '7', '', "Network IP data (-i), bluetooth, logical (-L), + RAID forced, full CPU $flags; triggers -xxx."], + ['2', '8', '', "Everything available, including advanced gpu EDID (--edid) + data, repos (-r), processes (-tcm), PCI slots (--slots); triggers + admin (-a)."], + ); + # if distro maintainers don't want the weather feature disable it + if ($use{'weather'}){ + push(@$rows, + ['1', '-w', '--weather', "Local weather data/time. To check an alternate + location, see -W. NO AUTOMATED QUERIES OR EXCESSIVE USE ALLOWED!"], + ['1', '-W', '--weather-location', "[location] Supported options for + [location]: postal code[,country/country code]; city, state (USA)/country + (country/two character country code); latitude, longitude. Only use if you + want the weather somewhere other than the machine running $self_name. Use + only ASCII characters, replace spaces in city/state/country names with '+'. + Example:^$self_name^-W^[new+york,ny^london,gb^madrid,es]"], + ['1', '', '--weather-source', "[1-9] Change weather data source. 1-4 + generally active, 5-9 check. See man."], + ['1', '', '--weather-unit', "Set weather units to metric (m), imperial (i), + metric/imperial (mi), or imperial/metric (im)."], + ); + } + push(@$rows, + [0, '', '', "$line"], + ['0', '', '', "Filter Options:"], + ['1', '', '--host', "Turn on hostname for -S. Overrides -z."], + ['1', '', '--no-host', "Turn off hostname for -S. Useful if showing output + from servers etc. Activated by -z as well."], + ['1', '-z', '--filter', "Adds security filters for IP/MAC addresses, serial + numbers, location (-w), user home directory name, host name. Default on for + IRC clients."], + ['1', '', '--za,--filter-all', "Shortcut, triggers -z, --zl, --zu, --zv."], + ['1', '', '--zl,--filter-label', "Filters out ${partition_string} labels in + -j, -o, -p, -P, -Sa."], + ['1', '', '--zu,--filter-uuid', "Filters out ${partition_string} UUIDs in -j, + -o, -p, -P, -Sa."], + ['1', '', '--zv,--filter-vulnerabilities', "Filters out Vulnerabilities + report in -Ca."], + ['1', '-Z', '--no-filter', "Disable output filters. Useful for debugging + networking issues in IRC, or you needed to use --tty, for example."], + [0, '', '', "$line"], + ['0', '', '', "Output Control Options:"], + ['1', '-c', '--color', "Set color scheme (0-42). For piped or redirected + output, you must use an explicit color selector. Example:^$self_name^-c^11"], + ['1', '', '', "Color selectors let you set the config file value for the + selection (NOTE: IRC and global only show safe color set)"], + ['2', '94', '', "Console, out of X"], + ['2', '95', '', "Terminal, running in X - like xTerm"], + ['2', '96', '', "Gui IRC, running in X - like Xchat, Quassel, Konversation + etc."], + ['2', '97', '', "Console IRC running in X - like irssi in xTerm"], + ['2', '98', '', "Console IRC not in X"], + ['2', '99', '', "Global - Overrides/removes all settings. Setting specific + removes global."], + ['1', '', '--indent', "[11-20] Change default wide mode primary indentation + width."], + ['1', '', '--indents', "[0-10] Change wrapped mode primary indentation width, + and secondary / -y1 indent widths."], + ['1', '', '--limit', "[-1; 1-x] Set max output limit of IP addresses for -i + (default 10; -1 removes limit)."], + ['1', '', '--max-wrap,--wrap-max', "[70-xxx] Set maximum width where + $self_name autowraps line starters. Current: $size{'max-wrap'}"], + ['1', '', '--output', "[json|screen|xml] Change data output type. Requires + --output-file if not screen."], + ['1', '', '--output-file', "[Full filepath|print] Output file to be used for + --output."], + ['1', '', '--partition-sort', "[dev-base|fs|id|label|percent-used|size|uuid|used] + Change sort order of ${partition_string} output. See man page for specifics."], + ['1', '', '--separator, --sep', "[key:value separator character]. Change + separator character(s) for key: value pairs."], + ['1', '-y', '--width', "[empty|-1|1|60-xxx] Output line width max. Overrides + IRC/Terminal settings or actual widths. If no integer give, defaults to 80. + -1 removes line lengths. 1 switches output to 1 key/value pair per line. + Example:^inxi^-y^130"], + ['1', '-Y', '--height', "[empty|-3-xxx] Output height control. Similar to + 'less' command except colors preserved, defaults to console/terminal height. + -1 shows 1 primary Item: at a time; -2 retains color on redirect/piping (to + less -R); -3 removes configuration value; 0 or -Y sets to detected terminal + height. Greater than 0 shows x lines at a time."], + ['0', '', '', "$line"], + ['0', '', '', "Extra Data Options:"], + ['1', '-x', '--extra', "Adds the following extra data (only works with + verbose or line output, not short form):"], + ['2', '-A', '', "Specific vendor/product information (if relevant); + PCI/USB ID of device; Version/port(s)/driver version (if available); + inactive sound servers/APIs."], + ['2', '-B', '', "Current/minimum voltage, vendor/model, status (if available); + attached devices (e.g. wireless mouse, keyboard, if present)."], + ['2', '-C', '', "L1/L3 cache (if most Linux, or if root and dmidecode + installed); smt if disabled, CPU $flags (short list, use -f to see full list); + Highest core speed (if > 1 core); CPU boost (turbo) enabled/disabled, if + present; Bogomips on CPU; CPU microarchitecture + revision (if found, or + unless --admin, then shows as 'stepping')."], + ['2', '-d', '', "Extra optical drive features data; adds rev version to + optical drive."], + ['2', '-D', '', "HDD temp with disk data. Kernels >= 5.6: enable module + drivetemp if not enabled. Older systems require hddtemp, run as + as superuser, or as user if you have added hddtemp to /etc/sudoers + (sudo v. 1.7 or newer)(or try doas). + Example:^^ALL^=^NOPASSWD:^/usr/sbin/hddtemp"], + ['2', '-E', '', "PCI/USB Bus ID of device, driver version, + LMP version."], + ['2', '-G', '', "GPU arch (AMD/Intel/Nvidia only); Specific vendor/product + information (if relevant); PCI/USB ID of device; Screen number GPU is running + on (Nvidia only); device temp (Linux, if found); APIs: EGL: active/inactive + platforms; OpenGL: direct rendering status (in X); Vulkan device counts."], + ['2', '-i', '', "For IPv6, show additional scope addresses: Global, Site, + Temporary, Unknown. See --limit for large counts of IP addresses."], + ['2', '-I', '', "Default system GCC. With -xx, also shows other installed + GCC versions. If running in shell, not in IRC client, shows shell version + number, if detected. Init/RC type and runlevel/target (if available). Total + count of all packages discovered in system (if not -r)."], + ['2', '-j', '', "Add mapped: name if partition mapped."], + ['2', '-J', '', "For Device: driver; Si speed (base 10, bits/s)."], + ['2', '-L', '', "For VG > LV, and other Devices, dm:"], + ['2', '-m,--memory-modules', '', "Max memory module size (if available)."], + ['2', '-N', '', "Specific vendor/product information (if relevant); + PCI/USB ID of device; Version/port(s)/driver version (if available); device + temperature (Linux, if found)."], + ['2', '-o,-p,-P', '', "Add mapped: name if partition mapped."], + ['2', '-r', '', "Packages, see -Ix."], + ['2', '-R', '', "md-raid: second RAID Info line with extra data: + blocks, chunk size, bitmap (if present). Resync line, shows blocks + synced/total blocks. Hardware RAID driver version, bus-ID."], + ['2', '-s', '', "Basic voltages (ipmi, lm-sensors if present): 12v, 5v, 3.3v, + vbat."], + ['2', '-S', '', "Kernel gcc version; system base of distro (if relevant + and detected)"], + ['2', '', '--slots', "Adds BusID for slot."], + ['2', '-t', '', "Adds memory use output to CPU (-xt c), and CPU use to + memory (-xt m)."], + ); + if ($use{'weather'}){ + push(@$rows, + ['2', '-w,-W', '', "Wind speed and direction, humidity, pressure, + and time zone, if available."]); + } + push(@$rows, + ['0', '', '', ''], + ['1', '-xx', '--extra 2', "Show extra, extra data (only works with verbose + or line output, not short form):"], + ['2', '-A', '', "Chip vendor:product ID for each audio device; PCIe speed, + lanes (if found); USB rev, speed, lanes (if found); sound server/api helper + daemons/plugins."], + ['2', '-B', '', "Power used, in watts; serial number."], + ['2', '-D', '', "Disk transfer speed; NVMe lanes; USB rev, speed, lanes (if + found); Disk serial number; LVM volume group free space (if available); disk + duid (some BSDs)."], + ['2', '-E', '', "Chip vendor:product ID, LMP subversion; PCIe speed, lanes + (if found); USB rev, speed, lanes (if found)."], + ['2', '-G', '', "Chip vendor:product ID for each video device; Output ports, + used and empty; PCIe speed, lanes (if found); USB rev, speed, lanes (if + found); Xorg: Xorg compositor; alternate Xorg drivers (if available. Alternate + means driver is on automatic driver check list of Xorg for the device vendor, + but is not installed on system); Xorg Screen data: ID, s-res, dpi; Monitors: + ID, position (if > 1), resolution, dpi, model, diagonal; APIs: EGL: per + platform report; OpenGL: ES version, device-ID, display-ID (if not found in + Display line); Vulkan: per device report."], + ['2', '-I', '', "Other detected installed gcc versions (if present). System + default target/runlevel. Adds parent program (or pty/tty) for shell info if + not in IRC. Adds Init version number, RC (if found). Adds per package manager + installed package counts (if not -r)."], + ['2', '-j,-p,-P', '', "Swap priority."], + ['2', '-J', '', "Vendor:chip-ID; lanes (Linux only)."], + ['2', '-L', '', "Show internal LVM volumes, like raid image/meta volumes; + for LVM RAID, adds RAID report line (if not -R); show all components > + devices, number of 'c' or 'p' indicate depth of device."], + ['2', '-m,--memory-modules', '', "Manufacturer, part number; single/double + bank (if found); memory array voltage (legacy, rare); module voltage (if + available)."], + ['2', '-M', '', "Chassis info, BIOS ROM size (dmidecode only), if available."], + ['2', '-N', '', "Chip vendor:product ID; PCIe speed, lanes (if found); USB + rev, speed, lanes (if found)."], + ['2', '-r', '', "Packages, see -Ixx."], + ['2', '-R', '', "md-raid: Superblock (if present), algorithm. If resync, + shows progress bar. Hardware RAID Chip vendor:product ID."], + ['2', '-s', '', "DIMM/SOC voltages (ipmi only)."], + ['2', '-S', '', "Display manager (dm) in desktop output (e.g. kdm, + gdm3, lightdm); active window manager if detected; desktop toolkit, + if available (Xfce/KDE/Trinity only)."], + ['2', '--slots', '', "Slot length; slot voltage, if available."], + ); + if ($use{'weather'}){ + push(@$rows, + ['2', '-w,-W', '', "Snow, rain, precipitation, (last observed hour), + cloud cover, wind chill, dew point, heat index, if available."] + ); + } + push(@$rows, + ['0', '', '', ''], + ['1', '-xxx', '--extra 3', "Show extra, extra, extra data (only works + with verbose or line output, not short form):"], + ['2', '-A', '', "Serial number, class ID."], + ['2', '-B', '', "Chemistry, cycles, location (if available)."], + ['2', '-C', '', "CPU voltage, external clock speed (if root and dmidecode + installed); smt status, if available."], + ['2', '-D', '', "Firmware rev. if available; partition scheme, in some cases; + disk type, rotation rpm (if available)."], + ['2', '-E', '', "Serial number, class ID, bluetooth device class ID, HCI + version and revision."], + ['2', '-G', '', "Device serial number, class ID; Xorg Screen size, diag; + Monitors: hz, size, modes, serial, scale, modes (max/min); APIs: EGL: hardware + driver info; Vulkan: layer count, device hardware vendor."], + ['2', '-I', '', "For 'Shell:' adds ([doas|su|sudo|login]) to shell name if + present; adds default shell+version if different; for 'running in:' adds (SSH) + if SSH session; adds wakeups: (from suspend) to Uptime."], + ['2', '-J', '', "If present: Devices: serial number, interface count, max + power."], + ['2', '-m,--memory-modules', '', "Width of memory bus, data and total (if + present and greater than data); Detail for Type, if present; module current, + min, max voltages (if present and different from each other); serial number."], + ['2', '-N', '', "Serial number, class ID."], + ['2', '-R', '', "zfs-raid: portion allocated (used) by RAID devices/arrays. + md-raid: system md-raid support types (kernel support, read ahead, RAID + events). Hardware RAID rev, ports, specific vendor/product information."], + ['2', '-S', '', "Kernel clocksource; Panel/tray/bar/dock info in desktop + output, if in X (like lxpanel, xfce4-panel, mate-panel); (if available) dm + version number, window manager version number, virtual terminal number."], + ); + if ($use{'weather'}){ + push(@$rows, + ['2', '-w,-W', '', "Location (uses -z/irc filter), weather observation + time, altitude, sunrise/sunset, if available."] + ); + } + push(@$rows, + ['0', '', '', ''], + ['1', '-a', '--admin', "Adds advanced sys admin data (only works with + verbose or line output, not short form); check man page for explanations!; + also sets --extra=3:"], + ['2', '-A', '', "If available: list of alternate kernel modules/drivers + for device(s); PCIe lanes-max: gen, speed, lanes (if relevant); USB mode (if + found); list of installed tools for servers."], + ['2', '-C', '', "If available: microarchitecture level (64 bit AMD/Intel + only).CPU generation, process node, built years; CPU socket type, base/boost + speeds (dmidecode+root/sudo/doas required); Full topology line, with cores, + threads, threads per core, granular cache data, smt status; CPU + vulnerabilities (bugs); family, model-id, stepping - format: hex (decimal) + if greater than 9; microcode format: hex."], + ['2', '-d,-D', '', "If available: logical and physical block sizes; drive + family; maj:min; USB mode (if found); USB drive specifics; SMART report."], + ['2', '-E', '', "PCIe lanes-max: gen, speed, lanes (if relevant); USB mode + (if found); If available: in Report:, adds status: discoverable, pairing; + adds Info: line: acl-mtu, sco-mtu, link-policy, link-mode, service-classes."], + ['2', '-G', '', "GPU process node, built year (AMD/Intel/Nvidia only); + non-free driver info (Nvidia only); PCIe lanes-max: gen, speed, lanes (if + relevant); USB mode (if found); list of alternate kernel modules/drivers for + device(s) (if available); Monitor built year, gamma, screen ratio (if + available); APIs: OpenGL: device memory, unified memory status; Vulkan: adds + full device report, device name, driver version, surfaces."], + ['2', '-I', '', "Adds to Packages total number of lib files found for each + package manager and pm tools (if not -r); adds init service tool."], + ['2', '-j,-p,-P', '', "For swap (if available): swappiness and vfs cache + pressure, and if values are default or not."], + ['2', '-j', '', "Linux only: (if available): row one zswap data, and per zram + row, active and available zram compressions, max compression streams."], + ['2', '-J', '', "Adds USB mode (Linux only); IEC speed (base 2, Bytes/s)."], + ['2', '-L', '', "LV, Crypto, devices, components: add maj:min; show + full device/components report (speed, mapped names)."], + ['2', '-m', '', "Show full volts report, current, min, max, even if + identical."], + ['2', '-n,-N', '', "If available: list of alternate kernel modules/drivers + for device(s); PCIe lanes-max: gen, speed, lanes (if relevant); USB mode (if + found)."], + ['2', '-o', '', "If available: maj:min of device."], + ['2', '-p,-P', '', "If available: raw size of ${partition_string}s, maj:min, + percent available for user, block size of file system (root required)."], + ['2', '-r', '', "Packages, see -Ia."], + ['2', '-R', '', "mdraid: device maj:min; per component: size, maj:min, state."], + ['2', '-S', '', "If available: kernel alternate clocksources, boot + parameters."], + ['2', '', '--slots', "If available: slot bus ID children."], + ); + push(@$rows, + [0, '', '', "$line"], + [0, '', '', "Additional Options:"], + ['1', '--config', '--configuration', "Show active configurations, by file(s). + Last item listed overrides previous."], + ['1', '-h', '--help', "This help menu."], + ['1', '', '--recommends', "Checks $self_name application dependencies + + recommends, and directories, then shows what package(s) you need to install + to add support for that feature."], + ); + if ($use{'update'}){ + push(@$rows, + ['1', '-U', '--update', "Auto-update $self_name. Will also install/update + man page. Note: if you installed as root, you must be root to update, + otherwise user is fine. Man page installs require root. No arguments + downloads from main $self_name git repo."], + ['1', '', '', "Use alternate sources for updating $self_name"], + ['3', '3', '', "Get the dev server (smxi.org) version."], + ['3', '4', '', "Get the dev server (smxi.org) FTP version. Use if SSL issues + and --no-ssl doesn't work."], + ['2', '', '', "Get a version of $self_name from your own + server. Use the full download path, e.g. + ^$self_name^-U ^https://myserver.com/inxi"], + ); + } + push(@$rows, + ['1', '-V', '--version', "Prints full $self_name version info then exits."], + ['1', '', '--version-short,--vs', "Prints 1 line $self_name version info. Can + be used with other line options."], + ['0', '', '', "$line"], + ['0', '', '', "Advanced Options:"], + ['1', '', '--alt', "Trigger for various advanced options:"], + ['2', '40', '', "Bypass Perl as a downloader option."], + ['2', '41', '', "Bypass Curl as a downloader option."], + ['2', '42', '', "Bypass Fetch as a downloader option."], + ['2', '43', '', "Bypass Wget as a downloader option."], + ['2', '44', '', "Bypass Curl, Fetch, and Wget as downloader options. Forces + Perl if HTTP::Tiny present."], + ['1', '', '--bt-tool', "[bt-adapter btmgmt hciconfig rfkill] Force use of + given tool forbluetooth report. Or use --force [tool]."], + ['1', '', '--dig', "Overrides configuration item NO_DIG (resets to default)."], + ['1', '', '--display', "[:[0-9]] Try to get display data out of X (default: + display 0)."], + ['1', '', '--dmidecode', "Force use of dmidecode data instead of /sys where + relevant + (e.g. -M, -B)."], + ['1', '', '--downloader', "Force $self_name to use [curl fetch perl wget] for + downloads."], + ['1', '', '--force', "[bt-adapter btmgmt dmidecode hciconfig hddtemp ip + ifconfig lsusb meminfo rfkill usb-sys vmstat wmctrl]. + 1 or more in comma separated list. Force use of item(s). + See --hddtemp, --dmidecode, --wm, --usb-tool, --usb-sys."], + ['1', '', '--hddtemp', "Force use of hddtemp for disk temps."], + ['1', '', '--html-wan', "Overrides configuration item NO_HTML_WAN (resets to + default)."], + ['1', '', '--ifconfig', "Force use of ifconfig for IF with -i."], + ); + if ($use{'update'}){ + push(@$rows, + ['1', '', '--man', "Install correct man version for dev branch (-U 3) or + pinxi using -U."], + ); + } + push(@$rows, + ['1', '', '--no-dig', "Skip dig for WAN IP checks, use downloader program."], + ['1', '', '--no-doas', "Skip internal program use of doas features (not + related to starting $self_name with doas)."], + ['1', '', '--no-html-wan', "Skip HTML IP sources for WAN IP checks, use dig + only, or nothing if --no-dig."], + ); + if ($use{'update'}){ + push(@$rows, + ['1', '', '--no-man', "Disable man install for all -U update actions."], + ); + } + push(@$rows, + ['1', '', '--no-ssl', "Skip SSL certificate checks for all downloader actions + (Wget/Fetch/Curl/Perl-HTTP::Tiny)."], + ['1', '', '--no-sudo', "Skip internal program use of sudo features (not + related to starting $self_name with sudo)."], + ['1', '', '--rpm', "Force use of disabled package manager counts for packages + feature with -rx/-Ix. RPM disabled by default due to slow to massive rpm + package query times."], + ['1', '', '--sensors-default', "Removes configuration item SENSORS_USE and + SENSORS_EXCLUDE. Same as default behavior."], + ['1', '', '--sensors-exclude', "[sensor[s] name, comma separated] Exclude + supplied sensor array[s] for -s output (lm-sensors, /sys. Linux only)."], + ['1', '', '--sensors-use', "[sensor[s] name, comma separated] Use only + supplied sensor array[s] for -s output (lm-sensors, /sys. Linux only)."], + ['1', '', '--sleep', "[0-x.x] Change CPU sleep time, in seconds, for -C + (default:^$cpu_sleep). Allows system to catch up and show a more accurate CPU + use. Example:^$self_name^-Cxxx^--sleep^0.15"], + ['1', '', '--tty', "Forces irc flag to false. Generally useful if $self_name + is running inside of another tool like Chef or MOTD and returns corrupted + color codes. Please see man page or file an issue if you need to use this + flag. Must use -y [width] option if you want a specific output width. Always + put this option first in an option list. See -Z for disabling output filters + as well."], + ['1', '', '--usb-sys', "Force USB data to use only /sys as data source (Linux + only)."], + ['1', '', '--usb-tool', "Force USB data to use lsusb as data source [default] + (Linux only)."], + ['1', '', '--wan-ip-url', "[URL] Skips dig, uses supplied URL for WAN IP (-i). + URL output must end in the IP address. See man. + Example:^$self_name^-i^--wan-ip-url^https://yoursite.com/remote-ip"], + ['1', '', '--wm', "Force wm: to use wmctrl as data source. Default uses ps."], + ['0', '', '', $line ], + ['0', '', '', "Debugging Options:"], + ['1', '', '--dbg', "[1-xx[,1-xx]] Comma separated list of debugger numbers. + Each triggers specific debugger[s]. See man page or docs."], + ['2', '1', '', "Show downloader output. Turns off quiet mode."], + ['1', '', '--debug', "[1-3|10|11|20-22] Triggers debugging modes."], + ['2', '1-3', '', "On screen debugger output."], + ['2', '10', '', "Basic logging."], + ['2', '11', '', "Full file/system info logging."], + ['1', '', ,'', "The following create a tar.gz file of system data, plus + $self_name output. To automatically upload debugger data tar.gz file to + ftp.smxi.org: $self_name^--debug^21"], + ['2', '20', '', "Full system data collection: /sys; xorg conf and log data, + xrandr, xprop, xdpyinfo, glxinfo etc.; data from dev, disks, + ${partition_string}s, etc."], + ['2', '21', '', "Upload debugger dataset to $self_name debugger server + automatically, removes debugger data directory, leaves tar.gz debugger file."], + ['2', '22', '', "Upload debugger dataset to $self_name debugger server + automatically, removes debugger data directory and debugger tar.gz file."], + # ['1', '', '--debug-filter', "Add -z flag to debugger $self_name optiions."], + ['1', '', '--debug-id', "[short-string] Add given string to debugger file + name. Helps identify source of debugger dataset. Use with --debug 20-22."], + ['1', '', '--debug-proc', "Force debugger parsing of /proc as sudo/doas/root."], + ['1', '', '--debug-proc-print', "To locate file that /proc debugger hangs on."], + ['1', '', '--debug-no-exit', "Skip exit on error to allow completion."], + ['1', '', '--debug-no-proc', "Skip /proc debugging in case of a hang."], + ['1', '', '--debug-no-sys', "Skip /sys debugging in case of a hang."], + ['1', '', '--debug-sys', "Force PowerPC debugger parsing of /sys as + sudo/doas/root."], + ['1', '', '--debug-sys-print', "To locate file that /sys debugger hangs on."], + ['1', '', '--ftp', "Use with --debugger 21 to trigger an alternate FTP server + for upload. Format:^[ftp.xx.xx/yy]. Must include a remote directory to upload + to. Example:^$self_name^--debug^21^--ftp^ftp.myserver.com/incoming"], + ['0', '', '', "$line"], + ); + print_basic($rows); + exit 0; # shell true +} + +sub show_version { + # if not in PATH could be either . or directory name, no slash starting + my $working_path=$self_path; + my ($link,$self_string); + my $rows = []; + Cwd->import('getcwd'); # no point loading this on top use, we only use getcwd here + if ($working_path eq '.'){ + $working_path = getcwd(); + } + elsif ($working_path !~ /^\//){ + $working_path = getcwd() . "/$working_path"; + } + $working_path =~ s%/$%%; + # handle if it's a symbolic link, rare, but can happen with directories + # in irc clients which would only matter if user starts inxi with -! 30 override + # in irc client + if (-l "$working_path/$self_name"){ + $link="$working_path/$self_name"; + $working_path = readlink "$working_path/$self_name"; + $working_path =~ s/[^\/]+$//; + } + # strange output /./ ending, but just trim it off, I don't know how it happens + $working_path =~ s%/\./%/%; + push(@$rows, [ 0, '', '', "$self_name $self_version-$self_patch ($self_date)"]); + if (!$b_irc && !$show{'version-short'}){ + push(@$rows, [ 0, '', '', '']); + my $year = (split/-/, $self_date)[0]; + push(@$rows, + [ 0, '', '', "Copyright^(C)^2008-$year^Harald^Hope^aka^h2"], + [ 0, '', '', "Forked from Infobash 3.02: Copyright^(C)^2005-2007^Michiel^de^Boer^aka^locsmif." ], + [ 0, '', '', "Using Perl version: $]"], + [ 0, '', '', "Program Location: $working_path" ], + ); + if ($link){ + push(@$rows, [ 0, '', '', "Started via symbolic link: $link" ]); + } + push(@$rows, + [ 0, '', '', '' ], + [ 0, '', '', "Website:^https://codeberg.org/smxi/inxi^or^https://smxi.org/" ], + [ 0, '', '', "IRC:^irc.oftc.net channel:^#smxi" ], + [ 0, '', '', "Forums:^https://techpatterns.com/forums/forum-33.html" ], + [ 0, '', '', '' ], + [ 0, '', '', "This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by the Free Software + Foundation; either version 3 of the License, or (at your option) any later version. + (https://www.gnu.org/licenses/gpl.html)" ] + ); + } + print_basic($rows); + exit 0 if !$show{'version-short'} || $show{'short'}; # shell true +} + +######################################################################## +#### STARTUP DATA +######################################################################## + +## StartClient +{ +package StartClient; +# use warnings; +# use strict; +my $pppid = ''; + +# NOTE: there's no reason to create an object, we can just access +# the features statically. +# args: none +# sub new { +# my $class = shift; +# my $self = {}; +# # print "$f\n"; +# # print "$type\n"; +# return bless $self, $class; +# } + +sub set { + eval $start if $b_log; + main::set_ps_aux() if !$loaded{'ps-aux'}; + # $b_irc = 1; # for testing, like cli konvi start which shows as tty + if (!$b_irc){ + # we'll run ShellData::set() for -I, but only then + } + else { + $use{'filter'} = 1; + get_client_name(); + if ($client{'konvi'} == 1 || $client{'konvi'} == 3){ + set_konvi_data(); + } + } + eval $end if $b_log; +} + +sub get_client_name { + eval $start if $b_log; + my $client_name = ''; + # print "$ppid\n"; + if ($ppid && -e "/proc/$ppid/exe"){ + $client_name = lc(readlink "/proc/$ppid/exe"); + $client_name =~ s/^.*\///; + if ($client_name =~ /^(bash|csh|dash|fish|sh|python.*|perl.*|zsh)$/){ + $pppid = (main::grabber("ps -wwp $ppid -o ppid"))[1]; + # my @temp = (main::grabber("ps -wwp $ppid -o ppid 2>/dev/null"))[1]; + $pppid =~ s/^\s+|\s+$//g; + $client_name =~ s/[0-9\.]+$//; # clean things like python2.7 + if ($pppid && -f "/proc/$pppid/exe"){ + $client_name = lc(readlink "/proc/$pppid/exe"); + $client_name =~ s/^.*\///; + $client{'native'} = 0; + } + } + $client{'name'} = $client_name; + get_client_version(); + # print "c:$client_name p:$pppid\n"; + # print "$client{'name-print'}\n"; + } + else { + if (!check_modern_konvi()){ + $client_name = (main::grabber("ps -wwp $ppid 2>/dev/null"))[1]; + if ($client_name){ + my @data = split(/\s+/, $client_name); + if ($bsd_type){ + $client_name = lc($data[4]); + } + # gnu/linux uses last value + else { + $client_name = lc($data[-1]); + } + $client_name =~ s/.*\|-(|)//; + $client_name =~ s/[0-9\.]+$//; # clean things like python2.7 + $client{'name'} = $client_name; + $client{'native'} = 1; + get_client_version(); + } + else { + $client{'name'} = "PPID='$ppid' - Empty?"; + } + } + } + if ($b_log){ + my $string = "Client: $client{'name'} :: version: $client{'version'} :: konvi: $client{'konvi'} :: PPID: $ppid"; + main::log_data('data', $string); + } + eval $end if $b_log; +} + +sub get_client_version { + eval $start if $b_log; + @app = main::program_values($client{'name'}); + my (@data,@working,$string); + if (@app){ + $string = ($client{'name'} =~ /^gribble|limnoria|supybot$/) ? 'supybot' : $client{'name'}; + $client{'version'} = main::program_version($string,$app[0],$app[1],$app[2],$app[4],$app[5],$app[6]); + $client{'name-print'} = $app[3]; + $client{'console-irc'} = $app[4]; + } + if ($client{'name'} =~ /^(bash|csh|fish|dash|sh|zsh)$/){ + $client{'name-print'} = 'shell wrapper'; + $client{'console-irc'} = 1; + } + elsif ($client{'name'} eq 'bitchx'){ + @data = main::grabber("$client{'name'} -v"); + $string = awk(\@data,'Version'); + if ($string){ + $string =~ s/[()]|bitchx-//g; + @data = split(/\s+/, $string); + $_=lc for @data; + $client{'version'} = ($data[1] eq 'version') ? $data[2] : $data[1]; + } + } + # 'hexchat' => ['',0,'','HexChat',0,0], # special + # the hexchat author decided to make --version/-v return a gtk dialogue box, lol... + # so we need to read the actual config file for hexchat. Note that older hexchats + # used xchat config file, so test first for default, then legacy. Because it's possible + # for this file to be user edited, doing some extra checks here. + elsif ($client{'name'} eq 'hexchat'){ + if (-f '~/.config/hexchat/hexchat.conf'){ + @data = main::reader('~/.config/hexchat/hexchat.conf','strip'); + } + elsif (-f '~/.config/hexchat/xchat.conf'){ + @data = main::reader('~/.config/hexchat/xchat.conf','strip'); + } + if (@data){ + $client{'version'} = main::awk(\@data,'version',2,'\s*=\s*'); + } + # fingers crossed, hexchat won't open gui!! + if (!$client{'version'}){ + @data = main::grabber("$client{'name'} --version 2>/dev/null"); + $client{'version'} = main::awk(\@data,'hexchat',2,'\s+'); + } + $client{'name-print'} = 'HexChat'; + } + # note: see legacy inxi konvi logic if we need to restore any of the legacy code. + elsif ($client{'name'} eq 'konversation'){ + $client{'konvi'} = (!$client{'native'}) ? 2 : 1; + } + elsif ($client{'name'} =~ /quassel/i){ + @data = main::grabber("$client{'name'} -v 2>/dev/null"); + foreach (@data){ + if ($_ =~ /^Quassel IRC:/){ + $client{'version'} = (split(/\s+/, $_))[2]; + last; + } + elsif ($_ =~ /quassel\s[v]?[0-9]/){ + $client{'version'} = (split(/\s+/, $_))[1]; + last; + } + } + $client{'version'} ||= '(pre v0.4.1)?'; + } + # then do some perl type searches, do this last since it's a wildcard search + elsif ($client{'name'} =~ /^(perl.*|ksirc|dsirc)$/){ + my $cmdline = main::get_cmdline(); + # Dynamic runpath detection is too complex with KSirc, because KSirc is started from + # kdeinit. /proc//exe is a link to /usr/bin/kdeinit + # with one parameter which contains parameters separated by spaces(??), first param being KSirc. + # Then, KSirc runs dsirc as the perl irc script and wraps around it. When /exec is executed, + # dsirc is the program that runs inxi, therefore that is the parent process that we see. + # You can imagine how hosed I am if I try to make inxi find out dynamically with which path + # KSirc was run by browsing up the process tree in /proc. That alone is straightjacket material. + # (KSirc sucks anyway ;) + foreach (@$cmdline){ + if ($_ =~ /dsirc/){ + $client{'version'} = main::program_version('ksirc','KSirc:',2,'-v',0,0); + $client{'name'} = 'ksirc'; + $client{'name-print'} = 'KSirc'; + } + } + $client{'console-irc'} = 1; + perl_python_client(); + } + elsif ($client{'name'} =~ /python/){ + perl_python_client(); + } + # NOTE: these must be empirically determined, not all events that + # show no tty are actually IRC. tmux is not a vt, but runs inside one + if (!$client{'name-print'}){ + my $wl_terms = 'alacritty|altyo|\bate\b|black-screen|conhost|doas|evilvte|'; + $wl_terms .= 'foot|germinal|guake|havoc|hyper|kate|kitty|kmscon|konsole|'; + $wl_terms .= 'login|macwise|minicom|putty|rxvt|sakura|securecrt|'; + $wl_terms .= 'shellinabox|^st$|sudo|term|tilda|tilix|tmux|tym|wayst|xiki|'; + $wl_terms .= 'yaft|yakuake|\bzoc\b'; + my $wl_clients = 'ansible|chef|run-parts|slurm|sshd'; + my $whitelist = "$wl_terms|$wl_clients"; + # print "$client{'name'}\n"; + if ($client{'name'} =~ /($whitelist)/i){ + if ($client{'name'} =~ /($wl_terms)/i){ + ShellData::set(); + } + else { + $client{'name-print'} = $client{'name'}; + } + $b_irc = 0; + $use{'filter'} = 0; + } + else { + $client{'name-print'} = 'Unknown Client: ' . $client{'name'}; + } + } + eval $end if $b_log; +} + +sub get_cmdline { + eval $start if $b_log; + my @cmdline; + my $i = 0; + if (! -e "/proc/$ppid/cmdline"){ + return 1; + } + local $\ = ''; + open(my $fh, '<', "/proc/$ppid/cmdline") or + print_line("Open /proc/$ppid/cmdline failed: $!"); + my @rows = <$fh>; + close $fh; + foreach (@rows){ + push(@cmdline, $_); + $i++; + last if $i > 31; + } + if ($i == 0){ + $cmdline[0] = $rows[0]; + $i = ($cmdline[0]) ? 1 : 0; + } + main::log_data('string',"cmdline: @cmdline count: $i") if $b_log; + eval $end if $b_log; + return [@cmdline]; +} + +sub perl_python_client { + eval $start if $b_log; + return 1 if $client{'version'}; + # this is a hack to try to show konversation if inxi is running but started via /cmd + # OR via program shortcuts, both cases in fact now + # main::print_line("konvi: " . scalar grep { $_ =~ /konversation/ } @ps_cmd); + if ($b_display && main::check_program('konversation') && + (grep { $_ =~ /konversation/ } @ps_cmd)){ + @app = main::program_values('konversation'); + $client{'version'} = main::program_version('konversation',$app[0],$app[1],$app[2],$app[5],$app[6]); + $client{'name'} = 'konversation'; + $client{'name-print'} = $app[3]; + $client{'console-irc'} = $app[4]; + } + ## NOTE: supybot only appears in ps aux using 'SHELL' command; the 'CALL' command + ## gives the user system irc priority, and you don't see supybot listed, so use SHELL + elsif (!$b_display && + (main::check_program('supybot') || + main::check_program('gribble') || main::check_program('limnoria')) && + (grep { $_ =~ /supybot/ } @ps_cmd)){ + @app = main::program_values('supybot'); + $client{'version'} = main::program_version('supybot',$app[0],$app[1],$app[2],$app[5],$app[6]); + if ($client{'version'}){ + if (grep { $_ =~ /gribble/ } @ps_cmd){ + $client{'name'} = 'gribble'; + $client{'name-print'} = 'Gribble'; + } + if (grep { $_ =~ /limnoria/ } @ps_cmd){ + $client{'name'} = 'limnoria'; + $client{'name-print'} = 'Limnoria'; + } + else { + $client{'name'} = 'supybot'; + $client{'name-print'} = 'Supybot'; + } + } + else { + $client{'name'} = 'supybot'; + $client{'name-print'} = 'Supybot'; + } + $client{'console-irc'} = 1; + } + else { + $client{'name-print'} = "Unknown $client{'name'} client"; + } + if ($b_log){ + my $string = "namep: $client{'name-print'} name: $client{'name'} version: $client{'version'}"; + main::log_data('data',$string); + } + eval $end if $b_log; +} + +# Try to infer the use of Konversation >= 1.2, which shows $PPID improperly +# no known method of finding Konvi >= 1.2 as parent process, so we look to see if it is running, +# and all other irc clients are not running. As of 2014-03-25 this isn't used in my cases +sub check_modern_konvi { + eval $start if $b_log; + return 0 if !$client{'qdbus'}; + my ($b_modern_konvi,$konvi,$konvi_version,$pid) = (0,'','',''); + # main::log_data('data',"name: $client{'name'} :: qdb: $client{'qdbus'} :: version: $client{'version'} :: konvi: $client{'konvi'} :: PPID: $ppid") if $b_log; + # sabayon uses /usr/share/apps/konversation as path + # Paths not checked for BSDs to see what they are. + if (-d '/usr/share/kde4/apps/konversation' || -d '/usr/share/apps/konversation'){ + # much faster test, added 2022, newer konvis support + # can also query qdbus to see if it's running, but that's a subshell and grep + if ($ENV{'PYTHONPATH'} && $ENV{'PYTHONPATH'} =~ /konversation/i){ + $konvi = 'konversation'; + } + # was -session, then -qwindowtitle; cli start, nothing, just konversation$ + elsif ($pid = main::awk(\@ps_aux,'konversation( -|$)',2,'\s+')){ + main::log_data('data',"pid: $pid") if $b_log; + if (-e "/proc/$pid/exe"){ + $konvi = readlink("/proc/$pid/exe"); + $konvi =~ s/^.*\///; # basename + } + } + # print "$pid $konvi\n"; + if ($konvi){ + @app = main::program_values('konversation'); + $konvi_version = main::program_version($konvi,$app[0],$app[1],$app[2],$app[5],$app[6]); + $client{'console-irc'} = $app[4]; + $client{'konvi'} = 3; + $client{'name'} = 'konversation'; + $client{'name-print'} = $app[3]; + $client{'version'} = $konvi_version; + # note: we need to change this back to a single dot number, like 1.3, not 1.3.2 + my @temp = split('\.', $konvi_version); + $konvi_version = $temp[0] . "." . $temp[1]; + if ($konvi_version > 1.1){ + $b_modern_konvi = 1; + } + } + } + main::log_data('data',"name: $client{'name'} name print: $client{'name-print'} + qdb: $client{'qdbus'} version: $konvi_version konvi: $konvi PID: $pid") if $b_log; + main::log_data('data',"b_is_qt4: $b_modern_konvi") if $b_log; + ## for testing this module + # my $ppid = getppid(); + # system('qdbus org.kde.konversation', '/irc', 'say', $client{'dserver'}, $client{'dtarget'}, + # "getpid_dir: verNum: $konvi_version pid: $pid ppid: $ppid"); + # print "verNum: $konvi_version pid: $pid ppid: $ppid\n"; + eval $end if $b_log; + return $b_modern_konvi; +} + +sub set_konvi_data { + eval $start if $b_log; + # https://userbase.kde.org/Konversation/Scripts/Scripting_guide + if ($client{'konvi'} == 3){ + $client{'dserver'} = shift @ARGV; + $client{'dtarget'} = shift @ARGV; + $client{'dobject'} = 'default'; + } + elsif ($client{'konvi'} == 1){ + $client{'dport'} = shift @ARGV; + $client{'dserver'} = shift @ARGV; + $client{'dtarget'} = shift @ARGV; + $client{'dobject'} = 'Konversation'; + } + # for some reason this logic hiccups on multiple spaces between args + @ARGV = grep { $_ ne '' } @ARGV; + eval $end if $b_log; +} +} + +######################################################################## +#### OUTPUT +######################################################################## + +#### ------------------------------------------------------------------- +#### CLEANERS, FILTERS, AND TOOLS +#### ------------------------------------------------------------------- + +sub clean { + my ($item) = @_; + return $item if !$item;# handle cases where it was 0 or '' or undefined + # note: |nee trips engineering, but I don't know why nee was filtered + $item =~ s/chipset|company|components|computing|computer|corporation|communications|electronics?|electric(al)?|group|incorporation|industrial|international|limited|\bnee\b|?|revision|semiconductor|software|technolog(ies|y)|?|ltd\.||\bltd\b|inc\.||\binc\b|intl\.|co\.||corp\.||\(tm\)|\(r\)|®|\(rev ..\)|\'|\"|\?//gi; + $item =~ s/,|\*/ /g; + $item =~ s/^\s+|\s+$//g; + $item =~ s/\s\s+/ /g; + return $item; +} + +sub clean_arm { + my ($item) = @_; + $item =~ s/(\([^\(]*Device Tree[^\)]*\))//gi; + $item =~ s/^\s+|\s+$//g; + $item =~ s/\s\s+/ /g; + return $item; +} + +sub clean_characters { + my ($data) = @_; + # newline, pipe, brackets, + sign, with space, then clear doubled + # spaces and then strip out trailing/leading spaces. + # etc/issue often has junk stuff like (\l) \n \l + return if !$data; + $data =~ s/[:\47]|\\[a-z]|\n|,|\"|\*|\||\+|\[\s\]|n\/a|\s\s+/ /g; + $data =~ s/\(\s*\)//; + $data =~ s/^\s+|\s+$//g; + return $data; +} + +sub clean_disk { + my ($item) = @_; + return $item if !$item; + # ?| + $item =~ s/vendor.*|product.*|O\.?E\.?M\.?//gi; + $item =~ s/^\s+|\s+$//g; + $item =~ s/\s\s+/ /g; + return $item; +} + +sub clean_dmi { + my ($string) = @_; + $string = clean_unset($string,'AssetTagNum|^Base Board .*|^Chassis .*|' . + 'Manufacturer.*| Or Motherboard|\bOther\b.*|PartNum.*|SerNum|' . + '^System .*|^0x[0]+$'); + $string =~ s/\bbios\b|\bacpi\b//gi; + $string =~ s/http:\/\/www.abit.com.tw\//Abit/i; + $string =~ s/^[\s'"]+|[\s'"]+$//g; + $string =~ s/\s\s+/ /g; + $string = remove_duplicates($string) if $string; + return $string; +} + +sub clean_pci { + my ($string,$type) = @_; + # print "st1 $type:$string\n"; + my $filter = 'and\ssubsidiaries|compatible\scontroller|licensed\sby|'; + $filter .= '\b(device|controller|connection|multimedia)\b|\([^)]+\)'; + # \[[^\]]+\]$| not trimming off ending [...] initial type filters removes end + $filter = '\[[^\]]+\]$|' . $filter if $type eq 'pci'; + $string =~ s/($filter)//ig; + $string =~ s/^[\s'"]+|[\s'"]+$//g; + $string =~ s/\s\s+/ /g; + # print "st2 $type:$string\n"; + $string = remove_duplicates($string) if $string; + return $string; +} + +sub clean_pci_subsystem { + my ($string) = @_; + # we only need filters for features that might use vendor, -AGN + my $filter = 'and\ssubsidiaries|adapter|(hd\s)?audio|definition|desktop|ethernet|'; + $filter .= 'gigabit|graphics|hdmi(\/[\S]+)?|high|integrated|licensed\sby|'; + $filter .= 'motherboard|network|onboard|raid|pci\s?express'; + $string =~ s/\b($filter)\b//ig; + $string =~ s/^[\s'"]+|[\s'"]+$//g; + $string =~ s/\s\s+/ /g; + return $string; +} + +# Use sparingly, but when we need regex type stuff +# stripped out for reliable string compares, it's better. +# sometimes the pattern comes from unknown strings +# which can contain regex characters, get rid of those +sub clean_regex { + my ($string) = @_; + return if !$string; + $string =~ s/(\{|\}|\(|\)|\[|\]|\|)/ /g; + $string =~ s/^\s+|\s+$//g; + $string =~ s/\s\s+/ /g; + return $string; +} + +# args: 0: string; 1: optional, if you want to add custom filter to defaults +sub clean_unset { + my ($string,$extra) = @_; + my $cleaner = '^(\.)+$|Bad Index|default string|\[?empty\]?|\bnone\b|N\/A|^not |'; + $cleaner .= 'not set|OUT OF SPEC|To be filled|O\.?E\.?M|undefine|unknow|unspecif'; + $cleaner .= '|' . $extra if $extra; + $string =~ s/.*($cleaner).*//i; + return $string; +} + +sub filter { + my ($string) = @_; + if ($string){ + if ($use{'filter'} && $string ne message('root-required')){ + $string = $filter_string; + } + } + else { + $string = 'N/A'; + } + return $string; +} + +# Note, let the print logic handle N/A cases +sub filter_partition { + my ($source,$string,$type) = @_; + return $string if !$string || $string eq 'N/A'; + if ($source eq 'system'){ + my $test = ($type eq 'label') ? '=LABEL=': '=UUID='; + $string =~ s/$test[^\s]+/$test$filter_string/g; + } + else { + $string = $filter_string; + } + return $string; +} + +sub filter_pci_long { + my ($string) = @_; + if ($string =~ /\[AMD(\/ATI)?\]/){ + $string =~ s/Advanced\sMicro\sDevices\s\[AMD(\/ATI)?\]/AMD/; + } + return $string; +} + +# args: 0: list of values. Return the first one that is defined. +sub get_defined { + for (@_){ + return $_ if defined $_; + } + return; # don't return undef explicitly, only implicitly! +} + +# args: 0: vendor id; 1: product id. +# Returns print ready vendor:chip id string, or na variants +sub get_chip_id { + my ($vendor,$product)= @_; + my $id = 'N/A'; + if ($vendor && $product){ + $id = "$vendor:$product"; + } + elsif ($vendor){ + $id = "$vendor:n/a"; + } + elsif ($product){ + $id = "n/a:$product"; + } + return $id; +} + +# args: 0: size in KiB, return KiB, MiB, GiB, TiB, PiB, EiB; 1: 'string'; +# 2: default value if null. Assumes KiB input. +# Returns string with units or array or size unmodified if not numeric +sub get_size { + my ($size,$type,$empty) = @_; + my (@data); + $type ||= ''; + $empty ||= ''; + return $empty if !defined $size; + if (!is_numeric($size)){ + $data[0] = $size; + $data[1] = ''; + } + elsif ($size > 1024**5){ + $data[0] = sprintf("%.2f",$size/1024**5); + $data[1] = 'EiB'; + } + elsif ($size > 1024**4){ + $data[0] = sprintf("%.2f",$size/1024**4); + $data[1] = 'PiB'; + } + elsif ($size > 1024**3){ + $data[0] = sprintf("%.2f",$size/1024**3); + $data[1] = 'TiB'; + } + elsif ($size > 1024**2){ + $data[0] = sprintf("%.2f",$size/1024**2); + $data[1] = 'GiB'; + } + elsif ($size > 1024){ + $data[0] = sprintf("%.1f",$size/1024); + $data[1] = 'MiB'; + } + else { + $data[0] = sprintf("%.0f",$size); + $data[1] = 'KiB'; + } + $data[0] += 0 if $data[1]; # trim trailing 0s + # note: perl throws strict error if you try to convert string to int + # $data[0] = int($data[0]) if $b_int && $data[0]; + if ($type eq 'string'){ + return ($data[1]) ? join(' ', @data) : $size; + } + else { + return @data; + } +} + +# not used, but keeping logic for now +sub increment_starters { + my ($key,$indexes) = @_; + my $result = $key; + if (defined $indexes->{$key}){ + $indexes->{$key}++; + $result = "$key-$indexes->{$key}"; + } + return $result; +} + +sub make_line { + my $line = ''; + foreach (0 .. $size{'max-cols-basic'} - 2){ + $line .= '-'; + } + return $line; +} + +# args: 0: type; 1: info [optional]; 2: info [optional] +sub message { + my ($type,$id,$id2) = @_; + $id ||= ''; + $id2 ||= ''; + my %message = ( + 'arm-cpu-f' => 'Use -f option to see features', + 'audio-server-on-pipewire-pulse' => 'off (using pipewire-pulse)', + 'audio-server-process-on' => 'active (process)', + 'audio-server-root-na' => 'n/a (root, process)', + 'audio-server-root-on' => 'active (root, process)', + 'battery-data' => 'No system battery data found. Is one present?', + 'battery-data-bsd' => 'No battery data found. Try with --dmidecode', + 'battery-data-sys' => 'No /sys data found.', + 'bluetooth-data' => 'No bluetooth data found.', + 'bluetooth-down' => "tool can't run", + 'cpu-bugs-null' => 'No CPU vulnerability/bugs data available.', + 'cpu-model-null' => 'Model N/A', + 'cpu-speeds' => 'No per core speed data found.', + 'cpu-speeds-bsd' => 'No OS support for core speeds.', + 'darwin-feature' => 'Feature not supported iu Darwin/OSX.', + 'dev' => 'Feature under development', + 'device-data' => 'No device data found.', + 'disk-data' => 'No disk data found.', + 'disk-data-bsd' => 'No disk data found.', + 'disk-size-0' => 'Total N/A', + 'display-driver-na' => 'X driver n/a', # legacy, leave for now + 'display-driver-na-try-root' => 'X driver n/a, try sudo/root', + 'display-server' => 'No display server data found. Headless machine?', + 'dmesg-boot-permissions' => 'dmesg.boot permissions', + 'dmesg-boot-missing' => 'dmesg.boot not found', + 'dmidecode-dev-mem' => 'dmidecode is not allowed to read /dev/mem', + 'dmidecode-smbios' => 'No SMBIOS data for dmidecode to process', + 'edid-revision' => "invalid EDID revision: $id", + 'edid-sync' => "bad sync value: $id", + 'edid-version' => "invalid EDID version: $id", + 'egl-null' => 'No EGL data available.', + 'egl-missing' => 'EGL data requires eglinfo. Check --recommends.', + 'file-unreadable' => 'File not readable (permissions?)', + 'gfx-api' => 'No display API data available.', + 'gfx-api-console' => 'No API data available in console. Headless machine?', + 'glx-console-glxinfo-missing' => 'GL data unavailable in console, glxinfo missing.', + 'glx-console-root' => 'GL data unavailable in console for root.', + 'glx-console-try' => 'GL data unavailable in console. Try -G --display', + 'glx-display-root' => 'GL data unavailable for root.', + 'glx-egl' => 'incomplete (EGL sourced)', + 'glx-egl-console' => 'console (EGL sourced)', + 'glx-egl-missing' => 'glxinfo missing (EGL sourced)', + 'glx-null' => 'No GL data available.', + 'glx-value-empty' => 'Unset. Missing GL driver?', + 'glxinfo-missing' => 'Unable to show GL data. glxinfo is missing.', + 'IP' => "No $id found. Connected to web? SSL issues?", + 'IP-dig' => "No $id found. Connected to web? SSL issues? Try --no-dig", + 'IP-no-dig' => "No $id found. Connected to web? SSL issues? Try enabling dig", + 'logical-data' => 'No logical block device data found.', + 'logical-data-bsd' => "Logical block device feature unsupported in $id.", + 'machine-data' => 'No machine data: try newer kernel.', + 'machine-data-bsd' => 'No machine data: Is dmidecode installed? Try -M --dmidecode.', + 'machine-data-dmidecode' => 'No machine data: try newer kernel. Is dmidecode installed? Try -M --dmidecode.', + 'machine-data-force-dmidecode' => 'No machine data: try newer kernel. Is dmidecode installed? Try -M --dmidecode.', + 'machine-data-fruid' => 'No machine data: Is fruid_print installed?', + 'monitor-console' => 'N/A in console', + 'monitor-id' => 'not-matched', + 'monitor-na' => 'N/A', + 'monitor-wayland' => 'no compositor data', + 'note-check' => 'check', + 'note-est' => 'est.', + 'note-not-reliable' => 'not reliable', + 'nv-current' => "current (as of $id)", + 'nv-current-eol' => "current (as of $id; EOL~$id2)", + 'nv-legacy-active' => "legacy-active (EOL~$id)", + 'nv-legacy-eol' => "legacy (EOL~$id)", + 'optical-data' => 'No optical or floppy data found.', + 'optical-data-bsd' => 'No optical or floppy data found.', + 'output-control' => "-:: 'Enter' to continue to next block. Any key + 'Enter' to exit:", + 'output-control-exit' => 'Exiting output. Have a nice day.', + 'output-limit' => "Output throttled. IPs: $id; Limit: $limit; Override: --limit [1-x;-1 all]", + 'package-data' => 'No packages detected. Unsupported package manager?', + 'partition-data' => 'No partition data found.', + 'partition-hidden' => 'N/A (hidden?)', + 'pci-advanced-data' => 'bus/chip ids n/a', + 'pci-card-data' => 'No PCI device data found.', + 'pci-card-data-root' => 'PCI device data requires root.', + 'pci-slot-data' => 'No PCI Slot data found.', + 'pm-rpm-disabled' => 'see --rpm', + 'ps-data-null' => 'No process data available.', + 'raid-data' => 'No RAID data found.', + 'ram-data' => 'No RAM data found.', + 'ram-data-complete' => 'For complete report, try with --dmidecode', + 'ram-data-dmidecode' => 'No RAM data found. Try with --dmidecode', + 'recommends' => 'see --recommends', + 'repo-data', "No repo data detected. Does $self_name support your package manager?", + 'repo-data-bsd', "No repo data detected. Does $self_name support $id?", + 'risc-pci' => 'No ' . uc($id) . ' data found for this feature.', + 'root-feature' => 'Feature requires superuser permissions.', + 'root-item-incomplete' => "Full $id report requires superuser permissions.", + 'root-required' => '', + 'root-suggested' => 'try sudo/root',# gdm only + 'screen-wayland' => 'no compositor data', + 'screen-xvesa' => 'no Xvesa data', + 'sensor-data-bsd' => "$id sensor data found but not usable.", + 'sensor-data-bsd-ok' => 'No sensor data found. Are data sources present?', + 'sensor-data-bsd-unsupported' => 'Sensor data not available. Unsupported BSD variant.', + 'sensor-data-ipmi' => 'No ipmi sensor data found.', + 'sensor-data-ipmi-root' => 'Unable to run ipmi sensors. Root privileges required.', + 'sensors-data-linux' => 'No sensor data found. Missing /sys/class/hwmon, lm-sensors.', + 'sensor-data-lm-sensors' => 'No sensor data found. Is lm-sensors configured?', + 'sensor-data-sys' => 'No sensor data found in /sys/class/hwmon.', + 'sensor-data-sys-lm' => 'No sensor data found using /sys/class/hwmon or lm-sensors.', + 'smartctl-command' => 'A mandatory SMART command failed. Various possible causes.', + 'smartctl-open' => 'Unable to open device. Wrong device ID given?', + 'smartctl-udma-crc' => 'Bad cable/connection?', + 'smartctl-usb' => 'Unknown USB bridge. Flash drive/Unsupported enclosure?', + 'stopped' => 'stopped', + 'swap-admin' => 'No admin swap data available.', + 'swap-data' => 'No swap data was found.', + 'tool-missing-basic' => "", + 'tool-missing-incomplete' => "Missing system tool: $id. Output will be incomplete", + 'tool-missing-os' => "No OS support. Is a comparable $id tool available?", + 'tool-missing-recommends' => "Required tool $id not installed. Check --recommends", + 'tool-missing-required' => "Required program $id not available", + 'tool-permissions' => "Unable to run $id. Root privileges required.", + 'tool-present' => 'Present and working', + 'tool-unknown-error' => "Unknown $id error. Unable to generate data.", + 'tools-missing' => "This feature requires one of these tools: $id", + 'tools-missing-bsd' => "This feature requires one of these tools: $id", + 'undefined' => '', + 'unmounted-data' => 'No unmounted partitions found.', + 'unmounted-data-bsd' => "Unmounted partition feature unsupported in $id.", + 'unmounted-file' => 'No /proc/partitions file found.', + 'unsupported' => '', + 'usb-data' => 'No USB data found. Server?', + 'usb-mode-mismatch' => '', + 'unknown-cpu-topology' => 'ERR-103', + 'unknown-desktop-version' => 'ERR-101', + 'unknown-dev' => 'ERR-102', + 'unknown-device-id' => 'unknown device ID', + 'unknown-shell' => 'ERR-100', + 'vulkan-null' => 'No Vulkan data available.', + 'weather-error' => "Error: $id", + 'weather-null' => "No $id found. Internet connection working?", + 'xvesa-null' => 'No Xvesa VBE/GOP data found.', + ); + return $message{$type}; +} + +# args: 0: string of range types (2-5; 3 4; 3,4,2-12) to generate single regex +# string for +sub regex_range { + return if ! defined $_[0]; + my @processed; + foreach my $item (split(/[,\s]+/,$_[0])){ + if ($item =~ /(\d+)-(\d+)/){ + $item = join('|',($1..$2)); + } + push(@processed,$item); + } + return join('|',@processed); +} + +# Handles duplicates occuring anywhere in string +sub remove_duplicates { + my ($string) = @_; + return if !$string; + my (%holder,@temp); + foreach (split(/\s+/, $string)){ + if (!$holder{lc($_)}){ + push(@temp, $_); + $holder{lc($_)} = 1; + } + } + $string = join(' ', @temp); + return $string; +} + +# args: 0: string to turn to KiB integer value. +# Convert string passed to KB, based on GB/MB/TB id +# NOTE: 1 [K 1000; kB: 1000; KB 1024; KiB 1024] bytes +# The logic will turn false MB to M for this tool +# Hopefully one day sizes will all be in KiB type units +sub translate_size { + my ($working) = @_; + my ($size,$unit) = (0,''); + # print ":$working:\n"; + return if !defined $working; + my $math = ($working =~ /B$/) ? 1000: 1024; + if ($working =~ /^([0-9\.]+)\s*([kKMGTPE])i?B?$/i){ + $size = $1; + $unit = uc($2); + } + if ($unit eq 'K'){ + $size = $1; + } + elsif ($unit eq 'M'){ + $size = $1 * $math; + } + elsif ($unit eq 'G'){ + $size = $1 * $math**2; + } + elsif ($unit eq 'T'){ + $size = $1 * $math**3; + } + elsif ($unit eq 'P'){ + $size = $1 * $math**4; + } + elsif ($unit eq 'E'){ + $size = $1 * $math**5; + } + $size = int($size) if $size; + return $size; +} + +#### ------------------------------------------------------------------- +#### GENERATE OUTPUT +#### ------------------------------------------------------------------- + +sub check_output_path { + my ($path) = @_; + my ($b_good,$dir,$file); + $dir = $path; + $dir =~ s/([^\/]+)$//; + $file = $1; + # print "file: $file : dir: $dir\n"; + $b_good = 1 if (-d $dir && -w $dir && $dir =~ /^\// && $file); + return $b_good; +} + +# Passing along hash ref +sub output_handler { + my ($data) = @_; + # print Dumper \%data; + if ($output_type eq 'screen'){ + print_data($data); + } + elsif ($output_type eq 'json'){ + generate_json($data); + } + elsif ($output_type eq 'xml'){ + generate_xml($data); + } +} + +# Passing along hash ref +# NOTE: file has already been set and directory verified +sub generate_json { + eval $start if $b_log; + my ($data) = @_; + my ($json); + my $b_debug = 0; + my ($b_cpanel,$b_valid); + error_handler('not-in-irc', 'help') if $b_irc; + print Dumper $data if $b_debug; + load_json() if !$loaded{'json'}; + print Data::Dumper::Dumper $use{'json'} if $b_debug; + if ($use{'json'}){ + # ${$use{'json'}->{'new'}}->canonical(1); + # $json = ${$use{'json'}->{'new'}}->json_encode($data); + # ${$use{'json'}->{'new-json'}}->canonical(1); + # $json = ${$use{'json'}->{'new-json'}}->encode_json($data); + $json = &{$use{'json'}->{'encode'}}($data); + } + else { + error_handler('required-module', 'json', 'JSON::PP, Cpanel::JSON::XS or JSON::XS'); + } + if ($json){ + #$json =~ s/"[0-9]+#/"/g; + if ($output_file eq 'print'){ + #$json =~ s/\}/}\n/g; + print "$json"; + } + else { + print_line("Writing JSON data to: $output_file\n"); + open(my $fh, '>', $output_file) or error_handler('open',$output_file,"$!"); + print $fh "$json"; + close $fh; + print_line("Data written successfully.\n"); + } + } + eval $end if $b_log; +} + +# NOTE: So far xml is substantially more difficult than json, so +# using a crude dumper rather than making a nice xml file, but at +# least xml has some output now. +sub generate_xml { + eval $start if $b_log; + my ($data) = @_; + my ($xml); + my $b_debug = 0; + error_handler('not-in-irc', 'help') if $b_irc; + # print Dumper $data if $b_debug; + if (check_perl_module('XML::Dumper')){ + XML::Dumper->import; + $xml = XML::Dumper::pl2xml($data); + #$xml =~ s/"[0-9]+#/"/g; + if ($output_file eq 'print'){ + print "$xml"; + } + else { + print_line("Writing XML data to: $output_file\n"); + open(my $fh, '>', $output_file) or error_handler('open',$output_file,"$!"); + print $fh "$xml"; + close $fh; + print_line("Data written successfully.\n"); + } + } + else { + error_handler('required-module', 'xml', 'XML::Dumper'); + } + eval $end if $b_log; +} + +sub key { + return sprintf("%03d#%s#%s#%s", $_[0],$_[1],$_[2],$_[3]); +} + +sub output_control { + print message('output-control'); + chomp(my $response = ); + if (!$response){ + $size{'lines'} = 1; + } + else { + print message('output-control-exit'), "\n"; + exit 0; + } +} + +sub print_basic { + my ($data) = @_; + my $indent = 18; + my $indent_static = 18; + my $indent1_static = 5; + my $indent2_static = 8; + my $indent1 = 5; + my $indent2 = 8; + my $length = @$data; + my ($start,$i,$j,$line); + my $width = $size{'max-cols-basic'}; + if ($width > 110){ + $indent_static = 22; + } + elsif ($width < 90){ + $indent_static = 15; + } + # print $length . "\n"; + for my $i (0 .. $#$data){ + # print "0: $data->[$i][0]\n"; + if ($data->[$i][0] == 0){ + $indent = 0; + $indent1 = 0; + $indent2 = 0; + } + elsif ($data->[$i][0] == 1){ + $indent = $indent_static; + $indent1 = $indent1_static; + $indent2= $indent2_static; + } + elsif ($data->[$i][0] == 2){ + $indent = ($indent_static + 7); + $indent1 = ($indent_static + 5); + $indent2 = 0; + } + $data->[$i][3] =~ s/\n/ /g; + $data->[$i][3] =~ s/\s+/ /g; + if ($data->[$i][1] && $data->[$i][2]){ + $data->[$i][1] = $data->[$i][1] . ', '; + } + $start = sprintf("%${indent1}s%-${indent2}s",$data->[$i][1],$data->[$i][2]); + if ($indent > 1 && (length($start) > ($indent - 1))){ + $line = sprintf("%-${indent}s\n", "$start"); + print_line($line); + $start = ''; + # print "1-print.\n"; + } + if (($indent + length($data->[$i][3])) < $width){ + $data->[$i][3] =~ s/\^/ /g; + $line = sprintf("%-${indent}s%s\n", "$start", $data->[$i][3]); + print_line($line); + # print "2-print.\n"; + } + else { + my $holder = ''; + my $sep = ' '; + # note: special case, split ' ' trims leading, trailing spaces, + # then splits like awk, on one or more white spaces. + foreach my $word (split(' ', $data->[$i][3])){ + # print "$word\n"; + if (($indent + length($holder) + length($word)) < $width){ + $word =~ s/\^/ /g; + $holder .= $word . $sep; + # print "3-hold.\n"; + } + # elsif (($indent + length($holder) + length($word)) >= $width){ + else { + $line = sprintf("%-${indent}s%s\n", "$start", $holder); + print_line($line); + $start = ''; + $word =~ s/\^/ /g; + $holder = $word . $sep; + # print "4-print-hold.\n"; + } + } + if ($holder !~ /^[ ]*$/){ + $line = sprintf("%-${indent}s%s\n", "$start", $holder); + print_line($line); + # print "5-print-last.\n"; + } + } + } +} + +# This has to get a hash of hashes, at least for now. Because perl does not +# retain insertion order, I use a prefix for each hash key to force sorts. +sub print_data { + my ($data) = @_; + my ($counter,$length,$split_count) = (0,0,0); + my ($hash_id,$holder,$holder2,$start,$start2,$start_holder) = ('','','','','',''); + my $indent = $size{'indent'}; + my (%ids); + my ($b_container,$b_ni2,$key,$line,$val2,$val3); + # these 2 sets are single logic items + my $b_single = ($size{'max-cols'} == 1) ? 1: 0; + my ($b_row1,$indent_2,$indent_use,$indentx) = (1,0,0,0); + # $size{'max-cols'} = 88; + # NOTE: indent < 11 would break the output badly in some cases + if ($size{'max-cols'} < $size{'max-wrap'} || $size{'indent'} < 11){ + $indent = $size{'indents'}; + } + foreach my $key1 (sort { substr($a,0,3) <=> substr($b,0,3) } keys %$data){ + $key = (split('#', $key1))[3]; + $b_row1 = 1; + if ($key ne 'SHORT'){ + $start = sprintf("$colors{'c1'}%-${indent}s$colors{'cn'}","$key$sep{'s1'}"); + if ($use{'output-block'}){ + output_control() if $use{'output-block'} > 1; + $use{'output-block'}++; + } + $start_holder = $key; + $indent_2 = $indent + $size{'indents'}; + $b_ni2 = ($start_holder eq 'Info') ? 1 : 0; + if ($indent < 10){ + $line = "$start\n"; + print_line($line); + $start = ''; + $line = ''; + } + } + else { + $indent = 0; + } + next if ref($data->{$key1}) ne 'ARRAY'; + # Line starters that will be -x incremented always + # It's a tiny bit faster manually resetting rather than using for loop + %ids = ( + 'Array' => 1, # RAM or RAID + 'Battery' => 1, + 'Card' => 1, + 'Device' => 1, + 'Floppy' => 1, + 'Hardware' => 1, # hardware raid report + 'Hub' => 1, + 'ID' => 1, + 'IF-ID' => 1, + 'LV' => 1, + 'Monitor' => 1, + 'Optical' => 1, + 'Screen' => 1, + 'Server' => 1, # was 'Sound Server' + 'variant' => 1, # arm > 1 cpu type + ); + foreach my $val1 (@{$data->{$key1}}){ + if (ref($val1) eq 'HASH'){ + if (!$b_single){ + $indent_use = $length = ($b_row1 && $key !~ /^(Features)$/) ? $indent : $indent_2; + } + ($counter,$b_row1,$split_count) = (0,1,0); + foreach my $key2 (sort {substr($a,0,3) <=> substr($b,0,3)} keys %$val1){ + ($hash_id,$b_container,$indentx,$key) = (split('#', $key2)); + if (!$b_single){ + $indent_use = ($b_row1 || $b_ni2) ? $indent: $indent_2; + } + # print "m-1: r1: $b_row1 iu: $indent_use\n"; + if ($start_holder eq 'Graphics' && $key eq 'Screen'){ + $ids{'Monitor'} = 1; + } + elsif ($start_holder eq 'Memory' && $key eq 'Array'){ + $ids{'Device'} = 1; + } + elsif ($start_holder eq 'RAID' && $key eq 'Device'){ + $ids{'Array'} = 1; + } + elsif ($start_holder eq 'USB' && $key eq 'Hub'){ + $ids{'Device'} = 1; + } + elsif ($start_holder eq 'Logical' && $key eq 'Device'){ + $ids{'LV'} = 1; + } + if ($counter == 0 && defined $ids{$key}){ + $key .= '-' . $ids{$key}++; + } + $val2 = $val1->{$key2}; + # we have to handle cases where $val2 is 0 + if (!$b_single && $val2 || $val2 eq '0'){ + $val2 .= " "; + } + # See: Use of implicit split to @_ is deprecated. Only get this + # warning in Perl 5.08 oddly enough. ie, no: scalar (split(...)); + my @values = split(/\s+/, $val2); + $split_count = scalar @values; + # print "sc: $split_count l: " . (length("$key$sep{'s2'} $val2") + $indent_use), " val2: $val2\n"; + if (!$b_single && + (length("$key$sep{'s2'} $val2") + $length) <= $size{'max-cols'}){ + # print "h-1: r1: $b_row1 iu: $indent_use\n"; + $length += length("$key$sep{'s2'} $val2"); + $holder .= "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2"; + } + # Handle case where the key/value pair is > max, and where there are + # a lot of terms, like cpu flags, raid types supported. Raid can have + # the last row have a lot of devices, or many raid types. But we don't + # want to wrap things like: 3.45 MiB (6.3%) + elsif (!$b_single && $split_count > 2 && length($val2) > 24 && + !defined $ids{$key} && + (length("$key$sep{'s2'} $val2") + $indent_use + $length) > $size{'max-cols'}){ + # print "m-2 r1: $b_row1 iu: $indent_use\n"; + $val3 = shift @values; + $start2 = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val3 "; + # Case where not first item in line, but when key+first word added, + # is wider than max width. + if ($holder && + ($length + length("$key$sep{'s2'} $val3")) > $size{'max-cols'}){ + # print "p-1a r1: $b_row1 iu: $indent_use\n"; + $holder =~ s/\s+$//; + $line = sprintf("%-${indent_use}s%s$colors{'cn'}\n","$start","$holder"); + print_line($line); + $b_row1 = 0; + $start = ''; + $holder = ''; + $length = $indent_use; + } + $length += length("$key$sep{'s2'} $val3 "); + # print scalar @values,"\n"; + foreach (@values){ + # my $l = (length("$_ ") + $length); + # print "$l\n"; + $indent_use = ($b_row1 || $b_ni2) ? $indent : $indent_2; + if ((length("$_ ") + $length) < $size{'max-cols'}){ + # print "h-2: r1: $b_row1 iu: $indent_use\n"; + # print "a\n"; + if ($start2){ + $holder2 .= "$start2$_ "; + $start2 = ''; + } + else { + $holder2 .= "$_ "; + } + $length += length("$_ "); + } + else { + # print "p-1b: r1: $b_row1 iu: $indent_use\n"; + if ($start2){ + $holder2 = "$start2$holder2"; + } + else { + $holder2 = "$colors{'c2'}$holder2"; + } + # print "xx:$holder"; + $holder2 =~ s/\s+$//; + $line = sprintf("%-${indent_use}s%s$colors{'cn'}\n","$start","$holder$holder2"); + print_line($line); + # make sure wrapped value is indented correctly! + $b_row1 = 0; + $indent_use = ($b_row1) ? $indent : $indent_2; + $holder = ''; + $holder2 = "$_ "; + # print "h2: $holder2\n"; + $length = length($holder2) + $indent_use; + $start2 = ''; + $start = ''; + } + } + # We don't want to start a new line, continue until full length. + if ($holder2 !~ /^\s*$/){ + # print "p-2: r1: $b_row1 iu: $indent_use\n"; + $holder2 = "$colors{'c2'}$holder2"; + $holder = $holder2; + $b_row1 = 0; + $holder2 = ''; + $start2 = ''; + $start = ''; + } + } + # NOTE: only these and the last fallback are used for b_single output + else { + if ($holder){ + # print "p-3: r1: $b_row1 iu: $indent_use\n"; + $holder =~ s/\s+$//; + $line = sprintf("%-${indent_use}s%s$colors{'cn'}\n",$start,"$holder"); + $length = length("$key$sep{'s2'} $val2") + $indent_use; + print_line($line); + $b_row1 = 0; + $start = ''; + } + else { + # print "h-3a: r1: $b_row1 iu: $indent_use\n"; + $length = $indent_use; + } + if ($b_single){ + $indent_use = ($indent * $indentx); + } + else { + $indent_use = ($b_row1 || $b_ni2) ? $indent: $indent_2; + } + $holder = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2"; + # print "h-3b: r1: $b_row1 iu: $indent_use\n"; + } + $counter++; + } + if ($holder !~ /^\s*$/){ + # print "p-4: r1: $b_row1 iu: $indent_use\n"; + $holder =~ s/\s+$//; + $line = sprintf("%-${indent_use}s%s$colors{'cn'}\n",$start,"$start2$holder"); + print_line($line); + $b_row1 = 0; + $holder = ''; + $length = 0; + $start = ''; + } + } + # Only for repos currently + elsif (ref($val1) eq 'ARRAY'){ + # print "p-5: r1: $b_row1 iu: $indent_use\n"; + my $num = 0; + my ($l1,$l2); + $indent_use = $indent_2; + foreach my $item (@$val1){ + $num++; + if ($size{'max-lines'}){ + $l1 = length("$num$sep{'s2'} $item") + $indent_use; + # Cut down the line string until it's short enough to fit in term + if ($l1 > $size{'term-cols'}){ + $l2 = length("$num$sep{'s2'} ") + $indent_use + 6; + # print "$l1 $size{'term-cols'} $l2 $num $indent_use\n"; + $item = substr($item,0,$size{'term-cols'} - $l2) . '[...]'; + } + } + $line = "$colors{'c1'}$num$sep{'s2'} $colors{'c2'}$item$colors{'cn'}"; + $line = sprintf("%-${indent_use}s%s\n","","$line"); + print_line($line); + } + + } + } + # We want a space between data blocks for single + print_line("\n") if $b_single; + } +} + +sub print_line { + my ($line) = @_; + if ($b_irc && $client{'test-konvi'}){ + $client{'konvi'} = 3; + $client{'dobject'} = 'Konversation'; + } + if ($client{'konvi'} == 1 && $client{'dcop'}){ + # konvi doesn't seem to like \n characters, it just prints them literally + $line =~ s/\n//g; + #qx('dcop "$client{'dport'}" "$client{'dobject'}" say "$client{'dserver'}" "$client{'dtarget'}" "$line 1"); + system('dcop', $client{'dport'}, $client{'dobject'}, 'say', $client{'dserver'}, $client{'dtarget'}, "$line 1"); + } + elsif ($client{'konvi'} == 3 && $client{'qdbus'}){ + # print $line; + $line =~ s/\n//g; + #qx(qdbus org.kde.konversation /irc say "$client{'dserver'}" "$client{'dtarget'}" "$line"); + system('qdbus', 'org.kde.konversation', '/irc', 'say', $client{'dserver'}, $client{'dtarget'}, $line); + } + else { + # print "tl: $size{'term-lines'} ml: $size{'max-lines'} l:$size{'lines'}\n"; + if ($size{'max-lines'}){ + # -y1 + -Y can result in start of output scrolling off screen if terminal + # wrapped lines happen. + if ((($size{'max-lines'} >= $size{'term-lines'}) && + $size{'max-lines'} == $size{'lines'}) || + ($size{'max-lines'} < $size{'term-lines'} && + $size{'max-lines'} + 1 == $size{'lines'})){ + output_control(); + } + } + print $line; + $size{'lines'}++ if $size{'max-lines'}; + } +} + +######################################################################## +#### ITEM PROCESSORS +######################################################################## + +#### ------------------------------------------------------------------- +#### ITEM GENERATORS +#### ------------------------------------------------------------------- + +## AudioItem +{ +package AudioItem; + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + if (%risc && !$use{'soc-audio'} && !$use{'pci-tool'}){ + my $key = 'Message'; + @$rows = ({ + main::key($num++,0,1,$key) => main::message('risc-pci',$risc{'id'}) + }); + } + else { + device_output($rows); + } + if (((%risc && !$use{'soc-audio'} && !$use{'pci-tool'}) || !@$rows) && + (my $file = $system_files{'asound-cards'})){ + asound_output($rows,$file); + } + usb_output($rows); + # note: for servers often no audio, so we don't care about pci specific + if (!@$rows){ + my $key = 'Message'; + my $type = 'device-data'; + if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){ + $type = 'pci-card-data-root'; + } + @$rows = ({main::key($num++,0,1,$key) => main::message($type,'')}); + } + sound_output($rows); + eval $end if $b_log; + return $rows; +} + +sub device_output { + eval $start if $b_log; + return if !$devices{'audio'}; + my $rows = $_[0]; + my ($j,$num) = (0,1); + foreach my $row (@{$devices{'audio'}}){ + $num = 1; + $j = scalar @$rows; + my $driver = $row->[9]; + $driver ||= 'N/A'; + my $device = $row->[4]; + $device = ($device) ? main::clean_pci($device,'output') : 'N/A'; + # have seen absurdly verbose card descriptions, with non related data etc + if (length($device) > 85 || $size{'max-cols'} < 110){ + $device = main::filter_pci_long($device); + } + push(@$rows, { + main::key($num++,1,1,'Device') => $device, + }); + if ($extra > 0 && $use{'pci-tool'} && $row->[12]){ + my $item = main::get_pci_vendor($row->[4],$row->[12]); + $rows->[$j]{main::key($num++,0,2,'vendor')} = $item if $item; + } + $rows->[$j]{main::key($num++,1,2,'driver')} = $driver; + if ($extra > 0 && !$bsd_type){ + if ($row->[9]){ + my $version = main::get_module_version($row->[9]); + $rows->[$j]{main::key($num++,0,3,'v')} = $version if $version; + } + } + if ($b_admin && $row->[10]){ + $row->[10] = main::get_driver_modules($row->[9],$row->[10]); + $rows->[$j]{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10]; + } + if ($extra > 0){ + my $bus_id = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]"; + if ($extra > 1 && $bus_id ne 'N/A'){ + main::get_pcie_data($bus_id,$j,$rows,\$num); + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + } + if ($extra > 1){ + my $chip_id = main::get_chip_id($row->[5],$row->[6]); + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $chip_id; + if ($extra > 2 && $row->[1]){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = $row->[1]; + } + } + # print "$row->[0]\n"; + } + eval $end if $b_log; +} + +# this handles fringe cases where there is no card on pcibus, +# but there is a card present. I don't know the exact architecture +# involved but I know this situation exists on at least one old machine. +sub asound_output { + eval $start if $b_log; + my ($file,$rows) = @_; + my ($device,$driver,$j,$num) = ('','',0,1); + my @asound = main::reader($file); + foreach (@asound){ + # filtering out modems and usb devices like webcams, this might get a + # usb audio card as well, this will take some trial and error + if (!/modem|usb/i && /^\s*[0-9]/){ + $num = 1; + my @working = split(/:\s*/, $_); + # now let's get 1 2 + $working[1] =~ /(.*)\s+-\s+(.*)/; + $device = $2; + $driver = $1; + if ($device){ + $j = scalar @$rows; + $driver ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Device') => $device, + main::key($num++,1,2,'driver') => $driver, + }); + if ($extra > 0){ + my $version = main::get_module_version($driver); + $rows->[$j]{main::key($num++,0,3,'v')} = $version if $version; + $rows->[$j]{main::key($num++,0,2,'message')} = main::message('pci-advanced-data',''); + } + } + } + } + # print Data::Dumper:Dumper $rows; + eval $end if $b_log; +} + +sub usb_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@ids,$path_id,$product,@temp2); + my ($j,$num) = (0,1); + return if !$usb{'audio'}; + foreach my $row (@{$usb{'audio'}}){ + $num = 1; + $j = scalar @$rows; + # make sure to reset, or second device trips last flag + ($path_id,$product) = ('',''); + $product = main::clean($row->[13]) if $row->[13]; + $product ||= 'N/A'; + $row->[15] ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Device') => $product, + main::key($num++,0,2,'driver') => $row->[15], + main::key($num++,1,2,'type') => 'USB', + }); + if ($extra > 0){ + # print "$j \n"; + if ($extra > 1){ + $row->[8] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'rev')} = $row->[8]; + if ($row->[17]){ + $rows->[$j]{main::key($num++,0,3,'speed')} = $row->[17]; + } + if ($row->[24]){ + $rows->[$j]{main::key($num++,0,3,'lanes')} = $row->[24]; + } + if ($b_admin && $row->[22]){ + $rows->[$j]{main::key($num++,0,3,'mode')} = $row->[22]; + } + } + $path_id = $row->[2] if $row->[2]; + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]"; + if ($extra > 1){ + $row->[7] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $row->[7]; + } + if ($extra > 2){ + if (defined $row->[5] && $row->[5] ne ''){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]"; + } + if ($row->[16]){ + $rows->[$j]{main::key($num++,0,2,'serial')} = main::filter($row->[16]); + } + } + } + } + eval $end if $b_log; +} + +sub sound_output { + eval $start if $b_log; + my $rows = $_[0]; + my ($key,$program,$value); + my ($j,$num) = (0,0); + foreach my $server (@{sound_data()}){ + next if $extra < 1 && (!$server->[3] || $server->[3] !~ /^(active|.*api)/); + $j = scalar @$rows; + $server->[2] ||= 'N/A'; + $server->[3] ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,$server->[0]) => $server->[1], + main::key($num++,0,2,'v') => $server->[2], + main::key($num++,0,2,'status') => $server->[3], + }); + if ($extra > 1 && defined $server->[4] && ref $server->[4] eq 'ARRAY'){ + my $b_multi = (scalar @{$server->[4]} > 1) ? 1: 0; + my $b_start; + my $k = 0; + foreach my $item (@{$server->[4]}){ + if ($item->[2] eq 'daemon'){ + $key = 'status'; + $value = $item->[3]; + } + else { + $key = 'type'; + $value = $item->[2]; + } + if (!$b_multi){ + $rows->[$j]{main::key($num++,1,2,$item->[0])} = $item->[1]; + $rows->[$j]{main::key($num++,0,3,$key)} = $value; + } + else { + $rows->[$j]{main::key($num++,1,2,$item->[0])} = '' if !$b_start; + $b_start = 1; + $k++; + $rows->[$j]{main::key($num++,1,3,$k)} = $item->[1]; + $rows->[$j]{main::key($num++,0,4,$key)} = $value; + } + } + } + if ($b_admin){ + # Let long lines wrap for high tool counts, but best avoid too many tools + my $join = (defined $server->[5] && length(join(',',@{$server->[5]})) > 40) ? ', ': ','; + my $val = (defined $server->[5]) ? join($join,@{$server->[5]}) : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'tools')} = $val; + } + } + eval $end if $b_log; +} + +# see docs/inxi-audio.txt for unused or alternate helpers/tools +sub sound_data { + eval $start if $b_log; + my ($config,$helpers,$name,$program,$status,$test,$tools,$type,$version); + my $data = []; + ## API Types ## + # not yet, user lib: || main::globber('/usr/lib*{,/*}/libasound.so*') + # the config test is expensive but will only trigger on servers with no audio + # devices. Checks if kernel was compiled with SND_ items, even if no devices. + if (!$bsd_type && -r "/boot/config-$uname[2]"){ + $config = "/boot/config-$uname[2]"; + } + if ($system_files{'asound-version'} || + ($config && (grep {/^CONFIG_SND_/} @{main::reader($config,'','ref')}))){ + $name = 'ALSA'; + $type = 'API'; + # always true until find better test for inactive API test + if ($system_files{'asound-version'}){ + # avoid possible second line if compiled by user + my $content = main::reader($system_files{'asound-version'},'',0); + # we want the string after driver version for old and new ALSA + # some alsa strings have the build date in (...) after Version + if ($content =~ /Driver Version (\S+)(\s|\.?$)/){ + $version = $1; + $version =~ s/\.$//; # trim off period + } + $status = 'kernel-api'; + } + else { + $status = 'inactive'; + $version = $uname[2]; + $version =~ s/^k//; # avoid double kk possible result + $version = 'k' . $version; + } + if ($extra > 1){ + $test = [['osspd','daemon'],['aoss','oss-emulator'], + ['apulse','pulse-emulator'],]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(alsactl alsamixer alsamixergui amixer)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + # sndstat file may be removed in linux oss, but ossinfo part of oss4-base + # alsa oss compat driver will create /dev/sndstat in linux however + # Note: kernel compile: SOUND_OSS + if ((-e '/dev/sndstat' && !$system_files{'asound-version'}) || + main::check_program('ossinfo')){ + $name = 'OSS'; + # not a great test, but ok for now, check on current Linux, seems unlikely + # to find OSS on OpenBSD in general. + if ($bsd_type){ + $status = (-e '/dev/sndstat') ? 'kernel-api' : 'inactive'; + } + else { + $status = (-e '/dev/sndstat') ? 'active' : 'off?'; + } + $type = 'API'; # not strictly an API on linux, but almost nobody uses it. + # not certain to be cross distro, Debian/Ubuntu at least. + if (-e '/etc/oss4/version.dat'){ + $version = main::reader('/etc/oss4/version.dat','',0); + } + elsif ($sysctl{'audio'}){ + $version = (grep {/^hw.snd.version:/} @{$sysctl{'audio'}})[0]; + $version = (split(/:\s*/,$version),1)[1] if $version; + $version =~ s|/.*$|| if $version; + } + if ($extra > 1){ + # virtual_oss freebsd, not verified; osspd-alsa/pulseaudio no path exec + $test = [['virtual_oss','daemon'],['virtual_equalizer','plugin']]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + # *mixer are FreeBSD tools + $test = [qw(dsbmixer mixer ossctl ossinfo ossmix ossxmix vmixctl)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + if ($program = main::check_program('sndiod')){ + if ($bsd_type){ + push(@$data, ['API','sndio',undef,'sound-api',undef,undef]); + } + $name = 'sndiod'; + # verified: accurate + $status = (grep {/sndiod/} @ps_cmd) ? 'active': 'off'; + $type = 'Server'; + # $version: no known method + if ($b_admin){ + $test = [qw(aucat midicat mixerctl sndioctl)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + ## Servers ## + if ($program = main::check_program('artsd')){ + $name = 'aRts'; + $status = (grep {/artsd/} @ps_cmd) ? 'active': 'off'; + $type = 'Server'; + $version = main::program_version($program,'^artsd',2,'-v',1); + if ($extra > 1){ + $test = [['artswrapper','daemon'],]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(artsbuilder artsdsp)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + # pulseaudio-esound-compat has esd pointing to esdcompat + if (($program = main::check_program('esd')) && + !main::check_program('esdcompat')){ + $name = 'EsounD'; + $status = (grep {/\besd\b/} @ps_cmd) ? 'active': 'off'; + $type = 'Server'; + $version = main::program_version($program,'^Esound',3,'--version',1,1); + # if ($extra > 1){ + # $test = [['','daemon'],]; + # $helpers = sound_helpers($test); + # } + if ($b_admin){ + $test = [qw(esdcat esdctl esddsp)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + if ($program = main::check_program('jackd')){ + $name = 'JACK'; + $status = jack_status(); + $type = 'Server'; + $version = main::program_version($program,'^jackd',3,'--version',1); + if ($extra > 1){ + $test = [['a2jmidid','daemon'],['nsmd','daemon']]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(agordejo cadence jack_control jack_mixer qjackctl)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + if ($program = main::check_program('nasd')){ + $name = 'NAS'; + $status = (grep {/(^|\/)nasd/} @ps_cmd) ? 'active': 'off'; + $type = 'Server'; + $version = main::program_version($program,'^Network Audio',5,'-V',1); + if ($extra > 1){ + $test = [['audiooss','oss-compat'],]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(auctl auinfo)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + if ($program = main::check_program('pipewire')){ + $name = 'PipeWire'; + $status = pipewire_status(); + $type = 'Server'; + $version = main::program_version($program,'^Compiled with libpipe',4,'--version',1); + if ($extra > 1){ + # pipewire-alsa is a plugin, but is just some config files + $test = [['pipewire-pulse','daemon'],['pipewire-media-session','daemon'], + ['wireplumber','daemon'], + ['pipewire-alsa','plugin','/etc/alsa/conf.d/*-pipewire-default.conf'], + ['pw-jack','plugin']]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(pw-cat pw-cli wpctl)]; + # note: pactl can be used w/pipewire-pulse; + if (!main::check_program('pulseaudio') && + main::check_program('pipewire-pulse')){ + splice(@$test,0,0,'pactl'); + } + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + # note: pactl info/list/stat could be used + if ($program = main::check_program('pulseaudio')){ + $name = 'PulseAudio'; + $status = pulse_status($program); + $type = 'Server'; + $version = main::program_version($program,'^pulseaudio',2,'--version',1); + if ($extra > 1){ + $test = [['pulseaudio-dlna','daemon'], + ['pulseaudio-alsa','plugin','/etc/alsa/conf.d/*-pulseaudio-default.conf'], + ['esdcompat','plugin'], + ['pulseaudio-jack','module','/usr/lib/pulse*/modules/module-jack-sink.so']]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(pacat pactl paman pamix pamixer pavucontrol pulsemixer)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + if ($program = main::check_program('roard')){ + $name = 'RoarAudio'; + $status = (grep {/roard/} @ps_cmd) ? 'active': 'off'; + $type = 'Server'; + # no version so far + if ($extra > 1){ + $test = [['roarplaylistd','daemon'],['roarify','pulse/viff-emulation']]; + $helpers = sound_helpers($test); + } + if ($b_admin){ + $test = [qw(roarcat roarctl)]; + $tools = sound_tools($test); + } + push(@$data,[$type,$name,$version,$status,$helpers,$tools]); + ($status,$version,$helpers,$tools) = ('','',undef,undef); + } + main::log_data('dump','sound data: @$data',$data) if $b_log; + print 'Sound data: ', Data::Dumper::Dumper $data if $dbg[26]; + eval $end if $b_log; + return $data; +} + +# assume if jackd running we have active jack, update if required +sub jack_status { + eval $start if $b_log; + my $status; + if (grep {/jackd/} @ps_cmd){ + if (my $program = main::check_program('jack_control')){ + system("$program status > /dev/null 2>&1"); + # 0 means running, always, else 1. + if ($? == 0){ + $status = 'active'; + } + else { + $status = ($b_root) ? main::message('audio-server-root-na') : 'off'; + } + } + $status = main::message('audio-server-process-on') if !$status; + } + else { + $status = 'off'; + } + eval $end if $b_log; + return $status; +} + +# pipewire is complicated, it can be there and running without being active server +# This is NOT verified as valid true/yes case!! +sub pipewire_status { + eval $start if $b_log; + my ($b_process,$program,$status,@data); + if (grep {/(^|\/)pipewire(d|\s|:|$)/} @ps_cmd){ + # note: if pipewire was stopped but not masked, pw-cli can start service so + # only use if pipewire process already running + if ($program = main::check_program('pw-cli')){ + @data = qx($program ls 2>/dev/null); + main::log_data('dump','pw-cli @data', \@data) if $b_log; + print 'pw-cli: ', Data::Dumper::Dumper \@data if $dbg[52]; + if (@data){ + $status = (grep {/media\.class\s*=\s*"(Audio|Midi)/i} @data) ? 'active' : 'off'; + } + elsif ($b_root){ + $status = main::message('audio-server-root-na'); + } + } + $status = main::message('audio-server-process-on') if !$status; + } + else { + $status = 'off'; + } + eval $end if $b_log; + return $status; +} + +# pulse might be running through pipewire +sub pulse_status { + eval $start if $b_log; + my $program = $_[0]; + my ($status,@data); + if (grep {/(^|\/)pulseaudiod?\b/} @ps_cmd){ + # this is almost certainly not needed, but keep for now + system("$program --check > /dev/null 2>&1"); + # 0 means running, always, other could be an error. + if ($? == 0){ + $status = 'active'; + } + else { + $status = ($b_root) ? main::message('audio-server-root-on') : 'off'; + } + } + else { + # can't use pactl info test because starts pulseaudio/pipewire if unmasked + if (main::check_program('pipewire-pulse') && + (grep {/(^|\/)pipewire-pulse/} @ps_cmd)){ + $status = main::message('audio-server-on-pipewire-pulse'); + } + else { + $status = 'off'; + } + } + eval $end if $b_log; + return $status; +} + +sub sound_helpers { + eval $start if $b_log; + my $test = $_[0]; + my ($helpers,$name,$status,$key); + foreach my $item (@$test){ + if (main::check_program($item->[0]) || + (defined $item->[2] && main::globber($item->[2]))){ + $name = $item->[0]; + $key = 'with'; + # these are active/off daemons unless not a daemon + if ($item->[1] eq 'daemon'){ + $status = (grep {/$item->[0]/} @ps_cmd) ? 'active':'off' ; + } + else { + $status = $item->[1]; + } + push(@$helpers,[$key,$name,$item->[1],$status]); + } + } + # push(@$helpers, ['with','pipewire-pulse','daemon','active'],['with','pw-jack','plugin']); + # push(@$helpers, ['with','pipewire-pulse','daemon','active']); + eval $end if $b_log; + # print Data::Dumper::Dumper $helpers; + return $helpers; +} + +sub sound_tools { + eval $start if $b_log; + my $test = $_[0]; + my $tools; + foreach my $item (@$test){ + if (main::check_program($item)){ + push(@$tools,$item); + } + } + eval $end if $b_log; + # print Data::Dumper::Dumper $tools; + return $tools; +} +} + +## BatteryItem +{ +package BatteryItem; +my (@upower_items,$b_upower,$upower); + +sub get { + eval $start if $b_log; + my ($key1,$val1); + my $battery = {}; + my $rows = []; + my $num = 0; + if ($force{'dmidecode'}){ + if ($alerts{'dmidecode'}->{'action'} ne 'use'){ + $key1 = $alerts{'dmidecode'}->{'action'}; + $val1 = $alerts{'dmidecode'}->{'message'}; + $key1 = ucfirst($key1); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + else { + battery_data_dmi($battery); + if (!%$battery){ + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::message('battery-data',''); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + } + else { + battery_output($rows,$battery); + } + } + } + elsif ($bsd_type && ($sysctl{'battery'} || $show{'battery-forced'})){ + battery_data_sysctl($battery) if $sysctl{'battery'}; + if (!%$battery){ + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::message('battery-data-bsd',''); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + } + else { + battery_output($rows,$battery); + } + } + elsif (-d '/sys/class/power_supply/'){ + battery_data_sys($battery); + if (!%$battery){ + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::message('battery-data',''); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + } + else { + battery_output($rows,$battery); + } + } + else { + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = (!$bsd_type) ? main::message('battery-data-sys'): main::message('battery-data-bsd'); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + } + (@upower_items,$b_upower,$upower) = (); + eval $end if $b_log; + return $rows; +} + +# alarm capacity capacity_level charge_full charge_full_design charge_now +# cycle_count energy_full energy_full_design energy_now location manufacturer model_name +# power_now present serial_number status technology type voltage_min_design voltage_now +# 0: name - battery id, not used +# 1: status +# 2: present +# 3: technology +# 4: cycle_count +# 5: voltage_min_design +# 6: voltage_now +# 7: power_now +# 8: energy_full_design +# 9: energy_full +# 10: energy_now +# 11: capacity +# 12: capacity_level +# 13: of_orig +# 14: model_name +# 15: manufacturer +# 16: serial_number +# 17: location +sub battery_output { + eval $start if $b_log; + my ($rows,$battery) = @_; + my ($key); + my $num = 0; + my $j = 0; + # print Data::Dumper::Dumper $battery; + foreach $key (sort keys %$battery){ + $num = 0; + my ($charge,$condition,$model,$serial,$status) = ('','','','',''); + my ($chemistry,$cycles,$location) = ('','',''); + next if !$battery->{$key}{'purpose'} || $battery->{$key}{'purpose'} ne 'primary'; + # $battery->{$key}{''}; + # we need to handle cases where charge or energy full is 0 + if (defined $battery->{$key}{'energy_now'} && $battery->{$key}{'energy_now'} ne ''){ + $charge = "$battery->{$key}{'energy_now'} Wh"; + if ($battery->{$key}{'energy_full'} && + main::is_numeric($battery->{$key}{'energy_full'})){ + my $percent = sprintf("%.1f", $battery->{$key}{'energy_now'}/$battery->{$key}{'energy_full'}*100); + $charge .= ' (' . $percent . '%)'; + } + } + # better than nothing, shows the charged percent + elsif (defined $battery->{$key}{'capacity'} && $battery->{$key}{'capacity'} ne ''){ + $charge = $battery->{$key}{'capacity'} . '%' + } + else { + $charge = 'N/A'; + } + if ($battery->{$key}{'energy_full'} || $battery->{$key}{'energy_full_design'}){ + $battery->{$key}{'energy_full_design'} ||= 'N/A'; + $battery->{$key}{'energy_full'} = (defined $battery->{$key}{'energy_full'} && + $battery->{$key}{'energy_full'} ne '') ? $battery->{$key}{'energy_full'} : 'N/A'; + $condition = "$battery->{$key}{'energy_full'}/$battery->{$key}{'energy_full_design'} Wh"; + if ($battery->{$key}{'of_orig'}){ + $condition .= " ($battery->{$key}{'of_orig'}%)"; + } + } + $condition ||= 'N/A'; + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'ID') => $key, + main::key($num++,0,2,'charge') => $charge, + main::key($num++,0,2,'condition') => $condition, + }); + if ($extra > 2){ + if ($battery->{$key}{'power_now'}){ + $rows->[$j]{main::key($num++,0,2,'power')} = sprintf('%0.1f W',($battery->{$key}{'power_now'}/10**6)); + } + } + if ($extra > 0 || ($battery->{$key}{'voltage_now'} && + $battery->{$key}{'voltage_min_design'} && + ($battery->{$key}{'voltage_now'} - $battery->{$key}{'voltage_min_design'}) < 0.5)){ + $battery->{$key}{'voltage_now'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,2,'volts')} = $battery->{$key}{'voltage_now'}; + if ($battery->{$key}{'voltage_now'} ne 'N/A' || $battery->{$key}{'voltage_min_design'}){ + $battery->{$key}{'voltage_min_design'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'min')} = $battery->{$key}{'voltage_min_design'}; + } + } + if ($extra > 0){ + if ($battery->{$key}{'manufacturer'} || $battery->{$key}{'model_name'}){ + if ($battery->{$key}{'manufacturer'} && $battery->{$key}{'model_name'}){ + $model = "$battery->{$key}{'manufacturer'} $battery->{$key}{'model_name'}"; + } + elsif ($battery->{$key}{'manufacturer'}){ + $model = $battery->{$key}{'manufacturer'}; + } + elsif ($battery->{$key}{'model_name'}){ + $model = $battery->{$key}{'model_name'}; + } + } + else { + $model = 'N/A'; + } + $rows->[$j]{main::key($num++,0,2,'model')} = $model; + if ($extra > 2){ + $chemistry = ($battery->{$key}{'technology'}) ? $battery->{$key}{'technology'}: 'N/A'; + $rows->[$j]{main::key($num++,0,2,'type')} = $chemistry; + } + if ($extra > 1){ + $serial = main::filter($battery->{$key}{'serial_number'}); + $rows->[$j]{main::key($num++,0,2,'serial')} = $serial; + } + $status = ($battery->{$key}{'status'}) ? $battery->{$key}{'status'}: 'N/A'; + $rows->[$j]{main::key($num++,0,2,'status')} = $status; + if ($extra > 2){ + if ($battery->{$key}{'cycle_count'}){ + $rows->[$j]{main::key($num++,0,2,'cycles')} = $battery->{$key}{'cycle_count'}; + } + if ($battery->{$key}{'location'}){ + $rows->[$j]{main::key($num++,0,2,'location')} = $battery->{$key}{'location'}; + } + } + } + $battery->{$key} = undef; + } + # print Data::Dumper::Dumper \%$battery; + # now if there are any devices left, print them out, excluding Mains + if ($extra > 0){ + $upower = main::check_program('upower'); + foreach $key (sort keys %$battery){ + $num = 0; + next if !defined $battery->{$key} || $battery->{$key}{'purpose'} eq 'mains'; + my ($charge,$model,$serial,$percent,$status,$vendor) = ('','','','','',''); + $j = scalar @$rows; + my $upower_data = ($upower) ? upower_data($key) : {}; + if ($upower_data->{'percent'}){ + $charge = $upower_data->{'percent'}; + } + elsif ($battery->{$key}{'capacity_level'} && + lc($battery->{$key}{'capacity_level'}) ne 'unknown'){ + $charge = $battery->{$key}{'capacity_level'}; + } + else { + $charge = 'N/A'; + } + $model = $battery->{$key}{'model_name'} if $battery->{$key}{'model_name'}; + $vendor = $battery->{$key}{'manufacturer'} if $battery->{$key}{'manufacturer'}; + if ($vendor || $model){ + if ($vendor && $model){ + $model = "$vendor $model"; + } + elsif ($vendor){ + $model = $vendor; + } + } + else { + $model = 'N/A'; + } + push(@$rows, { + main::key($num++,1,1,'Device') => $key, + main::key($num++,0,2,'model') => $model, + },); + if ($extra > 1){ + $serial = main::filter($battery->{$key}{'serial_number'}); + $rows->[$j]{main::key($num++,0,2,'serial')} = $serial; + } + $rows->[$j]{main::key($num++,0,2,'charge')} = $charge; + if ($extra > 2 && $upower_data->{'rechargeable'}){ + $rows->[$j]{main::key($num++,0,2,'rechargeable')} = $upower_data->{'rechargeable'}; + } + $status = ($battery->{$key}{'status'}) ? $battery->{$key}{'status'}: 'N/A' ; + $rows->[$j]{main::key($num++,0,2,'status')} = $status; + } + } + eval $end if $b_log; +} + +# charge: mAh energy: Wh +sub battery_data_sys { + eval $start if $b_log; + my $battery = $_[0]; + my ($b_ma,$file,$id,$item,$path,$value); + my $num = 0; + my @batteries = main::globber("/sys/class/power_supply/*"); + # note: there is no 'location' file, but dmidecode has it + # 'type' is generic, like: Battery, Mains + # capacity_level is a string, like: Normal + my @items = qw(alarm capacity capacity_level charge_full charge_full_design + charge_now constant_charge_current constant_charge_current_max cycle_count + energy_full energy_full_design energy_now location manufacturer model_name + power_now present scope serial_number status technology type voltage_min_design + voltage_now); + foreach $item (@batteries){ + $b_ma = 0; + $id = $item; + $id =~ s%/sys/class/power_supply/%%g; + foreach $file (@items){ + $path = "$item/$file"; + # android shows some files only root readable + $value = (-r $path) ? main::reader($path,'',0): ''; + # mains, plus in psu + if ($file eq 'type' && $value && lc($value) ne 'battery'){ + $battery->{$id}{'purpose'} = 'mains'; + } + if ($value){ + $value = main::trimmer($value); + if ($file eq 'voltage_min_design'){ + $value = sprintf("%.1f", $value/1000000); + } + elsif ($file eq 'voltage_now'){ + $value = sprintf("%.1f", $value/1000000); + } + elsif ($file eq 'energy_full_design'){ + $value = $value/1000000; + } + elsif ($file eq 'energy_full'){ + $value = $value/1000000; + } + elsif ($file eq 'energy_now'){ + $value = sprintf("%.1f", $value/1000000); + } + # note: the following 3 were off, 100000 instead of 1000000 + # why this is, I do not know. I did not document any reason for that + # so going on assumption it is a mistake. + # CHARGE is mAh, which are converted to Wh by: mAh x voltage. + # Note: voltage fluctuates so will make results vary slightly. + elsif ($file eq 'charge_full_design'){ + $value = $value/1000000; + $b_ma = 1; + } + elsif ($file eq 'charge_full'){ + $value = $value/1000000; + $b_ma = 1; + } + elsif ($file eq 'charge_now'){ + $value = $value/1000000; + $b_ma = 1; + } + elsif ($file eq 'manufacturer'){ + $value = main::clean_dmi($value); + } + elsif ($file eq 'model_name'){ + $value = main::clean_dmi($value); + } + # Valid values: Unknown,Charging,Discharging,Not charging,Full + # don't use clean_unset because Not charging is a valid value. + elsif ($file eq 'status'){ + $value = lc($value); + $value =~ s/unknown//; + + } + } + elsif ($b_root && -e $path && ! -r $path){ + $value = main::message('root-required'); + } + $battery->{$id}{$file} = $value; + # print "$battery->{$id}{$file}\n"; + } + # note, too few data sets, there could be sbs-charger but not sure + if (!$battery->{$id}{'purpose'}){ + # NOTE: known ids: BAT[0-9] CMB[0-9]. arm may be like: sbs- sbm- but just check + # if the energy/charge values exist for this item, if so, it's a battery, if not, + # it's a device. + if ($id =~ /^(BAT|CMB).*$/i || + ($battery->{$id}{'energy_full'} || $battery->{$id}{'charge_full'} || + $battery->{$id}{'energy_now'} || $battery->{$id}{'charge_now'} || + $battery->{$id}{'energy_full_design'} || $battery->{$id}{'charge_full_design'}) || + $battery->{$id}{'voltage_min_design'} || $battery->{$id}{'voltage_now'}){ + $battery->{$id}{'purpose'} = 'primary'; + } + else { + $battery->{$id}{'purpose'} = 'device'; + } + } + # note:voltage_now fluctuates, which will make capacity numbers change a bit + # if any of these values failed, the math will be wrong, but no way to fix that + # tests show more systems give right capacity/charge with voltage_min_design + # than with voltage_now + if ($b_ma && $battery->{$id}{'voltage_min_design'}){ + if ($battery->{$id}{'charge_now'}){ + $battery->{$id}{'energy_now'} = $battery->{$id}{'charge_now'} * $battery->{$id}{'voltage_min_design'}; + } + if ($battery->{$id}{'charge_full'}){ + $battery->{$id}{'energy_full'} = $battery->{$id}{'charge_full'}*$battery->{$id}{'voltage_min_design'}; + } + if ($battery->{$id}{'charge_full_design'}){ + $battery->{$id}{'energy_full_design'} = $battery->{$id}{'charge_full_design'} * $battery->{$id}{'voltage_min_design'}; + } + } + if ($battery->{$id}{'energy_now'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'capacity'} = 100 * $battery->{$id}{'energy_now'}/$battery->{$id}{'energy_full'}; + $battery->{$id}{'capacity'} = sprintf("%.1f", $battery->{$id}{'capacity'}); + } + if ($battery->{$id}{'energy_full_design'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'of_orig'} = 100 * $battery->{$id}{'energy_full'}/$battery->{$id}{'energy_full_design'}; + $battery->{$id}{'of_orig'} = sprintf("%.1f", $battery->{$id}{'of_orig'}); + } + if ($battery->{$id}{'energy_now'}){ + $battery->{$id}{'energy_now'} = sprintf("%.1f", $battery->{$id}{'energy_now'}); + } + if ($battery->{$id}{'energy_full_design'}){ + $battery->{$id}{'energy_full_design'} = sprintf("%.1f",$battery->{$id}{'energy_full_design'}); + } + if ($battery->{$id}{'energy_full'}){ + $battery->{$id}{'energy_full'} = sprintf("%.1f", $battery->{$id}{'energy_full'}); + } + } + print Data::Dumper::Dumper $battery if $dbg[33]; + main::log_data('dump','sys: %$battery',$battery) if $b_log; + eval $end if $b_log; +} + +sub battery_data_sysctl { + eval $start if $b_log; + my $battery = $_[0]; + my ($id); + for (@{$sysctl{'battery'}}){ + if (/^(hw\.sensors\.)acpi([^\.]+)(\.|:)/){ + $id = uc($2); + } + if (/volt[^:]+:([0-9\.]+)\s+VDC\s+\(voltage\)/){ + $battery->{$id}{'voltage_min_design'} = $1; + } + elsif (/volt[^:]+:([0-9\.]+)\s+VDC\s+\(current voltage\)/){ + $battery->{$id}{'voltage_now'} = $1; + } + elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(design capacity\)/){ + $battery->{$id}{'energy_full_design'} = $1; + } + elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(last full capacity\)/){ + $battery->{$id}{'energy_full'} = $1; + } + elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(remaining capacity\)/){ + $battery->{$id}{'energy_now'} = $1; + } + elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(design capacity\)/){ + $battery->{$id}{'charge_full_design'} = $1; + } + elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(last full capacity\)/){ + $battery->{$id}{'charge_full'} = $1; + } + elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(remaining capacity\)/){ + $battery->{$id}{'charge_now'} = $1; + } + elsif (/raw[^:]+:[0-9\.]+\s+\((battery) ([^\)]+)\)/){ + $battery->{$id}{'status'} = $2; + } + elsif (/^acpi[\S]+:at [^:]+:\s*$id\s+/i){ + if (/\s+model\s+(.*?)\s*/){ + $battery->{$id}{'model_name'} = main::clean_dmi($1); + } + if (/\s*serial\s+([\S]*?)\s*/){ + $battery->{$id}{'serial_number'} = main::clean_unset($1,'^(0x)0+$'); + } + if (/\s*type\s+(.*?)\s*/){ + $battery->{$id}{'technology'} = $1; + } + if (/\s*oem\s+(.*)/){ + $battery->{$id}{'manufacturer'} = main::clean_dmi($1); + } + } + } + # then do the condition/charge percent math + for my $id (keys %$battery){ + $battery->{$id}{'purpose'} = 'primary'; + # CHARGE is Ah, which are converted to Wh by: Ah x voltage. + if ($battery->{$id}{'voltage_min_design'}){ + if ($battery->{$id}{'charge_now'}){ + $battery->{$id}{'energy_now'} = $battery->{$id}{'charge_now'} * $battery->{$id}{'voltage_min_design'}; + } + if ($battery->{$id}{'charge_full'}){ + $battery->{$id}{'energy_full'} = $battery->{$id}{'charge_full'}*$battery->{$id}{'voltage_min_design'}; + } + if ($battery->{$id}{'charge_full_design'}){ + $battery->{$id}{'energy_full_design'} = $battery->{$id}{'charge_full_design'} * $battery->{$id}{'voltage_min_design'}; + } + } + if ($battery->{$id}{'energy_full_design'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'of_orig'} = 100 * $battery->{$id}{'energy_full'}/$battery->{$id}{'energy_full_design'}; + $battery->{$id}{'of_orig'} = sprintf("%.1f", $battery->{$id}{'of_orig'}); + } + if ($battery->{$id}{'energy_now'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'capacity'} = 100 * $battery->{$id}{'energy_now'}/$battery->{$id}{'energy_full'}; + $battery->{$id}{'capacity'} = sprintf("%.1f", $battery->{$id}{'capacity'}); + } + if ($battery->{$id}{'energy_now'}){ + $battery->{$id}{'energy_now'} = sprintf("%.1f", $battery->{$id}{'energy_now'}); + } + if ($battery->{$id}{'energy_full'}){ + $battery->{$id}{'energy_full'} = sprintf("%.1f", $battery->{$id}{'energy_full'}); + } + if ($battery->{$id}{'energy_full_design'}){ + $battery->{$id}{'energy_full_design'} = sprintf("%.1f", $battery->{$id}{'energy_full_design'}); + } + } + print Data::Dumper::Dumper $battery if $dbg[33]; + main::log_data('dump','dmi: %$battery',$battery) if $b_log; + eval $end if $b_log; +} + +# note, dmidecode does not have charge_now or charge_full +sub battery_data_dmi { + eval $start if $b_log; + my $battery = $_[0]; + my ($id); + my $i = 0; + foreach my $row (@dmi){ + # Portable Battery + if ($row->[0] == 22){ + $id = "BAT$i"; + $i++; + $battery->{$id}{'purpose'} = 'primary'; + # skip first three row, we don't need that data + foreach my $item (@$row[3 .. $#$row]){ + my @value = split(/:\s+/, $item); + next if !$value[0]; + if ($value[0] eq 'Location'){ + $battery->{$id}{'location'} = $value[1]} + elsif ($value[0] eq 'Manufacturer'){ + $battery->{$id}{'manufacturer'} = main::clean_dmi($value[1])} + elsif ($value[0] =~ /Chemistry/){ + $battery->{$id}{'technology'} = $value[1]} + elsif ($value[0] =~ /Serial Number/){ + $battery->{$id}{'serial_number'} = $value[1]} + elsif ($value[0] =~ /^Name/){ + $battery->{$id}{'model_name'} = main::clean_dmi($value[1])} + elsif ($value[0] eq 'Design Capacity'){ + $value[1] =~ s/\s*mwh$//i; + $battery->{$id}{'energy_full_design'} = sprintf("%.1f", $value[1]/1000); + } + elsif ($value[0] eq 'Design Voltage'){ + $value[1] =~ s/\s*mv$//i; + $battery->{$id}{'voltage_min_design'} = sprintf("%.1f", $value[1]/1000); + } + } + if ($battery->{$id}{'energy_now'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'capacity'} = 100 * $battery->{$id}{'energy_now'} / $battery->{$id}{'energy_full'}; + $battery->{$id}{'capacity'} = sprintf("%.1f%", $battery->{$id}{'capacity'}); + } + if ($battery->{$id}{'energy_full_design'} && $battery->{$id}{'energy_full'}){ + $battery->{$id}{'of_orig'} = 100 * $battery->{$id}{'energy_full'} / $battery->{$id}{'energy_full_design'}; + $battery->{$id}{'of_orig'} = sprintf("%.0f%", $battery->{$id}{'of_orig'}); + } + } + elsif ($row->[0] > 22){ + last; + } + } + print Data::Dumper::Dumper $battery if $dbg[33]; + main::log_data('dump','dmi: %$battery',$battery) if $b_log; + eval $end if $b_log; +} + +sub upower_data { + my ($id) = @_; + eval $start if $b_log; + my $data = {}; + if (!$b_upower && $upower){ + @upower_items = main::grabber("$upower -e",'','strip'); + $b_upower = 1; + } + if ($upower && @upower_items){ + foreach (@upower_items){ + if ($_ =~ /$id/){ + my @working = main::grabber("$upower -i $_",'','strip'); + foreach my $row (@working){ + my @temp = split(/\s*:\s*/, $row); + if ($temp[0] eq 'percentage'){ + $data->{'percent'} = $temp[1]; + } + elsif ($temp[0] eq 'rechargeable'){ + $data->{'rechargeable'} = $temp[1]; + } + } + last; + } + } + } + main::log_data('dump','upower: %$data',$data) if $b_log; + eval $end if $b_log; + return $data; +} +} + +## BluetoothItem +{ +package BluetoothItem; +my ($b_bluetooth,$b_hci_error,$b_hci,$b_rfk,$b_service); +my ($service); +my (%hci); + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + if ($fake{'bluetooth'} || (@ps_cmd && (grep {m|/bluetoothd\b|} @ps_cmd))){ + $b_bluetooth = 1; + } + # note: rapi 4 has pci bus + if (%risc && !$use{'soc-bluetooth'} && !$use{'pci-tool'}){ + # do nothing, but keep the test conditions to force + # the non risc case to always run + # my $key = 'Message'; + # @$rows = ({ + # main::key($num++,0,1,$key) => main::message('risc-pci',$risc{'id'}) + # }); + } + else { + device_output($rows); + } + usb_output($rows); + if (!@$rows){ + if ($show{'bluetooth-forced'}){ + my $key = 'Message'; + @$rows = ({main::key($num++,0,1,$key) => main::message('bluetooth-data')}); + } + } + # if there are any unhandled hci items print them out + if (%hci){ + advanced_output($rows,'check',''); + } + eval $end if $b_log; + return $rows; +} + +sub device_output { + eval $start if $b_log; + return if !$devices{'bluetooth'}; + my $rows = $_[0]; + my ($bus_id); + my ($j,$num) = (0,1); + foreach my $row (@{$devices{'bluetooth'}}){ + $num = 1; + $bus_id = ''; + $j = scalar @$rows; + my $driver = ($row->[9]) ? $row->[9] : 'N/A'; + my $device = $row->[4]; + $device = ($device) ? main::clean_pci($device,'output') : 'N/A'; + # have seen absurdly verbose card descriptions, with non related data etc + if (length($device) > 85 || $size{'max-cols'} < 110){ + $device = main::filter_pci_long($device); + } + push(@$rows, { + main::key($num++,1,1,'Device') => $device, + },); + if ($extra > 0 && $use{'pci-tool'} && $row->[12]){ + my $item = main::get_pci_vendor($row->[4],$row->[12]); + $rows->[$j]{main::key($num++,0,2,'vendor')} = $item if $item; + } + $rows->[$j]{main::key($num++,1,2,'driver')} = $driver; + if ($extra > 0 && $row->[9] && !$bsd_type){ + my $version = main::get_module_version($row->[9]); + $rows->[$j]{main::key($num++,0,3,'v')} = $version if $version; + } + if ($b_admin && $row->[10]){ + $row->[10] = main::get_driver_modules($row->[9],$row->[10]); + $rows->[$j]{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10]; + } + if ($extra > 0){ + $bus_id = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]"; + if ($extra > 1 && $bus_id ne 'N/A'){ + main::get_pcie_data($bus_id,$j,$rows,\$num); + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + } + if ($extra > 1){ + my $chip_id = main::get_chip_id($row->[5],$row->[6]); + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $chip_id; + if ($extra > 2 && $row->[1]){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = $row->[1]; + } + } + # weird serial rpi bt + if ($use{'soc-bluetooth'}){ + # /sys/devices/platform/soc/fe201000.serial/ + $bus_id = "$row->[6].$row->[1]" if defined $row->[1] && defined $row->[6]; + } + else { + # only theoretical, never seen one + $bus_id = "$row->[2].$row->[3]" if defined $row->[2] && defined $row->[3]; + } + advanced_output($rows,'pci',$bus_id) if $bus_id; + # print "$row->[0]\n"; + } + eval $end if $b_log; +} + +sub usb_output { + eval $start if $b_log; + return if !$usb{'bluetooth'}; + my $rows = $_[0]; + my ($path_id,$product); + my ($j,$num) = (0,1); + foreach my $row (@{$usb{'bluetooth'}}){ + # print Data::Dumper::Dumper $row; + $num = 1; + $j = scalar @$rows; + # makre sure to reset, or second device trips last flag + ($path_id,$product) = ('',''); + $product = main::clean($row->[13]) if $row->[13]; + $product ||= 'N/A'; + $row->[15] ||= 'N/A'; + $path_id = $row->[2] if $row->[2]; + push(@$rows, { + main::key($num++,1,1,'Device') => $product, + main::key($num++,1,2,'driver') => $row->[15], + },); + if ($extra > 0 && $row->[15] && !$bsd_type){ + my $version = main::get_module_version($row->[15]); + $rows->[$j]{main::key($num++,0,3,'v')} = $version if $version; + } + $rows->[$j]{main::key($num++,1,2,'type')} = 'USB'; + if ($extra > 0){ + if ($extra > 1){ + $row->[8] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'rev')} = $row->[8]; + if ($row->[17]){ + $rows->[$j]{main::key($num++,0,3,'speed')} = $row->[17]; + } + if ($row->[24]){ + $rows->[$j]{main::key($num++,0,3,'lanes')} = $row->[24]; + } + if ($b_admin && $row->[22]){ + $rows->[$j]{main::key($num++,0,3,'mode')} = $row->[22]; + } + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]"; + if ($extra > 1){ + $row->[7] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $row->[7]; + } + if ($extra > 2){ + if (defined $row->[5] && $row->[5] ne ''){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]"; + } + if ($row->[16]){ + $rows->[$j]{main::key($num++,0,2,'serial')} = main::filter($row->[16]); + } + } + } + advanced_output($rows,'usb',$path_id) if $path_id; + } + eval $end if $b_log; +} + +sub advanced_output { + eval $start if $b_log; + my ($rows,$type,$bus_id) = @_; + my (@temp); + my ($j,$num,$k,$l,$m,$n,$address,$id,$note,$tool) = (0,1,2,3,4,5,'','','',''); + set_bluetooth_data(\$tool); + # print "bid: $bus_id\n"; + if ($type ne 'check'){ + @temp = main::globber('/sys/class/bluetooth/*'); + @temp = map {$_ = Cwd::abs_path($_);$_} @temp if @temp; + # print Data::Dumper::Dumper \@temp; + @temp = grep {/$bus_id/} @temp if @temp; + @temp = map {$_ =~ s|^/.*/||;$_;} @temp if @temp; + # print Data::Dumper::Dumper \@temp; + } + elsif ($type eq 'check' && %hci){ + @temp = keys %hci; + $id = '-ID'; + ($k,$l,$m,$n) = (1,2,3,4); + } + if (@temp && %hci){ + if ($hci{'alert'}){ + if (keys %hci == 1){ + check_service(); # sets $service + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,$k,'Report')} = $tool; + $rows->[$j]{main::key($num++,0,$l,'bt-service')} = $service; + $rows->[$j]{main::key($num++,0,$l,'note')} = $hci{'alert'}; + } + else { + $note = $hci{'alert'}; + } + delete $hci{'alert'}; + } + foreach my $item (@temp){ + if ($hci{$item}){ + $j = scalar @$rows; + push(@$rows,{ + main::key($num++,1,$k,'Report' . $id) => $tool, + },); + if ($note){ + $rows->[$j]{main::key($num++,0,$l,'note')} = $note; + } + # synthesize for rfkill + if (!$hci{$item}->{'state'}){ + $hci{$item}->{'state'} = ($b_bluetooth) ? 'up' : 'down'; + } + $rows->[$j]{main::key($num++,0,$l,'ID')} = $item; + if (defined $hci{$item}->{'rf-index'} && + ($extra > 0 || $hci{$item}->{'state'} eq 'down')){ + $rows->[$j]{main::key($num++,0,$m,'rfk-id')} = $hci{$item}->{'rf-index'}; + } + $rows->[$j]{main::key($num++,1,$l,'state')} = $hci{$item}->{'state'}; + # this only appears for hciconfig, bt-adapter does not run without bt service + if (!$b_bluetooth || $hci{$item}->{'state'} eq 'down'){ + if (!$b_bluetooth || $hci{$item}->{'state'} eq 'down'){ + check_service(); # sets $service + $rows->[$j]{main::key($num++,0,$m,'bt-service')} = $service; + } + if ($hci{$item}->{'hard-blocked'}){ + $rows->[$j]{main::key($num++,1,$m,'rfk-block')} = ''; + $rows->[$j]{main::key($num++,0,$n,'hardware')} = $hci{$item}->{'hard-blocked'}; + $rows->[$j]{main::key($num++,0,$n,'software')} = $hci{$item}->{'soft-blocked'}; + } + } + if (!$hci{$item}->{'address'} && $tool eq 'rfkill'){ + $address = main::message('recommends'); + } + else { + $address = main::filter($hci{$item}->{'address'}); + } + $rows->[$j]{main::key($num++,0,$l,'address')} = $address; + # lmp/hci version only hciconfig + if ($hci{$item}->{'bt-version'}){ + $rows->[$j]{main::key($num++,0,$l,'bt-v')} = $hci{$item}->{'bt-version'}; + } + if ($extra > 0 && defined $hci{$item}->{'lmp-version'}){ + $rows->[$j]{main::key($num++,0,$l,'lmp-v')} = $hci{$item}->{'lmp-version'}; + if ($extra > 1 && $hci{$item}->{'lmp-subversion'}){ + $rows->[$j]{main::key($num++,0,$m,'sub-v')} = $hci{$item}->{'lmp-subversion'}; + } + } + if ($extra > 0 && defined $hci{$item}->{'hci-version'} && + ($extra > 2 || !$hci{$item}->{'lmp-version'} || + ($hci{$item}->{'lmp-version'} && + $hci{$item}->{'lmp-version'} ne $hci{$item}->{'hci-version'}))){ + $rows->[$j]{main::key($num++,0,$l,'hci-v')} = $hci{$item}->{'hci-version'}; + if ($extra > 1 && $hci{$item}->{'hci-revision'}){ + $rows->[$j]{main::key($num++,0,$m,'rev')} = $hci{$item}->{'hci-revision'}; + } + } + if ($b_admin && + ($hci{$item}->{'discoverable'} || $hci{$item}->{'pairable'})){ + $rows->[$j]{main::key($num++,1,$l,'status')} = ''; + if ($hci{$item}->{'discoverable'}){ + $rows->[$j]{main::key($num++,1,$m,'discoverable')} = $hci{$item}->{'discoverable'}; + if ($hci{$item}->{'discovering'}){ + $rows->[$j]{main::key($num++,1,$n,'active')} = $hci{$item}->{'discovering'}; + } + } + if ($hci{$item}->{'pairable'}){ + $rows->[$j]{main::key($num++,0,$m,'pairing')} = $hci{$item}->{'pairable'}; + } + } + if ($extra > 2 && $hci{$item}->{'class'}){ + $rows->[$j]{main::key($num++,0,$l,'class-ID')} = $hci{$item}->{'class'}; + } + # this data only from hciconfig + if ($b_admin && ($hci{$item}->{'acl-mtu'} || $hci{$item}->{'sco-mtu'} || + $hci{$item}->{'link-policy'})){ + $j = scalar @$rows; + push(@$rows,{ + main::key($num++,1,$l,'Info') => '', + },); + if ($hci{$item}->{'acl-mtu'}){ + $rows->[$j]{main::key($num++,0,$m,'acl-mtu')} = $hci{$item}->{'acl-mtu'}; + } + if ($hci{$item}->{'sco-mtu'}){ + $rows->[$j]{main::key($num++,0,$m,'sco-mtu')} = $hci{$item}->{'sco-mtu'}; + } + if ($hci{$item}->{'link-policy'}){ + $rows->[$j]{main::key($num++,0,$m,'link-policy')} = $hci{$item}->{'link-policy'}; + } + if ($hci{$item}->{'link-mode'}){ + $rows->[$j]{main::key($num++,0,$m,'link-mode')} = $hci{$item}->{'link-mode'}; + } + if ($hci{$item}->{'service-classes'}){ + $rows->[$j]{main::key($num++,0,$m,'service-classes')} = $hci{$item}->{'service-classes'}; + } + } + delete $hci{$item}; + } + } + } + # since $rows is ref, we need to just check if no $j were set. + if (!$j && !$b_hci_error && ($alerts{'hciconfig'}->{'action'} ne 'use' && + $alerts{'bt-adapter'}->{'action'} ne 'use' && + $alerts{'btmgmt'}->{'action'} ne 'use')){ + my $key = 'Report'; + my $value = ''; + if ($alerts{'hciconfig'}->{'action'} eq 'platform' || + $alerts{'bt-adapter'}->{'action'} eq 'platform' || + $alerts{'btmgmt'}->{'action'} eq 'platform'){ + $value = main::message('tool-missing-os','bluetooth'); + } + else { + $value = main::message('tools-missing','hciconfig/bt-adapter'); + } + push(@$rows,{ + main::key($num++,0,1,$key) => $value, + },); + $b_hci_error = 1; + } + eval $end if $b_log; +} + +# note: echo 'show' | bluetoothctl outputs everything but hciX ID, and is fast +# args: 0: $tool, by ref +sub set_bluetooth_data { + eval $start if $b_log; + if (!$b_hci && !$force{'bt-adapter'} && !$force{'btmgmt'} && + !$force{'rfkill'} && + ($fake{'bluetooth'} || $alerts{'hciconfig'}->{'action'} eq 'use')){ + hciconfig_data(); + ${$_[0]} = 'hciconfig'; + } + elsif (!$b_hci && !$force{'rfkill'} && !$force{'bt-adapter'} && + ($fake{'bluetooth'} || $alerts{'btmgmt'}->{'action'} eq 'use')){ + btmgmt_data(); + ${$_[0]} = 'btmgmt'; + } + elsif (!$b_hci && !$force{'rfkill'} && + ($fake{'bluetooth'} || $alerts{'bt-adapter'}->{'action'} eq 'use')){ + bt_adapter_data(); + ${$_[0]} = 'bt-adapter'; + } + if (!$b_rfk && ($fake{'bluetooth'} || -e '/sys/class/bluetooth/')){ + rfkill_data(); + ${$_[0]} = 'rfkill' if !${$_[0]}; + } + eval $end if $b_log; +} + +sub bt_adapter_data { + eval $start if $b_log; + $b_hci = 1; + my (@data,$id); + if ($fake{'bluetooth'}){ + my $file; + $file = ""; + @data = main::reader($file,'strip'); + } + else { + if ($b_bluetooth){ + my $cmd = "$alerts{'bt-adapter'}->{'path'} --info 2>/dev/null"; + @data = main::grabber($cmd,'','strip'); + } + } + # print Data::Dumper::Dumper \@data; + main::log_data('dump','@data', \@data) if $b_log; + foreach (@data){ + my @working = split(/:\s*/,$_); + # print Data::Dumper::Dumper \@working; + next if ! @working; + if ($working[0] =~ /^\[([^\]]+)\]/){ + $id = $1; + } + elsif ($working[0] eq 'Address'){ + $hci{$id}->{'address'} = join(':',@working[1 .. $#working]); + } + elsif ($working[0] eq 'Class' && $working[1] =~ /^0x0*(\S+)/){ + $hci{$id}->{'class'} = $1; + } + elsif ($working[0] eq 'Powered'){ + $hci{$id}->{'state'} = ($working[1] =~ /^(1|yes)\b/) ? 'up': 'down'; + } + elsif ($working[0] eq 'Discoverable'){ + $hci{$id}->{'discoverable'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no'; + } + elsif ($working[0] eq 'Pairable'){ + $hci{$id}->{'pairable'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no'; + } + elsif ($working[0] eq 'Discovering'){ + $hci{$id}->{'discovering'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no'; + } + } + if (!@data && !$b_bluetooth){ + $hci{'alert'} = main::message('bluetooth-down'); + } + print 'bt-adapter: ', Data::Dumper::Dumper \%hci if $dbg[27]; + main::log_data('dump','%hci', \%hci) if $b_log; + eval $end if $b_log; +} + +sub btmgmt_data { + eval $start if $b_log; + $b_hci = 1; + my (@data,$id); + if ($fake{'bluetooth'}){ + my $file; + $file = "$ENV{'HOME'}/bin/scripts/inxi/data/bluetooth/btmgmt-2.txt"; + @data = main::reader($file,'strip'); + } + else { + if ($b_bluetooth){ + my $cmd = "$alerts{'btmgmt'}->{'path'} info 2>/dev/null"; + @data = main::grabber($cmd,'', 'strip'); + } + } + # print Data::Dumper::Dumper \@data; + main::log_data('dump','@data', \@data) if $b_log; + foreach (@data){ + next if /^Index list/; + if (/^(hci[0-9]+):\s+/){ + $id = $1; + } + # addr 4C:F3:72:9C:B4:D3 version 6 manufacturer 15 class 0x000104 + elsif (/^addr\s+([0-9A-F:]+)\s+version\s+([0-9]+)\s/){ + $hci{$id}->{'address'} = $1; + $hci{$id}->{'lmp-version'} = $2; # assume non hex integer + $hci{$id}->{'bt-version'} = bluetooth_version($2); + if (/ class\s+0x0*(\S+)\b/){ + $hci{$id}->{'class'} = $1; + } + } + elsif (/^current settings:\s+(.*)/){ + my $settings = $1; + $hci{$id}->{'state'} = ($settings =~ /\bpowered\b/) ? 'up' : 'down'; + $hci{$id}->{'discoverable'} = ($settings =~ /\bdiscoverable\b/) ? 'yes' : 'no'; + $hci{$id}->{'pairable'} = ($settings =~ /\bconnectable\b/) ? 'yes' : 'no'; + } + } + print 'btmgmt: ', Data::Dumper::Dumper \%hci if $dbg[27]; + main::log_data('dump','%hci', \%hci) if $b_log; + eval $end if $b_log; +} + +sub hciconfig_data { + eval $start if $b_log; + $b_hci = 1; + my (@data,$id); + if ($fake{'bluetooth'}){ + my $file; + $file = "$ENV{'HOME'}/bin/scripts/inxi/data/bluetooth/hciconfig-a-2.txt"; + @data = main::reader($file,'strip'); + } + else { + my $cmd = "$alerts{'hciconfig'}->{'path'} -a 2>/dev/null"; + @data = main::grabber($cmd,'', 'strip'); + } + # print Data::Dumper::Dumper \@data; + main::log_data('dump','@data', \@data) if $b_log; + foreach (@data){ + if (/^(hci[0-9]+):\s+Type:\s+(.*)\s+Bus:\s+([\S]+)/){ + $id = $1; + $hci{$id} = { + 'type'=> $2, + 'bus' => $3, + }; + } + elsif (/^BD Address:\s+([0-9A-F:]*)\s+ACL\s+MTU:\s+([0-9:]+)\s+SCO MTU:\s+([0-9:]+)/){ + $hci{$id}->{'address'} = $1; + $hci{$id}->{'acl-mtu'} = $2; + $hci{$id}->{'sco-mtu'} = $3; + } + elsif (/^(UP|DOWN).*/){ + $hci{$id}->{'state'} = lc($1); + } + elsif (/^Class:\s+0x0*(\S+)/){ + $hci{$id}->{'class'} = $1; + } + # HCI Version: 4.0 (0x6) Revision: 0x1000 + # HCI Version: 6.6 Revision: 0x1000 [don't know if this exists] + # HCI Version: (0x7) Revision: 0x3101 + elsif (/^HCI Version:\s+(([0-9\.]+)\s+)?\(0x([0-9a-f]+)\)\s+Revision:\s+0x([0-9a-f]+)/i){ + $hci{$id}->{'hci-revision'} = $4; + if (defined $3){ + $hci{$id}->{'bt-version'} = bluetooth_version(hex($3)); + $hci{$id}->{'hci-version'} = hex($3); + $hci{$id}->{'hci-version-hex'} = $3; + } + } + # LMP Version: 4.0 (0x6) Subversion: 0x220e + # LMP Version: 6.6 Revision: 0x1000 [don't know if this exists] + # LMP Version: (0x7) Subversion: 0x1 + elsif (/^LMP Version:\s+(([0-9\.]+)\s+)?\(0x([0-9a-f]+)\)\s+Subversion:\s+0x([0-9a-f]+)/i){ + $hci{$id}->{'lmp-subversion'} = $4; + $hci{$id}->{'bt-version'} = bluetooth_version(hex($3)); + $hci{$id}->{'lmp-version'} = hex($3); + $hci{$id}->{'lmp-version-hex'} = $3; + } + elsif (/^Link policy:\s+(.*)/){ + $hci{$id}->{'link-policy'} = lc($1); + } + elsif (/^Link mode:\s+(.*)/){ + $hci{$id}->{'link-mode'} = lc($1); + } + elsif (/^Service Classes?:\s+(.+)/){ + $hci{$id}->{'service-classes'} = main::clean_unset(lc($1)); + } + } + print 'hciconfig: ', Data::Dumper::Dumper \%hci if $dbg[27]; + main::log_data('dump','%hci', \%hci) if $b_log; + eval $end if $b_log; +} + +sub rfkill_data { + eval $start if $b_log; + $b_rfk = 1; + my (@data,$id,$value); + if ($fake{'bluetooth'}){ + my $file; + $file = ""; + @data = main::reader($file,'strip'); + } + else { + # /state is the state of rfkill, NOT bluetooth! + @data = main::globber('/sys/class/bluetooth/hci*/rfkill*/{hard,index,soft}'); + } + # print Data::Dumper::Dumper \@data; + main::log_data('dump','@data', \@data) if $b_log; + foreach (@data){ + $id = (split(/\//,$_))[4]; + if (m|/soft$|){ + $value = main::reader($_,'strip',0); + $hci{$id}->{'soft-blocked'} = ($value) ? 'yes': 'no'; + $hci{$id}->{'state'} = 'down' if $hci{$id}->{'soft-blocked'} eq 'yes'; + } + elsif (m|/hard$|){ + $value = main::reader($_,'strip',0); + $hci{$id}->{'hard-blocked'} = ($value) ? 'yes': 'no'; + $hci{$id}->{'state'} = 'down' if $hci{$id}->{'hard-blocked'} eq 'yes'; + } + elsif (m|/index$|){ + $value = main::reader($_,'strip',0); + $hci{$id}->{'rf-index'} = $value; + } + } + print 'rfkill: ', Data::Dumper::Dumper \%hci if $dbg[27]; + main::log_data('dump','%hci', \%hci) if $b_log; + eval $end if $b_log; +} + +sub check_service { + eval $start if $b_log; + if (!$b_service){ + $service = ServiceData::get('status','bluetooth'); + $service ||= 'N/A'; + $b_service = 1; + } + eval $end if $b_log; +} + +# args: 0: lmp versoin - could be hex, but probably decimal, like 6.6 +sub bluetooth_version { + eval $start if $b_log; + my ($lmp) = @_; + return if !defined $lmp; + return if !main::is_numeric($lmp); + $lmp = int($lmp); + # Conveniently, LMP starts with 0, so perfect for array indexes. + # 6.0 is coming, but might be 5.5 first, nobody knows. + my @bt = qw(1.0b 1.1 1.2 2.0 2.1 3.0 4.0 4.1 4.2 5.0 5.1 5.2 5.3 5.4); + return $bt[$lmp]; + eval $end if $b_log; +} +} + +## CpuItem +{ +package CpuItem; +my ($type); + +sub get { + eval $start if $b_log; + ($type) = @_; + my $rows = []; + if ($type eq 'short' || $type eq 'basic'){ + # note, for short form, just return the raw data, not the processed output + my $cpu = short_data(); + if ($type eq 'basic'){ + short_output($rows,$cpu); + } + else { + $rows = $cpu; + } + } + else { + full_output($rows); + } + eval $end if $b_log; + return $rows; +} + +## OUTPUT HANDLERS ## +sub full_output { + eval $start if $b_log; + my $rows = $_[0]; + my $num = 0; + my ($b_speeds,$core_speeds_value,$cpu); + my $sleep = $cpu_sleep * 1000000; + if (my $file = $system_files{'proc-cpuinfo'}){ + $cpu = cpuinfo_data($file); + } + elsif ($bsd_type){ + my ($key1,$val1) = ('',''); + if ($alerts{'sysctl'}){ + if ($alerts{'sysctl'}->{'action'} eq 'use'){ +# $key1 = 'Status'; +# $val1 = main::message('dev'); + $cpu = sysctl_data(); + } + else { + $key1 = ucfirst($alerts{'sysctl'}->{'action'}); + $val1 = $alerts{'sysctl'}->{'message'}; + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + return; + } + } + } + my $properties = cpu_properties($cpu); + my $type = ($properties->{'cpu-type'}) ? $properties->{'cpu-type'}: ''; + my $j = scalar @$rows; + $cpu->{'model_name'} ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Info') => $properties->{'topology-string'}, + main::key($num++,0,2,'model') => $cpu->{'model_name'}, + },); + if ($cpu->{'system-cpus'}){ + my %system_cpus = %{$cpu->{'system-cpus'}}; + my $i = 1; + my $counter = (%system_cpus && scalar keys %system_cpus > 1) ? '-' : ''; + foreach my $key (keys %system_cpus){ + $counter = '-' . $i++ if $counter; + $rows->[$j]{main::key($num++,0,2,'variant'.$counter)} = $key; + } + } + if ($b_admin && $properties->{'socket'}){ + if ($properties->{'upgrade'}){ + $rows->[$j]{main::key($num++,1,2,'socket')} = $properties->{'socket'} . ' (' . $properties->{'upgrade'} . ')'; + $rows->[$j]{main::key($num++,0,3,'note')} = main::message('note-check'); + } + else { + $rows->[$j]{main::key($num++,0,2,'socket')} = $properties->{'socket'}; + } + } + $properties->{'bits-sys'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'bits')} = $properties->{'bits-sys'}; + if ($type){ + $rows->[$j]{main::key($num++,0,2,'type')} = $type; + if (!$properties->{'topology-full'} && $cpu->{'smt'} && ($extra > 2 || + ($extra > 0 && $cpu->{'smt'} eq 'disabled'))){ + $rows->[$j]{main::key($num++,0,2,'smt')} = $cpu->{'smt'}; + } + } + if ($extra > 0){ + $cpu->{'arch'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,2,'arch')} = $cpu->{'arch'}; + if ($cpu->{'arch-note'}){ + $rows->[$j]{main::key($num++,0,3,'note')} = $cpu->{'arch-note'}; + } + if ($b_admin && $cpu->{'gen'}){ + $rows->[$j]{main::key($num++,0,3,'gen')} = $cpu->{'gen'}; + } + if ($b_admin && $properties->{'arch-level'}){ + $rows->[$j]{main::key($num++,1,2,'level')} = $properties->{'arch-level'}[0]; + if ($properties->{'arch-level'}[1]){ + $rows->[$j]{main::key($num++,0,3,'note')} = $properties->{'arch-level'}[1]; + } + } + if ($b_admin){ + if ($cpu->{'year'}){ + $rows->[$j]{main::key($num++,0,2,'built')} = $cpu->{'year'}; + } + if ($cpu->{'process'}){ + $rows->[$j]{main::key($num++,0,2,'process')} = $cpu->{'process'}; + } + } + # note: had if arch, but stepping can be defined where arch failed, stepping can be 0 + if (!$b_admin && (defined $cpu->{'stepping'} || defined $cpu->{'revision'})){ + my $rev = main::get_defined($cpu->{'stepping'},$cpu->{'revision'}); + $rows->[$j]{main::key($num++,0,2,'rev')} = $rev; + } + } + if ($b_admin){ + $rows->[$j]{main::key($num++,0,2,'family')} = hex_and_decimal($cpu->{'family'}); + $rows->[$j]{main::key($num++,0,2,'model-id')} = hex_and_decimal($cpu->{'model-id'}); + if (defined $cpu->{'stepping'}){ + $rows->[$j]{main::key($num++,0,2,'stepping')} = hex_and_decimal($cpu->{'stepping'}); + } + elsif (defined $cpu->{'revision'}){ + $rows->[$j]{main::key($num++,0,2,'rev')} = $cpu->{'revision'}; + } + if (!%risc && $cpu->{'type'} ne 'elbrus'){ + $cpu->{'microcode'} = ($cpu->{'microcode'}) ? '0x' . $cpu->{'microcode'} : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'microcode')} = $cpu->{'microcode'}; + } + } + # note, risc cpus are using l1, L2, L3 more often, but if risc and no L2, skip + if ($properties->{'topology-string'} && (($extra > 1 && + ($properties->{'l1-cache'} || $properties->{'l3-cache'})) || + (!%risc || $properties->{'l2-cache'}) || $properties->{'cache'})){ + full_output_caches($j,$properties,\$num,$rows); + } + # all tests already done to load this, admin, etc + if ($properties->{'topology-full'}){ + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'Topology') => '', + },); + my ($id,$var) = (2,''); + if (scalar @{$properties->{'topology-full'}} > 1){ + $var = 'variant'; + $id = 3; + } + foreach my $topo (@{$properties->{'topology-full'}}){ + if ($var){ + $rows->[$j]{main::key($num++,1,2,'variant')} = ''; + } + my $x = ($size{'max-cols'} == 1 || $output_type ne 'screen') ? '' : 'x'; + $rows->[$j]{main::key($num++,0,$id,'cpus')} = $topo->{'cpus'} . $x; + $rows->[$j]{main::key($num++,1,$id+1,'cores')} = $topo->{'cores'}; + if ($topo->{'cores-mt'} && $topo->{'cores-st'}){ + $rows->[$j]{main::key($num++,1,$id+2,'mt')} = $topo->{'cores-mt'}; + $rows->[$j]{main::key($num++,0,$id+3,'tpc')} = $topo->{'tpc'}; + $rows->[$j]{main::key($num++,0,$id+2,'st')} = $topo->{'cores-st'}; + } + elsif ($topo->{'cores-mt'}){ + $rows->[$j]{main::key($num++,0,$id+2,'tpc')} = $topo->{'tpc'}; + } + if ($topo->{'max'} || $topo->{'min'}){ + my ($freq,$key) = ('',''); + if ($topo->{'max'} && $topo->{'min'}){ + $key = 'min/max'; + $freq = $topo->{'min'} . '/' . $topo->{'max'}; + } + elsif ($topo->{'max'}){ + $key = 'max'; + $freq = $topo->{'max'}; + } + else { + $key = 'min'; + $freq = $topo->{'min'}; + } + $rows->[$j]{main::key($num++,0,$id+1,$key)} = $freq; + } + if ($topo->{'threads'}){ + $rows->[$j]{main::key($num++,0,$id+1,'threads')} = $topo->{'threads'}; + } + if ($topo->{'dies'}){ + $rows->[$j]{main::key($num++,0,$id+1,'dies')} = $topo->{'dies'}; + } + } + $cpu->{'smt'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'smt')} = $cpu->{'smt'}; + full_output_caches($j,$properties,\$num,$rows); + } + my $speeds = $cpu->{'processors'}; + my $core_key = (defined $speeds && scalar @{$speeds} > 1) ? 'cores' : 'core'; + my $speed_key = ($properties->{'speed-key'}) ? $properties->{'speed-key'}: 'Speed'; + my $min_max = ($properties->{'min-max'}) ? $properties->{'min-max'}: 'N/A'; + my $min_max_key = ($properties->{'min-max-key'}) ? $properties->{'min-max-key'}: 'min/max'; + my $speed = ''; + if (!$properties->{'avg-speed-key'}){ + $speed = (defined $properties->{'speed'}) ? $properties->{'speed'}: 'N/A'; + } + # Aren't able to get per core speeds in BSDs. Why don't they support this? + if (defined $speeds && @$speeds){ + # only if defined and not 0 + if (grep {$_} @{$speeds}){ + $core_speeds_value = ''; + $b_speeds = 1; + } + else { + my $id = ($bsd_type) ? 'cpu-speeds-bsd' : 'cpu-speeds'; + $core_speeds_value = main::message($id); + } + } + else { + $core_speeds_value = main::message('cpu-speeds'); + } + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,$speed_key) => $speed, + }); + if ($properties->{'avg-speed-key'}){ + $rows->[$j]{main::key($num++,0,2,$properties->{'avg-speed-key'})} = $properties->{'speed'}; + if ($extra > 0 && $properties->{'high-speed-key'}){ + $rows->[$j]{main::key($num++,0,2,$properties->{'high-speed-key'})} = $cpu->{'high-freq'}; + } + } + $rows->[$j]{main::key($num++,0,2,$min_max_key)} = $min_max; + if ($extra > 0 && defined $cpu->{'boost'}){ + $rows->[$j]{main::key($num++,0,2,'boost')} = $cpu->{'boost'}; + } + if ($b_admin && $properties->{'dmi-speed'} && $properties->{'dmi-max-speed'}){ + $rows->[$j]{main::key($num++,0,2,'base/boost')} = $properties->{'dmi-speed'} . '/' . $properties->{'dmi-max-speed'}; + } + if ($b_admin && ($cpu->{'governor'} || $cpu->{'scaling-driver'})){ + $rows->[$j]{main::key($num++,1,2,'scaling')} = ''; + $cpu->{'driver'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'driver')} = $cpu->{'scaling-driver'}; + $cpu->{'governor'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'governor')} = $cpu->{'governor'}; + # only set if different from cpu min/max + if ($cpu->{'scaling-min-max'} && $cpu->{'scaling-min-max-key'}){ + $rows->[$j]{main::key($num++,0,3,$cpu->{'scaling-min-max-key'})} = $cpu->{'scaling-min-max'}; + } + } + if ($extra > 2){ + if ($properties->{'volts'}){ + $rows->[$j]{main::key($num++,0,2,'volts')} = $properties->{'volts'} . ' V'; + } + if ($properties->{'ext-clock'}){ + $rows->[$j]{main::key($num++,0,2,'ext-clock')} = $properties->{'ext-clock'}; + } + } + $rows->[$j]{main::key($num++,1,2,$core_key)} = $core_speeds_value; + my $i = 1; + # if say 96 0 speed cores, no need to print all those 0s + if ($b_speeds){ + foreach (@{$speeds}){ + $rows->[$j]{main::key($num++,0,3,$i++)} = $_; + } + } + if ($extra > 0 && !$bsd_type){ + my $bogomips = ($cpu->{'bogomips'} && + main::is_numeric($cpu->{'bogomips'})) ? int($cpu->{'bogomips'}) : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'bogomips')} = $bogomips; + } + if (($extra > 0 && !$show{'cpu-flag'}) || $show{'cpu-flag'}){ + my @flags = ($cpu->{'flags'}) ? split(/\s+/, $cpu->{'flags'}) : (); + my $flag_key = (%risc || $bsd_type) ? 'Features': 'Flags'; + my $flag = 'N/A'; + if (!$show{'cpu-flag'}){ + if (@flags){ + # failure to read dmesg.boot: dmesg.boot permissions; then short -Cx list flags + @flags = grep {/^(dmesg.boot|permissions|avx[2-9]?|ht|lm|nx|pae|pni|(sss|ss)e([2-9])?([a-z])?(_[0-9])?|svm|vmx)$/} @flags; + @flags = map {s/pni/sse3/; $_} @flags if @flags; + @flags = sort @flags; + } + # only ARM has Features, never seen them for MIPS/PPC/SPARC/RISCV, but check + if ($risc{'arm'} && $flag eq 'N/A'){ + $flag = main::message('arm-cpu-f'); + } + } + if (@flags){ + @flags = sort @flags; + $flag = join(' ', @flags); + } + push(@$rows, { + main::key($num++,0,1,$flag_key) => $flag, + },); + } + if ($b_admin){ + my $value = ''; + if (!defined $cpu->{'bugs-hash'}){ + if ($cpu->{'bugs-string'}){ + my @proc_bugs = split(/\s+/, $cpu->{'bugs-string'}); + @proc_bugs = sort @proc_bugs; + $value = join(' ', @proc_bugs); + } + else { + $value = main::message('cpu-bugs-null'); + } + } + if ($use{'filter-vulnerabilities'} && + (defined $cpu->{'bugs-hash'} || $cpu->{'bugs-string'})){ + $value = $filter_string; + undef $cpu->{'bugs-hash'}; + } + push(@$rows, { + main::key($num++,1,1,'Vulnerabilities') => $value, + },); + if (defined $cpu->{'bugs-hash'}){ + $j = scalar @$rows; + foreach my $key (sort keys %{$cpu->{'bugs-hash'}}){ + $rows->[$j]{main::key($num++,1,2,'Type')} = $key; + $rows->[$j]{main::key($num++,0,3,$cpu->{'bugs-hash'}->{$key}[0])} = $cpu->{'bugs-hash'}->{$key}[1]; + $j++; + } + } + } + eval $end if $b_log; +} + +# $num, $rows passed by reference +sub full_output_caches { + eval $start if $b_log; + my ($j,$properties,$num,$rows) = @_; + my $value = ''; + if (!$properties->{'l1-cache'} && !$properties->{'l2-cache'} && + !$properties->{'l3-cache'}){ + $value = ($properties->{'cache'}) ? $properties->{'cache'} : 'N/A'; + } + $rows->[$j]{main::key($$num++,1,2,'cache')} = $value; + if ($extra > 0 && $properties->{'l1-cache'}){ + $rows->[$j]{main::key($$num++,2,3,'L1')} = $properties->{'l1-cache'}; + if ($b_admin && ($properties->{'l1d-desc'} || $properties->{'l1i-desc'})){ + my $desc = ''; + if ($properties->{'l1d-desc'}){ + $desc .= 'd-' . $properties->{'l1d-desc'}; + } + if ($properties->{'l1i-desc'}){ + $desc .= '; ' if $desc; + $desc .= 'i-' . $properties->{'l1i-desc'}; + } + $rows->[$j]{main::key($$num++,0,4,'desc')} = $desc; + } + } + # $rows->[$j]{main::key($$num++,1,$l,$key)} = $support; + if (!$value){ + $properties->{'l2-cache'} = ($properties->{'l2-cache'}) ? $properties->{'l2-cache'} : 'N/A'; + $rows->[$j]{main::key($$num++,1,3,'L2')} = $properties->{'l2-cache'}; + if ($b_admin && $properties->{'l2-desc'}){ + $rows->[$j]{main::key($$num++,0,4,'desc')} = $properties->{'l2-desc'}; + } + } + if ($extra > 0 && $properties->{'l3-cache'}){ + $rows->[$j]{main::key($$num++,1,3,'L3')} = $properties->{'l3-cache'}; + if ($b_admin && $properties->{'l3-desc'}){ + $rows->[$j]{main::key($$num++,0,4,'desc')} = $properties->{'l3-desc'}; + } + } + if ($properties->{'cache-check'}){ + $rows->[$j]{main::key($$num++,0,3,'note')} = $properties->{'cache-check'}; + } + eval $end if $b_log; +} + +sub short_output { + eval $start if $b_log; + my ($rows,$cpu) = @_; + my $num = 0; + $cpu->[1] ||= main::message('cpu-model-null'); + $cpu->[2] ||= 'N/A'; + push(@$rows,{ + main::key($num++,1,1,'Info') => $cpu->[0] . ' ' . $cpu->[1] . ' [' . $cpu->[2] . ']' + #main::key($num++,0,2,'type') => $cpu->[2], + }); + if ($extra > 0){ + $rows->[0]{main::key($num++,1,2,'arch')} = $cpu->[8]; + if ($cpu->[9]){ + $rows->[0]{main::key($num++,0,3,'note')} = $cpu->[9]; + } + } + my $value = ($cpu->[7]) ? '' : $cpu->[4]; + $rows->[0]{main::key($num++,1,2,$cpu->[3])} = $value; + if ($cpu->[7]){ + $rows->[0]{main::key($num++,0,3,$cpu->[7])} = $cpu->[4]; + } + if ($cpu->[6]){ + $rows->[0]{main::key($num++,0,3,$cpu->[5])} = $cpu->[6]; + } + eval $end if $b_log; +} + +## SHORT OUTPUT DATA ## +sub short_data { + eval $start if $b_log; + my $num = 0; + my ($cpu,$data,%speeds); + my $sys = '/sys/devices/system/cpu/cpufreq/policy0'; + # NOTE: : Permission denied, ie, this is not always readable + # /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq + if (my $file = $system_files{'proc-cpuinfo'}){ + $cpu = cpuinfo_data($file); + } + elsif ($bsd_type){ + my ($key1,$val1) = ('',''); + if ($alerts{'sysctl'}){ + if ($alerts{'sysctl'}->{'action'} eq 'use'){ +# $key1 = 'Status'; +# $val1 = main::message('dev'); + $cpu = sysctl_data($type); + } + else { + $key1 = ucfirst($alerts{'sysctl'}->{'action'}); + $val1 = $alerts{'sysctl'}->{'message'}; + $data = ({main::key($num++,0,1,$key1) => $val1,}); + return $data; + } + } + } + # $cpu{'cur-freq'} = $cpu[0]->{'core-id'}[0]{'speed'}; + $data = prep_short_data($cpu); + eval $end if $b_log; + return $data; +} + +sub prep_short_data { + eval $start if $b_log; + my ($cpu_data) = @_; + my $properties = cpu_properties($cpu_data); + my ($cpu,$speed_key,$speed,$type) = ('','speed',0,''); + $cpu = $cpu_data->{'model_name'} if $cpu_data->{'model_name'}; + $type = $properties->{'cpu-type'} if $properties->{'cpu-type'}; + $speed_key = $properties->{'speed-key'} if $properties->{'speed-key'}; + $speed = $properties->{'speed'} if $properties->{'speed'}; + my $result = [ + $properties->{'topology-string'}, + $cpu, + $type, + $speed_key, + $speed, + $properties->{'min-max-key'}, + $properties->{'min-max'}, + $properties->{'avg-speed-key'}, + ]; + if ($extra > 0){ + $cpu_data->{'arch'} ||= 'N/A'; + $result->[8] = $cpu_data->{'arch'}; + $result->[9] = $cpu_data->{'arch-note'}; + } + eval $end if $b_log; + return $result; +} + +## PRIMARY DATA GENERATORS ## +sub cpuinfo_data { + eval $start if $b_log; + my ($file)= @_; + my ($cpu,$arch,$note,$temp); + # has to be set above fake cpu section + set_cpu_data(\$cpu); + # sleep is also set in front of sysctl_data for BSDs, same idea + my $sleep = $cpu_sleep * 1000000; + if ($b_hires){ + eval 'Time::HiRes::usleep($sleep)'; + } + else { + select(undef, undef, undef, $cpu_sleep); + } + # Run this logic first to make sure we get the speeds as raw as possible. + # Not in function to avoid unnecessary cpu use, we have slept right before. + # ARM and legacy systems etc do not always have cpufreq. + # note that there can be a definite cost to reading scaling_cur_freq, which + # must be generated on the fly based on some time snippet sample. + if (-e '/sys/devices/system/cpu/'){ + my $glob = '/sys/devices/system/cpu/cpu*/cpufreq/{affected_cpus,'; + # reading cpuinfo WAY faster than scaling, but root only + if (-r '/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq'){ + $glob .= 'cpuinfo_cur_freq}'; + } + else { + $glob .= 'scaling_cur_freq}'; + } + my ($error,$file,$key,%working,%freq,@value); + foreach (main::globber($glob)){ + next if ! -r $_; + undef $error; + # $fh always non null, even on error + open(my $fh, '<', $_) or $error = $!; + if (!$error){ + m%/sys/devices/system/cpu/cpu(\d+)/cpufreq/(affected_cpus|(cpuinfo|scaling)_cur_freq)%; + $key = $1; + $file = $2; + chomp(@value = <$fh>); + close $fh; + if ($file eq 'affected_cpus'){ + # chomp seems to turn undefined into '', not sure why + $working{$key}->[0] = $value[0] if $value[0] ne ''; + } + else { + $working{$key}->[1] = clean_speed($value[0],'khz'); + } + } + } + if (%working){ + foreach (keys %working){ + $freq{sprintf("%04d",$_)} = $working{$_}->[1] if defined $working{$_}->[0]; + } + $cpu->{'sys-freq'} = \%freq if %freq; + } + } + cpuinfo_data_grabber($file,\$cpu->{'type'}) if !$loaded{'cpuinfo'}; + $cpu->{'type'} = cpu_vendor($cpu_arch) if $cpu_arch eq 'elbrus'; # already set to lower + my ($core_count,$proc_count,$speed) = (0,0,0); + my ($b_block_1) = (1); + # need to prime for arm cpus, which do not have physical/core ids usually + # level 0 is phys id, level 1 is die id, level 2 is core id + # note, there con be a lot of processors, 32 core HT would have 64, for example. + foreach my $block (@cpuinfo){ + # get the repeated data for CPUs, after assign the dynamic per core data + next if !$block; + if ($b_block_1){ + $b_block_1 = 0; + # this may also kick in for centaur/via types, but no data available, guess + if (!$cpu->{'type'} && $block->{'vendor_id'}){ + $cpu->{'type'} = cpu_vendor($block->{'vendor_id'}); + } + # PPC can use 'cpu', MIPS 'cpu model' + $temp = main::get_defined($block->{'model name'},$block->{'cpu'}, + $block->{'cpu model'}); + if ($temp){ + $cpu->{'model_name'} = $temp; + $cpu->{'model_name'} = main::clean($cpu->{'model_name'}); + $cpu->{'model_name'} = clean_cpu($cpu->{'model_name'}); + if ($risc{'arm'} || $cpu->{'model_name'} =~ /ARM|AArch/i){ + $cpu->{'type'} = 'arm'; + if ($cpu->{'model_name'} =~ /(.*)\srev\s([\S]+)\s(\(([\S]+)\))?/){ + $cpu->{'model_name'} = $1; + $cpu->{'stepping'} = $2; + if ($4){ + $cpu->{'arch'} = $4; + if ($cpu->{'model_name'} !~ /\Q$cpu->{'arch'}\E/i){ + $cpu->{'model_name'} .= ' ' . $cpu->{'arch'}; + } + } + # print "p0:\n"; + } + } + elsif ($risc{'mips'} || $cpu->{'model_name'} =~ /mips/i){ + $cpu->{'type'} = 'mips'; + } + } + $temp = main::get_defined($block->{'architecture'}, + $block->{'cpu family'},$block->{'cpu architecture'}); + if ($temp){ + if ($temp =~ /^\d+$/){ + # translate integers to hex + $cpu->{'family'} = uc(sprintf("%x",$temp)); + } + elsif ($risc{'arm'}){ + $cpu->{'arch'} = $temp; + } + } + # note: stepping and ARM cpu revision are integers + $temp = main::get_defined($block->{'stepping'},$block->{'cpu revision'}); + # can be 0, but can be 'unknown' + if (defined $temp || + ($cpu->{'type'} eq 'elbrus' && defined $block->{'revision'})){ + $temp = $block->{'revision'} if defined $block->{'revision'}; + if ($temp =~ /^\d+$/){ + $cpu->{'stepping'} = uc(sprintf("%x",$temp)); + } + } + # PPC revision is a string, but elbrus revision is hex + elsif (defined $block->{'revision'}){ + $cpu->{'revision'} = $block->{'revision'}; + } + # this is hex so uc for cpu arch id. raspi 4 has Model rather than Hardware + if (defined $block->{'model'}){ + # can be 0, but can be 'unknown' + $cpu->{'model-id'} = uc(sprintf("%x",$block->{'model'})); + } + if ($block->{'cpu variant'}){ + $cpu->{'model-id'} = uc($block->{'cpu variant'}); + $cpu->{'model-id'} =~ s/^0X//; + } + # this is per cpu, not total if > 1 pys cpus + if (!$cpu->{'cores'} && $block->{'cpu cores'}){ + $cpu->{'cores'} = $block->{'cpu cores'}; + } + ## this is only for -C full cpu output + if ($type eq 'full'){ + # note: in cases where only cache is there, don't guess, it can be L1, + # L2, or L3, but never all of them added togehter, so give up. + if ($block->{'cache size'} && + $block->{'cache size'} =~ /(\d+\s*[KMG])i?B?$/){ + $cpu->{'cache'} = main::translate_size($1); + } + if ($block->{'l1 cache size'} && + $block->{'l1 cache size'} =~ /(\d+\s*[KMG])i?B?$/){ + $cpu->{'l1-cache'} = main::translate_size($1); + } + if ($block->{'l2 cache size'} && + $block->{'l2 cache size'} =~ /(\d+\s*[KMG])i?B?$/){ + $cpu->{'l2-cache'} = main::translate_size($1); + } + if ($block->{'l3 cache size'} && + $block->{'l3 cache size'} =~ /(\d+\s*[KMG])i?B?$/){ + $cpu->{'l3-cache'} = main::translate_size($1); + } + $temp = main::get_defined($block->{'flags'} || $block->{'features'}); + if ($temp){ + $cpu->{'flags'} = $temp; + } + if ($b_admin){ + # note: not used unless maybe /sys data missing? + if ($block->{'bugs'}){ + $cpu->{'bugs-string'} = $block->{'bugs'}; + } + # unlike family and model id, microcode appears to be hex already + if ($block->{'microcode'}){ + if ($block->{'microcode'} =~ /0x/){ + $cpu->{'microcode'} = uc($block->{'microcode'}); + $cpu->{'microcode'} =~ s/^0X//; + } + else { + $cpu->{'microcode'} = uc(sprintf("%x",$block->{'microcode'})); + } + } + } + } + } + # These occurs in a separate block with E2C3, last in cpuinfo blocks, + # otherwise per block in E8C variants + if ($cpu->{'type'} eq 'elbrus' && (!$cpu->{'l1i-cache'} && + !$cpu->{'l1d-cache'} && !$cpu->{'l2-cache'} && !$cpu->{'l3-cache'})){ + # note: cache0 is L1i and cache1 L1d. cp_caches_fallback handles + if ($block->{'cache0'} && + $block->{'cache0'} =~ /size\s*=\s*(\d+)K\s/){ + $cpu->{'l1i-cache'} = $1; + } + if ($block->{'cache1'} && + $block->{'cache1'} =~ /size\s*=\s*(\d+)K\s/){ + $cpu->{'l1d-cache'} = $1; + } + if ($block->{'cache2'} && + $block->{'cache2'} =~ /size\s*=\s*(\d+)(K|M)\s/){ + $cpu->{'l2-cache'} = ($2 eq 'M') ? ($1*1024) : $1; + } + if ($block->{'cache3'} && + $block->{'cache3'} =~ /size\s*=\s*(\d+)(K|M)\s/){ + $cpu->{'l3-cache'} = ($2 eq 'M') ? ($1*1024) : $1; + } + } + ## Start incrementers + $temp = main::get_defined($block->{'cpu mhz'},$block->{'clock'}); + if ($temp){ + $speed = clean_speed($temp); + push(@{$cpu->{'processors'}},$speed); + } + # new arm shows bad bogomip value, so don't use it, however, ancient + # cpus, intel 486, can have super low bogomips, like 33.17 + if ($extra > 0 && $block->{'bogomips'} && ((%risc && + $block->{'bogomips'} > 50) || !%risc)){ + $cpu->{'bogomips'} += $block->{'bogomips'}; + } + # just to get core counts for ARM/MIPS/PPC systems + if (defined $block->{'processor'} && !$temp){ + if ($block->{'processor'} =~ /^\d+$/){ + push(@{$cpu->{'processors'}},0); + } + } + # note: for alder lake, could vary, depending on if e or p core but we + # only care aobut the highest value for crude logic here + if ($block->{'siblings'} && + (!$cpu->{'siblings'} || $block->{'siblings'} > $cpu->{'siblings'})){ + $cpu->{'siblings'} = $block->{'siblings'}; + } + # Ignoring trying to catch dies with $block->{'physical id'}, + # that's too buggy for cpuinfo + if (defined $block->{'core id'}){ + # https://www.pcworld.com/article/3214635/components-processors/ryzen-threadripper-review-we-test-amds-monster-cpu.html + my $phys = (defined $block->{'physical id'}) ? $block->{'physical id'}: 0; + my $die_id = 0; + if (!grep {$_ eq $block->{'core id'}} @{$cpu->{'ids'}->[$phys][$die_id]}){ + push(@{$cpu->{'ids'}->[$phys][$die_id]},$block->{'core id'}); + } + } + } + undef @cpuinfo; # we're done with it, dump it + undef %cpuinfo_machine; + if (%risc){ + if (!$cpu->{'type'}){ + $cpu->{'type'} = $risc{'id'}; + } + if (!$bsd_type){ + my $system_cpus = system_cpu_name(); + $cpu->{'system-cpus'} = $system_cpus if %$system_cpus; + } + } + main::log_data('dump','%$cpu',$cpu) if $b_log; + print Data::Dumper::Dumper $cpu if $dbg[8]; + eval $end if $b_log; + return $cpu; +} + +sub cpuinfo_data_grabber { + eval $start if $b_log; + my ($file,$cpu_type) = @_; # type by ref + $loaded{'cpuinfo'} = 1; + # use --arm flag when testing arm cpus, and --fake-cpu to trigger fake data + if ($fake{'cpu'}){ + ## CPU sys/cpuinfo pairs: + # $file = "$fake_data_dir/cpu/sys-ci-pairs/android-pocom3-fake-cpuinfo.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/arm-pine64-cpuinfo-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/arm-riscyslack2-cpuinfo-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/ppc-stuntkidz~cpuinfo.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/riscv-unmatched-2021~cpuinfo-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-brickwizard-atom-n270~cpuinfo-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-amd-phenom-chrisretusn-cpuinfo-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-drgibbon-intel-i7-cpuinfo.txt"; + ## ARM/MIPS + # $file = "$fake_data_dir/cpu/arm/arm-4-core-pinebook-1.txt"; + # $file = "$fake_data_dir/cpu/arm/armv6-single-core-1.txt"; + # $file = "$fake_data_dir/cpu/arm/armv7-dual-core-1.txt"; + # $file = "$fake_data_dir/cpu/arm/armv7-new-format-model-name-single-core.txt"; + # $file = "$fake_data_dir/cpu/arm/arm-2-die-96-core-rk01.txt"; + # $file = "$fake_data_dir/cpu/arm/arm-shevaplug-1.2ghz.txt"; + # $file = "$fake_data_dir/cpu/mips/mips-mainusg-cpuinfo.txt"; + # $file = "$fake_data_dir/cpu/ppc/ppc-debian-ppc64-cpuinfo.txt"; + ## x86 + # $file = "$fake_data_dir/cpu/amd/16-core-32-mt-ryzen.txt"; + # $file = "$fake_data_dir/cpu/amd/2-16-core-epyc-abucodonosor.txt"; + # $file = "$fake_data_dir/cpu/amd/2-core-probook-antix.txt"; + # $file = "$fake_data_dir/cpu/amd/4-core-jean-antix.txt"; + # $file = "$fake_data_dir/cpu/amd/4-core-althlon-mjro.txt"; + # $file = "$fake_data_dir/cpu/amd/4-core-apu-vc-box.txt"; + # $file = "$fake_data_dir/cpu/amd/4-core-a10-5800k-1.txt"; + # $file = "$fake_data_dir/cpu/intel/1-core-486-fourtysixandtwo.txt"; + # $file = "$fake_data_dir/cpu/intel/2-core-ht-atom-bruh.txt"; + # $file = "$fake_data_dir/cpu/intel/core-2-i3.txt"; + # $file = "$fake_data_dir/cpu/intel/8-core-i7-damentz64.txt"; + # $file = "$fake_data_dir/cpu/intel/2-10-core-xeon-ht.txt"; + # $file = "$fake_data_dir/cpu/intel/4-core-xeon-fake-dual-die-zyanya.txt"; + # $file = "$fake_data_dir/cpu/intel/2-core-i5-fake-dual-die-hek.txt"; + # $file = "$fake_data_dir/cpu/intel/2-1-core-xeon-vm-vs2017.txt"; + # $file = "$fake_data_dir/cpu/intel/4-1-core-xeon-vps-frodo1.txt"; + # $file = "$fake_data_dir/cpu/intel/4-6-core-xeon-no-mt-lathander.txt"; + ## Elbrus + # $cpu_type = 'elbrus'; # uncomment to test elbrus + # $file = "$fake_data_dir/cpu/elbrus/elbrus-2c3/cpuinfo.txt"; + # $file = "$fake_data_dir/cpu/elbrus/1xE1C-8.txt"; + # $file = "$fake_data_dir/cpu/elbrus/1xE2CDSP-4.txt"; + # $file = "$fake_data_dir/cpu/elbrus/1xE2S4-3-monocub.txt"; + # $file = "$fake_data_dir/cpu/elbrus/1xMBE8C-7.txt"; + # $file = "$fake_data_dir/cpu/elbrus/4xEL2S4-3.txt"; + # $file = "$fake_data_dir/cpu/elbrus/4xE8C-7.txt"; + # $file = "$fake_data_dir/cpu/elbrus/4xE2CDSP-4.txt"; + # $file = "$fake_data_dir/cpu/elbrus/cpuinfo.e8c2.txt"; + } + my $raw = main::reader($file,'','ref'); + @$raw = map {$_ =~ s/^\s*$/~~~/;$_;} @$raw; + push(@$raw,'~~~') if @$raw; + my ($b_processor,$key,$value); + my ($i) = (0); + my @key_tests = ('firmware','hardware','mmu','model','motherboard', + 'platform','system type','timebase'); + foreach my $row (@$raw){ + ($key,$value) = split(/\s*:\s*/,$row,2); + next if !defined $key; + # ARM: 'Hardware' can appear in processor block; system type (mips) + # ARM: CPU revision; machine: Revision/PPC: revision (CPU implied) + # orangepi3 has Hardware/Processor embedded in processor block + if (%risc && ((grep {lc($key) eq $_} @key_tests) || + (!$risc{'ppc'} && lc($key) eq 'revision'))){ + $b_processor = 0; + } + else { + $b_processor = 1; + } + if ($b_processor){ + if ($key eq '~~~'){ + $i++; + next; + } + # A small handful of ARM devices use Processor instead of 'model name' + # Processor : AArch64 Processor rev 4 (aarch64) + # Processor : Feroceon 88FR131 rev 1 (v5l) + $key = ($key eq 'Processor') ? 'model name' : lc($key); + $cpuinfo[$i]->{$key} = $value; + } + else { + next if $cpuinfo_machine{lc($key)}; + $cpuinfo_machine{lc($key)} = $value; + } + } + if ($b_log){ + main::log_data('dump','@cpuinfo',\@cpuinfo); + main::log_data('dump','%cpuinfo_machine',\%cpuinfo_machine); + } + if ($dbg[41]){ + print Data::Dumper::Dumper \@cpuinfo; + print Data::Dumper::Dumper \%cpuinfo_machine; + } + eval $end if $b_log; +} + +sub cpu_sys_data { + eval $start if $b_log; + my $sys_freq = $_[0]; + my $cpu_sys = {}; + my $working = sys_data_grabber(); + return $cpu_sys if !%$working; + $cpu_sys->{'data'} = $working->{'data'} if $working->{'data'}; + my ($core_id,$fake_core_id,$phys_id,) = (0,0,-1); + my (%cache_ids,@ci_freq_max,@ci_freq_min,@sc_freq_max,@sc_freq_min); + foreach my $key (sort keys %{$working->{'cpus'}}){ + ($core_id,$phys_id) = (0,0); + my $cpu_id = $key + 0; + my $speed; + my $cpu = $working->{'cpus'}{$key}; + if (defined $cpu->{'topology'}{'physical_package_id'}){ + $phys_id = sprintf("%04d",$cpu->{'topology'}{'physical_package_id'}); + } + if (defined $cpu->{'topology'}{'core_id'}){ + # id is not consistent, seen 5 digit id + $core_id = sprintf("%08d",$cpu->{'topology'}{'core_id'}); + if ($fake{'cpu'}){ + if (defined $cpu->{'cpufreq'}{'scaling_cur_freq'} && + $cpu->{'cpufreq'}{'affected_cpus'} && + $cpu->{'cpufreq'}{'affected_cpus'} ne 'UNDEFINED'){ + $speed = clean_speed($cpu->{'cpufreq'}{'scaling_cur_freq'},'khz'); + } + } + elsif (defined $sys_freq && defined $sys_freq->{$key}){ + $speed = $sys_freq->{$key}; + } + if (defined $speed){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'cores'}{$core_id}},$speed); + push(@{$cpu_sys->{'data'}{'speeds'}{'all'}},$speed); + } + else { + push(@{$cpu_sys->{'data'}{'speeds'}{'all'}},0); + # seen cases, riscv, where core id, phys id, are all -1 + my $id = ($core_id != -1) ? $core_id: $fake_core_id++; + push(@{$cpu_sys->{'cpus'}{$phys_id}{'cores'}{$id}},0); + } + # Only use if topology core-id exists, some virtualized cpus can list + # frequency data for the non available cores, but those do not show + # topology data. + # For max / min, we want to prep for the day 1 pys cpu has > 1 min/max freq + if (defined $cpu->{'cpufreq'}{'cpuinfo_max_freq'}){ + $cpu->{'cpufreq'}{'cpuinfo_max_freq'} = clean_speed($cpu->{'cpufreq'}{'cpuinfo_max_freq'},'khz'); + if (!grep {$_ eq $cpu->{'cpufreq'}{'cpuinfo_max_freq'}} @ci_freq_max){ + push(@ci_freq_max,$cpu->{'cpufreq'}{'cpuinfo_max_freq'}); + } + if (!grep {$_ eq $cpu->{'cpufreq'}{'cpuinfo_max_freq'}} @{$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}},$cpu->{'cpufreq'}{'cpuinfo_max_freq'}); + } + } + if (defined $cpu->{'cpufreq'}{'cpuinfo_min_freq'}){ + $cpu->{'cpufreq'}{'cpuinfo_min_freq'} = clean_speed($cpu->{'cpufreq'}{'cpuinfo_min_freq'},'khz'); + if (!grep {$_ eq $cpu->{'cpufreq'}{'cpuinfo_min_freq'}} @ci_freq_min){ + push(@ci_freq_min,$cpu->{'cpufreq'}{'cpuinfo_min_freq'}); + } + if (!grep {$_ eq $cpu->{'cpufreq'}{'cpuinfo_min_freq'}} @{$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}},$cpu->{'cpufreq'}{'cpuinfo_min_freq'}); + } + } + if (defined $cpu->{'cpufreq'}{'scaling_max_freq'}){ + $cpu->{'cpufreq'}{'scaling_max_freq'} = clean_speed($cpu->{'cpufreq'}{'scaling_max_freq'},'khz'); + if (!grep {$_ eq $cpu->{'cpufreq'}{'scaling_max_freq'}} @sc_freq_max){ + push(@sc_freq_max,$cpu->{'cpufreq'}{'scaling_max_freq'}); + } + if (!grep {$_ eq $cpu->{'cpufreq'}{'scaling_max_freq'}} @{$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}},$cpu->{'cpufreq'}{'scaling_max_freq'}); + } + } + if (defined $cpu->{'cpufreq'}{'scaling_min_freq'}){ + $cpu->{'cpufreq'}{'scaling_min_freq'} = clean_speed($cpu->{'cpufreq'}{'scaling_min_freq'},'khz'); + if (!grep {$_ eq $cpu->{'cpufreq'}{'scaling_min_freq'}} @sc_freq_min){ + push(@sc_freq_min,$cpu->{'cpufreq'}{'scaling_min_freq'}); + } + if (!grep {$_ eq $cpu->{'cpufreq'}{'scaling_min_freq'}} @{$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}},$cpu->{'cpufreq'}{'scaling_min_freq'}); + } + } + if (defined $cpu->{'cpufreq'}{'scaling_governor'}){ + if (!grep {$_ eq $cpu->{'cpufreq'}{'scaling_governor'}} @{$cpu_sys->{'cpus'}{$phys_id}{'governor'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'governor'}},$cpu->{'cpufreq'}{'scaling_governor'}); + } + } + if (defined $cpu->{'cpufreq'}{'scaling_driver'}){ + $cpu_sys->{'cpus'}{$phys_id}{'scaling-driver'} = $cpu->{'cpufreq'}{'scaling_driver'}; + } + } + if (!defined $cpu_sys->{'data'}{'cpufreq-boost'} && defined $cpu->{'cpufreq'}{'cpb'}){ + $cpu_sys->{'data'}{'cpufreq-boost'} = $cpu->{'cpufreq'}{'cpb'}; + } + if (defined $cpu->{'topology'}{'core_cpus_list'}){ + $cpu->{'topology'}{'thread_siblings_list'} = $cpu->{'topology'}{'core_cpus_list'}; + } + if (defined $cpu->{'cache'} && keys %{$cpu->{'cache'}} > 0){ + foreach my $key2 (sort keys %{$cpu->{'cache'}}){ + my $cache = $cpu->{'cache'}{$key2}; + my $type = ($cache->{'type'} =~ /^([DI])/i) ? lc($1): ''; + my $level = 'l' . $cache->{'level'} . $type; + # Very old systems, 2.6.xx do not have shared_cpu_list + if (!defined $cache->{'shared_cpu_list'} && defined $cache->{'shared_cpu_map'}){ + $cache->{'shared_cpu_list'} = $cache->{'shared_cpu_map'}; + } + # print Data::Dumper::Dumper $cache; + if (defined $cache->{'shared_cpu_list'}){ + # not needed, the cpu is always in the range + # my $range = main::regex_range($cache->{'shared_cpu_list'}); + my $size = main::translate_size($cache->{'size'}); + # print "cpuid: $cpu_id phys-core: $phys_id-$core_id level: $level range: $range shared: $cache->{'shared_cpu_list'}\n"; + if (!(grep {$_ eq $cache->{'shared_cpu_list'}} @{$cache_ids{$phys_id}->{$level}})){ + push(@{$cache_ids{$phys_id}->{$level}},$cache->{'shared_cpu_list'}); + push(@{$cpu_sys->{'cpus'}{$phys_id}{'caches'}{$level}},$size); + } + } + } + } + # die_id is relatively new, core_siblings_list has been around longer + if (defined $cpu->{'topology'}{'die_id'} || + defined $cpu->{'topology'}{'core_siblings_list'}){ + my $die = $cpu->{'topology'}{'die_id'}; + $die = $cpu->{'topology'}{'core_siblings_list'} if !defined $die; + if (!grep {$_ eq $die} @{$cpu_sys->{'cpus'}{$phys_id}{'dies'}}){ + push(@{$cpu_sys->{'cpus'}{$phys_id}{'dies'}},$die); + } + } + } + if (defined $cpu_sys->{'data'}{'cpufreq-boost'} && + $cpu_sys->{'data'}{'cpufreq-boost'} =~ /^[01]$/){ + if ($cpu_sys->{'data'}{'cpufreq-boost'}){ + $cpu_sys->{'data'}{'cpufreq-boost'} = 'enabled'; + } + else { + $cpu_sys->{'data'}{'cpufreq-boost'} = 'disabled'; + } + } + # cpuinfo_max_freq:["2000000"] cpuinfo_max_freq:["1500000"] + # cpuinfo_min_freq:["200000"] + if (@ci_freq_max){ + $cpu_sys->{'data'}{'speeds'}{'max-freq'} = join(':',@ci_freq_max); + } + if (@ci_freq_min){ + $cpu_sys->{'data'}{'speeds'}{'min-freq'} = join(':',@ci_freq_min); + } + # also handle off chance that cpuinfo_min/max not set, but scaling_min/max there + if (@sc_freq_max){ + $cpu_sys->{'data'}{'speeds'}{'scaling-max-freq'} = join(':',@sc_freq_max); + if (!$cpu_sys->{'data'}{'speeds'}{'max-freq'}){ + $cpu_sys->{'data'}{'speeds'}{'max-freq'} = $cpu_sys->{'data'}{'speeds'}{'scaling-max-freq'}; + } + } + if (@sc_freq_min){ + $cpu_sys->{'data'}{'speeds'}{'scaling-min-freq'} = join(':',@sc_freq_min); + if (!$cpu_sys->{'data'}{'speeds'}{'min-freq'}){ + $cpu_sys->{'data'}{'speeds'}{'min-freq'} = $cpu_sys->{'data'}{'speeds'}{'scaling-min-freq'}; + } + } + # this corrects a bug we see sometimes in min/max frequencies + if ((scalar @ci_freq_max < 2 && scalar @ci_freq_min < 2) && + (defined $cpu_sys->{'data'}{'speeds'}{'min-freq'} && + defined $cpu_sys->{'data'}{'speeds'}{'max-freq'}) && + ($cpu_sys->{'data'}{'speeds'}{'min-freq'} > $cpu_sys->{'data'}{'speeds'}{'max-freq'} || + $cpu_sys->{'data'}{'speeds'}{'min-freq'} == $cpu_sys->{'data'}{'speeds'}{'max-freq'})){ + $cpu_sys->{'data'}{'speeds'}{'min-freq'} = 0; + } + main::log_data('dump','%$cpu_sys',$cpu_sys) if $b_log; + print Data::Dumper::Dumper $cpu_sys if $dbg[8]; + eval $end if $b_log; + return $cpu_sys; +} + +sub sys_data_grabber { + eval $start if $b_log; + my (@files); + # this data has to match the data in cpuinfo grabber fake cpu, and remember + # to use --arm flag if arm tests + if ($fake{'cpu'}){ + # my $file; + ## CPU sys/cpuinfo pairs: + # $file = "$fake_data_dir/cpu/sys-ci-pairs/android-pocom3-fake-sys.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/arm-pine64-sys-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/arm-riscyslack2-sys-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/ppc-stuntkidz~sys.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/riscv-unmatched-2021~sys-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-brickwizard-atom-n270~sys-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-amd-phenom-chrisretusn-sys-1.txt"; + # $file = "$fake_data_dir/cpu/sys-ci-pairs/x86-drgibbon-intel-i7-sys.txt"; + # @files = main::reader($file); + } + # There's a massive time hit reading full globbed set of files, so grab and + # read only what we need. + else { + my $glob = '/sys/devices/system/cpu/{'; + if ($dbg[43]){ + $glob .= 'cpufreq,cpu*/topology,cpu*/cpufreq,cpu*/cache/index*,smt,vulnerabilities}/*'; + } + else { + $glob .= 'cpu*/topology/{core_cpus_list,core_id,core_siblings_list,die_id,'; + $glob .= 'physical_package_id,thread_siblings_list}'; + $glob .= ',cpufreq/{boost,ondemand}'; + $glob .= ',cpu*/cpufreq/{cpb,cpuinfo_max_freq,cpuinfo_min_freq,'; + $glob .= 'scaling_max_freq,scaling_min_freq'; + $glob .= ',scaling_driver,scaling_governor' if $type eq 'full' && $b_admin; + $glob .= '}'; + if ($type eq 'full'){ + $glob .= ',cpu*/cache/index*/{level,shared_cpu_list,shared_cpu_map,size,type}'; + } + $glob .= ',smt/{active,control}'; + $glob .= ',vulnerabilities/*' if $b_admin; + $glob .= '}'; + } + @files = main::globber($glob); + } + main::log_data('dump','@files',\@files) if $b_log; + print Data::Dumper::Dumper \@files if $dbg[40]; + my ($b_bug,$b_cache,$b_freq,$b_topo,$b_main); + my $working = {}; + my ($main_id,$main_key,$holder,$id,$item,$key) = ('','','','','',''); + # need to return hash reference on failure or old systems complain + return $working if !@files; + foreach (sort @files){ + if ($fake{'cpu'}){ + ($_,$item) = split(/::/,$_,2); + } + else { + next if -d $_ || ! -e $_; + undef $item; + } + $key = $_; + $key =~ m|/([^/]+)/([^/]+)$|; + my ($key_1,$key_2) = ($1,$2); + if (m|/cpu(\d+)/|){ + if (!$holder || $1 ne $holder){ + $id = sprintf("%04d",$1); + $holder = $1; + } + $b_bug = 0; + $b_cache = 0; + $b_freq = 0; + $b_main = 0; + $b_topo = 0; + if ($key_1 eq 'cpufreq'){ + $b_freq = 1; + $main_key = $key_2; + $key = $key_1; + } + elsif ($key_1 eq 'topology'){ + $b_topo = 1; + $main_key = $key_2; + $key = $key_1; + } + elsif ($key_1 =~ /^index(\d+)$/){ + $b_cache = 1; + $main_key = $key_2; + $main_id = sprintf("%02d",$1); + $key = 'cache'; + } + } + elsif ($key_1 eq 'vulnerabilities'){ + $id = $key_1; + $key = $key_2; + $b_bug = 1; + $b_cache = 0; + $b_main = 0; + $b_freq = 0; + $b_topo = 0; + $main_key = ''; + $main_id = ''; + } + else { + $id = $key_1 . '-' . $key_2; + $b_bug = 0; + $b_cache = 0; + $b_main = 1; + $b_freq = 0; + $b_topo = 0; + $main_key = ''; + $main_id = ''; + } + if (!$fake{'cpu'}){ + if (-r $_) { + my $error; + # significantly faster to skip reader() and do it directly + # $fh always non null, even on error + open(my $fh, '<', $_) or $error = $!; + if (!$error){ + chomp(my @value = <$fh>); + close $fh; + $item = $value[0]; + } + # $item = main::reader($_,'strip',0); + } + else { + $item = main::message('root-required'); + } + $item = main::message('undefined') if !defined $item; + } + # print "$key_1 :: $key_2 :: $item\n"; + if ($b_main){ + $working->{'data'}{$id} = $item; + } + elsif ($b_bug){ + my $type = ($item =~ /^Mitigation:/) ? 'mitigation': 'status'; + $item =~ s/Mitigation: //; + $working->{'data'}{$id}{$key} = [$type,$item]; + } + elsif ($b_cache){ + $working->{'cpus'}{$id}{$key}{$main_id}{$main_key} = $item; + } + elsif ($b_freq || $b_topo){ + $working->{'cpus'}{$id}{$key}{$main_key} = $item; + } + } + main::log_data('dump','%$working',$working) if $b_log; + print Data::Dumper::Dumper $working if $dbg[39]; + eval $end if $b_log; + return $working; +} + +sub sysctl_data { + eval $start if $b_log; + my ($cpu,@line,%speeds,@working); + my ($sep) = (''); + my ($die_holder,$die_id,$phys_holder,$phys_id,$proc_count,$speed) = (0,0,0,0,0,0,0); + set_cpu_data(\$cpu); + @{$sysctl{'cpu'}} = () if !$sysctl{'cpu'}; # don't want error next! + foreach (@{$sysctl{'cpu'}}){ + @line = split(/\s*:\s*/, $_); + next if !$line[0]; + # darwin shows machine, like MacBook7,1, not cpu + # machdep.cpu.brand_string: Intel(R) Core(TM)2 Duo CPU P8600 @ 2.40GHz + if (($bsd_type ne 'darwin' && $line[0] eq 'hw.model') || + $line[0] eq 'machdep.cpu.brand_string'){ + # cut L2 cache/cpu max speed out of model string, if available + # openbsd 5.6: AMD Sempron(tm) Processor 3400+ ("AuthenticAMD" 686-class, 256KB L2 cache) + # openbsd 6.x has Lx cache data in dmesg.boot + # freebsd 10: hw.model: AMD Athlon(tm) II X2 245 Processor + $line[1] = main::clean($line[1]); + $line[1] = clean_cpu($line[1]); + if ($line[1] =~ /([0-9]+)[\s-]*([KM]B)\s+L2 cache/i){ + my $multiplier = ($2 eq 'MB') ? 1024: 1; + $cpu->{'l2-cache'} = $1 * $multiplier; + } + if ($line[1] =~ /([^0-9\.][0-9\.]+)[\s-]*[MG]Hz/){ + $cpu->{'max-freq'} = $1; + if ($cpu->{'max-freq'} =~ /MHz/i){ + $cpu->{'max-freq'} =~ s/[\s-]*MHz//; + $cpu->{'max-freq'} = clean_speed($cpu->{'max-freq'},'mhz'); + } + elsif ($cpu->{'max-freq'} =~ /GHz/){ + $cpu->{'max-freq'} =~ s/[\s-]*GHz//i; + $cpu->{'max-freq'} = $cpu->{'max-freq'} / 1000; + $cpu->{'max-freq'} = clean_speed($cpu->{'max-freq'},'mhz'); + } + } + if ($line[1] =~ /\)$/){ + $line[1] =~ s/\s*\(.*\)$//; + } + $cpu->{'model_name'} = $line[1]; + $cpu->{'type'} = cpu_vendor($line[1]); + } + # NOTE: hw.l1icachesize: hw.l1dcachesize: ; in bytes, apparently + elsif ($line[0] eq 'hw.l1dcachesize'){ + $cpu->{'l1d-cache'} = $line[1]/1024; + } + elsif ($line[0] eq 'hw.l1icachesize'){ + $cpu->{'l1i-cache'} = $line[1]/1024; + } + elsif ($line[0] eq 'hw.l2cachesize'){ + $cpu->{'l2-cache'} = $line[1]/1024; + } + elsif ($line[0] eq 'hw.l3cachesize'){ + $cpu->{'l3-cache'} = $line[1]/1024; + } + # hw.smt: openbsd + elsif ($line[0] eq 'hw.smt'){ + $cpu->{'smt'} = ($line[1]) ? 'enabled' : 'disabled'; + } + # htl: maybe freebsd, never seen, 1 is disabled, sigh... + elsif ($line[0] eq 'machdep.hlt_logical_cpus'){ + $cpu->{'smt'} = ($line[1]) ? 'disabled' : 'enabled'; + } + # this is in mghz in samples + elsif (!$cpu->{'cur-freq'} && + ($line[0] eq 'hw.clockrate' || $line[0] eq 'hw.cpuspeed')){ + $cpu->{'cur-freq'} = $line[1]; + } + # these are in hz: 2400000000 + elsif ($line[0] eq 'hw.cpufrequency'){ + $cpu->{'cur-freq'} = $line[1]/1000000; + } + elsif ($line[0] eq 'hw.busfrequency_min'){ + $cpu->{'min-freq'} = $line[1]/1000000; + } + elsif ($line[0] eq 'hw.busfrequency_max'){ + $cpu->{'max-freq'} = $line[1]/1000000; + } + # FB seems to call freq something other than clock speed, unreliable + # eg: 1500 Mhz real shows as 2400 freq, which is wrong + # elsif ($line[0] =~ /^dev\.cpu\.([0-9]+)\.freq$/){ + # $speed = clean_speed($line[1]); + # $cpu->{'processors'}->[$1] = $speed; + # } + # weird FB thing, freq can be wrong, so just count the cores and call it + # done. + elsif ($line[0] =~ /^dev\.cpu\.([0-9]+)\./ && + (!$cpu->{'processors'} || !defined $cpu->{'processors'}->[$1])){ + $cpu->{'processors'}->[$1] = undef; + } + elsif ($line[0] eq 'machdep.cpu.vendor'){ + $cpu->{'type'} = cpu_vendor($line[1]); + } + # darwin only? + elsif ($line[0] eq 'machdep.cpu.features'){ + $cpu->{'flags'} = lc($line[1]); + } + # is this per phys or total? + elsif ($line[0] eq 'hw.ncpu'){ + $cpu->{'cores'} = $line[1]; + } + # Freebsd does some voltage hacking to actually run at lowest listed + # frequencies. The cpu does not actually support all the speeds output + # here but works in freebsd. Disabled this, the freq appear to refer to + # something else, not cpu clock. Remove XXX to enable + elsif ($line[0] eq 'dev.cpu.0.freq_levelsXXX'){ + $line[1] =~ s/^\s+|\/[0-9]+|\s+$//g; + if ($line[1] =~ /[0-9]+\s+[0-9]+/){ + # get rid of -1 in FB: 2400/-1 2200/-1 2000/-1 1800/-1 + $line[1] =~ s|/-1||g; + my @temp = split(/\s+/, $line[1]); + $cpu->{'max-freq'} = $temp[0]; + $cpu->{'min-freq'} = $temp[-1]; + $cpu->{'scalings'} = \@temp; + } + } + # Disabled w/XXX. this is almost certainly bad data, should not be used + elsif (!$cpu->{'cur-freq'} && $line[0] eq 'dev.cpu.0.freqXXX'){ + $cpu->{'cur-freq'} = $line[1]; + } + # the following have only been seen in DragonflyBSD data but thumbs up! + elsif ($line[0] eq 'hw.cpu_topology.members'){ + my @temp = split(/\s+/, $line[1]); + my $count = scalar @temp; + $count-- if $count > 0; + # no way to get per processor speeds yet, so assign 0 to each + foreach (0 .. $count){ + $cpu->{'processors'}->[$_] = 0; + } + } + elsif ($line[0] eq 'hw.cpu_topology.cpu1.physical_siblings'){ + # string, like: cpu0 cpu1 + my @temp = split(/\s+/, $line[1]); + $cpu->{'siblings'} = scalar @temp; + } + # increment by 1 for every new physical id we see. These are in almost all + # cases separate cpus, not separate dies within a single cpu body. + # This needs DATA!! Almost certainly wrong!! + elsif ($line[0] eq 'hw.cpu_topology.cpu0.physical_id'){ + if ($phys_holder != $line[1]){ + $phys_id++; + $phys_holder = $line[1]; + push(@{$cpu->{'ids'}->[$phys_id][$die_id]},0); + } + } + elsif ($line[0] eq 'hw.cpu_topology.cpu0.core_id'){ + $cpu->{'ids'}->[$phys_id][$line[1]] = $speed; + } + } + if (!$cpu->{'flags'} || !$cpu->{'family'}){ + my $dmesg_boot = dboot_data(); + # this core count may fix failed MT detection. + $cpu->{'cores'} = $dmesg_boot->{'cores'} if $dmesg_boot->{'cores'}; + $cpu->{'flags'} = $dmesg_boot->{'flags'} if !$cpu->{'flags'}; + $cpu->{'family'} = $dmesg_boot->{'family'} if !$cpu->{'family'}; + $cpu->{'l1d-cache'} = $dmesg_boot->{'l1d-cache'} if !$cpu->{'l1d-cache'}; + $cpu->{'l1i-cache'} = $dmesg_boot->{'l1i-cache'} if !$cpu->{'l1i-cache'}; + $cpu->{'l2-cache'} = $dmesg_boot->{'l2-cache'} if !$cpu->{'l2-cache'}; + $cpu->{'l3-cache'} = $dmesg_boot->{'l3-cache'} if !$cpu->{'l3-cache'}; + $cpu->{'microcode'} = $dmesg_boot->{'microcode'} if !$cpu->{'microcode'}; + $cpu->{'model-id'} = $dmesg_boot->{'model-id'} if !$cpu->{'model-id'}; + $cpu->{'max-freq'} = $dmesg_boot->{'max-freq'} if !$cpu->{'max-freq'}; + $cpu->{'min-freq'} = $dmesg_boot->{'min-freq'} if !$cpu->{'min-freq'}; + $cpu->{'scalings'} = $dmesg_boot->{'scalings'} if !$cpu->{'scalings'}; + $cpu->{'siblings'} = $dmesg_boot->{'siblings'} if !$cpu->{'siblings'}; + $cpu->{'stepping'} = $dmesg_boot->{'stepping'} if !$cpu->{'stepping'}; + $cpu->{'type'} = $dmesg_boot->{'type'} if !$cpu->{'type'}; + } + main::log_data('dump','%$cpu',$cpu) if $b_log; + print Data::Dumper::Dumper $cpu if $dbg[8]; + eval $end if $b_log; + return $cpu; +} + +## DATA GENERATOR DATA SOURCES ## +sub dboot_data { + eval $start if $b_log; + my ($max_freq,$min_freq,@scalings); + my ($family,$flags,$microcode,$model,$sep,$stepping,$type) = ('','','','','','',''); + my ($cores,$siblings) = (0,0); + my ($l1d,$l1i,$l2,$l3) = (0,0,0,0); + # this will be null if it was not readable + my $file = $system_files{'dmesg-boot'}; + if ($dboot{'cpu'}){ + foreach (@{$dboot{'cpu'}}){ + # can be ~Features/Features2/AMD Features + if (/Features/ || ($bsd_type eq "openbsd" && + /^cpu0:\s*[a-z0-9]{2,3}(\s|,)[a-z0-9]{2,3}(\s|,)/i)){ + my @line = split(/:\s*/, lc($_)); + # free bsd has to have weird syntax: <....,> + # Features2=0x1e98220b + $line[1] =~ s/^[^<]*<|>[^>]*$//g; + # then get rid of stuff + $line[1] =~ s/<[^>]+>//g; + # handle corner case like ,EL3 32, + $line[1] =~ s/ (32|64)/_$1/g; + # and replace commas with spaces + $line[1] =~ s/,/ /g; + $flags .= $sep . $line[1]; + $sep = ' '; + } + # cpu0:AMD E1-1200 APU with Radeon(tm) HD Graphics, 1398.66 MHz, 14-02-00 + elsif (/^cpu0:\s*([^,]+),\s+([0-9\.]+\s*MHz),\s+([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)/){ + $type = cpu_vendor($1); + $family = uc($3); + $model = uc($4); + $stepping = uc($5); + $family =~ s/^0//; + $model =~ s/^0//; + $stepping =~ s/^0//; # can be 00 + } + # note: cpu cache is in KiB MiB even though they call it KB and MB + # cpu31: 32KB 64b/line 8-way I-cache, 32KB 64b/line 8-way D-cache, 512KB 64b/line 8-way L2 cache + # 8-way means 1 per core, 16-way means 1/2 per core + elsif (/^cpu0:\s*[0-9\.]+[KMG]B\s/){ + # cpu0: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache + # cpu0:48KB 64b/line 3-way L1 PIPT I-cache, 32KB 64b/line 2-way L1 D-cache + if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sD[\s-]?cache/){ + $l1d = main::translate_size($1); + } + if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\s(L1 \S+\s)?I[\s-]?cache/){ + $l1i = main::translate_size($1); + } + if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sL2[\s-]?cache/){ + $l2 = main::translate_size($1); + } + if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sL3[\s-]?cache/){ + $l3 = main::translate_size($1); + } + } + elsif (/^~Origin:(.+?)[\s,]+(Id|Family|Model|Stepping)/){ + $type = cpu_vendor($1); + if (/\bId\s*=\s*(0x)?([0-9a-f]+)\b/){ + $microcode = ($1) ? uc($2) : $2; + } + if (/\bFamily\s*=\s*(0x)?([a-f0-9]+)\b/){ + $family = ($1) ? uc($2) : $2; + } + if (/\bModel\s*=\s*(0x)?([a-f0-9]+)\b/){ + $model = ($1) ? uc($2) : $2; + } + # they don't seem to use hex for steppings, so convert it + if (/\bStepping\s*=\s*(0x)?([0-9a-f]+)\b/){ + $stepping = (!$1) ? uc(sprintf("%X",$2)) : $2; + } + } + elsif (/^cpu0:.*?[0-9\.]+\s?MHz:\sspeeds:\s(.*?)\s?MHz/){ + @scalings = split(/[,\s]+/,$1); + $min_freq = $scalings[-1]; + $max_freq = $scalings[0]; + } + # 2 core MT Intel Core/Rzyen similar, use smt 0 as trigger to count: + # cpu2:smt 0, core 1, package 0 + # cpu3:smt 1, core 1, package 0 + ## but: older AMD Athlon 2 core: + # cpu0:smt 0, core 0, package 0 + # cpu0:smt 0, core 0, package 1 + elsif (/cpu([0-9]+):smt\s([0-9]+),\score\s([0-9]+)(,\spackage\s([0-9]+))?/){ + $siblings = $1 + 1; + $cores += 1 if $2 == 0; + } + } + if ($flags){ + $flags =~ s/\s+/ /g; + $flags =~ s/^\s+|\s+$//g; + } + } + else { + if ($file && ! -r $file){ + $flags = main::message('dmesg-boot-permissions'); + } + } + my $values = { + 'cores' => $cores, + 'family' => $family, + 'flags' => $flags, + 'l1d-cache' => $l1d, + 'l1i-cache' => $l1i, + 'l2-cache' => $l2, + 'l3-cache' => $l3, + 'max-freq' => $max_freq, + 'microcode' => $microcode, + 'min-freq' => $min_freq, + 'model-id' => $model, + 'scalings' => \@scalings, + 'siblings' => $siblings, + 'stepping' => $stepping, + 'type' => $type, + }; + print Data::Dumper::Dumper $values if $dbg[27]; + eval $end if $b_log; + return $values; +} + +sub dmidecode_data { + eval $start if $b_log; + my $dmi_data = {'L1' => 0, 'L2' => 0,'L3' => 0, 'phys-cnt' => 0, + 'ext-clock' => undef, 'socket' => undef, 'speed' => undef, + 'max-speed' => undef, 'upgrade' => undef, 'volts' => undef}; + return $dmi_data if !@dmi; + my ($id,$amount,$socket,$upgrade); + foreach my $item (@dmi){ + next if ref $item ne 'ARRAY'; + next if ($item->[0] < 4 || $item->[0] == 5 || $item->[0] == 6); + last if $item->[0] > 7; + if ($item->[0] == 7){ + # skip first three rows, we don't need that data + # seen very bad data, L2 labeled L3, and random phantom type 7 caches + ($id,$amount) = ('',0); + # Configuration: Disabled, Not Socketed, Level 2 + next if $item->[4] =~ /^Configuration:.*Disabled/i; + # labels have to be right before the block, otherwise exiting sub errors + DMI: + foreach my $value (@$item[3 .. $#$item]){ + next if $value =~ /^~/; + # variants: L3 - Cache; L3 Cache; L3-cache; L2 CACHE; CPU Internal L1 + if ($value =~ /^Socket Designation:.*? (L[1-3])\b/){ + $id = lc($1); + } + # some cpus only show Socket Designation: Internal cache + elsif (!$id && $value =~ /^Configuration:.* Level.*?([1-3])\b/){ + if ($value !~ /Disabled/i){ + $id = "l$1"; + } + } + # NOTE: cache is in KiB or MiB but they call it kB or MB + # so we send translate_size k or M which trips KiB/MiB mode + # if disabled can be 0. + elsif ($id && $value =~ /^Installed Size:\s+(.*?[kKM])i?B$/){ + # Config..Disabled test should have gotten this, but just in case 0 size + next DMI if !$1; + $amount = main::translate_size($1); + } + if ($id && $amount){ + $dmi_data->{$id} = $amount; + last; + } + } + } + # note: for multi cpu systems, we're hoping that these values are + # the same for each cpu, which in most pc situations they will be, + # and most ARM etc won't be using dmi data here anyway. + # Older dmidecode appear to have unreliable Upgrade outputs + elsif ($item->[0] == 4){ + # skip first three row,s we don't need that data + ($socket,$upgrade) = (); + $dmi_data->{'phys-cnt'}++; # try to catch bsds without physical cpu count + foreach my $value (@$item[3 .. $#$item]){ + next if $value =~ /^~/; + # note: on single cpu systems, Socket Designation shows socket type, + # but on multi, shows like, CPU1; CPU Socket #2; Socket 0; so check values a bit. + # Socket Designation: Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz + # Sometimes shows as CPU Socket... + if ($value =~ /^Socket Designation:\s*(CPU\s*Socket|Socket)?[\s-]*(.*)$/i){ + $upgrade = main::clean_dmi($2) if $2 !~ /(cpu|[mg]hz|onboard|socket|@|^#?[0-9]$)/i; + # print "$socket_temp\n"; + } + # normally we prefer this value, but sometimes it's garbage + # older systems often show: Upgrade: ZIF Socket which is a generic term, legacy + elsif ($value =~ /^Upgrade:\s*(CPU\s*Socket|Socket)?[\s-]*(.*)$/i){ + # print "$2\n"; + $socket = main::clean_dmi($2) if $2 !~ /(ZIF|\bslot\b)/i; + } + # seen: Voltage: 5.0 V 2.9 V + elsif ($value =~ /^Voltage:\s*([0-9\.]+)\s*(V|Volts)?\b/i){ + $dmi_data->{'volts'} = main::clean_dmi($1); + } + elsif ($value =~ /^Current Speed:\s*([0-9\.]+)\s*([MGK]Hz)?\b/i){ + $dmi_data->{'speed'} = main::clean_dmi($1); + } + elsif ($value =~ /^Max Speed:\s*([0-9\.]+)\s*([MGK]Hz)?\b/i){ + $dmi_data->{'max-speed'} = main::clean_dmi($1); + } + elsif ($value =~ /^External Clock:\s*([0-9\.]+\s*[MGK]Hz)\b/){ + $dmi_data->{'ext-clock'} = main::clean_dmi($1); + } + } + } + } + # Seen older cases where Upgrade: Other value exists + if ($socket || $upgrade){ + if ($socket && $upgrade){ + undef $upgrade if $socket eq $upgrade; + } + elsif ($upgrade){ + $socket = $upgrade; + undef $upgrade; + } + $dmi_data->{'socket'} = $socket; + $dmi_data->{'upgrade'} = $upgrade; + } + main::log_data('dump','%$dmi_data',$dmi_data) if $b_log; + print Data::Dumper::Dumper $dmi_data if $dbg[27]; + eval $end if $b_log; + return $dmi_data; +} + +## CPU PROPERTIES MAIN ## +sub cpu_properties { + my ($cpu) = @_; + my ($cpu_sys,$arch_level); + my $dmi_data = {}; + my $tests = {}; + my $caches = { + 'cache' => 0, # general, non id'ed from cpuinfo generic cache + 'l1' => 0, + 'l1d' => 0, + 'l1i' => 0, + 'l2' => 0, + 'l3' => 0, + }; + my $counts = { + 'dies' => 0, + 'cpu-cores' => 0, + 'cores' => 0, + 'cores-multiplier' => 0, + 'physical' => 0, + 'processors' => 0, + }; + my ($cache_check) = (''); + if (!$bsd_type && -d '/sys/devices' && !$force{'cpuinfo'}){ + $cpu_sys = cpu_sys_data($cpu->{'sys-freq'}); + } + cp_test_types($cpu,$tests) if $cpu->{'type'}; + undef $cpu_sys if $dbg[42]; + ## START CPU DATA HANDLERS ## + if (defined $cpu_sys->{'cpus'}){ + cp_data_sys( + $cpu, + $cpu_sys, + $caches, + $counts + ); + } + if (!defined $cpu_sys->{'cpus'} || !$counts->{'physical'} || + !$counts->{'cpu-cores'}){ + cp_data_fallback( + $cpu, + $caches, + \$cache_check, + $counts, + $tests, + ); + } + # some arm cpus report each core as its own die, but that's wrong + if (%risc && $counts->{'dies'} > 1 && + $counts->{'cpu-cores'} == $counts->{'dies'}){ + $counts->{'dies'} = 1; + $cpu->{'dies'} = 1; + } + if ($type eq 'full' && ($extra > 1 || ($bsd_type && !$cpu->{'l2-cache'}))){ + cp_data_dmi( + $cpu, + $dmi_data, + $caches, + $counts, # only to set BSD phys cpu counts if not found + \$cache_check, + ); + } + ## END CPU DATA HANDLERS ## + + # print "pc: $counts{'processors'} s: $cpu->{'siblings'} cpuc: $counts{'cpu-cores'} corec: $counts{'cores'}\n"; + + ## START CACHE PROCESSING ## + # Get BSD and legacy linux caches if not already from dmidecode or cpu_sys. + if ($type eq 'full' && + !$caches->{'l1'} && !$caches->{'l2'} && !$caches->{'l2'}){ + cp_caches_fallback( + $counts, + $cpu, + $caches, + \$cache_check, + ); + } + # nothing to check! + if ($type eq 'full'){ + if (!$caches->{'l1'} && !$caches->{'l2'} && !$caches->{'l3'} && + !$caches->{'cache'}){ + $cache_check = ''; + } + if ($caches->{'cache'}){ + # we don't want any math done on this one, who knows what it is + $caches->{'cache'} = cp_cache_processor($caches->{'cache'},1); + } + if ($caches->{'l1'}){ + $caches->{'l1'} = cp_cache_processor($caches->{'l1'},$counts->{'physical'}); + } + if ($caches->{'l2'}){ + $caches->{'l2'} = cp_cache_processor($caches->{'l2'},$counts->{'physical'}); + } + if ($caches->{'l3'}){ + $caches->{'l3'} = cp_cache_processor($caches->{'l3'},$counts->{'physical'}); + } + } + ## END CACHE PROCESSING ## + + ## START TYPE/LAYOUT/ARCH/BUGS ## + my ($cpu_type) = (''); + $cpu_type = cp_cpu_type( + $counts, + $cpu, + $tests + ); + my $topology = {}; + cp_cpu_topology($counts,$topology); + my $arch = cp_cpu_arch( + $cpu->{'type'}, + $cpu->{'family'}, + $cpu->{'model-id'}, + $cpu->{'stepping'}, + $cpu->{'model_name'}, + ); + # arm cpuinfo case only; confirm on bsds, not sure all get family/ids + if ($arch->[0] && !$cpu->{'arch'}){ + ($cpu->{'arch'},$cpu->{'arch-note'},$cpu->{'process'},$cpu->{'gen'}, + $cpu->{'year'}) = @$arch; + } + # cpu_arch comes from set_os() + if (!$cpu->{'arch'} && $cpu_arch && %risc){ + $cpu->{'arch'} = $cpu_arch; + } + if ($b_admin && defined $cpu_sys->{'data'}{'vulnerabilities'}){ + $cpu->{'bugs-hash'} = $cpu_sys->{'data'}{'vulnerabilities'}; + } + ## END TYPE/LAYOUT/ARCH/BUGS ## + + ## START SPEED/BITS ## + my $speed_info = cp_speed_data($cpu,$cpu_sys); + # seen case where 64 bit cpu with lm flag shows as i686 (tinycore) + if (!%risc && $cpu->{'flags'} && (!$bits_sys || $bits_sys == 32)){ + $bits_sys = ($cpu->{'flags'} =~ /\blm\b/) ? 64 : 32; + } + # must run after to make sure we have cpu bits + if ($b_admin && !%risc && $bits_sys && $bits_sys == 64 && $cpu->{'flags'}){ + $arch_level = cp_cpu_level( + $cpu->{'flags'} + ); + } + ## END SPEED/BITS ## + + ## LOAD %cpu_properties + my $cpu_properties = { + 'arch-level' => $arch_level, + 'avg-speed-key' => $speed_info->{'avg-speed-key'}, + 'bits-sys' => $bits_sys, + 'cache' => $caches->{'cache'}, + 'cache-check' => $cache_check, + 'cpu-type' => $cpu_type, + 'dmi-max-speed' => $dmi_data->{'max-speed'}, + 'dmi-speed' => $dmi_data->{'speed'}, + 'ext-clock' => $dmi_data->{'ext-clock'}, + 'high-speed-key' => $speed_info->{'high-speed-key'}, + 'l1-cache' => $caches->{'l1'}, + 'l1d-desc' => $caches->{'l1d-desc'}, + 'l1i-desc' => $caches->{'l1i-desc'}, + 'l2-cache' => $caches->{'l2'}, + 'l2-desc' => $caches->{'l2-desc'}, + 'l3-cache' => $caches->{'l3'}, + 'l3-desc' => $caches->{'l3-desc'}, + 'min-max-key' => $speed_info->{'min-max-key'}, + 'min-max' => $speed_info->{'min-max'}, + 'socket' => $dmi_data->{'socket'}, + 'scaling-min-max-key' => $speed_info->{'scaling-min-max-key'}, + 'scaling-min-max' => $speed_info->{'scaling-min-max'}, + 'speed-key' => $speed_info->{'speed-key'}, + 'speed' => $speed_info->{'speed'}, + 'topology-full' => $topology->{'full'}, + 'topology-string' => $topology->{'string'}, + 'upgrade' => $dmi_data->{'upgrade'}, + 'volts' => $dmi_data->{'volts'}, + }; + if ($b_log){ + main::log_data('dump','%$cpu_properties',$cpu_properties); + main::log_data('dump','%$topology',$topology); + } + # print Data::Dumper::Dumper $cpu; + if ($dbg[38]){ + print Data::Dumper::Dumper $cpu_properties; + print Data::Dumper::Dumper $topology; + } + # my $dc = scalar @dies; + # print 'phys: ' . $pc . ' dies: ' . $dc, "\n"; + eval $end if $b_log; + return $cpu_properties; +} + +## CPU DATA ENGINES ## +# everything is passed by reference so no need to return anything +sub cp_data_dmi { + eval $start if $b_log; + my ($cpu,$dmi_data,$caches,$counts,$cache_check) = @_; + my $cpu_dmi = dmidecode_data(); + # fix for bsds that do not show physical cpus, like openbsd + if ($cpu_dmi->{'phys-cnt'} && $counts->{'physical'} == 1 && + $cpu_dmi->{'phys-cnt'} > 1){ + $counts->{'physical'} = $cpu_dmi->{'phys-cnt'}; + } + # We have to undef all the sys stuff to get back to the true dmidecode results + # Too many variants to treat one by one, just clear it out if forced. + undef $caches if $force{'dmidecode'}; + # We don't want to use dmi L1/L2/L3 at all for non BSD systems unless forced + # because have seen totally gibberish dmidecode data for caches. /sys cache + # data preferred, more granular and basically consistently right. + # Only run for linux if no cache data found, but BSD use to fill in missing + # (we don't care about legacy errors for BSD since the data isn't adequate). + # legacy dmidecode cache data used the per cache value, NOT the per CPU total + # value like it does today. Which makes it impossible to know for sure if the + # given value is right (new, or if cache matched cpu total) or inadequate. + if ((!$bsd_type && !$caches->{'l1'} && !$caches->{'l2'} && !$caches->{'l3'}) || + ($bsd_type && (!$caches->{'l1'} || !$caches->{'l2'} || !$caches->{'l3'}))){ + # Newer dmi: cache type total per phys cpu; Legacy: raw cache size only + if ($cpu_dmi->{'l1'} && !$caches->{'l1'}){ + $caches->{'l1'} = $cpu_dmi->{'l1'}; + $$cache_check = main::message('note-check'); + } + # note: bsds often won't have L2 catch data found yet, but bsd sysctl can + # have these values so let's check just in case. OpenBSD does have it often. + if ($cpu_dmi->{'l2'} && !$caches->{'l2'}){ + $caches->{'l2'} = $cpu_dmi->{'l2'}; + $$cache_check = main::message('note-check'); + } + if ($cpu_dmi->{'l3'} && !$caches->{'l3'}){ + $caches->{'l3'} = $cpu_dmi->{'l3'}; + $$cache_check = main::message('note-check'); + } + } + $dmi_data->{'max-speed'} = $cpu_dmi->{'max-speed'}; + $dmi_data->{'socket'} = $cpu_dmi->{'socket'} if $cpu_dmi->{'socket'}; + $dmi_data->{'upgrade'} = $cpu_dmi->{'upgrade'} if $cpu_dmi->{'upgrade'}; + $dmi_data->{'speed'} = $cpu_dmi->{'speed'} if $cpu_dmi->{'speed'}; + $dmi_data->{'ext-clock'} = $cpu_dmi->{'ext-clock'} if $cpu_dmi->{'ext-clock'}; + $dmi_data->{'volts'} = $cpu_dmi->{'volts'} if $cpu_dmi->{'volts'}; + eval $end if $b_log; +} + +sub cp_data_fallback { + eval $start if $b_log; + my ($cpu,$caches,$cache_check,$counts,$tests) = @_; + if (!$counts->{'physical'}){ + # handle case where cpu reports say, phys id 0, 2, 4, 6 + foreach (@{$cpu->{'ids'}}){ + $counts->{'physical'}++ if $_; + } + } + # count unique processors ## + # note, this fails for intel cpus at times + # print ref $cpu->{'processors'}, "\n"; + if (!$counts->{'processors'}){ + $counts->{'processors'} = scalar @{$cpu->{'processors'}}; + } + # print "p count:$counts->{'processors'}\n"; + # print Data::Dumper::Dumper $cpu->{'processors'}; + # $counts->{'cpu-cores'} is per physical cpu + # note: elbrus supports turning off cores, so we need to add one for cases + # where rounds to 0 or 1 less + # print "$cpu{'type'},$cpu{'family'},$cpu{'model-id'},$cpu{'arch'}\n"; + if ($tests->{'elbrus'} && $counts->{'processors'}){ + my $elbrus = cp_elbrus_data($cpu->{'family'},$cpu->{'model-id'}, + $counts->{'processors'},$cpu->{'arch'}); + $counts->{'cpu-cores'} = $elbrus->[0]; + $counts->{'physical'} = $elbrus->[1]; + $cpu->{'arch'} = $elbrus->[2]; + # print 'model id: ' . $cpu->{'model-id'} . ' arch: ' . $cpu->{'arch'} . " cpc: $counts->{'cpu-cores'} phyc: $counts->{'physical'} proc: $counts->{'processors'} \n"; + } + $counts->{'physical'} ||= 1; # assume 1 if no id found, as with ARM + foreach my $die_ref (@{$cpu->{'ids'}}){ + next if ref $die_ref ne 'ARRAY'; + $counts->{'cores'} = 0; + $counts->{'dies'} = scalar @$die_ref; + #$cpu->{'dies'} = $counts->{'dies'}; + foreach my $core_ref (@$die_ref){ + next if ref $core_ref ne 'ARRAY'; + $counts->{'cores'} = 0;# reset for each die!! + # NOTE: the counters can be undefined because the index comes from + # core id: which can be 0 skip 1 then 2, which leaves index 1 undefined + # risc cpus do not actually show core id so ignore that counter + foreach my $id (@$core_ref){ + $counts->{'cores'}++ if defined $id && !%risc; + } + # print 'cores: ' . $counts->{'cores'}, "\n"; + } + } + # this covers potentially cases where ARM cpus have > 1 die + # maybe applies to all risc, not sure, but dies is broken anyway for cpuinfo + if (!$cpu->{'dies'}){ + if ($risc{'arm'} && $counts->{'dies'} <= 1 && $cpu->{'dies'} > 1){ + $counts->{'dies'} = $cpu->{'dies'}; + } + else { + $cpu->{'dies'} = $counts->{'dies'}; + } + } + # this is an attempt to fix the amd family 15 bug with reported cores vs actual cores + # NOTE: amd A6-4400M APU 2 core reports: cores: 1 siblings: 2 + # NOTE: AMD A10-5800K APU 4 core reports: cores: 2 siblings: 4 + if (!$counts->{'cpu-cores'}){ + if ($cpu->{'cores'} && !$counts->{'cores'} || + $cpu->{'cores'} >= $counts->{'cores'}){ + $counts->{'cpu-cores'} = $cpu->{'cores'}; + } + elsif ($counts->{'cores'} > $cpu->{'cores'}){ + $counts->{'cpu-cores'} = $counts->{'cores'}; + } + } + # print "cpu-c:$counts->{'cpu-cores'}\n"; + # $counts->{'cpu-cores'} = $cpu->{'cores'}; + # like, intel core duo + # NOTE: sadly, not all core intel are HT/MT, oh well... + # xeon may show wrong core / physical id count, if it does, fix it. A xeon + # may show a repeated core id : 0 which gives a fake num_of_cores=1 + if ($tests->{'intel'}){ + if ($cpu->{'siblings'} && $cpu->{'siblings'} > 1 && + $cpu->{'cores'} && $cpu->{'cores'} > 1){ + if ($cpu->{'siblings'}/$cpu->{'cores'} == 1){ + $tests->{'intel'} = 0; + $tests->{'ht'} = 0; + } + else { + $counts->{'cpu-cores'} = ($cpu->{'siblings'}/2); + $tests->{'ht'} = 1; + } + } + } + # ryzen is made out of blocks of 2, 4, or 8 core dies... + if ($tests->{'ryzen'}){ + $counts->{'cpu-cores'} = $cpu->{'cores'}; + # note: posix ceil isn't present in Perl for some reason, deprecated? + my $working = $counts->{'cpu-cores'} / 8; + my @temp = split('\.', $working); + $cpu->{'dies'} = ($temp[1] && $temp[1] > 0) ? $temp[0]++ : $temp[0]; + $counts->{'dies'} = $cpu->{'dies'}; + } + # these always have 4 dies + elsif ($tests->{'epyc'}){ + $counts->{'cpu-cores'} = $cpu->{'cores'}; + $counts->{'dies'} = $cpu->{'dies'} = 4; + } + # final check, override the num of cores value if it clearly is wrong + # and use the raw core count and synthesize the total instead of real count + if ($counts->{'cpu-cores'} == 0 && + $cpu->{'cores'} * $counts->{'physical'} > 1){ + $counts->{'cpu-cores'} = ($cpu->{'cores'} * $counts->{'physical'}); + } + # last check, seeing some intel cpus and vms with intel cpus that do not show any + # core id data at all, or siblings. + if ($counts->{'cpu-cores'} == 0 && $counts->{'processors'} > 0){ + $counts->{'cpu-cores'} = $counts->{'processors'}; + } + # this happens with BSDs which have very little cpu data available + if ($counts->{'processors'} == 0 && $counts->{'cpu-cores'} > 0){ + $counts->{'processors'} = $counts->{'cpu-cores'}; + if ($bsd_type && ($tests->{'ht'} || $tests->{'amd-zen'}) && + $counts->{'cpu-cores'} > 2){ + $counts->{'cpu-cores'} = $counts->{'cpu-cores'}/2;; + } + my $count = $counts->{'processors'}; + $count-- if $count > 0; + $cpu->{'processors'}[$count] = 0; + # no way to get per processor speeds yet, so assign 0 to each + # must be a numeric value. Could use raw speed from core 0, but + # that would just be a hack. + foreach (0 .. $count){ + $cpu->{'processors'}[$_] = 0; + } + } + # so far only OpenBSD has a way to detect MT cpus, but Openbsd has disabled MT + if ($bsd_type){ + if ($cpu->{'siblings'} && + $counts->{'cpu-cores'} && $counts->{'cpu-cores'} > 1){ + $counts->{'cores-multiplier'} = $counts->{'cpu-cores'}; + } + # if no siblings we couldn't get MT status of cpu so can't trust cache + else { + $$cache_check = main::message('note-check'); + } + } + # only elbrus shows L1 / L3 cache data in cpuinfo, cpu_sys data should show + # for newer full linux. + elsif ($counts->{'cpu-cores'} && + ($tests->{'elbrus'} || $counts->{'cpu-cores'} > 1)) { + $counts->{'cores-multiplier'} = $counts->{'cpu-cores'}; + } + # last test to catch some corner cases + # seen a case where a xeon vm in a dual xeon system actually had 2 cores, no MT + # so it reported 4 siblings, 2 cores, but actually only had 1 core per virtual cpu + # print "prc: $counts->{'processors'} phc: $counts->{'physical'} coc: $counts->{'cores'} cpc: $counts->{'cpu-cores'}\n"; + # this test was for arm but I think it applies to all risc, but risc will be sys + if (!%risc && + $counts->{'processors'} == $counts->{'physical'} * $counts->{'cores'} && + $counts->{'cpu-cores'} > $counts->{'cores'}){ + $tests->{'ht'} = 0; + # $tests->{'xeon'} = 0; + $tests->{'intel'} = 0; + $counts->{'cpu-cores'} = 1; + $counts->{'cores'} = 1; + $cpu->{'siblings'} = 1; + } + eval $end if $b_log; +} + +# all values passed by reference so no need for returns +sub cp_data_sys { + eval $start if $b_log; + my ($cpu,$cpu_sys,$caches,$counts) = @_; + my (@keys) = (sort keys %{$cpu_sys->{'cpus'}}); + return if !@keys; + $counts->{'physical'} = scalar @keys; + if ($type eq 'full' && $cpu_sys->{'cpus'}{$keys[0]}{'caches'}){ + cp_sys_caches($cpu_sys->{'cpus'}{$keys[0]}{'caches'},$caches,'l1','l1d'); + cp_sys_caches($cpu_sys->{'cpus'}{$keys[0]}{'caches'},$caches,'l1','l1i'); + cp_sys_caches($cpu_sys->{'cpus'}{$keys[0]}{'caches'},$caches,'l2',''); + cp_sys_caches($cpu_sys->{'cpus'}{$keys[0]}{'caches'},$caches,'l3',''); + } + if ($cpu_sys->{'data'}{'speeds'}{'all'}){ + $counts->{'processors'} = scalar @{$cpu_sys->{'data'}{'speeds'}{'all'}}; + } + if (defined $cpu_sys->{'data'}{'smt-active'}){ + if ($cpu_sys->{'data'}{'smt-active'}){ + $cpu->{'smt'} = 'enabled'; + } + # values: on/off/notsupported/notimplemented + elsif (defined $cpu_sys->{'data'}{'smt-control'} && + $cpu_sys->{'data'}{'smt-control'} =~ /^not/){ + $cpu->{'smt'} = main::message('unsupported'); + } + else { + $cpu->{'smt'} = 'disabled'; + } + } + my $i = 0; + my (@governor,@max,@min,@phys_cores); + foreach my $phys_id (@keys){ + if ($cpu_sys->{'cpus'}{$phys_id}{'cores'}){ + my ($mt,$st) = (0,0); + my (@core_keys) = keys %{$cpu_sys->{'cpus'}{$phys_id}{'cores'}}; + $cpu->{'cores'} = $counts->{'cpu-cores'} = scalar @core_keys; + $counts->{'cpu-topo'}[$i]{'cores'} = $cpu->{'cores'}; + if ($cpu_sys->{'cpus'}{$phys_id}{'dies'}){ + $counts->{'cpu-topo'}[$i]{'dies'} = scalar @{$cpu_sys->{'cpus'}{$phys_id}{'dies'}}; + $cpu->{'dies'} = $counts->{'cpu-topo'}[$i]{'dies'}; + } + # If we ever get > 1 min/max speed per phy cpu, we'll need to fix the [0] + if ($cpu_sys->{'cpus'}{$phys_id}{'max-freq'}[0]){ + if (!grep {$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}[0] eq $_} @max){ + push(@max,$cpu_sys->{'cpus'}{$phys_id}{'max-freq'}[0]); + } + $counts->{'cpu-topo'}[$i]{'max'} = $cpu_sys->{'cpus'}{$phys_id}{'max-freq'}[0]; + } + if ($cpu_sys->{'cpus'}{$phys_id}{'min-freq'}[0]){ + if (!grep {$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}[0] eq $_} @min){ + push(@min,$cpu_sys->{'cpus'}{$phys_id}{'min-freq'}[0]); + } + $counts->{'cpu-topo'}[$i]{'min'} = $cpu_sys->{'cpus'}{$phys_id}{'min-freq'}[0]; + } + # cheating, this is not a count, but we need the data for topology, must + # sort since governors can be in different order if > 1 + if ($cpu_sys->{'cpus'}{$phys_id}{'governor'}){ + foreach my $gov (@{$cpu_sys->{'cpus'}{$phys_id}{'governor'}}){ + push(@governor,$gov) if !grep {$_ eq $gov} @governor; + } + $cpu->{'governor'} = join(',',@governor); + } + if ($cpu_sys->{'cpus'}{$phys_id}{'scaling-driver'}){ + $cpu->{'scaling-driver'} = $cpu_sys->{'cpus'}{$phys_id}{'scaling-driver'}; + } + if ($cpu_sys->{'cpus'}{$phys_id}{'scaling-driver'}){ + $cpu->{'scaling-driver'} = $cpu_sys->{'cpus'}{$phys_id}{'scaling-driver'}; + } + if ($cpu_sys->{'cpus'}{$phys_id}{'scaling-max-freq'}){ + $cpu->{'scaling-max-freq'} = $cpu_sys->{'cpus'}{$phys_id}{'scaling-max-freq'}; + } + if ($cpu_sys->{'cpus'}{$phys_id}{'scaling-min-freq'}){ + $cpu->{'scaling-min-freq'} = $cpu_sys->{'cpus'}{$phys_id}{'scaling-min-freq'}; + } + if (!grep {$counts->{'cpu-cores'} eq $_} @phys_cores){ + push(@phys_cores,$counts->{'cpu-cores'}); + } + if ($counts->{'processors'}){ + if ($counts->{'processors'} > $counts->{'cpu-cores'}){ + for my $key (@core_keys){ + if ((my $threads = scalar @{$cpu_sys->{'cpus'}{$phys_id}{'cores'}{$key}}) > 1){ + $counts->{'cpu-topo'}[$i]{'cores-mt'}++; + $counts->{'cpu-topo'}[$i]{'threads'} += $threads; + # note: for mt+st type cpus, we need to handle tpc on output per type + $counts->{'cpu-topo'}[$i]{'tpc'} = $threads; + $counts->{'struct-mt'} = 1; + } + else { + $counts->{'cpu-topo'}[$i]{'cores-st'}++; + $counts->{'cpu-topo'}[$i]{'threads'}++; + $counts->{'struct-st'} = 1; + } + } + } + } + $i++; + } + } + $counts->{'struct-max'} = 1 if scalar @max > 1; + $counts->{'struct-min'} = 1 if scalar @min > 1; + $counts->{'struct-cores'} = 1 if scalar @phys_cores > 1; + if ($b_log){ + main::log_data('dump','%cpu_properties',$caches); + main::log_data('dump','%cpu_properties',$counts); + } + # print Data::Dumper::Dumper $caches; + # print Data::Dumper::Dumper $counts; + eval $end if $b_log; +} + +sub cp_sys_caches { + eval $start if $b_log; + my ($sys_caches,$caches,$id,$id_di) = @_; + my $cache_id = ($id_di) ? $id_di: $id; + my %cache_desc; + if ($sys_caches->{$cache_id}){ + # print Data::Dumper::Dumper $cpu_sys->{'cpus'}; + foreach (@{$sys_caches->{$cache_id}}){ + # android seen to have cache data without size item + next if !defined $_; + $caches->{$cache_id} += $_; + $cache_desc{$_}++ if $b_admin; + } + $caches->{$id} += $caches->{$id_di} if $id_di; + $caches->{$cache_id . '-desc'} = cp_cache_desc(\%cache_desc) if $b_admin; + } + eval $end if $b_log; +} + +## CPU PROPERTIES TOOLS ## +sub cp_cache_desc { + my ($cache_desc) = @_; + my ($desc,$sep) = ('',''); + foreach (sort keys %{$cache_desc}){ + $desc .= $sep . $cache_desc->{$_} . 'x' . main::get_size($_,'string'); + $sep = ', '; + } + undef $cache_desc; + return $desc; +} + +# args: 0: $caches passed by reference +sub cp_cache_processor { + my ($cache,$count) = @_; + my $output; + if ($count > 1){ + $output = $count . 'x ' . main::get_size($cache,'string'); + $output .= ' (' . main::get_size($cache * $count,'string') . ')'; + } + else { + $output = main::get_size($cache,'string'); + } + # print "$cache :: $count :: $output\n"; + return $output; +} + +sub cp_caches_fallback { + eval $start if $b_log; + my ($counts,$cpu,$caches,$cache_check) = @_; + # L1 Cache + if ($cpu->{'l1-cache'}){ + $caches->{'l1'} = $cpu->{'l1-cache'} * $counts->{'cores-multiplier'}; + } + else { + if ($cpu->{'l1d-cache'}){ + $caches->{'l1d-desc'} = $counts->{'cores-multiplier'} . 'x'; + $caches->{'l1d-desc'} .= main::get_size($cpu->{'l1d-cache'},'string'); + $caches->{'l1'} += $cpu->{'l1d-cache'} * $counts->{'cores-multiplier'}; + } + if ($cpu->{'l1i-cache'}){ + $caches->{'l1i-desc'} = $counts->{'cores-multiplier'} . 'x'; + $caches->{'l1i-desc'} .= main::get_size($cpu->{'l1i-cache'},'string'); + $caches->{'l1'} += $cpu->{'l1i-cache'} * $counts->{'cores-multiplier'}; + } + } + # L2 Cache + # If summed by dmidecode or from cpu_sys don't use this + if ($cpu->{'l2-cache'}){ + # the only possible change for bsds is if dmidecode method gives phy counts + # Looks like Intel on bsd shows L2 per core, not total. Note: Pentium N3540 + # uses 2(not 4)xL2 cache size for 4 cores, sigh... you just can't win... + if ($bsd_type){ + $caches->{'l2'} = $cpu->{'l2-cache'} * $counts->{'cores-multiplier'}; + } + # AMD SOS chips appear to report full L2 cache per cpu + elsif ($cpu->{'type'} eq 'amd' && ($cpu->{'family'} eq '14' || + $cpu->{'family'} eq '15' || $cpu->{'family'} eq '16')){ + $caches->{'l2'} = $cpu->{'l2-cache'}; + } + elsif ($cpu->{'type'} ne 'intel'){ + $caches->{'l2'} = $cpu->{'l2-cache'} * $counts->{'cpu-cores'}; + } + # note: this handles how intel reports L2, total instead of per core like + # AMD does when cpuinfo sourced, when caches sourced, is per core as expected + else { + $caches->{'l2'} = $cpu->{'l2-cache'}; + } + } + # l3 Cache - usually per physical cpu, but some rzyen will have per ccx. + if ($cpu->{'l3-cache'}){ + $caches->{'l3'} = $cpu->{'l3-cache'}; + } + # don't do anything with it, we have no ideaw if it's L1, L2, or L3, generic + # cpuinfo fallback, it's junk data essentially, and will show as cache: + # only use this fallback if no cache data was found + if ($cpu->{'cache'} && !$caches->{'l1'} && !$caches->{'l2'} && + !$caches->{'l3'}){ + $caches->{'cache'} = $cpu->{'cache'}; + $$cache_check = main::message('note-check'); + } + eval $end if $b_log; +} + +## START CPU ARCH ## +sub cp_cpu_arch { + eval $start if $b_log; + my ($type,$family,$model,$stepping,$name) = @_; + # we can get various random strings for rev/stepping, particularly for arm,ppc + # but we want stepping to be integer for math comparisons, so convert, or set + # to 0 so it won't break anything. + if (defined $stepping && $stepping =~ /^[A-F0-9]{1,3}$/i){ + $stepping = hex($stepping); + } + else { + $stepping = 0 + } + $family ||= ''; + $model = '' if !defined $model; # model can be 0 + my ($arch,$gen,$note,$process,$year); + my $check = main::message('note-check'); + # See: docs/inxi-cpu.txt + # print "type:$type fam:$family model:$model step:$stepping\n"; + # Note: AMD family is not Ext fam . fam but rather Ext-fam + fam. + # But model is Ext model . model... + if ($type eq 'amd'){ + if ($family eq '3'){ + $arch = 'Am386'; + $process = 'AMD 900-1500nm'; + $year = '1991-92'; + } + elsif ($family eq '4'){ + if ($model =~ /^(3|7|8|9|A)$/){ + $arch = 'Am486'; + $process = 'AMD 350-700nm'; + $year = '1993-95';} + elsif ($model =~ /^(E|F)$/){ + $arch = 'Am5x86'; + $process = 'AMD 350nm'; + $year = '1995-99';} + } + elsif ($family eq '5'){ + ## verified + if ($model =~ /^(0|1|2|3)$/){ + $arch = 'K5'; + $process = 'AMD 350nm'; + $year = '1996-97';} + elsif ($model =~ /^(6)$/){ + $arch = 'K6'; + $process = 'AMD 350nm'; + $year = '1997-98';} + elsif ($model =~ /^(7)$/){ + $arch = 'K6'; + $process = 'AMD 250nm'; + $year = '1997-98';} + elsif ($model =~ /^(8)$/){ + $arch = 'K6-2'; + $process = 'AMD 250nm'; + $year = '1998-2003';} + elsif ($model =~ /^(9)$/){ + $arch = 'K6-3'; + $process = 'AMD 250nm'; + $year = '1999-2003';} + elsif ($model =~ /^(D)$/){ + $arch = 'K6-3'; + $process = 'AMD 180nm'; + $year = '1999-2003';} + ## unverified + elsif ($model =~ /^(A)$/){ + $arch = 'K6 Geode'; + $process = 'AMD 150-350nm'; + $year = '1999';} # dates uncertain, 1999 start + ## fallback + else { + $arch = 'K6'; + $process = 'AMD 250-350nm'; + $year = '1999-2003';} + } + elsif ($family eq '6'){ + ## verified + if ($model =~ /^(1)$/){ + $arch = 'K7'; # 1:2:argon + $process = 'AMD 250nm'; + $year = '1999-2001';} + elsif ($model =~ /^(2|3|4|6)$/){ + # 3:0:duron;3:1:spitfire;4:2,4:thunderbird; 6:2:Palomino, duron; 2:1:Pluto + $arch = 'K7'; + $process = 'AMD 180nm'; + $year = '2000-01';} + elsif ($model =~ /^(7|8|A)$/){ + $arch = 'K7'; # 7:0,1:Morgan;8:1:thoroughbred,duron-applebred; A:0:barton + $process = 'AMD 130nm'; + $year = '2002-04';} + ## fallback + else { + $arch = 'K7'; + $process = 'AMD 130-180nm'; + $year = '2003-14';} + } + # note: family F K8 needs granular breakdowns, was a long lived family + elsif ($family eq 'F'){ + ## verified + # check: B|E|F + if ($model =~ /^(4|5|7|8|B|C|E|F)$/){ + # 4:0:clawhammer;5:8:sledgehammer;8:2,4:8:dubin;7:A;C:0:NewCastle; + $arch = 'K8'; + $process = 'AMD 130nm'; + $year = '2004-05';} + # check: 14|17|18|1B|25|48|4B|5D + elsif ($model =~ /^(14|15|17|18|1B|1C|1F|21|23|24|25|27|28|2C|2F|37|3F|41|43|48|4B|4C|4F|5D|5F|C1)$/){ + # 1C:0,2C:2:Palermo;21:0,2,23:2:denmark;1F:0:winchester;2F:2:Venice; + # 27:1,37:2:san diego;28:1,3F:2:Manchester;23:2:Toledo;$F:2,5F:2,3:Orleans; + # 5F:2:Manila?;37:2;C1:3:windsor fx;43:2,3:santa ana;41:2:santa rosa; + # 4C:2:Keene;2C:2:roma;24:2:newark + $arch = 'K8'; + $process = 'AMD 90nm'; + $year = '2004-06';} + elsif ($model =~ /^(68|6B|6C|6F|7C|7F)$/){ + $arch = 'K8'; # 7F:1,2:Lima; 68:1,6B:1,2:Brisbane;6F:2:conesus;7C:2:sherman + $process = 'AMD 65nm'; + $year = '2005-08';} + ## fallback + else { + $arch = 'K8'; + $process = 'AMD 65-130nm'; + $year = '2004-2008';} + } + # K9 was planned but skipped + elsif ($family eq '10'){ # 1F + ## verified + if ($model =~ /^(2)$/){ + $arch = 'K10'; # 2:2:budapest;2:1,3:barcelona + $process = 'AMD 65nm'; + $year = '2007-08';} + elsif ($model =~ /^(4|5|6|8|9|A)$/){ + # 4:2:Suzuka;5:2,3:propus;6:2:Regor;8:0:Istanbul;9:1:maranello + $arch = 'K10'; + $process = 'AMD 45nm'; + $year = '2009-13';} + ## fallback + else { + $arch = 'K10'; + $process = 'AMD 45-65nm'; + $year = '2007-13';} + } + # very loose, all stepping 1: covers athlon x2, sempron, turion x2 + # years unclear, could be 2005 start, or 2008 + elsif ($family eq '11'){ # 2F + if ($model =~ /^(3)$/){ + $arch = 'K11 Turion X2'; # mix of K8/K10 + $note = $check; + $process = 'AMD 65-90nm'; + $year = ''; } + } + # might also need cache handling like 14/16 + elsif ($family eq '12'){ # 3F + if ($model =~ /^(1)$/){ + $arch = 'K12 Fusion'; # K10 based apu, llano + $process = 'GF 32nm'; + $year = '2011';} # check years + else { + $arch = 'K12 Fusion'; + $process = 'GF 32nm'; + $year = '2011';} # check years + } + # SOC, apu + elsif ($family eq '14'){ # 5F + if ($model =~ /^(1|2)$/){ + $arch = 'Bobcat'; + $process = 'GF 40nm'; + $year = '2011-13';} + else { + $arch = 'Bobcat'; + $process = 'GF 40nm'; + $year = '2011-13';} + } + elsif ($family eq '15'){ # 6F + # note: only model 1 confirmd + if ($model =~ /^(0|1|3|4|5|6|7|8|9|A|B|C|D|E|F)$/){ + $arch = 'Bulldozer'; + $process = 'GF 32nm'; + $year = '2011';} + # note: only 2,10,13 confirmed + elsif ($model =~ /^(2|10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F)$/){ + $arch = 'Piledriver'; + $process = 'GF 32nm'; + $year = '2012-13';} + # note: only 30,38 confirmed + elsif ($model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/){ + $arch = 'Steamroller'; + $process = 'GF 28nm'; + $year = '2014';} + # note; only 60,65,70 confirmed + elsif ($model =~ /^(60|61|62|63|64|65|66|67|68|69|6A|6B|6C|6D|6E|6F|70|71|72|73|74|75|76|77|78|79|7A|7B|7C|7D|7E|7F)$/){ + $arch = 'Excavator'; + $process = 'GF 28nm'; + $year = '2015';} + else { + $arch = 'Bulldozer'; + $process = 'GF 32nm'; + $year = '2011-12';} + } + # SOC, apu + elsif ($family eq '16'){ # 7F + if ($model =~ /^(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)$/){ + $arch = 'Jaguar'; + $process = 'GF 28nm'; + $year = '2013-14';} + elsif ($model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/){ + $arch = 'Puma'; + $process = 'GF 28nm'; + $year = '2014-15';} + else { + $arch = 'Jaguar'; + $process = 'GF 28nm'; + $year = '2013-14';} + } + elsif ($family eq '17'){ # 8F + # can't find stepping/model for no ht 2x2 core/die models, only first ones + if ($model =~ /^(1|11|20)$/){ + $arch = 'Zen'; + $process = 'GF 14nm'; + $year = '2017-19';} + # Seen: stepping 1 is Zen+ Ryzen 7 3750H. But stepping 1 Zen is: Ryzen 3 3200U + # AMD Ryzen 3 3200G is stepping 1, Zen+ + # Unknown if stepping 0 is Zen or either. + elsif ($model =~ /^(18)$/){ + $arch = 'Zen/Zen+'; + $gen = '1'; + $process = 'GF 12nm'; + $note = $check; + $year = '2019';} + # shares model 8 with zen, stepping unknown + elsif ($model =~ /^(8)$/){ + $arch = 'Zen+'; + $gen = '2'; + $process = 'GF 12nm'; + $year = '2018-21';} + # used this but it didn't age well: ^(2[0123456789ABCDEF]| + elsif ($model =~ /^(3.|4.|5.|6.|7.|8.|9.|A.)$/){ + $arch = 'Zen 2'; + $gen = '3'; + $process = 'TSMC n7 (7nm)'; # some consumer maybe GF 14nm + $year = '2020-22';} + else { + $arch = 'Zen'; + $note = $check; + $process = '7-14nm'; + $year = '';} + } + # Joint venture between AMD and Chinese companies. Type amd? or hygon? + elsif ($family eq '18'){ # 9F + # model 0, zen 1 + $arch = 'Zen (Hygon Dhyana)'; + $gen = '1'; + $process = 'GF 14nm'; + $year = '';} + elsif ($family eq '19'){ # AF + # zen 4 raphael, phoenix 1 use n5 I believe + # Epyc Bergamo zen4c 4nm, only few full model IDs, update when appear + # zen4c is for cloud hyperscale + if ($model =~ /^(78)$/){ + $arch = 'Zen 4c'; + $gen = '5'; + $process = 'TSMC n4 (4nm)'; + $year = '2023+';} + # ext model 6,7, base models trickling in + # 10 engineering sample + elsif ($model =~ /^(1.|6.|7.|A.)$/){ + $arch = 'Zen 4'; + $gen = '5'; + $process = 'TSMC n5 (5nm)'; + $year = '2022+';} + # double check 40, 44; 21 confirmed + elsif ($model =~ /^(21|4.)$/){ + $arch = 'Zen 3+'; + $gen = '4'; + $process = 'TSMC n6 (7nm)'; + $year = '2022';} + # 21, 50: step 0; known: 21, 3x, 50 + elsif ($model =~ /^(0|1|8|2.|3.|5.)$/){ + $arch = 'Zen 3'; + $gen = '4'; + $process = 'TSMC n7 (7nm)'; + $year = '2021-22';} + else { + $arch = 'Zen 3/4'; + $note = $check; + $process = 'TSMC n5 (5nm)'; + $year = '2021-22';} + } + # Zen 5: TSMC n3/n4, epyc turin / granite ridge? / turin dense zen 5c 3nm + elsif ($family eq '20'){ # BF + if ($model =~ /^(0)$/){ + $arch = 'Zen 5'; + $gen = '5'; + $process = 'TSMC n3 (3nm)'; # turin could be 4nm, need more data + $year = '2023+';} + elsif ($model =~ /^(20|40)$/){ + $arch = 'Zen 5'; + $gen = '5'; + $process = 'TSMC n3 (3nm)'; # desktop, granite ridge, confirm 2024 + $year = '2024+';} + else { + $arch = 'Zen 5'; + $note = $check; + $process = 'TSMC n3/n4 (3,4nm)'; + $year = '2024+';} + } + # Roadmap: check to verify, AMD is usually closer to target than Intel + # Epyc 4 genoa: zen 4, nm, 2022+ (dec 2022), cxl-1.1,pcie-5, ddr-5 + } + # we have no advanced data for ARM cpus, this is an area that could be improved? + elsif ($type eq 'arm'){ + if ($family ne ''){ + $arch="ARMv$family";} + else { + $arch='ARM';} + } + # elsif ($type eq 'ppc'){ + # $arch='PPC'; + # } + # aka VIA + elsif ($type eq 'centaur'){ + if ($family eq '5'){ + if ($model =~ /^(4)$/){ + $arch = 'WinChip C6'; + $process = '250nm'; + $year = '';} + elsif ($model =~ /^(8)$/){ + $arch = 'WinChip 2'; + $process = '250nm'; + $year = '';} + elsif ($model =~ /^(9)$/){ + $arch = 'WinChip 3'; + $process = '250nm'; + $year = '';} + } + elsif ($family eq '6'){ + if ($model =~ /^(6)$/){ + $arch = 'Via Cyrix III (WinChip 5)'; + $process = '150nm'; # guess + $year = '';} + elsif ($model =~ /^(7|8)$/){ + $arch = 'Via C3'; + $process = '150nm'; + $year = '';} + elsif ($model =~ /^(9)$/){ + $arch = 'Via C3-2'; + $process = '130nm'; + $year = '';} + elsif ($model =~ /^(A|D)$/){ + $arch = 'Via C7'; + $process = '90nm'; + $year = '';} + elsif ($model =~ /^(F)$/){ + if ($stepping <= 1){ + $arch = 'Via CN Nano (Isaah)';} + elsif ($stepping <= 2){ + $arch = 'Via Nano (Isaah)';} + elsif ($stepping <= 10){ + $arch = 'Via Nano (Isaah)';} + elsif ($stepping <= 12){ + $arch = 'Via Isaah';} + elsif ($stepping <= 13){ + $arch = 'Via Eden';} + elsif ($stepping <= 14){ + $arch = 'Zhaoxin ZX';} + $process = '90nm'; # guess + $year = '';} + } + elsif ($family eq '7'){ + if ($model =~ /^(1.|3.)$/){ + $arch = 'Zhaoxin ZX'; + $process = '90nm'; # guess + $year = ''; + } + } + } + # note, to test uncoment $cpu{'type'} = Elbrus in proc/cpuinfo logic + # ExpLicit Basic Resources Utilization Scheduling + elsif ($type eq 'elbrus'){ + # E8CB + if ($family eq '4'){ + if ($model eq '1'){ + $arch = 'Elbrus 2000 (gen-1)'; + $process = 'Mikron 130nm'; + $year = '2005';} + elsif ($model eq '2'){ + $arch = 'Elbrus-S (gen-2)'; + $process = 'Mikron 90nm'; + $year = '2010';} + elsif ($model eq '3'){ + $arch = 'Elbrus-4C (gen-3)'; + $process = 'TSMC 65nm'; + $year = '2014';} + elsif ($model eq '4'){ + $arch = 'Elbrus-2C+ (gen-2)'; + $process = 'Mikron 90nm'; + $year = '2011';} + elsif ($model eq '6'){ + $arch = 'Elbrus-2CM (gen-2)'; + $note = $check; + $process = 'Mikron 90nm'; + $year = '2011 (?)';} + elsif ($model eq '7'){ + if ($stepping >= 2){ + $arch = 'Elbrus-8C1 (gen-4)'; + $process = 'TSMC 28nm'; + $year = '2016';} + else { + $arch = 'Elbrus-8C (gen-4)'; + $process = 'TSMC 28nm'; + $year = '2016';} + } # note: stepping > 1 may be 8C1 + elsif ($model eq '8'){ + $arch = 'Elbrus-1C+ (gen-4)'; + $process = 'TSMC 40nm'; + $year = '2016';} + # 8C2 morphed out of E8CV, but the two were the same die + elsif ($model eq '9'){ + $arch = 'Elbrus-8CV/8C2 (gen-4/5)'; + $process = 'TSMC 28nm'; + $note = $check; + $year = '2016/2020';} + elsif ($model eq 'A'){ + $arch = 'Elbrus-12C (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + elsif ($model eq 'B'){ + $arch = 'Elbrus-16C (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + elsif ($model eq 'C'){ + $arch = 'Elbrus-2C3 (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + else { + $arch = 'Elbrus-??';; + $note = $check; + $year = '';} + } + elsif ($family eq '5'){ + if ($model eq '9'){ + $arch = 'Elbrus-8C2 (gen-4)'; + $process = 'TSMC 28nm'; + $year = '2020';} + else { + $arch = 'Elbrus-??'; + $note = $check; + $process = ''; + $year = '';} + } + elsif ($family eq '6'){ + if ($model eq 'A'){ + $arch = 'Elbrus-12C (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + elsif ($model eq 'B'){ + $arch = 'Elbrus-16C (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + elsif ($model eq 'C'){ + $arch = 'Elbrus-2C3 (gen-6)'; + $process = 'TSMC 16nm'; + $year = '2021+';} + # elsif ($model eq '??'){ + # $arch = 'Elbrus-32C (gen-7)'; + # $process = '?? 7nm'; + # $year = '2025';} + else { + $arch = 'Elbrus-??'; + $note = $check; + $process = ''; + $year = '';} + } + else { + $arch = 'Elbrus-??'; + $note = $check; + } + } + elsif ($type eq 'intel'){ + if ($family eq '4'){ + if ($model =~ /^(0|1|2)$/){ + $arch = 'i486'; + $process = '1000nm'; # 33mhz + $year = '1989-98';} + elsif ($model =~ /^(3)$/){ + $arch = 'i486'; + $process = '800nm'; # 66mhz + $year = '1992-98';} + elsif ($model =~ /^(4|5|6|7|8|9)$/){ + $arch = 'i486'; + $process = '600nm'; # 100mhz + $year = '1993-98';} + else { + $arch = 'i486'; + $process = '600-1000nm'; + $year = '1989-98';} + } + # 1993-2000 + elsif ($family eq '5'){ + # verified + if ($model =~ /^(1)$/){ + $arch = 'P5'; + $process = 'Intel 800nm'; # 1:3,5,7:800 + $year = '1993-94';} + elsif ($model =~ /^(2)$/){ + $arch = 'P5'; # 2:5:MMX + # 2:C:350[or 600]; 2:1,4,5,6:600;but: + if ($stepping > 9){ + $process = 'Intel 350nm'; + $year = '1996-2000';} + else { + $process = 'Intel 600nm'; + $year = '1993-95';} + } + elsif ($model =~ /^(4)$/){ + $arch = 'P5'; + $process = 'Intel 350nm'; # MMX. 4:3:P55C + $year = '1997';} + # unverified + elsif ($model =~ /^(3|7)$/){ + $arch = 'P5'; # 7:0:MMX + $process = 'Intel 350-600nm'; + $year = '';} + elsif ($model =~ /^(8)$/){ + $arch = 'P5'; + $process = 'Intel 350-600nm'; # MMX + $year = '';} + elsif ($model =~ /^(9|A)$/){ + $arch = 'Lakemont'; + $process = 'Intel 350nm'; + $year = '';} + # fallback + else { + $arch = 'P5'; + $process = 'Intel 350-600nm'; # MMX + $year = '1994-2000';} + } + elsif ($family eq '6'){ + if ($model =~ /^(1)$/){ + $arch = 'P6 Pro'; + $process = 'Intel 350nm'; + $year = '';} + elsif ($model =~ /^(3)$/){ + $arch = 'P6 II Klamath'; + $process = 'Intel 350nm'; + $year = '';} + elsif ($model =~ /^(5)$/){ + $arch = 'P6 II Deschutes'; + $process = 'Intel 250nm'; + $year = '';} + elsif ($model =~ /^(6)$/){ + $arch = 'P6 II Mendocino'; + $process = 'Intel 250nm'; # 6:5:P6II-celeron-mendo + $year = '1999';} + elsif ($model =~ /^(7)$/){ + $arch = 'P6 III Katmai'; + $process = 'Intel 250nm'; + $year = '1999';} + elsif ($model =~ /^(8)$/){ + $arch = 'P6 III Coppermine'; + $process = 'Intel 180nm'; + $year = '1999';} + elsif ($model =~ /^(9)$/){ + $arch = 'M Banias'; # Pentium M + $process = 'Intel 130nm'; + $year = '2003';} + elsif ($model =~ /^(A)$/){ + $arch = 'P6 III Xeon'; + $process = 'Intel 180-250nm'; + $year = '1999';} + elsif ($model =~ /^(B)$/){ + $arch = 'P6 III Tualitin'; # 6:B:1,4 + $process = 'Intel 130nm'; + $year = '2001';} + elsif ($model =~ /^(D)$/){ + $arch = 'M Dothan'; # Pentium M + $process = 'Intel 90nm'; + $year = '2003-05';} + elsif ($model =~ /^(E)$/){ + $arch = 'M Yonah'; + $process = 'Intel 65nm'; + $year = '2006-08';} + elsif ($model =~ /^(F|16)$/){ + $arch = 'Core2 Merom'; # 16:1:conroe-l[65nm] + $process = 'Intel 65nm'; + $year = '2006-09';} + elsif ($model =~ /^(15)$/){ + $arch = 'M Tolapai'; # pentium M system on chip + $process = 'Intel 90nm'; + $year = '2008';} + elsif ($model =~ /^(17)$/){ + $arch = 'Penryn'; # 17:A:Core 2,Celeron-wolfdale,yorkfield + $process = 'Intel 45nm'; + $year = '2008';} + # had 25 also, but that's westmere, at least for stepping 2 + elsif ($model =~ /^(1A|1E|1F|2C|2E|2F)$/){ + $arch = 'Nehalem'; + $process = 'Intel 45nm'; + $year = '2008-10';} + elsif ($model =~ /^(1C|26)$/){ + $arch = 'Bonnell'; + $process = 'Intel 45nm'; + $year = '2008-13';} # atom Bonnell? 27? + elsif ($model =~ /^(1D)$/){ + $arch = 'Penryn'; + $process = 'Intel 45nm'; + $year = '2007-08';} + # 25 may be nahelem in a stepping, check. Stepping 2 is westmere + elsif ($model =~ /^(25|2C|2F)$/){ + $arch = 'Westmere'; # die shrink of nehalem + $process = 'Intel 32nm'; + $year = '2010-11';} + elsif ($model =~ /^(27|35|36)$/){ + $arch = 'Saltwell'; + $process = 'Intel 32nm'; + $year = '2011-13';} + elsif ($model =~ /^(2A|2D)$/){ + $arch = 'Sandy Bridge'; + $process = 'Intel 32nm'; + $year = '2010-12';} + elsif ($model =~ /^(37|4A|4D|5A|5D)$/){ + $arch = 'Silvermont'; + $process = 'Intel 22nm'; + $year = '2013-15';} + elsif ($model =~ /^(3A|3E)$/){ + $arch = 'Ivy Bridge'; + $process = 'Intel 22nm'; + $year = '2012-15';} + elsif ($model =~ /^(3C|3F|45|46)$/){ + $arch = 'Haswell'; + $process = 'Intel 22nm'; + $year = '2013-15';} + elsif ($model =~ /^(3D|47|4F|56)$/){ + $arch = 'Broadwell'; + $process = 'Intel 14nm'; + $year = '2015-18';} + elsif ($model =~ /^(4C)$/){ + $arch = 'Airmont'; + $process = 'Intel 14nm'; + $year = '2015-17';} + elsif ($model =~ /^(4E)$/){ + $arch = 'Skylake'; + $process = 'Intel 14nm'; + $year = '2015';} + # need to find stepping for these, guessing stepping 4 is last for SL + elsif ($model =~ /^(55)$/){ + if ($stepping >= 5 && $stepping <= 7){ + $arch = 'Cascade Lake'; + $process = 'Intel 14nm'; + $year = '2019';} + elsif ($stepping >= 8){ + $arch = 'Cooper Lake'; # 55:A:14nm + $process = 'Intel 14nm'; + $year = '2020';} + else { + $arch = 'Skylake'; + $process = 'Intel 14nm'; + $year = '';}} + elsif ($model =~ /^(57)$/){ + $arch = 'Knights Landing'; + $process = 'Intel 14nm'; + $year = '2016+';} + elsif ($model =~ /^(5C|5F)$/){ + $arch = 'Goldmont'; + $process = 'Intel 14nm'; + $year = '2016';} + elsif ($model =~ /^(5E)$/){ + $arch = 'Skylake-S'; + $process = 'Intel 14nm'; + $year = '2015';} + elsif ($model =~ /^(66|67)$/){ + $arch = 'Cannon Lake'; + $process = 'Intel 10nm'; + $year = '2018';} + # 6 are servers, 7 not + elsif ($model =~ /^(6A|6C|7D|7E|9F)$/){ + $arch = 'Ice Lake'; + $process = 'Intel 10nm'; + $year = '2019-21';} + elsif ($model =~ /^(7A)$/){ + $arch = 'Goldmont Plus'; + $process = 'Intel 14nm'; + $year = '2017';} + elsif ($model =~ /^(85)$/){ + $arch = 'Knights Mill'; + $process = 'Intel 14nm'; + $year = '2017-19';} + elsif ($model =~ /^(86)$/){ + $arch = 'Tremont Snow Ridge'; # embedded + $process = 'Intel 10nm'; + $year = '2020';} + elsif ($model =~ /^(87)$/){ + $arch = 'Tremont Parker Ridge'; # embedded + $process = 'Intel 10nm'; + $year = '2022';} + elsif ($model =~ /^(8A)$/){ + $arch = 'Tremont Lakefield'; + $process = 'Intel 10nm'; + $year = '2020';} # ? + elsif ($model =~ /^(96)$/){ + $arch = 'Tremont Elkhart Lake'; + $process = 'Intel 10nm'; + $year = '2020';} # ? + elsif ($model =~ /^(8C|8D)$/){ + $arch = 'Tiger Lake'; + $process = 'Intel 10nm'; + $year = '2020';} + elsif ($model =~ /^(8E)$/){ + # can be AmberL or KabyL + if ($stepping == 9){ + $arch = 'Amber/Kaby Lake'; + $note = $check; + $process = 'Intel 14nm'; + $year = '2017';} + elsif ($stepping == 10){ + $arch = 'Coffee Lake'; + $process = 'Intel 14nm'; + $year = '2017';} + elsif ($stepping == 11){ + $arch = 'Whiskey Lake'; + $process = 'Intel 14nm'; + $year = '2018';} + # can be WhiskeyL or CometL + elsif ($stepping == 12){ + $arch = 'Comet/Whiskey Lake'; + $note = $check; + $process = 'Intel 14nm'; + $year = '2018';} + # note: had it as > 13, but 0xC seems to be CL + elsif ($stepping >= 13){ + $arch = 'Comet Lake'; # 10 gen + $process = 'Intel 14nm'; + $year = '2019-20';} + # NOTE: not enough info to lock this down + else { + $arch = 'Kaby Lake'; + $note = $check; + $process = 'Intel 14nm'; + $year = '~2018-20';} + } + elsif ($model =~ /^(8F)$/){ + $arch = 'Sapphire Rapids'; + $process = 'Intel 7 (10nm ESF)'; + $year = '2023+';} # server + elsif ($model =~ /^(97|9A|9C|BE)$/){ + $arch = 'Alder Lake'; # socket LG 1700 + $process = 'Intel 7 (10nm ESF)'; + $year = '2021+';} + elsif ($model =~ /^(9E)$/){ + if ($stepping == 9){ + $arch = 'Kaby Lake'; + $process = 'Intel 14nm'; + $year = '2018';} + elsif ($stepping >= 10 && $stepping <= 13){ + $arch = 'Coffee Lake'; # 9E:A,B,C,D + $process = 'Intel 14nm'; + $year = '2018';} + else { + $arch = 'Kaby Lake'; + $note = $check; + $process = 'Intel 14nm'; + $year = '2018';} + } + elsif ($model =~ /^(A5|A6)$/){ + $arch = 'Comet Lake'; # 10 gen; stepping 0-5 + $process = 'Intel 14nm'; + $year = '2020';} + elsif ($model =~ /^(A7|A8)$/){ + $arch = 'Rocket Lake'; # 11 gen; stepping 1 + $process = 'Intel 14nm'; + $year = '2021+';} + # More info: comet: shares family/model, need to find stepping numbers + # Coming: meteor lake; granite rapids; emerald rapids, diamond rapids + ## IDS UNKNOWN, release late 2022 + elsif ($model =~ /^(AA|AB|AC|B5)$/){ + $arch = 'Meteor Lake'; # 14 gen + $process = 'Intel 4 (7nm)'; + $year = '2023+';} + elsif ($model =~ /^(AD|AE)$/){ + $arch = 'Granite Rapids'; # ? + $process = 'Intel 3 (7nm+)'; # confirm + $year = '2024+';} + elsif ($model =~ /^(B6)$/){ + $arch = 'Grand Ridge'; # 14 gen + $process = 'Intel 4 (7nm)'; # confirm + $year = '2023+';} + elsif ($model =~ /^(B7|BA|BF)$/){ + $arch = 'Raptor Lake'; # 13 gen, socket LG 1700,1800 + $process = 'Intel 7 (10nm)'; + $year = '2022+';} + elsif ($model =~ /^(BC|BD)$/){ + $arch = 'Lunar Lake'; # 15 gn + $process = 'Intel 18a (1.8nm)'; + $year = '2024+';} # check when actually in production + # Meteor Lake-S maybe cancelled, replaced by arrow + elsif ($model =~ /^(C5|C6)$/){ + $arch = 'Arrow Lake'; # 14 gn + # gfx tile is TSMC 3nm + $process = 'Intel 20a (2nm)';# TSMC 3nm (corei3-5)/Intel 20A 2nm (core i5-9) + $year = '2024+';} # check when actually in production + elsif ($model =~ /^(CF)$/){ + $arch = 'Emerald Rapids'; # 5th gen xeon + $process = 'Intel 7 (10nm)'; + $year = '2023+';} + ## roadmaps: check and update, since Intel misses their targets often + # Sapphire Rapids: 13 gen (?), Intel 7 (10nm), 2023 + # Emerald Rapids: Intel 7 (10nm), 2023 + # Granite Rapids: Intel 3 (7nm+), 2024 + # Diamond Rapids: Intel 3 (7nm+), 2025 + # Raptor Lake: 13 gen, Intel 7 (10nm), 2022 + # Meteor Lake: 14 gen, Intel 4 (7nm+) + # Arrow Lake - 14 gen, TSMC 3nm (corei3-5)/Intel 20A 2nm (core i5-9), 2024 + # Lunar Lake - 15 gen, Intel 18A (1.8nm), 2024-5 + # Panther Lake - 15 gen, ?, late 2025, cougar cove Xe3 Celestial GPU architecture + # Beast Lake - 16 gen, ?, 2026? + # Nova Lake - 16 gen, Intel 18A (1.8nm), 2026 + } + # itanium 1 family 7 all recalled + elsif ($family eq 'B'){ + if ($model =~ /^(0)$/){ + $arch = 'Knights Ferry'; + $process = 'Intel 45nm'; + $year = '2010-11';} + if ($model =~ /^(1)$/){ + $arch = 'Knights Corner'; + $process = 'Intel 22nm'; + $year = '2012-13';} + } + # pentium 4 + elsif ($family eq 'F'){ + if ($model =~ /^(0|1)$/){ + $arch = 'Netburst Willamette'; + $process = 'Intel 180nm'; + $year = '2000-01';} + elsif ($model =~ /^(2)$/){ + if ($stepping <= 4 || $stepping > 6){ + $arch = 'Netburst Northwood';} + elsif ($stepping == 5){ + $arch = 'Netburst Gallatin';} + else { + $arch = 'Netburst';} + $process = 'Intel 130nm'; + $year = '2002-03';} + elsif ($model =~ /^(3)$/){ + $arch = 'Netburst Prescott'; + $process = 'Intel 90nm'; + $year = '2004-06';} # 6? Nocona + elsif ($model =~ /^(4)$/){ + # these are vague, and same stepping can have > 1 core names + if ($stepping < 10){ + $arch = 'Netburst Prescott'; # 4:1,9:prescott + $process = 'Intel 90nm'; + $year = '2004-06';} + else { + $arch = 'Netburst Smithfield'; + $process = 'Intel 90nm'; + $year = '2005-06';} # 6? Nocona + } + elsif ($model =~ /^(6)$/){ + $arch = 'Netburst Presler'; # 6:2,4,5:presler + $process = 'Intel 65nm'; + $year = '2006';} + else { + $arch = 'Netburst'; + $process = 'Intel 90-180nm'; + $year = '2000-06';} + } + # this is not going to e accurate, WhiskyL or Kaby L can ID as Skylake + # but if it's a new cpu microarch not handled yet, it may give better + # than nothing result. This is intel only + # This is probably the gcc/clang -march/-mtune value, which is not + # necessarily the same as actual microarch, and varies between gcc/clang versions + if (!$arch){ + my $file = '/sys/devices/cpu/caps/pmu_name'; + $arch = main::reader($file,'strip',0) if -r $file; + $note = $check if $arch; + } + # gen 1 had no gen, only 3 digits: Core i5-661 Core i5-655K; Core i5 M 520 + # EXCEPT gen 1: Core i7-720QM Core i7-740QM Core i7-840QM + # 2nd: Core i5-2390T Core i7-11700F Core i5-8400 + # 2nd variants: Core i7-1165G7 + if ($name){ + if ($name =~ /\bi[357][\s-]([A-Z][\s-]?)?(\d{3}([^\d]|\b)|[78][24]00M)/){ + $gen = ($gen) ? "$gen (core 1)": 'core 1'; + } + elsif ($name =~ /\bi[3579][\s-]([A-Z][\s-]?)?([2-9]|1[0-4])(\d{3}|\d{2}[A-Z]\d)/){ + $gen = ($gen) ? "$gen (core $2)" : "core $2"; + } + } + } + eval $end if $b_log; + return [$arch,$note,$process,$gen,$year]; +} +## END CPU ARCH ## + +# Only AMD/Intel 64 bit cpus +sub cp_cpu_level { + eval $start if $b_log; + my %flags = map {$_ =>1} split(/\s+/,$_[0]); + my ($level,$note,@found); + # note, each later cpu level must contain all subsequent cpu flags + # baseline: all x86_64 cpus lm cmov cx8 fpu fxsr mmx syscall sse2 + my @l1 = qw(cmov cx8 fpu fxsr lm mmx syscall sse2); + my @l2 = qw(cx16 lahf_lm popcnt sse4_1 sse4_2 ssse3); + my @l3 = qw(abm avx avx2 bmi1 bmi2 f16c fma movbe xsave); + my @l4 = qw(avx512f avx512bw avx512cd avx512dq avx512vl); + if ((@found = grep {$flags{$_}} @l1) && scalar(@found) == scalar(@l1)){ + $level = 'v1'; + # print 'v1: ', Data::Dumper::Dumper \@found; + if ((@found = grep {$flags{$_}} @l2) && scalar(@found) == scalar(@l2)){ + $level = 'v2'; + # print 'v2: ', Data::Dumper::Dumper \@found; + # It's not 100% certain that if flags exist v3/v4 supported. flags don't + # give full possible outcomes in these cases. See: docs/inxi-cpu.txt + if ((@found = grep {$flags{$_}} @l3) && scalar(@found) == scalar(@l3)){ + $level = 'v3'; + # print 'v3: ', Data::Dumper::Dumper \@found; + $note = main::message('note-check'); + if ((@found = grep {$flags{$_}} @l4) && scalar(@found) == scalar(@l4)){ + $level = 'v4'; + # print 'v4: ', Data::Dumper::Dumper \@found; + } + } + } + } + $level = [$level,$note] if $level; + eval $end if $b_log; + return $level; +} + +sub cp_cpu_topology { + my ($counts,$topology) = @_; + my @alpha = qw(Single Dual Triple Quad); + my ($sep) = (''); + my (%keys,%done); + my @tests = ('x'); # prefill [0] because iterator runs before 'next' test. + if ($counts->{'cpu-topo'}){ + # first we want to find out how many of each physical variant there are + foreach my $topo (@{$counts->{'cpu-topo'}}){ + # turn sorted hash into string + my $test = join('::', map{$_ . ':' . $topo->{$_}} sort keys %$topo); + if ($keys{$test}){ + $keys{$test}++; + } + else { + $keys{$test} = 1; + } + push(@tests,$test); + } + my ($i,$j) = (0,0); + # then we build up the topology data per variant + foreach my $topo (@{$counts->{'cpu-topo'}}){ + my $key = ''; + $i++; + next if $done{$tests[$i]}; + $done{$tests[$i]} = 1; + if ($b_admin && $type eq 'full'){ + $topology->{'full'}[$j]{'cpus'} = $keys{$tests[$i]}; + $topology->{'full'}[$j]{'cores'} = $topo->{'cores'}; + if ($topo->{'threads'} && $topo->{'cores'} != $topo->{'threads'}){ + $topology->{'full'}[$j]{'threads'} = $topo->{'threads'}; + } + if ($topo->{'dies'} && $topo->{'dies'} > 1){ + $topology->{'full'}[$j]{'dies'} = $topo->{'dies'}; + } + if ($counts->{'struct-mt'}){ + $topology->{'full'}[$j]{'cores-mt'} = $topo->{'cores-mt'}; + } + if ($counts->{'struct-st'}){ + $topology->{'full'}[$j]{'cores-st'} = $topo->{'cores-st'}; + } + if ($counts->{'struct-max'} || $counts->{'struct-min'}){ + $topology->{'full'}[$j]{'max'} = $topo->{'max'}; + $topology->{'full'}[$j]{'min'} = $topo->{'min'}; + } + if ($topo->{'smt'}){ + $topology->{'full'}[$j]{'smt'} = $topo->{'smt'}; + } + if ($topo->{'tpc'}){ + $topology->{'full'}[$j]{'tpc'} = $topo->{'tpc'}; + } + $j++; + } + else { + # start building string + $topology->{'string'} .= $sep; + $sep = ','; + if ($counts->{'physical'} > 1) { + my $phys = ($topology->{'struct-cores'}) ? $keys{$tests[$i]} : $counts->{'physical'}; + $topology->{'string'} .= $phys . 'x '; + $topology->{'string'} .= $topo->{'cores'} . '-core'; + } + else { + $topology->{'string'} .= cp_cpu_alpha($topo->{'cores'}); + } + # alder lake type cpu + if ($topo->{'cores-st'} && $topo->{'cores-mt'}){ + $topology->{'string'} .= ' (' . $topo->{'cores-mt'} . '-mt/'; + $topology->{'string'} .= $topo->{'cores-st'} . '-st)'; + } + # we only want to show > 1 phys short form basic if cpus have different + # core counts, not different min/max frequencies + last if !$topology->{'struct-cores'}; + } + } + } + else { + if ($counts->{'physical'} > 1) { + $topology->{'string'} = $counts->{'physical'} . 'x '; + $topology->{'string'} .= $counts->{'cpu-cores'} . '-core'; + } + else { + $topology->{'string'} = cp_cpu_alpha($counts->{'cpu-cores'}); + } + } + $topology->{'string'} ||= ''; +} + +sub cp_cpu_alpha { + my $cores = $_[0]; + my $string = ''; + if ($cores > 4){ + $string = $cores . '-core'; + } + elsif ($cores == 0){ + $string = main::message('unknown-cpu-topology'); + } + else { + my @alpha = qw(single dual triple quad); + $string = $alpha[$cores-1] . ' core'; + } + return $string; +} + +# Logic: +# if > 1 processor && processor id (physical id) == core id then Multi threaded (MT) +# if siblings > 1 && siblings == 2 * num_of_cores ($cpu->{'cores'}) then Multi threaded (MT) +# if > 1 processor && processor id (physical id) != core id then Multi-Core Processors (MCP) +# if > 1 processor && processor ids (physical id) > 1 then Symmetric Multi Processing (SMP) +# if = 1 processor then single core/processor Uni-Processor (UP) +sub cp_cpu_type { + eval $start if $b_log; + my ($counts,$cpu,$tests) = @_; + my $cpu_type = ''; + if ($counts->{'processors'} > 1 || + (defined $tests->{'intel'} && $tests->{'intel'} && $cpu->{'siblings'} > 0)){ + # cpu_sys detected MT + if ($counts->{'struct-mt'}){ + if ($counts->{'struct-mt'} && $counts->{'struct-st'}){ + $cpu_type .= 'MST'; + } + else { + $cpu_type .= 'MT'; + } + } + # handle case of OpenBSD that has hw.smt but no other meaningful topology + elsif ($cpu->{'smt'}){ + $cpu_type .= 'MT' if $cpu->{'smt'} eq 'enabled'; + } + # non-multicore MT, with 2 or more threads per core + elsif ($counts->{'processors'} && $counts->{'physical'} && + $counts->{'cpu-cores'} && + $counts->{'processors'}/($counts->{'physical'} * $counts->{'cpu-cores'}) >= 2){ + # print "mt:1\n"; + $cpu_type .= 'MT'; + } + # 2 or more siblings per cpu real core + elsif ($cpu->{'siblings'} > 1 && $cpu->{'siblings'}/$counts->{'cpu-cores'} >= 2){ + # print "mt:3\n"; + $cpu_type .= 'MT'; + } + # non-MT multi-core or MT multi-core + if ($counts->{'cpu-cores'} > 1){ + if ($counts->{'struct-mt'} && $counts->{'struct-st'}){ + $cpu_type .= ' AMCP'; + } + else { + $cpu_type .= ' MCP'; + } + } + # only solidly known > 1 die cpus will use this + if ($cpu->{'dies'} > 1){ + $cpu_type .= ' MCM'; + } + # >1 cpu sockets active: Symetric Multi Processing + if ($counts->{'physical'} > 1){ + if ($counts->{'struct-cores'} || $counts->{'struct-max'} || + $counts->{'struct-min'}){ + $cpu_type .= ' AMP'; + } + else { + $cpu_type .= ' SMP'; + } + } + $cpu_type =~ s/^\s+//; + } + else { + $cpu_type = 'UP'; + } + eval $end if $b_log; + return $cpu_type; +} + +# Legacy: this data should be comfing from the /sys tool now. +# Was needed because no physical_id in cpuinfo, but > 1 cpu systems exist +# returns: 0: per cpu cores; 1: phys cpu count; 2: override model defaul names +sub cp_elbrus_data { + eval $start if $b_log; + my ($family_id,$model_id,$count,$arch) = @_; + # 0: cores + my $return = [0,1,$arch]; + my %cores = ( + # key=family id + model id + '41' => 1, + '42' => 1, + '43' => 4, + '44' => 2, + '46' => 1, + '47' => 8, + '48' => 1, + '49' => 8, + '59' => 8, + '4A' => 12, + '4B' => 16, + '4C' => 2, + '6A' => 12, + '6B' => 16, + '6C' => 2, + ); + $return->[0] = $cores{$family_id . $model_id} if $cores{$family_id . $model_id}; + if ($return->[0]){ + $return->[1] = ($count % $return->[0]) ? int($count/$return->[0]) + 1 : $count/$return->[0]; + } + eval $end if $b_log; + return $return; +} + +sub cp_speed_data { + eval $start if $b_log; + my ($cpu,$cpu_sys) = @_; + my $info = {}; + if (defined $cpu_sys->{'data'}){ + if (defined $cpu_sys->{'data'}{'speeds'}{'min-freq'}){ + $cpu->{'min-freq'} = $cpu_sys->{'data'}{'speeds'}{'min-freq'}; + } + if (defined $cpu_sys->{'data'}{'speeds'}{'max-freq'}){ + $cpu->{'max-freq'} = $cpu_sys->{'data'}{'speeds'}{'max-freq'}; + } + if (defined $cpu_sys->{'data'}{'speeds'}{'scaling-min-freq'}){ + $cpu->{'scaling-min-freq'} = $cpu_sys->{'data'}{'speeds'}{'scaling-min-freq'}; + } + if (defined $cpu_sys->{'data'}{'speeds'}{'scaling-max-freq'}){ + $cpu->{'scaling-max-freq'} = $cpu_sys->{'data'}{'speeds'}{'scaling-max-freq'}; + } + # we don't need to see these if they are the same + if ($cpu->{'min-freq'} && $cpu->{'max-freq'} && + $cpu->{'scaling-min-freq'} && $cpu->{'scaling-max-freq'} && + $cpu->{'min-freq'} eq $cpu->{'scaling-min-freq'} && + $cpu->{'max-freq'} eq $cpu->{'scaling-max-freq'}){ + undef $cpu->{'scaling-min-freq'}; + undef $cpu->{'scaling-max-freq'}; + } + if (defined $cpu_sys->{'data'}{'speeds'}{'all'}){ + # only replace if we got actual speed values from cpufreq, or if no legacy + # sourced processors data. Handles fake syz core speeds for counts. + if ((grep {$_} @{$cpu_sys->{'data'}{'speeds'}{'all'}}) || + !@{$cpu->{'processors'}}){ + $cpu->{'processors'} = $cpu_sys->{'data'}{'speeds'}{'all'}; + } + } + if (defined $cpu_sys->{'data'}{'cpufreq-boost'}){ + $cpu->{'boost'} = $cpu_sys->{'data'}{'cpufreq-boost'}; + } + } + if (defined $cpu->{'processors'}){ + if (scalar @{$cpu->{'processors'}} > 1){ + my ($agg,$high) = (0,0); + for (@{$cpu->{'processors'}}){ + next if !$_; # bsds might have 0 or undef value, that's junk + $agg += $_; + $high = $_ if $_ > $high; + } + if ($agg){ + $cpu->{'avg-freq'} = int($agg/scalar @{$cpu->{'processors'}}); + $cpu->{'cur-freq'} = $high; + $info->{'avg-speed-key'} = 'avg'; + $info->{'speed'} = $cpu->{'avg-freq'}; + if ($high > $cpu->{'avg-freq'}){ + $cpu->{'high-freq'} = $high; + $info->{'high-speed-key'} = 'high'; + } + } + } + elsif ($cpu->{'processors'}[0]) { + $cpu->{'cur-freq'} = $cpu->{'processors'}[0]; + $info->{'speed'} = $cpu->{'cur-freq'}; + } + } + # BSDs generally will have processors count, but not per core speeds + if ($cpu->{'cur-freq'} && !$info->{'speed'}){ + $info->{'speed'} = $cpu->{'cur-freq'}; + } + if ($cpu->{'min-freq'} || $cpu->{'max-freq'}){ + ($info->{'min-max'},$info->{'min-max-key'}) = cp_speed_min_max( + $cpu->{'min-freq'}, + $cpu->{'max-freq'}); + } + if ($cpu->{'scaling-min-freq'} || $cpu->{'scaling-max-freq'}){ + ($info->{'scaling-min-max'},$info->{'scaling-min-max-key'}) = cp_speed_min_max( + $cpu->{'scaling-min-freq'}, + $cpu->{'scaling-max-freq'}, + 'sc'); + } + if ($cpu->{'cur-freq'}){ + if ($show{'short'}){ + $info->{'speed-key'} = 'speed'; + } + elsif ($show{'cpu-basic'}){ + $info->{'speed-key'} = 'speed (MHz)'; + } + else { + $info->{'speed-key'} = 'Speed (MHz)'; + } + } + eval $end if $b_log; + return $info; +} + +sub cp_speed_min_max { + my ($min,$max,$type) = @_; + my ($min_max,$key); + if ($min && $max){ + $min_max = "$min/$max"; + $key = "min/max"; + } + elsif ($max){ + $min_max = $max; + $key = "max"; + } + elsif ($min){ + $min_max = $min; + $key = "min"; + } + $key = $type . '-' . $key if $type && $key; + return ($min_max,$key); +} + +# args: 0: cpu, by ref; 1: update $tests by reference +sub cp_test_types { + my ($cpu,$tests) = @_; + if ($cpu->{'type'} eq 'intel'){ + $$tests{'intel'} = 1; + $$tests{'xeon'} = 1 if $cpu->{'model_name'} =~ /Xeon/i; + } + elsif ($cpu->{'type'} eq 'amd'){ + if ($cpu->{'family'} && $cpu->{'family'} eq '17'){ + $$tests{'amd-zen'} = 1; + if ($cpu->{'model_name'}){ + if ($cpu->{'model_name'} =~ /Ryzen/i){ + $$tests{'ryzen'} = 1; + } + elsif ($cpu->{'model_name'} =~ /EPYC/i){ + $$tests{'epyc'} = 1; + } + } + } + } + elsif ($cpu->{'type'} eq 'elbrus'){ + $$tests{'elbrus'} = 1; + } +} + +## CPU UTILITIES ## +# only elbrus ID is actually used live +sub cpu_vendor { + eval $start if $b_log; + my ($string) = @_; + my ($vendor) = (''); + $string = lc($string); + if ($string =~ /intel/){ + $vendor = "intel"; + } + elsif ($string =~ /amd/){ + $vendor = "amd"; + } + # via/centaur/zhaoxin branding + elsif ($string =~ /centaur|zhaoxin/){ + $vendor = "centaur"; + } + elsif ($string eq 'elbrus'){ + $vendor = "elbrus"; + } + eval $end if $b_log; + return $vendor; +} + +# do not define model-id, stepping, or revision, those can be 0 valid value +sub set_cpu_data { + ${$_[0]} = { + 'arch' => '', + 'avg-freq' => 0, # MHz + 'bogomips' => 0, + 'cores' => 0, + 'cur-freq' => 0, # MHz + 'dies' => 0, + 'family' => '', + 'flags' => '', + 'ids' => [], + 'l1-cache' => 0, # store in KB + 'l2-cache' => 0, # store in KB + 'l3-cache' => 0, # store in KB + 'max-freq' => 0, # MHz + 'min-freq' => 0, # MHz + 'model_name' => '', + 'processors' => [], + 'scalings' => [], + 'siblings' => 0, + 'type' => '', + }; +} + +sub system_cpu_name { + eval $start if $b_log; + my ($compat,@working); + my $cpus = {}; + if (@working = main::globber('/sys/firmware/devicetree/base/cpus/cpu@*/compatible')){ + foreach my $file (@working){ + $compat = main::reader($file,'',0); + next if $compat =~ /timer/; # seen on android + # these can have non printing ascii... why? As long as we only have the + # splits for: null 00/start header 01/start text 02/end text 03 + $compat = (split(/\x01|\x02|\x03|\x00/, $compat))[0] if $compat; + $compat = (split(/,\s*/, $compat))[-1] if $compat; + $cpus->{$compat} = ($cpus->{$compat}) ? ++$cpus->{$compat}: 1; + } + } + # synthesize it, [4] will be like: cortex-a15-timer; sunxi-timer + # so far all with this directory show soc name, not cpu name for timer + elsif (! -d '/sys/firmware/devicetree/base' && $devices{'timer'}){ + foreach my $working (@{$devices{'timer'}}){ + next if $working->[0] ne 'timer' || !$working->[4] || $working->[4] =~ /timer-mem$/; + $working->[4] =~ s/(-system)?-timer$//; + $compat = $working->[4]; + $cpus->{$compat} = ($cpus->{$compat}) ? ++$cpus->{$compat}: 1; + } + } + main::log_data('dump','%$cpus',$cpus) if $b_log; + eval $end if $b_log; + return $cpus; +} + +## CLEANERS/OUTPUT HANDLERS ## +# MHZ - cell cpus +sub clean_speed { + my ($speed,$opt) = @_; + # eq '0' might be for string typing; value can be: + return if !$speed || $speed eq '0' || $speed =~ /^\D/; + $speed =~ s/[GMK]HZ$//gi; + $speed = ($speed/1000) if $opt && $opt eq 'khz'; + $speed = sprintf("%.0f", $speed); + return $speed; +} + +sub clean_cpu { + my ($cpu) = @_; + return if !$cpu; + my $filters = '@|cpu |cpu deca|([0-9]+|single|dual|two|triple|three|tri|quad|four|'; + $filters .= 'penta|five|hepta|six|hexa|seven|octa|eight|multi)[ -]core|'; + $filters .= 'ennea|genuine|multi|processor|single|triple|[0-9\.]+ *[MmGg][Hh][Zz]'; + $cpu =~ s/$filters//ig; + $cpu =~ s/\s\s+/ /g; + $cpu =~ s/^\s+|\s+$//g; + return $cpu; +} + +sub hex_and_decimal { + my ($data) = @_; + $data = '' if !defined $data; + if ($data =~ /\S/){ + # only handle if a short hex number!! No need to prepend 0x to 0-9 + if ($data =~ /^[0-9a-f]{1,3}$/i && hex($data) ne $data){ + $data .= ' (' . hex($data) . ')'; + $data = '0x' . $data; + } + } + else { + $data = 'N/A'; + } + return $data; +} +} + +## DriveItem +{ +package DriveItem; +my ($b_hddtemp,$b_nvme,$smartctl_missing,$vendors); +my ($hddtemp,$nvme) = ('',''); +my (@by_id,@by_path); +my ($debugger_dir); +# main::writer("$debugger_dir/system-repo-data-urpmq.txt",\@data2) if $debugger_dir; + +sub get { + eval $start if $b_log; + my ($type) = @_; + $type ||= 'standard'; + my ($key1,$val1); + my $rows = []; + my $num = 0; + my $data = drive_data($type); + # NOTE: + if (@$data){ + if ($type eq 'standard'){ + storage_output($rows,$data); + drive_output($rows,$data) if $show{'disk'}; + if ($bsd_type && !$dboot{'disk'} && $type eq 'standard' && $show{'disk'}){ + $key1 = 'Drive Report'; + my $file = $system_files{'dmesg-boot'}; + if ($file && ! -r $file){ + $val1 = main::message('dmesg-boot-permissions'); + } + elsif (!$file){ + $val1 = main::message('dmesg-boot-missing'); + } + else { + $val1 = main::message('disk-data-bsd'); + } + push(@$rows,{main::key($num++,0,1,$key1) => $val1,}); + } + } + # used by short form, raw data returned + else { + $rows = $data; + # print Data::Dumper::Dumper $rows; + } + } + else { + $key1 = 'Message'; + $val1 = main::message('disk-data'); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + if (!@$rows){ + $key1 = 'Message'; + $val1 = main::message('disk-data'); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + # push(@rows,@data); + if ($show{'optical'} || $show{'optical-basic'}){ + OpticalItem::get($rows); + } + ($b_hddtemp,$b_nvme,$hddtemp,$nvme,$vendors) = (); + (@by_id,@by_path) = (); + eval $end if $b_log; + return $rows; +} + +sub storage_output { + eval $start if $b_log; + my ($rows,$disks) = @_; + my ($num,$j) = (0,0); + my ($size,$size_value,$used) = ('','',''); + push(@$rows, { + main::key($num++,1,1,'Local Storage') => '', + }); + # print Data::Dumper::Dumper $disks; + $size = main::get_size($disks->[0]{'size'},'string','N/A'); + if ($disks->[0]{'logical-size'}){ + $rows->[$j]{main::key($num++,1,2,'total')} = ''; + $rows->[$j]{main::key($num++,0,3,'raw')} = $size; + $size = main::get_size($disks->[0]{'logical-size'},'string'); + $size_value = $disks->[0]{'logical-size'}; + # print Data::Dumper::Dumper $disks; + $rows->[$j]{main::key($num++,1,3,'usable')} = $size; + } + else { + $size_value = $disks->[0]{'size'} if $disks->[0]{'size'}; + $rows->[$j]{main::key($num++,0,2,'total')} = $size; + } + $used = main::get_size($disks->[0]{'used'},'string','N/A'); + if ($extra > 0 && $disks->[0]{'logical-free'}){ + $size = main::get_size($disks->[0]{'logical-free'},'string'); + $rows->[$j]{main::key($num++,0,4,'lvm-free')} = $size; + } + if (($size_value && $size_value =~ /^[0-9]/) && + ($used && $disks->[0]{'used'} =~ /^[0-9]/)){ + $used = $used . ' (' . sprintf("%0.1f", $disks->[0]{'used'}/$size_value*100) . '%)'; + } + $rows->[$j]{main::key($num++,0,2,'used')} = $used; + shift @$disks; + eval $end if $b_log; +} + +sub drive_output { + eval $start if $b_log; + my ($rows,$disks) = @_; + # print Data::Dumper::Dumper $disks; + my ($b_smart_permissions,$block,$smart_age,$smart_basic,$smart_fail); + my ($num,$j) = (0,0); + my ($id,$model,$size) = ('','',''); + # note: specific smartctl non-missing errors handled inside loop + if ($smartctl_missing){ + $j = scalar @$rows; + $rows->[$j]{main::key($num++,0,1,'SMART Message')} = $smartctl_missing; + } + elsif ($b_admin){ + my $result = smartctl_fields(); + ($smart_age,$smart_basic,$smart_fail) = @$result; + } + foreach my $row (sort { $a->{'id'} cmp $b->{'id'} } @$disks){ + ($id,$model,$size) = ('','',''); + $num = 1; + $model = ($row->{'model'}) ? $row->{'model'}: 'N/A'; + $id = ($row->{'id'}) ? "/dev/$row->{'id'}":'N/A'; + $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A'; + # print Data::Dumper::Dumper $disks; + $j = scalar @$rows; + if (!$b_smart_permissions && $row->{'smart-permissions'}){ + $b_smart_permissions = 1; + $rows->[$j]{main::key($num++,0,1,'SMART Message')} = $row->{'smart-permissions'}; + $j = scalar @$rows; + } + push(@$rows, { + main::key($num++,1,1,'ID') => $id, + }); + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'}; + } + + if ($row->{'vendor'}){ + $rows->[$j]{main::key($num++,0,2,'vendor')} = $row->{'vendor'}; + } + $rows->[$j]{main::key($num++,0,2,'model')} = $model; + if ($row->{'drive-vendor'}){ + $rows->[$j]{main::key($num++,0,2,'drive vendor')} = $row->{'drive-vendor'}; + } + if ($row->{'drive-model'}){ + $rows->[$j]{main::key($num++,0,2,'drive model')} = $row->{'drive-model'}; + } + if ($row->{'family'}){ + $rows->[$j]{main::key($num++,0,2,'family')} = $row->{'family'}; + } + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + if ($b_admin && $row->{'block-physical'}){ + $rows->[$j]{main::key($num++,1,2,'block-size')} = ''; + $rows->[$j]{main::key($num++,0,3,'physical')} = "$row->{'block-physical'} B"; + $block = ($row->{'block-logical'}) ? "$row->{'block-logical'} B" : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'logical')} = $block; + } + if ($row->{'type'}){ + $rows->[$j]{main::key($num++,1,2,'type')} = $row->{'type'}; + if ($extra > 1 && $row->{'type'} eq 'USB' && $row->{'abs-path'} && + $usb{'disk'}){ + foreach my $device (@{$usb{'disk'}}){ + if ($device->[8] && $device->[26] && + $row->{'abs-path'} =~ /^$device->[26]/){ + $rows->[$j]{main::key($num++,0,3,'rev')} = $device->[8]; + if ($device->[17]){ + $rows->[$j]{main::key($num++,0,3,'spd')} = $device->[17]; + } + if ($device->[24]){ + $rows->[$j]{main::key($num++,0,3,'lanes')} = $device->[24]; + } + if ($b_admin && $device->[22]){ + $rows->[$j]{main::key($num++,0,3,'mode')} = $device->[22]; + } + last; + } + } + } + } + if ($extra > 1 && $row->{'speed'}){ + if ($row->{'sata'}){ + $rows->[$j]{main::key($num++,0,2,'sata')} = $row->{'sata'}; + } + $rows->[$j]{main::key($num++,0,2,'speed')} = $row->{'speed'}; + $rows->[$j]{main::key($num++,0,2,'lanes')} = $row->{'lanes'} if $row->{'lanes'}; + } + if ($extra > 2){ + $row->{'tech'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,2,'tech')} = $row->{'tech'}; + if ($row->{'rotation'}){ + $rows->[$j]{main::key($num++,0,2,'rpm')} = $row->{'rotation'}; + } + } + if ($extra > 1){ + if (!$row->{'serial'} && $alerts{'bioctl'} && + $alerts{'bioctl'}->{'action'} eq 'permissions'){ + $row->{'serial'} = main::message('root-required'); + } + else { + $row->{'serial'} = main::filter($row->{'serial'}); + } + $rows->[$j]{main::key($num++,0,2,'serial')} = $row->{'serial'}; + if ($row->{'drive-serial'}){ + $rows->[$j]{main::key($num++,0,2,'drive serial')} = main::filter($row->{'drive-serial'}); + } + if ($row->{'firmware'}){ + $rows->[$j]{main::key($num++,0,2,'fw-rev')} = $row->{'firmware'}; + } + if ($row->{'drive-firmware'}){ + $rows->[$j]{main::key($num++,0,2,'drive-rev')} = $row->{'drive-firmware'}; + } + } + if ($extra > 0 && $row->{'temp'}){ + $rows->[$j]{main::key($num++,0,2,'temp')} = $row->{'temp'} . ' C'; + } + if ($extra > 1 && $alerts{'bioctl'}){ + if (!$row->{'duid'} && $alerts{'bioctl'}->{'action'} eq 'permissions'){ + $rows->[$j]{main::key($num++,0,2,'duid')} = main::message('root-required'); + } + elsif ($row->{'duid'}){ + $rows->[$j]{main::key($num++,0,2,'duid')} = main::filter($row->{'duid'}); + } + } + # Extra level tests already done + if (defined $row->{'partition-table'}){ + $rows->[$j]{main::key($num++,0,2,'scheme')} = $row->{'partition-table'}; + } + if ($row->{'smart'} || $row->{'smart-error'}){ + $j = scalar @$rows; + ## Basic SMART and drive info ## + smart_output('basic',$smart_basic,$row,$j,\$num,$rows); + ## Old-Age errors ## + smart_output('age',$smart_age,$row,$j,\$num,$rows); + ## Pre-Fail errors ## + smart_output('fail',$smart_fail,$row,$j,\$num,$rows); + } + } + eval $end if $b_log; +} + +# args: $num and $rows passed by reference +sub smart_output { + eval $start if $b_log; + my ($type,$smart_data,$row,$j,$num,$rows) = @_; + my ($b_found); + my ($l,$m,$p) = ($type eq 'basic') ? (2,3,0) : (3,4,0); + my ($m_h,$p_h) = ($m,$p); + for (my $i = 0; $i < scalar @$smart_data;$i++){ + if ($row->{$smart_data->[$i][0]}){ + if (!$b_found){ + my ($key,$support) = ('',''); + if ($type eq 'basic'){ + $support = ($row->{'smart'}) ? $row->{'smart'}: $row->{'smart-error'}; + $key = $smart_data->[$i][1]; + } + elsif ($type eq 'age'){$key = 'Old-Age';} + elsif ($type eq 'fail'){$key = 'Pre-Fail';} + $rows->[$j]{main::key($$num++,1,$l,$key)} = $support; + $b_found = 1; + next if $type eq 'basic'; + } + if ($type ne 'basic'){ + if ($smart_data->[$i][0] =~ /-a[vr]?$/){ + ($p,$m) = (1,$m_h); + } + elsif ($smart_data->[$i][0] =~ /-[ftvw]$/){ + ($p,$m) = (0,5); + } + else { + ($p,$m) = ($p_h,$m_h); + } + } + $rows->[$j]{main::key($$num++,$p,$m,$smart_data->[$i][1])} = $row->{$smart_data->[$i][0]}; + } + } + eval $end if $b_log; +} + +sub drive_data { + eval $start if $b_log; + my ($type) = @_; + my ($data,@devs); + my $num = 0; + my ($used) = (0); + PartitionItem::set_partitions() if !$loaded{'set-partitions'}; + RaidItem::raid_data() if !$loaded{'raid'}; + # see docs/inxi-partitions.txt > FILE SYSTEMS for more on remote/fuse fs + my $fs_skip = PartitionItem::get_filters('fs-exclude'); + foreach my $row (@partitions){ + # don't count remote/distributed/union type fs towards used + next if ($row->{'fs'} && $row->{'fs'} =~ /^$fs_skip$/); + # don't count non partition swap + next if ($row->{'swap-type'} && $row->{'swap-type'} ne 'partition'); + # in some cases, like redhat, mounted cdrom/dvds show up in partition data + next if ($row->{'dev-base'} && $row->{'dev-base'} =~ /^sr[0-9]+$/); + # this is used for specific cases where bind, or incorrect multiple mounts + # to same partitions, or btrfs sub volume mounts, is present. The value is + # searched for an earlier appearance of that partition and if it is present, + # the data is not added into the partition used size. + if ($row->{'dev-base'} !~ /^(\/\/|:\/)/ && !(grep {/$row->{'dev-base'}/} @devs)){ + $used += $row->{'used'} if $row->{'used'}; + push(@devs, $row->{'dev-base'}); + } + } + if (!$bsd_type){ + $data = proc_data($used); + } + else { + $data = bsd_data($used); + } + if ($b_admin){ + if ($alerts{'smartctl'} && $alerts{'smartctl'}->{'action'} eq 'use'){ + smartctl_data($data); + } + else { + $smartctl_missing = $alerts{'smartctl'}->{'message'}; + } + } + print Data::Dumper::Dumper $data if $dbg[13]; + main::log_data('data',"used: $used") if $b_log; + eval $end if $b_log; + return $data; +} + +sub proc_data { + eval $start if $b_log; + my ($used) = @_; + my (@drives); + my ($b_hdx,$logical_size,$size) = (0,0,0); + PartitionData::set() if !$bsd_type && !$loaded{'partition-data'}; + foreach my $row (@proc_partitions){ + if ($row->[-1] =~ /^(fio[a-z]+|[hsv]d[a-z]+|(ada|mmcblk|n[b]?d|nvme[0-9]+n)[0-9]+)$/){ + $b_hdx = 1 if $row->[-1] =~ /^hd[a-z]/; + push(@drives, { + 'firmware' => '', + 'id' => $row->[-1], + 'maj-min' => $row->[0] . ':' . $row->[1], + 'model' => '', + 'serial' => '', + 'size' => $row->[2], + 'spec' => '', + 'speed' => '', + 'temp' => '', + 'type' => '', + 'vendor' => '', + }); + } + # See http://lanana.org/docs/device-list/devices-2.6+.txt for major numbers used below + # See https://www.mjmwired.net/kernel/Documentation/devices.txt for kernel 4.x device numbers + # if ($row->[0] =~ /^(3|22|33|8)$/ && $row->[1] % 16 == 0) { + # $size += $row->[2]; + # } + # special case from this data: 8 0 156290904 sda + # 43 0 48828124 nbd0 + # note: known starters: vm: 252/253/254; grsec: 202; nvme: 259 mmcblk: 179 + # Note: with > 1 nvme drives, the minor number no longer passes the modulus tests, + # It appears to just increase randomly from the first 0 minor of the first nvme to + # nvme partitions to next nvme, so it only passes the test for the first nvme drive. + # note: 66 16 9766436864 sdah ; 65 240 9766436864 sdaf[maybe special case when double letters? + # Check /proc/devices for major number matches + if ($row->[0] =~ /^(3|8|22|33|43|6[5-9]|7[12]|12[89]|13[0-5]|179|202|252|253|254|259)$/ && + $row->[-1] =~ /(mmcblk[0-9]+|n[b]?d[0-9]+|nvme[0-9]+n[0-9]+|fio[a-z]+|[hsv]d[a-z]+)$/ && + ($row->[1] % 16 == 0 || $row->[1] % 16 == 8 || $row->[-1] =~ /(nvme[0-9]+n[0-9]+)$/)){ + $size += $row->[2]; + } + } + # raw_logical[0] is total of all logical raid/lvm found + # raw_logical[1] is total of all components found. If this totally fails, + # and we end up with raw logical less than used, give up + if (@raw_logical && $raw_logical[0] && (!$used || $raw_logical[0] > $used)){ + $logical_size = ($size - $raw_logical[1] + $raw_logical[0]); + } + # print Data::Dumper::Dumper \@drives; + main::log_data('data',"size: $size") if $b_log; + my $result = [{ + 'logical-size' => $logical_size, + 'logical-free' => $raw_logical[2], + 'size' => $size, + 'used' => $used, + }]; + # print Data::Dumper::Dumper \@data; + if ($show{'disk'}){ + unshift(@drives,@$result); + # print 'drives:', Data::Dumper::Dumper \@drives; + $result = proc_data_advanced($b_hdx,\@drives); + } + main::log_data('dump','@$result',$result) if $b_log; + print Data::Dumper::Dumper $result if $dbg[24]; + eval $end if $b_log; + return $result; +} + +sub proc_data_advanced { + eval $start if $b_log; + my ($b_hdx,$drives) = @_; + my ($i) = (0); + my ($disk_data,$scsi,@temp,@working); + my ($pt_cmd) = ('unset'); + my ($block_type,$file,$firmware,$model,$path, + $partition_scheme,$serial,$vendor,$working_path); + @by_id = main::globber('/dev/disk/by-id/*'); + # these do not contain any useful data, no serial or model name + # wwn-0x50014ee25fb50fc1 and nvme-eui.0025385b71b07e2e + # scsi-SATA_ST980815A_ simply repeats ata-ST980815A_; same with scsi-0ATA_WDC_WD5000L31X + # we also don't need the partition items + my $pattern = '^\/dev\/disk\/by-id\/(md-|lvm-|dm-|wwn-|nvme-eui|raid-|scsi-([0-9]ATA|SATA))|-part[0-9]+$'; + @by_id = grep {!/$pattern/} @by_id if @by_id; + # print join("\n", @by_id), "\n"; + @by_path = main::globber('/dev/disk/by-path/*'); + ## check for all ide type drives, non libata, only do it if hdx is in array + ## this is now being updated for new /sys type paths, this may handle that ok too + ## skip the first rows in the loops since that's the basic size/used data + if ($b_hdx){ + for ($i = 1; $i < scalar @$drives; $i++){ + $file = "/proc/ide/$drives->[$i]{'id'}/model"; + if ($drives->[$i]{'id'} =~ /^hd[a-z]/ && -e $file){ + $model = main::reader($file,'strip',0); + $drives->[$i]{'model'} = $model; + } + } + } + # scsi stuff + if ($file = $system_files{'proc-scsi'}){ + $scsi = scsi_data($file); + } + # print 'drives:', Data::Dumper::Dumper $drives; + for ($i = 1; $i < scalar @$drives; $i++){ + #next if $drives->[$i]{'id'} =~ /^hd[a-z]/; + ($block_type,$firmware,$model,$partition_scheme, + $serial,$vendor,$working_path) = ('','','','','','',''); + # print "$drives->[$i]{'id'}\n"; + $disk_data = disk_data_by_id("/dev/$drives->[$i]{'id'}"); + main::log_data('dump','@$disk_data', $disk_data) if $b_log; + if ($drives->[$i]{'id'} =~ /[sv]d[a-z]/){ + $block_type = 'sdx'; + $working_path = "/sys/block/$drives->[$i]{'id'}/device/"; + } + elsif ($drives->[$i]{'id'} =~ /mmcblk/){ + $block_type = 'mmc'; + $working_path = "/sys/block/$drives->[$i]{'id'}/device/"; + } + elsif ($drives->[$i]{'id'} =~ /nvme/){ + $block_type = 'nvme'; + # this results in: + # /sys/devices/pci0000:00/0000:00:03.2/0000:06:00.0/nvme/nvme0/nvme0n1 + # but we want to go one level down so slice off trailing nvme0n1 + $working_path = Cwd::abs_path("/sys/block/$drives->[$i]{'id'}"); + $working_path =~ s/nvme[^\/]*$//; + } + if ($working_path){ + $drives->[$i]{'abs-path'} = Cwd::abs_path($working_path); + } + main::log_data('data',"working path: $working_path") if $b_log; + if ($b_admin && -e "/sys/block/"){ + ($drives->[$i]{'block-logical'},$drives->[$i]{'block-physical'}) = @{block_data($drives->[$i]{'id'})}; + } + if ($block_type && $scsi && @$scsi && @by_id && ! -e "${working_path}model" && + ! -e "${working_path}name"){ + ## ok, ok, it's incomprehensible, search /dev/disk/by-id for a line that contains the + # discovered disk name AND ends with the correct identifier, sdx + # get rid of whitespace for some drive names and ids, and extra data after - in name + SCSI: + foreach my $row (@$scsi){ + if ($row->{'model'}){ + $row->{'model'} = (split(/\s*-\s*/,$row->{'model'}))[0]; + foreach my $id (@by_id){ + if ($id =~ /$row->{'model'}/ && "/dev/$drives->[$i]{'id'}" eq Cwd::abs_path($id)){ + $drives->[$i]{'firmware'} = $row->{'firmware'}; + $drives->[$i]{'model'} = $row->{'model'}; + $drives->[$i]{'vendor'} = $row->{'vendor'}; + last SCSI; + } + } + } + } + } + # note: an entire class of model names gets truncated by /sys so that should be the last + # in priority re tests. + elsif ((!@$disk_data || !$disk_data->[0]) && $block_type){ + # NOTE: while path ${working_path}vendor exists, it contains junk value, like: ATA + $path = "${working_path}model"; + if (-r $path){ + $model = main::reader($path,'strip',0); + $drives->[$i]{'model'} = $model if $model; + } + elsif ($block_type eq 'mmc' && -r "${working_path}name"){ + $path = "${working_path}name"; + $model = main::reader($path,'strip',0); + $drives->[$i]{'model'} = $model if $model; + } + } + if (!$drives->[$i]{'model'} && @$disk_data){ + $drives->[$i]{'model'} = $disk_data->[0] if $disk_data->[0]; + $drives->[$i]{'vendor'} = $disk_data->[1] if $disk_data->[1]; + } + # maybe rework logic if find good scsi data example, but for now use this + elsif ($drives->[$i]{'model'} && !$drives->[$i]{'vendor'}){ + $drives->[$i]{'model'} = main::clean_disk($drives->[$i]{'model'}); + my $result = disk_vendor($drives->[$i]{'model'},''); + $drives->[$i]{'model'} = $result->[1] if $result->[1]; + $drives->[$i]{'vendor'} = $result->[0] if $result->[0]; + } + if ($working_path){ + $path = "${working_path}removable"; + if (-r $path && main::reader($path,'strip',0)){ + $drives->[$i]{'type'} = 'Removable' ; # 0/1 value + } + } + my $peripheral = peripheral_data($drives->[$i]{'id'}); + # note: we only want to update type if we found a peripheral, otherwise preserve value + $drives->[$i]{'type'} = $peripheral if $peripheral; + # print "type:$drives->[$i]{'type'}\n"; + if ($extra > 0){ + $drives->[$i]{'temp'} = hdd_temp("$drives->[$i]{'id'}"); + if ($extra > 1){ + my $speed_data = drive_speed($drives->[$i]{'id'}); + # only assign if defined / not 0 + $drives->[$i]{'speed'} = $speed_data->[0] if $speed_data->[0]; + $drives->[$i]{'lanes'} = $speed_data->[1] if $speed_data->[1]; + if (@$disk_data && $disk_data->[2]){ + $drives->[$i]{'serial'} = $disk_data->[2]; + } + else { + $path = "${working_path}serial"; + if (-r $path){ + $serial = main::reader($path,'strip',0); + $drives->[$i]{'serial'} = $serial if $serial; + } + } + if ($extra > 2 && !$drives->[$i]{'firmware'}){ + my @fm = ('rev','fmrev','firmware_rev'); # 0 ~ default; 1 ~ mmc; 2 ~ nvme + foreach my $firmware (@fm){ + $path = "${working_path}$firmware"; + if (-r $path){ + $drives->[$i]{'firmware'} = main::reader($path,'strip',0); + last; + } + } + } + } + } + if ($extra > 2){ + my $result = disk_data_advanced($pt_cmd,$drives->[$i]{'id'}); + $pt_cmd = $result->[0]; + $drives->[$i]{'partition-table'} = uc($result->[1]) if $result->[1]; + if ($result->[2]){ + $drives->[$i]{'rotation'} = $result->[2]; + $drives->[$i]{'tech'} = 'HDD'; + } + elsif (($block_type && $block_type ne 'sdx') || + # note: this case could conceivabley be wrong for a spun down HDD + (defined $result->[2] && $result->[2] eq '0') || + ($drives->[$i]{'model'} && + $drives->[$i]{'model'} =~ /(flash|mmc|msata|\bm[\.-]?2\b|nvme|ssd|solid\s?state)/i)){ + $drives->[$i]{'tech'} = 'SSD'; + } + } + } + main::log_data('dump','$drives',$drives) if $b_log; + print Data::Dumper::Dumper $drives if $dbg[24]; + eval $end if $b_log; + return $drives; +} + +# camcontrol identify |grep ^serial (this might be (S)ATA specific) +# smartcl -i |grep ^Serial +# see smartctl; camcontrol devlist; gptid status; +sub bsd_data { + eval $start if $b_log; + my ($used) = @_; + my (@drives,@softraid,@temp); + my ($i,$logical_size,$size,$working) = (0,0,0,0); + my $file = $system_files{'dmesg-boot'}; + DiskDataBSD::set() if !$loaded{'disk-data-bsd'}; + # we don't want non dboot disk data from gpart or disklabel + if ($file && ! -r $file){ + $size = main::message('dmesg-boot-permissions'); + } + elsif (!$file){ + $size = main::message('dmesg-boot-missing'); + } + elsif (%disks_bsd){ + if ($sysctl{'softraid'}){ + @softraid = map {$_ =~ s/.*\(([^\)]+)\).*/$1/;$_} @{$sysctl{'softraid'}}; + } + foreach my $id (sort keys %disks_bsd){ + next if !$disks_bsd{$id} || !$disks_bsd{$id}->{'size'}; + $drives[$i]->{'id'} = $id; + $drives[$i]->{'firmware'} = ''; + $drives[$i]->{'temp'} = ''; + $drives[$i]->{'type'} = ''; + $drives[$i]->{'vendor'} = ''; + $drives[$i]->{'block-logical'} = $disks_bsd{$id}->{'block-logical'}; + $drives[$i]->{'block-physical'} = $disks_bsd{$id}->{'block-physical'}; + $drives[$i]->{'partition-table'} = $disks_bsd{$id}->{'scheme'}; + $drives[$i]->{'serial'} = $disks_bsd{$id}->{'serial'}; + $drives[$i]->{'size'} = $disks_bsd{$id}->{'size'}; + # don't count OpenBSD RAID/CRYPTO virtual disks! + if ($drives[$i]->{'size'} && (!@softraid || !(grep {$id eq $_} @softraid))){ + $size += $drives[$i]->{'size'} if $drives[$i]->{'size'}; + } + $drives[$i]->{'spec'} = $disks_bsd{$id}->{'spec'}; + $drives[$i]->{'speed'} = $disks_bsd{$id}->{'speed'}; + $drives[$i]->{'type'} = $disks_bsd{$id}->{'type'}; + # generate the synthetic model/vendor data + $drives[$i]->{'model'} = $disks_bsd{$id}->{'model'}; + if ($drives[$i]->{'model'}){ + my $result = disk_vendor($drives[$i]->{'model'},''); + $drives[$i]->{'vendor'} = $result->[0] if $result->[0]; + $drives[$i]->{'model'} = $result->[1] if $result->[1]; + } + if ($disks_bsd{$id}->{'duid'}){ + $drives[$i]->{'duid'} = $disks_bsd{$id}->{'duid'}; + } + if ($disks_bsd{$id}->{'partition-table'}){ + $drives[$i]->{'partition-table'} = $disks_bsd{$id}->{'partition-table'}; + } + $i++; + } + # raw_logical[0] is total of all logical raid/lvm found + # raw_logical[1] is total of all components found. If this totally fails, + # and we end up with raw logical less than used, give up + if (@raw_logical && $size && $raw_logical[0] && + (!$used || $raw_logical[0] > $used)){ + $logical_size = ($size - $raw_logical[1] + $raw_logical[0]); + } + if (!$size){ + $size = main::message('data-bsd'); + } + } + my $result = [{ + 'logical-size' => $logical_size, + 'logical-free' => $raw_logical[2], + 'size' => $size, + 'used' => $used, + }]; + #main::log_data('dump','$data',\@data) if $b_log; + if ($show{'disk'}){ + push(@$result,@drives); + # print 'data:', Data::Dumper::Dumper \@data; + } + main::log_data('dump','$result',$result) if $b_log; + print Data::Dumper::Dumper $result if $dbg[24]; + eval $end if $b_log; + return $result; +} + +# return indexes: 0 - age; 1 - basic; 2 - fail +# make sure to update if fields added in smartctl_data() +sub smartctl_fields { + eval $start if $b_log; + my $data = [ + [ # age + ['smart-gsense-error-rate-ar','g-sense error rate'], + ['smart-media-wearout-a','media wearout'], + ['smart-media-wearout-t','threshold'], + ['smart-media-wearout-f','alert'], + ['smart-multizone-errors-av','write error rate'], + ['smart-multizone-errors-t','threshold'], + ['smart-udma-crc-errors-ar','UDMA CRC errors'], + ['smart-udma-crc-errors-f','alert'], + ], + [ # basic + ['smart','SMART'], + ['smart-error','SMART Message'], + ['smart-support','state'], + ['smart-status','health'], + ['smart-power-on-hours','on'], + ['smart-cycles','cycles'], + ['smart-units-read','read-units'], + ['smart-units-written','written-units'], + ['smart-read','read'], + ['smart-written','written'], + ], + [ # fail + ['smart-end-to-end-av','end-to-end'], + ['smart-end-to-end-t','threshold'], + ['smart-end-to-end-f','alert'], + ['smart-raw-read-error-rate-av','read error rate'], + ['smart-raw-read-error-rate-t','threshold'], + ['smart-raw-read-error-rate-f','alert'], + ['smart-reallocated-sectors-av','reallocated sector'], + ['smart-reallocated-sectors-t','threshold'], + ['smart-reallocated-sectors-f','alert'], + ['smart-retired-blocks-av','retired block'], + ['smart-retired-blocks-t','threshold'], + ['smart-retired-blocks-f','alert'], + ['smart-runtime-bad-block-av','runtime bad block'], + ['smart-runtime-bad-block-t','threshold'], + ['smart-runtime-bad-block-f','alert'], + ['smart-seek-error-rate-av', 'seek error rate'], + ['smart-seek-error-rate-t', 'threshold'], + ['smart-seek-error-rate-f', 'alert'], + ['smart-spinup-time-av','spin-up time'], + ['smart-spinup-time-t','threshold'], + ['smart-spinup-time-f','alert'], + ['smart-ssd-life-left-av','life left'], + ['smart-ssd-life-left-t','threshold'], + ['smart-ssd-life-left-f','alert'], + ['smart-unused-reserve-block-av','unused reserve block'], + ['smart-unused-reserve-block-t','threshold'], + ['smart-unused-reserve-block-f','alert'], + ['smart-used-reserve-block-av','used reserve block'], + ['smart-used-reserve-block-t','threshold'], + ['smart-used-reserve-block-f','alert'], + ['smart-unknown-1-a','attribute'], + ['smart-unknown-1-v','value'], + ['smart-unknown-1-w','worst'], + ['smart-unknown-1-t','threshold'], + ['smart-unknown-1-f','alert'], + ['smart-unknown-2-a','attribute'], + ['smart-unknown-2-v','value'], + ['smart-unknown-2-w','worst'], + ['smart-unknown-2-t','threshold'], + ['smart-unknown-2-f','alert'], + ['smart-unknown-3-a','attribute'], + ['smart-unknown-3-v','value'], + ['smart-unknown-3-w','worst'], + ['smart-unknown-3-t','threshold'], + ['smart-unknown-4-f','alert'], + ['smart-unknown-4-a','attribute'], + ['smart-unknown-4-v','value'], + ['smart-unknown-4-w','worst'], + ['smart-unknown-4-t','threshold'], + ['smart-unknown-4-f','alert'], + ['smart-unknown-5-f','alert'], + ['smart-unknown-5-a','attribute'], + ['smart-unknown-5-v','value'], + ['smart-unknown-5-w','worst'], + ['smart-unknown-5-t','threshold'], + ['smart-unknown-5-f','alert'], + ] + ]; + eval $end if $b_log; + return $data; +} + +sub smartctl_data { + eval $start if $b_log; + my ($data) = @_; + my ($b_attributes,$b_intel,$b_kingston,$cmd,%holder,$id,@working,@result,@split); + my ($splitter,$num,$a,$f,$r,$t,$v,$w,$y) = (':\s*',0,0,8,1,5,3,4,6); # $y is type, $t threshold, etc + for (my $i = 0; $i < scalar @$data; $i++){ + next if !$data->[$i]{'id'}; + ($b_attributes,$b_intel,$b_kingston,$splitter,$num,$a,$r) = (0,0,0,':\s*',0,0,1); + %holder = (); + # print $data->[$i]{'id'},"\n"; + # m2 nvme failed on nvme0n1 drive id: + $id = $data->[$i]{'id'}; + $id =~ s/n[0-9]+$// if $id =~ /^nvme/; + # openbsd needs the 'c' partition, which is the entire disk + $id .= 'c' if $bsd_type && $bsd_type eq 'openbsd'; + $cmd = $alerts{'smartctl'}->{'path'} . " -AHi /dev/" . $id . ' 2>/dev/null'; + @result = main::grabber("$cmd", '', 'strip'); + main::log_data('dump','@result', \@result) if $b_log; # log before cleanup + @result = grep {!/^(smartctl|Copyright|==)/} @result; + print 'Drive:/dev/' . $id . ":\n", Data::Dumper::Dumper\@result if $dbg[12]; + if (scalar @result < 5){ + if (grep {/failed: permission denied/i} @result){ + $data->[$i]{'smart-permissions'} = main::message('tool-permissions','smartctl'); + } + elsif (grep {/unknown usb bridge/i} @result){ + $data->[$i]{'smart-error'} = main::message('smartctl-usb'); + } + # can come later in output too + elsif (grep {/A mandatory SMART command failed/i} @result){ + $data->[$i]{'smart-error'} = main::message('smartctl-command'); + } + elsif (grep {/open device.*Operation not supported by device/i} @result){ + $data->[$i]{'smart-error'} = main::message('smartctl-open'); + } + else { + $data->[$i]{'smart-error'} = main::message('tool-unknown-error','smartctl'); + } + next; + } + else { + foreach my $row (@result){ + if ($row =~ /^ID#/){ + $splitter = '\s+'; + $b_attributes = 1; + $a = 1; + $r = 9; + next; + } + @split = split(/$splitter/, $row); + next if !$b_attributes && ! defined $split[$r]; + # some cases where drive not in db threshhold will be: --- + # value is usually 0 padded which confuses perl. However this will + # make subsequent tests easier, and will strip off leading 0s + if ($b_attributes){ + $split[$t] = (main::is_numeric($split[$t])) ? int($split[$t]) : 0; + $split[$v] = (main::is_numeric($split[$v])) ? int($split[$v]) : 0; + } + # can occur later in output so retest it here + if ($split[$a] =~ /A mandatory SMART command failed/i){ + $data->[$i]{'smart-error'} = main::message('smartctl-command'); + } + ## DEVICE INFO ## + if ($split[$a] eq 'Device Model'){ + $b_intel = 1 if $split[$r] =~/\bintel\b/i; + $b_kingston = 1 if $split[$r] =~/kingston/i; + # usb/firewire/thunderbolt enclosure id method + if ($data->[$i]{'type'}){ + my $result = disk_vendor("$split[$r]"); + if ($data->[$i]{'model'} && $data->[$i]{'model'} ne $result->[1]){ + $data->[$i]{'drive-model'} = $result->[1]; + } + if ($data->[$i]{'vendor'} && $data->[$i]{'vendor'} ne $result->[0]){ + $data->[$i]{'drive-vendor'} = $result->[0]; + } + } + # fallback for very corner cases where primary model id failed + if (!$data->[$i]{'model'} && $split[$r]){ + my $result = disk_vendor("$split[$r]"); + $data->[$i]{'model'} = $result->[1] if $result->[1]; + $data->[$i]{'vendor'} = $result->[0] if $result->[0] && !$data->[$i]{'vendor'}; + } + } + elsif ($split[$a] eq 'Model Family'){ + my $result = disk_vendor("$split[$r]"); + $data->[$i]{'family'} = $result->[1] if $result->[1]; + # $data->[$i]{'family'} =~ s/$data->[$i]{'vendor'}\s*// if $data->[$i]{'vendor'}; + } + elsif ($split[$a] eq 'Firmware Version'){ + # 01.01A01 vs 1A01 + if ($data->[$i]{'firmware'} && $split[$r] !~ /$data->[$i]{'firmware'}/){ + $data->[$i]{'drive-firmware'} = $split[$r]; + } + elsif (!$data->[$i]{'firmware'}){ + $data->[$i]{'firmware'} = $split[$r]; + } + } + elsif ($split[$a] eq 'Rotation Rate'){ + if ($split[$r] !~ /^Solid/){ + $data->[$i]{'rotation'} = $split[$r]; + $data->[$i]{'rotation'} =~ s/\s*rpm$//i; + $data->[$i]{'tech'} = 'HDD'; + } + else { + $data->[$i]{'tech'} = 'SSD'; + } + } + elsif ($split[$a] eq 'Serial Number'){ + if (!$data->[$i]{'serial'}){ + $data->[$i]{'serial'} = $split[$r]; + } + elsif ($data->[$i]{'type'} && $split[$r] ne $data->[$i]{'serial'}){ + $data->[$i]{'drive-serial'} = $split[$r]; + } + } + elsif ($split[$a] eq 'SATA Version is'){ + if ($split[$r] =~ /SATA ([0-9.]+), ([0-9.]+ [^\s]+)(\(current: ([1-9.]+ [^\s]+)\))?/){ + $data->[$i]{'sata'} = $1; + $data->[$i]{'speed'} = $2 if !$data->[$i]{'speed'}; + } + } + # seen both Size and Sizes. Linux will usually have both, BSDs not physical + elsif ($split[$a] =~ /^Sector Sizes?$/){ + if ($data->[$i]{'type'} || !$data->[$i]{'block-logical'} || !$data->[$i]{'block-physical'}){ + if ($split[$r] =~ m|^([0-9]+) bytes logical/physical|){ + $data->[$i]{'block-logical'} = $1; + $data->[$i]{'block-physical'} = $1; + } + # 512 bytes logical, 4096 bytes physical + elsif ($split[$r] =~ m|^([0-9]+) bytes logical, ([0-9]+) bytes physical|){ + $data->[$i]{'block-logical'} = $1; + $data->[$i]{'block-physical'} = $2; + } + } + } + ## SMART STATUS/HEALTH ## + elsif ($split[$a] eq 'SMART support is'){ + if ($split[$r] =~ /^(Available|Unavailable) /){ + $data->[$i]{'smart'} = $1; + $data->[$i]{'smart'} = ($data->[$i]{'smart'} eq 'Unavailable') ? 'no' : 'yes'; + } + elsif ($split[$r] =~ /^(Enabled|Disabled)/){ + $data->[$i]{'smart-support'} = lc($1); + } + } + elsif ($split[$a] eq 'SMART overall-health self-assessment test result'){ + $data->[$i]{'smart-status'} = $split[$r]; + # seen nvme that only report smart health, not smart support + $data->[$i]{'smart'} = 'yes' if !$data->[$i]{'smart'}; + } + + ## DEVICE CONDITION: temp/read/write/power on/cycles ## + # Attributes data fields, sometimes are same syntax as info block:... + elsif ($split[$a] eq 'Power_Cycle_Count' || $split[$a] eq 'Power Cycles'){ + $data->[$i]{'smart-cycles'} = $split[$r] if $split[$r]; + } + elsif ($split[$a] eq 'Power_On_Hours' || $split[$a] eq 'Power On Hours' || + $split[$a] eq 'Power_On_Hours_and_Msec'){ + if ($split[$r]){ + $split[$r] =~ s/,//; + # trim off: h+0m+00.000s which is useless and at times empty anyway + $split[$r] =~ s/h\+.*$// if $split[$a] eq 'Power_On_Hours_and_Msec'; + # $split[$r] = 43; + if ($split[$r] =~ /^([0-9]+)$/){ + if ($1 > 9000){ + $data->[$i]{'smart-power-on-hours'} = int($1/(24*365)) . 'y ' . int($1/24)%365 . 'd ' . $1%24 . 'h'; + } + elsif ($1 > 100){ + $data->[$i]{'smart-power-on-hours'} = int($1/24) . 'd ' . $1%24 . 'h'; + } + else { + $data->[$i]{'smart-power-on-hours'} = $split[$r] . ' hrs'; + } + } + else { + $data->[$i]{'smart-power-on-hours'} = $split[$r]; + } + } + } + # 'Airflow_Temperature_Cel' like: 29 (Min/Max 14/43) so can't use -1 index + # Temperature like 29 Celsisu + elsif ($split[$a] eq 'Temperature_Celsius' || $split[$a] eq 'Temperature' || + $split[$a] eq 'Airflow_Temperature_Cel'){ + if (!$data->[$i]{'temp'} && $split[$r]){ + $data->[$i]{'temp'} = $split[$r]; + } + } + ## DEVICE USE: Reads/Writes ## + elsif ($split[$a] eq 'Data Units Read'){ + $data->[$i]{'smart-units-read'} = $split[$r]; + } + elsif ($split[$a] eq 'Data Units Written'){ + $data->[$i]{'smart-units-written'} = $split[$r]; + } + elsif ($split[$a] eq 'Host_Reads_32MiB'){ + $split[$r] = $split[$r] * 32 * 1024; + $data->[$i]{'smart-read'} = main::get_size($split[$r],'string'); + } + elsif ($split[$a] eq 'Host_Writes_32MiB'){ + $split[$r] = $split[$r] * 32 * 1024; + $data->[$i]{'smart-written'} = main::get_size($split[$r],'string'); + } + elsif ($split[$a] eq 'Lifetime_Reads_GiB'){ + $data->[$i]{'smart-read'} = $split[$r] . ' GiB'; + } + elsif ($split[$a] eq 'Lifetime_Writes_GiB'){ + $data->[$i]{'smart-written'} = $split[$r] . ' GiB'; + } + elsif ($split[$a] eq 'Total_LBAs_Read'){ + if (main::is_numeric($split[$r])){ + # blocks in bytes, so convert to KiB, the internal unit here + # reports in 32MiB units, sigh + if ($b_intel){ + $split[$r] = $split[$r] * 32 * 1024; + } + # reports in 1 GiB units, sigh + elsif ($b_kingston){ + $split[$r] = $split[$r] * 1024 * 1024; + } + # rare fringe cases, cygwin run as user, block size will not be found + # this is what it's supposed to refer to + elsif ($data->[$i]{'block-logical'}) { + $split[$r] = int($data->[$i]{'block-logical'} * $split[$r] / 1024); + } + if ($b_intel || $b_kingston || $data->[$i]{'block-logical'}){ + $data->[$i]{'smart-read'} = main::get_size($split[$r],'string'); + } + } + } + elsif ($split[$a] eq 'Total_LBAs_Written'){ + if (main::is_numeric($split[$r]) && $data->[$i]{'block-logical'}){ + # blocks in bytes, so convert to KiB, the internal unit here + # reports in 32MiB units, sigh + if ($b_intel){ + $split[$r] = $split[$r] * 32 * 1024; + } + # reports in 1 GiB units, sigh + elsif ($b_kingston){ + $split[$r] = $split[$r] * 1024 * 1024; + } + # rare fringe cases, cygwin run as user, block size will not be found + # this is what it's supposed to refer to, in byte blocks + elsif ($data->[$i]{'block-logical'}) { + $split[$r] = int($data->[$i]{'block-logical'} * $split[$r] / 1024); + } + if ($b_intel || $b_kingston || $data->[$i]{'block-logical'}){ + $data->[$i]{'smart-written'} = main::get_size($split[$r],'string'); + } + } + } + ## DEVICE OLD AGE ## + # 191 G-Sense_Error_Rate 0x0032 001 001 000 Old_age Always - 291 + elsif ($split[$a] eq 'G-Sense_Error_Rate'){ + # $data->[$i]{'smart-media-wearout'} = $split[$r]; + if ($b_attributes && $split[$r] > 100){ + $data->[$i]{'smart-gsense-error-rate-ar'} = $split[$r]; + } + } + elsif ($split[$a] eq 'Media_Wearout_Indicator'){ + # $data->[$i]{'smart-media-wearout'} = $split[$r]; + # seen case where they used hex numbers because values + # were in 47 billion range in hex. You can't hand perl an unquoted + # hex number that is > 2^32 without tripping a perl warning + if ($b_attributes && $split[$r] && !main::is_hex("$split[$r]") && $split[$r] > 0){ + $data->[$i]{'smart-media-wearout-av'} = $split[$v]; + $data->[$i]{'smart-media-wearout-t'} = $split[$t]; + $data->[$i]{'smart-media-wearout-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Multi_Zone_Error_Rate'){ + # note: all t values are 0 that I have seen + if (($split[$v] - $split[$t]) < 50){ + $data->[$i]{'smart-multizone-errors-av'} = $split[$v]; + $data->[$i]{'smart-multizone-errors-t'} = $split[$v]; + } + + } + elsif ($split[$a] eq 'UDMA_CRC_Error_Count'){ + if (main::is_numeric($split[$r]) && $split[$r] > 50){ + $data->[$i]{'smart-udma-crc-errors-ar'} = $split[$r]; + $data->[$i]{'smart-udma-crc-errors-f'} = main::message('smartctl-udma-crc') if $split[$r] > 500; + } + } + + ## DEVICE PRE-FAIL ## + elsif ($split[$a] eq 'Available_Reservd_Space'){ + # $data->[$i]{'smart-available-reserved-space'} = $split[$r]; + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-available-reserved-space-av'} = $split[$v]; + $data->[$i]{'smart-available-reserved-space-t'} = $split[$t]; + $data->[$i]{'smart-available-reserved-space-f'} = $split[$f] if $split[$f] ne '-'; + } + } + ## nvme splits these into two field/value sets + elsif ($split[$a] eq 'Available Spare'){ + $split[$r] =~ s/%$//; + $holder{'spare'} = int($split[$r]) if main::is_numeric($split[$r]); + } + elsif ($split[$a] eq 'Available Spare Threshold'){ + $split[$r] =~ s/%$//; + if ($holder{'spare'} && main::is_numeric($split[$r]) && $split[$r]/$holder{'spare'} > 0.92){ + $data->[$i]{'smart-available-reserved-space-ar'} = $holder{'spare'}; + $data->[$i]{'smart-available-reserved-space-t'} = int($split[$r]); + } + } + elsif ($split[$a] eq 'End-to-End_Error'){ + if ($b_attributes && int($split[$r]) > 0 && $split[$t]){ + $data->[$i]{'smart-end-to-end-av'} = $split[$v]; + $data->[$i]{'smart-end-to-end-t'} = $split[$t]; + $data->[$i]{'smart-end-to-end-f'} = $split[$f] if $split[$f] ne '-'; + } + } + # seen raw value: 0/8415644 + elsif ($split[$a] eq 'Raw_Read_Error_Rate'){ + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-raw-read-error-rate-av'} = $split[$v]; + $data->[$i]{'smart-raw-read-error-rate-t'} = $split[$t]; + $data->[$i]{'smart-raw-read-error-rate-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Reallocated_Sector_Ct'){ + if ($b_attributes && int($split[$r]) > 0 && $split[$t]){ + $data->[$i]{'smart-reallocated-sectors-av'} = $split[$v]; + $data->[$i]{'smart-reallocated-sectors-t'} = $split[$t]; + $data->[$i]{'smart-reallocated-sectors-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Retired_Block_Count'){ + if ($b_attributes && int($split[$r]) > 0 && $split[$t]){ + $data->[$i]{'smart-retired-blocks-av'} = $split[$v]; + $data->[$i]{'smart-retired-blocks-t'} = $split[$t]; + $data->[$i]{'smart-retired-blocks-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Runtime_Bad_Block'){ + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-runtime-bad-block-av'} = $split[$v]; + $data->[$i]{'smart-runtime-bad-block-t'} = $split[$t]; + $data->[$i]{'smart-runtime-bad-block-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Seek_Error_Rate'){ + # value 72; threshold either 000 or 30 + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-seek-error-rate-av'} = $split[$v]; + $data->[$i]{'smart-seek-error-rate-t'} = $split[$t]; + $data->[$i]{'smart-seek-error-rate-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Spin_Up_Time'){ + # raw will always be > 0 on spinning disks + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-spinup-time-av'} = $split[$v]; + $data->[$i]{'smart-spinup-time-t'} = $split[$t]; + $data->[$i]{'smart-spinup-time-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'SSD_Life_Left'){ + # raw will always be > 0 on spinning disks + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-ssd-life-left-av'} = $split[$v]; + $data->[$i]{'smart-ssd-life-left-t'} = $split[$t]; + $data->[$i]{'smart-ssd-life-left-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Unused_Rsvd_Blk_Cnt_Tot'){ + # raw will always be > 0 on spinning disks + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-unused-reserve-block-av'} = $split[$v]; + $data->[$i]{'smart-unused-reserve-block-t'} = $split[$t]; + $data->[$i]{'smart-unused-reserve-block-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($split[$a] eq 'Used_Rsvd_Blk_Cnt_Tot'){ + # raw will always be > 0 on spinning disks + if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){ + $data->[$i]{'smart-used-reserve-block-av'} = $split[$v]; + $data->[$i]{'smart-used-reserve-block-t'} = $split[$t]; + $data->[$i]{'smart-used-reserve-block-f'} = $split[$f] if $split[$f] ne '-'; + } + } + elsif ($b_attributes){ + if ($split[$y] eq 'Pre-fail' && ($split[$f] ne '-' || + ($split[$t] && $split[$v] && $split[$t]/$split[$v] > 0.92))){ + $num++; + $data->[$i]{'smart-unknown-' . $num . '-a'} = $split[$a]; + $data->[$i]{'smart-unknown-' . $num . '-v'} = $split[$v]; + $data->[$i]{'smart-unknown-' . $num . '-w'} = $split[$v]; + $data->[$i]{'smart-unknown-' . $num . '-t'} = $split[$t]; + $data->[$i]{'smart-unknown-' . $num . '-f'} = $split[$f] if $split[$f] ne '-'; + } + } + } + } + } + print Data::Dumper::Dumper $data if $dbg[19]; + eval $end if $b_log; +} + +# check for usb/firewire/[and thunderbolt when data found] +sub peripheral_data { + eval $start if $b_log; + my ($id) = @_; + my ($type) = (''); + # print "$id here\n"; + if (@by_id){ + foreach (@by_id){ + if ("/dev/$id" eq Cwd::abs_path($_)){ + # print "$id here\n"; + if (/usb-/i){ + $type = 'USB'; + } + elsif (/ieee1394-/i){ + $type = 'FireWire'; + } + last; + } + } + } + # note: sometimes with wwn- numbering usb does not appear in by-id but it does in by-path + if (!$type && @by_path){ + foreach (@by_path){ + if ("/dev/$id" eq Cwd::abs_path($_)){ + if (/usb-/i){ + $type = 'USB'; + } + elsif (/ieee1394--/i){ + $type = 'FireWire'; + } + last; + } + } + } + eval $end if $b_log; + return $type; +} + +sub disk_data_advanced { + eval $start if $b_log; + my ($set_cmd,$id) = @_; + my ($cmd,$pt,$program,@data); + my $advanced = []; + if ($set_cmd ne 'unset'){ + $advanced->[0] = $set_cmd; + } + else { + # runs as user, but is SLOW: udisksctl info -b /dev/sda + # line: org.freedesktop.UDisks2.PartitionTable: + # Type: dos + if ($program = main::check_program('udevadm')){ + $advanced->[0] = "$program info -q property -n "; + } + elsif ($b_root && -e "/lib/udev/udisks-part-id"){ + $advanced->[0] = "/lib/udev/udisks-part-id /dev/"; + } + elsif ($b_root && ($program = main::check_program('fdisk'))){ + $advanced->[0] = "$program -l /dev/"; + } + if (!$advanced->[0]){ + $advanced->[0] = 'na' + } + } + if ($advanced->[0] ne 'na'){ + $cmd = "$advanced->[0]$id 2>&1"; + main::log_data('cmd',$cmd) if $b_log; + @data = main::grabber($cmd); + # for pre ~ 2.30 fdisk did not show gpt, but did show gpt scheme error, so + # if no gpt match, it's dos = mbr + if ($cmd =~ /fdisk/){ + foreach (@data){ + if (/^WARNING:\s+GPT/){ + $advanced->[1] = 'gpt'; + last; + } + elsif (/^Disklabel\stype:\s*(.+)/i){ + $advanced->[1] = $1; + last; + } + } + $advanced->[1] = 'dos' if !$advanced->[1]; + } + else { + foreach (@data){ + if (/^(UDISKS_PARTITION_TABLE_SCHEME|ID_PART_TABLE_TYPE)/){ + my @working = split('=', $_); + $advanced->[1] = $working[1]; + } + elsif (/^ID_ATA_ROTATION_RATE_RPM/){ + my @working = split('=', $_); + $advanced->[2] = $working[1]; + } + last if defined $advanced->[1] && defined $advanced->[2]; + } + } + $advanced->[1] = 'mbr' if $advanced->[1] && lc($advanced->[1]) eq 'dos'; + } + eval $end if $b_log; + return $advanced; +} + +sub scsi_data { + eval $start if $b_log; + my ($file) = @_; + my @temp = main::reader($file); + my $scsi = []; + my ($firmware,$model,$vendor) = ('','',''); + foreach (@temp){ + if (/Vendor:\s*(.*)\s+Model:\s*(.*)\s+Rev:\s*(.*)/i){ + $vendor = $1; + $model = $2; + $firmware = $3; + } + if (/Type:/i){ + if (/Type:\s*Direct-Access/i){ + push(@$scsi, { + 'vendor' => $vendor, + 'model' => $model, + 'firmware' => $firmware, + }); + } + else { + ($firmware,$model,$vendor) = ('','',''); + } + } + } + main::log_data('dump','@$scsi', $scsi) if $b_log; + eval $end if $b_log; + return $scsi; +} + +# @b_id has already been cleaned of partitions, wwn-, nvme-eui +sub disk_data_by_id { + eval $start if $b_log; + my ($device) = @_; + my ($model,$serial,$vendor) = ('','',''); + my $disk_data = []; + foreach (@by_id){ + if ($device eq Cwd::abs_path($_)){ + my @data = split('_', $_); + last if scalar @data < 2; # scsi-3600508e000000000876995df43efa500 + $serial = pop @data if @data; + # usb-PNY_USB_3.0_FD_3715202280-0:0 + $serial =~ s/-[0-9]+:[0-9]+$//; + $model = join(' ', @data); + # get rid of the ata-|nvme-|mmc- etc + $model =~ s/^\/dev\/disk\/by-id\/([^-]+-)?//; + $model = main::clean_disk($model); + my $result = disk_vendor($model,$serial); + $vendor = $result->[0] if $result->[0]; + $model = $result->[1] if $result->[1]; + # print $device, '::', Cwd::abs_path($_),'::', $model, '::', $vendor, '::', $serial, "\n"; + @$disk_data = ($model,$vendor,$serial); + last; + } + } + eval $end if $b_log; + return $disk_data; +} + +## START DISK VENDOR BLOCK ## +# 0 - match pattern; 1 - replace pattern; 2 - vendor print; 3 - serial pattern +sub set_disk_vendors { + eval $start if $b_log; + $vendors = [ + ## MOST LIKELY/COMMON MATCHES ## + ['(Crucial|^(C[34]00$|(C300-)?CTF|(FC)?CT|DDAC|M4(\b|SSD))|-CT|Gizmo!)','Crucial','Crucial',''], + # H10 HBRPEKNX0202A NVMe INTEL 512GB + ['(\bINTEL\b|^(SSD(PAM|SA2)|HBR|(MEM|SSD)PEB?K|SSD(MCE|S[AC])))','\bINTEL\b','Intel',''], + # note: S[AV][1-9]\d can trigger false positives + ['(K(ING)?STON|^(OM8P|RBU|S[AV][1234]00|S[HMN]S|SK[CY]|SQ5|SS200|SVP|SS0|SUV|SNV|T52|T[AB]29|Ultimate CF)|V100|DataTraveler|DT\s?(DUO|Microduo|101)|HyperX|13fe\b)','(KINGSTON|13fe)','Kingston',''], # maybe SHS: SHSS37A SKC SUV + # must come before samsung MU. NOTE: toshiba can have: TOSHIBA_MK6475GSX: mush: MKNSSDCR120GB_ + ['(^MKN|Mushkin)','Mushkin','Mushkin',''], # MKNS + # MU = Multiple_Flash_Reader too risky: |M[UZ][^L] HD103SI HD start risky + # HM320II HM320II HM + ['(SAMSUNG|^(AWMB|[BC]DS20|[BC]WB|BJ[NT]|[BC]GND|CJ[NT]|CKT|CUT|[DG]3 Station|DUO\b|DUT|EB\dMW|GE4S5|[GS]2 Portable|GN|HD\d{3}[A-Z]{2}$|(HM|SP)\d{2}|HS\d|M[AB]G\d[FG]|MCC|MCBOE|MCG\d+GC|[CD]JN|MZ|^G[CD][1-9][QS]|P[BM]\d|(SSD\s?)?SM\s?841)|^SSD\s?[89]\d{2}\s(DCT|PRO|QVD|\d+[GT]B)|\bEVO\b|SV\d|[BE][A-Z][1-9]QT|YP\b|[CH]N-M|MMC[QR]E)','SAMSUNG','Samsung',''], # maybe ^SM, ^HM + # Android UMS Composite?U1 + ['(SanDisk|0781|^(A[BCD]LC[DE]|AFGCE|D[AB]4|DX[1-9]|Extreme|Firebird|S[CD]\d{2}G|SD(S[S]?[ADQ]|SL\d+G|SU\d|\sUltra)|SDW[1-9]|SE\d{2}|SEM[1-9]|\d[STU]|U(3\b|1\d0))|Clip Sport|Cruzer|iXpand|SN(\d+G|128|256)|SSD (Plus|U1[01]0) [1-9]|ULTRA\s(FIT|trek|II)|X[1-6]\d{2})','(SanDisk|0781)','SanDisk',''], + # these are HP/Sandisk cobranded. DX110064A5xnNMRI ids as HP and Sandisc + ['(^DX[1-9])','^(HP\b|SANDDISK)','Sandisk/HP',''], # ssd drive, must come before seagate ST test + # real, SSEAGATE Backup+; XP1600HE30002 | 024 HN (spinpoint) ; possible usb: 24AS + # ST[numbers] excludes other ST starting devices + ['([S]?SEAGATE|^((Barra|Fire)Cuda|BUP|EM\d{3}|Expansion|(ATA\s|HDD\s)?ST\d{2}|5AS|X[AFP])|Backup(\+|\s?Plus)\s?(Hub)?|DS2\d|Expansion Desk|FreeAgent|GoFlex|INIC|IronWolf|OneTouch|Slim\s?BK)','[S]?SEAGATE','Seagate',''], + ['^(WD|WL[0]9]|Western Digital|My (Book|Passport)|\d*LPCX|Elements|easystore|EA[A-Z]S|EARX|EFRX|EZRX|\d*EAVS|G[\s-]Drive|i HTS|0JD|JP[CV]|MD0|M000|\d+(BEV|(00)?AAK|AAV|AZL|EA[CD]S)|PC\sSN|SN530|SPZX|3200[AB]|2500[BJ]|20G2|5000[AB]|6400[AB]|7500[AB]|00[ABL][A-Z]{2}|SSC\b)','(^WDC|Western\s?Digital)','Western Digital',''], + # rare cases WDC is in middle of string + ['(\bWDC\b|1002FAEX)','','Western Digital',''], + + ## THEN BETTER KNOWN ONES ## + ['^Acer','^Acer','Acer',''], + # A-Data can be in middle of string + ['^(.*\bA-?DATA|ASP\d|AX[MN]|CH11|FX63|HV[1-9]|IM2|HD[1-9]|HDD\s?CH|IUM|SX\d|Swordfish)','A-?DATA','A-Data',''], + ['^(ASUS|ROG)','^ASUS','ASUS',''], # ROG ESD-S1C + # ATCS05 can be hitachi travelstar but not sure + ['^ATP','^ATP\b','ATP',''], + # Force MP500 + ['^(Corsair|Force\s|(Flash\s*)?(Survivor|Voyager)|Neutron|Padlock)','^Corsair','Corsair',''], + ['^(FUJITSU|MJA|MH[RTVWYZ]\d|MP|MAP\d|F\d00s?-)','^FUJITSU','Fujitsu',''], + # MAB3045SP shows as HP or Fujitsu, probably HP branded fujitsu + ['^(MAB\d)','^(HP\b|FUJITSU)','Fujitsu/HP',''], + # note: 2012: wdc bought hgst + ['^(DKR|HGST|Touro|54[15]0|7250|HC[CT]\d)','^HGST','HGST (Hitachi)',''], # HGST HUA + ['^((ATA\s)?Hitachi|HCS|HD[PST]|DK\d|IC|(HDD\s)?HT|HU|HMS|HDE|0G\d|IHAT)','Hitachi','Hitachi',''], + # vb: VB0250EAVER but clashes with vbox; HP_SSD_S700_120G ;GB0500EAFYL GB starter too generic? + ['^(HP\b|c350|DF\d|EG0\d{3}|EX9\d\d|G[BJ]\d|F[BK]|0-9]|HC[CPY]\d|MM\d{4}|[MV]B[0-6]|PSS|VO0|VK0|v\d{3}[bgorw]$|x\d{3}[w]$|XR\d{4})','^HP','HP',''], + ['^(Lexar|LSD|JumpDrive|JD\s?Firefly|LX\d|WorkFlow)','^Lexar','Lexar',''], # mmc-LEXAR_0xb016546c; JD Firefly; + # these must come before maxtor because STM + ['^STmagic','^STmagic','STmagic',''], + ['^(STMicro|SMI|CBA)','^(STMicroelectronics|SMI)','SMI (STMicroelectronics)',''], + # note M2 M3 is usually maxtor, but can be samsung. Can conflict with Team: TM\d{4}| + ['^(MAXTOR|Atlas|4R\d{2}|E0\d0L|L(250|500)|[KL]0[1-9]|Y\d{3}[A-Z]|STM\d|F\d{3}L)','^MAXTOR','Maxtor',''], + # OCZSSD2-2VTXE120G is OCZ-VERTEX2_3.5 + ['^(OCZ|Agility|APOC|D2|DEN|DEN|DRSAK|EC188|FTNC|GFGC|MANG|MMOC|NIMC|NIMR|PSIR|RALLY2|TALOS2|TMSC|TRSAK|VERTEX|Trion|Onyx|Vector[\s-]?15)','^OCZ[\s-]','OCZ',''], + ['^(OWC|Aura|Mercury[\s-]?(Electra|Extreme))','^OWC\b','OWC',''], + ['^(Philips|GoGear)','^Philips','Philips',''], + ['^PIONEER','^PIONEER','Pioneer',''], + ['^(PNY|Hook\s?Attache|SSD2SC|(SSD7?)?EP7|CS\d{3}|Elite\s?P)','^PNY\s','PNY','','^PNY'], + # note: get rid of: M[DGK] becasue mushkin starts with MK + # note: seen: KXG50ZNV512G NVMe TOSHIBA 512GB | THNSN51T02DUK NVMe TOSHIBA 1024GB + ['(TOSHIBA|TransMemory|KBG4|^((A\s)?DT01A|M[GKQ]\d|HDW|SA\d{2}G$|(008|016|032|064|128)G[379E][0-9A]$|[S]?TOS|THN)|0930|KSG\d)','S?(TOSHIBA|0930)','Toshiba',''], # scsi-STOSHIBA_STOR.E_EDITION_ + + ## LAST: THEY ARE SHORT AND COULD LEAD TO FALSE ID, OR ARE UNLIKELY ## + # unknown: AL25744_12345678; ADP may be usb 2.5" adapter; udisk unknown: Z1E6FTKJ 00AAKS + # SSD2SC240G726A10 MRS020A128GTS25C EHSAJM0016GB + ['^2[\s-]?Power','^2[\s-]?Power','2-Power',''], + ['^(3ware|9650SE)','^3ware','3ware (controller)',''], + ['^5ACE','^5ACE','5ACE',''], # could be seagate: ST316021 5ACE + ['^(Aar(vex)?|AX\d{2})','^AARVEX','AARVEX',''], + ['^(AbonMax|ASU\d)','^AbonMax','AbonMax',''], + ['^Acasis','^Acasis','Acasis (hub)',''], + ['^Acclamator','^Acclamator','Acclamator',''], + ['^(Actions|HS USB Flash|10d6)','^(Actions|10d6)','Actions',''], + ['^(A-?DATA|ED\d{3}|NH01|Swordfish|SU\d{3}|SX\d{3}|XM\d{2})','^A-?DATA','ADATA',''], + ['^Addlink','^Addlink','Addlink',''], + ['^(ADplus|SuperVer\b)','^ADplus','ADplus',''], + ['^ADTRON','^ADTRON','Adtron',''], + ['^(Advantech|SQF)','^Advantech','Advantech',''], + ['^AEGO','^AEGO','AEGO',''], + ['^AFOX','^AFOX','AFOX',''], + ['^AFTERSHOCK','^AFTERSHOCK','AFTERSHOCK',''], + ['^(Agile|AGI)','^(AGI|Agile\s?Gear\s?Int[a-z]*)','AGI',''], + ['^Aigo','^Aigo','Aigo',''], + ['^AirDisk','^AirDisk','AirDisk',''], + ['^Aireye','^Aireye','Aireye',''], + ['^Alcatel','^Alcatel','Alcatel',''], + ['^(Alcor(\s?Micro)?|058F)','^(Alcor(\s?Micro)?|058F)','Alcor Micro',''], + ['^Alfawise','^Alfawise','Alfawise',''], + ['(^ALKETRON|FireWizard)','^ALKETRON','ALKETRON',''], + ['^Android','^Android','Android',''], + ['^ANACOMDA','^ANACOMDA','ANACOMDA',''], + ['^Ant[\s_-]?Esports','^Ant[\s_-]?Esports','Ant Esports',''], + ['^Anucell','^Anucell','Anucell',''], + ['^Apotop','^Apotop','Apotop',''], + # must come before AP|Apacer + ['^(APPLE|iPod|SSD\sSM\d+[CEGT])','^APPLE','Apple',''], + ['^(AP|Apacer)','^Apacer','Apacer',''], + ['^(Apricom|SATAWire)','^Apricom','Apricom',''], + ['^(A-?RAM|ARSSD)','^A-?RAM','A-RAM',''], + ['^Arch','^Arch(\s*Memory)?','Arch Memory',''], + ['^(Asenno|AS[1-9])','^Asenno','Asenno',''], + ['^Asgard','^Asgard','Asgard',''], + ['^(ASM|2115)','^ASM','ASMedia',''],#asm1153e + ['^ASolid','^ASolid','ASolid',''], + # ASTC (Advanced Storage Technology Consortium) + ['^(AVEXIR|AVSSD)','^AVEXIR','Avexir',''], + ['^Axiom','^Axiom','Axiom',''], + ['^(Baititon|BT\d)','^Baititon','Baititon',''], + ['^Bamba','^Bamba','Bamba',''], + ['^(Beckhoff)','^Beckhoff','Beckhoff',''], + ['^Bell\b','^Bell','Packard Bell',''], + ['^(BelovedkaiAE|GhostPen)','^BelovedkaiAE','BelovedkaiAE',''], + ['^(BHT|WR20)','^BHT','BHT',''], + ['^(Big\s?Reservoir|B[RG][_\s-])','^Big\s?Reservoir','Big Reservoir',''], + ['^BIOSTAR','^BIOSTAR','Biostar',''], + ['^BIWIN','^BIWIN','BIWIN',''], + ['^Blackpcs','^Blackpcs','Blackpcs',''], + ['^(BlitzWolf|BW-?PSSD)','^BlitzWolf','BlitzWolf',''], + ['^(BlueRay|SDM\d)','^BlueRay','BlueRay',''], + ['^Bory','^Bory','Bory',''], + ['^Braveeagle','^Braveeagle','BraveEagle',''], + ['^(BUFFALO|BSC)','^BUFFALO','Buffalo',''], # usb: BSCR05TU2 + ['^Bugatek','^Bugatek','Bugatek',''], + ['^Bulldozer','^Bulldozer','Bulldozer',''], + ['^BUSlink','^BUSlink','BUSlink',''], + ['^(Canon|MP49)','^Canon','Canon',''], + ['^Centerm','^Centerm','Centerm',''], + ['^(Centon|DS pro)','^Centon','Centon',''], + ['^(CFD|CSSD)','^CFD','CFD',''], + ['^CHIPAL','^CHIPAL','CHIPAL',''], + ['^(Chipsbank|CHIPSBNK)','^Chipsbank','Chipsbank',''], + ['^(Chipfancie)','^Chipfancier','Chipfancier',''], + ['^Clover','^Clover','Clover',''], + ['^CODi','^CODi','CODi',''], + ['^Colorful\b','^Colorful','Colorful',''], + ['^CONSISTENT','^CONSISTENT','Consistent',''], + # note: www.cornbuy.com is both a brand and also sells other brands, like newegg + # addlink; colorful; goldenfir; kodkak; maxson; netac; teclast; vaseky + ['^Corn','^Corn','Corn',''], + ['^CnMemory|Spaceloop','^CnMemory','CnMemory',''], + ['^(Creative|(Nomad\s?)?MuVo)','^Creative','Creative',''], + ['^CSD','^CSD','CSD',''], + ['^CYX\b','^CYX','CYX',''], + ['^(Dane-?Elec|Z Mate)','^Dane-?Elec','DaneElec',''], + ['^DATABAR','^DATABAR','DataBar',''], + # Daplink vfs is an ARM software thing + ['^(Data\s?Memory\s?Systems|DMS)','^Data\s?Memory\s?Systems','Data Memory Systems',''], + ['^Dataram','^Dataram','Dataram',''], + ['^DELAIHE','^DELAIHE','DELAIHE',''], + # DataStation can be Trekstore or I/O gear + ['^Dell\b','^Dell','Dell',''], + ['^DeLOCK','^Delock(\s?products)?','Delock',''], + ['^Derler','^Derler','Derler',''], + ['^detech','^detech','DETech',''], + ['^DEXP','^DEXP','DEXP',''], + ['^DGM','^DGM\b','DGM',''], + ['^(DICOM|MAESTRO)','^DICOM','DICOM',''], + ['^Digifast','^Digifast','Digifast',''], + ['^DIGITAL\s?FILM','DIGITAL\s?FILM','Digital Film',''], + ['^(Digma|Run(\sY2)?\b)','^Digma','Digma',''], + ['^Dikom','^Dikom','Dikom',''], + ['^DINGGE','^DINGGE','DINGGE',''], + ['^Disain','^Disain','Disain',''], + ['^(Disco|Go-Infinity)','^Disco','Disco',''], + ['^(Disney|PIX[\s]?JR)','^Disney','Disney',''], + ['^(Doggo|DQ-|Sendisk|Shenchu)','^(doggo|Sendisk(.?Shenchu)?|Shenchu(.?Sendisk)?)','Doggo (SENDISK/Shenchu)',''], + ['^(Dogfish|M\.2 2242|Shark)','^Dogfish(\s*Technology)?','Dogfish Technology',''], + ['^DragonDiamond','^DragonDiamond','DragonDiamond',''], + ['^(DREVO\b|X1\s\d+[GT])','^DREVO','Drevo',''], + ['^DSS','^DSS DAHUA','DSS DAHUA',''], + ['^(Duex|DX\b)','^Duex','Duex',''], # DX\d may be starter for sandisk string + ['^(Dynabook|AE[1-3]00)','^Dynabook','Dynabook',''], + # DX1100 is probably sandisk, but could be HP, or it could be hp branded sandisk + ['^(Eaget|V8$)','^Eaget','Eaget',''], + ['^(Easy[\s-]?Memory)','^Easy[\s-]?Memory','Easy Memory',''], + ['^EDGE','^EDGE','EDGE Tech',''], + ['^Elecom','^Elecom','Elecom',''], + ['^Eluktro','^Eluktronics','Eluktronics',''], + ['^Emperor','^Emperor','Emperor',''], + ['^Emtec','^Emtec','Emtec',''], + ['^ENE\b','^ENE','ENE',''], + ['^Energy','^Energy','Energy',''], + ['^eNova','^eNOVA','eNOVA',''], + ['^Epson','^Epson','Epson',''], + ['^(Etelcom|SSD051)','^Etelcom','Etelcom',''], + ['^(Shenzhen\s)?Etopso(\sTechnology)?','^(Shenzhen\s)?Etopso(\sTechnology)?','Etopso',''], + ['^EURS','^EURS','EURS',''], + ['^eVAULT','^eVAULT','eVAULT',''], + ['^EVM','^EVM','EVM',''], + ['^eVtran','^eVtran','eVtran',''], + # NOTE: ESA3... may be IBM PCIe SAD card/drives + ['^(EXCELSTOR|r technology)','^EXCELSTOR( TECHNO(LOGY)?)?','ExcelStor',''], + ['^EXRAM','^EXRAM','EXRAM',''], + ['^EYOTA','^EYOTA','EYOTA',''], + ['^EZCOOL','^EZCOOL','EZCOOL',''], + ['^EZLINK','^EZLINK','EZLINK',''], + ['^Fantom','^Fantom( Drive[s]?)?','Fantom Drives',''], + ['^Fanxiang','^Fanxiang','Fanxiang',''], + ['^(Faspeed|K3[\s-])','^Faspeed','Faspeed',''], + ['^FASTDISK','^FASTDISK','FASTDISK',''], + ['^Festtive','^Festtive','Festtive',''], + ['^FiiO','^FiiO','FiiO',''], + ['^(FIKWOT|FS\d{3})','^FIKWOT','Kikwot',''], + ['^Fordisk','^Fordisk','Fordisk',''], + # FK0032CAAZP/FB160C4081 FK or FV can be HP but can be other things + ['^(FORESEE|B[123]0)|P900F|S900M','^FORESEE','Foresee',''], + ['^Founder','^Founder','Founder',''], + ['^(FOXLINE|FLD)','^FOXLINE','Foxline',''], # russian vendor? + ['^(GALAX\b|Gamer\s?L|TA\dD|Gamer[\s-]?V)','^GALAX','GALAX',''], + ['^Freecom','^Freecom(\sFreecom)?','Freecom',''], + ['^(FronTech)','^FronTech','Frontech',''], + ['^(Fuhler|FL-D\d{3})','^Fuhler','Fuhler',''], + ['^Gaiver','^Gaiver','Gaiver',''], + ['^Galaxy\b','^Galaxy','Galaxy',''], + ['^Gamer[_\s-]?Black','^Gamer[_\s-]?Black','Gamer Black',''], + ['^(Garmin|Fenix|Nuvi|Zumo)','^Garmin','Garmin',''], + ['^Geil','^Geil','Geil',''], + ['^GelL','^GelL','GelL',''], # typo for Geil? GelL ZENITH R3 120GB + ['^(Generic|A3A|G1J3|M0S00|SCA\d{2}|SCY|SLD|S0J\d|UY[567])','^Generic','Generic',''], + ['^(Genesis(\s?Logic)?|05e3)','(Genesis(\s?Logic)?|05e3)','Genesis Logic',''], + ['^Geonix','^Geonix','Geonix',''], + ['^Getrich','^Getrich','Getrich',''], + ['^(Gigabyte|GP-G)','^Gigabyte','Gigabyte',''], # SSD + ['^Gigastone','^Gigastone','Gigastone',''], + ['^Gigaware','^Gigaware','Gigaware',''], + ['^GJN','^GJN\b','GJN',''], + ['^(Gloway|FER\d)','^Gloway','Gloway',''], + ['^GLOWY','^GLOWY','Glowy',''], + ['^Goldendisk','^Goldendisk','Goldendisk',''], + ['^Goldenfir','^Goldenfir','Goldenfir',''], + ['^(Goldkey|GKH\d)','^Goldkey','Goldkey',''], + ['^Golden[\s_-]?Memory','^Golden[\s_-]?Memory','Golden Memory',''], + ['^(Goldkey|GKP)','^Goldkey','GoldKey',''], + ['^(Goline)','^Goline','Goline',''], + # Wilk Elektronik SA, poland + ['^(Wilk\s*)?(GOODRAM|GOODDRIVE|IR[\s-]?SSD|IRP|SSDPR|Iridium)','^GOODRAM','GOODRAM',''], + ['^(GreatWall|GW\d{3})','^GreatWall','GreatWall',''], + ['^(GreenHouse|GH\b)','^GreenHouse','GreenHouse',''], + ['^Gritronix','^Gritronixx?','Gritronix',''], + # supertalent also has FM: |FM + ['^(G[\.]?SKILL)','^G[\.]?SKILL','G.SKILL',''], + ['^G[\s-]*Tech','^G[\s-]*Tech(nology)?','G-Technology',''], + ['^(Gudga|GIM\d+|GVR\d)','^Gudga','Gudga',''], + ['^(Hajaan|HS[1-9])','^Haajan','Haajan',''], + ['^Haizhide','^Haizhide','Haizhide',''], + ['^(Hama|FlashPen\s?Fancy)','^Hama','Hama',''], + ['^(Hanye|Q60)','^Hanye','Hanye',''], + ['^HDC','^HDC\b','HDC',''], + ['^Hectron','^Hectron','Hectron',''], + ['^HEMA','^HEMA','HEMA',''], + ['(HEORIADY|^HX-0)','^HEORIADY','HEORIADY',''], + ['^(Hikvision|HKVSN|HS-SSD)','^Hikvision','Hikvision',''], + ['^Hi[\s-]?Level ','^Hi[\s-]?Level ','Hi-Level',''], # ^HI\b with no Level? + ['^(Hisense|H8G)','^Hisense','Hisense',''], + ['^Hoodisk','^Hoodisk','Hoodisk',''], + ['^HUAWEI','^HUAWEI','Huawei',''], + ['^Hypertec','^Hypertec','Hypertec',''], + ['^HyperX','^HyperX','HyperX',''], + ['^(HYSSD|HY-)','^HYSSD','HYSSD',''], + ['^(Hyundai|C2S\d|Sapphire)','^Hyundai','Hyundai',''], + ['^(IBM|DT|ESA[1-9]|ServeRaid)','^IBM','IBM',''], # M5110 too common + ['^IEI Tech','^IEI Tech(\.|nology)?( Corp(\.|oration)?)?','IEI Technology',''], + ['^(IGEL|UD Pocket)','^IGEL','IGEL',''], + ['^(Imation|Nano\s?Pro|HQT)','^Imation(\sImation)?','Imation',''], # Imation_ImationFlashDrive; TF20 is imation/tdk + ['^(IMC|Kanguru)','^IMC\b','IMC',''], + ['^(Inateck|FE20)','^Inateck','Inateck',''], + ['^(Inca\b|Npenterprise)','^Inca','Inca',''], + ['^(Indilinx|IND-)','^Indilinx','Indilinx',''], + ['^INDMEM','^INDMEM','INDMEM',''], + ['^(Infokit)','^Infokit','Infokit',''], + # note: Initio default controller, means master/slave jumper is off/wrong, not a vendor + ['^Inland','^Inland','Inland',''], + ['^(InnoDisk|DEM\d|Innolite|SATA\s?Slim|DRPS)','^InnoDisk( Corp.)?','InnoDisk',''], + ['(Innostor|1f75)','(Innostor|1f75)','Innostor',''], + ['(^Innovation|Innovation\s?IT)','Innovation(\s*IT)?','Innovation IT',''], + ['^Innovera','^Innovera','Innovera',''], + ['^(I\.?norys|INO-?IH])','^I\.?norys','I.norys',''], + ['^Intaiel','^Intaiel','Intaiel',''], + ['^(INM|Integral|V\s?Series)','^Integral(\s?Memory)?','Integral Memory',''], + ['^(lntenso|Intenso|(Alu|Basic|Business|Micro|c?Mobile|Premium|Rainbow|Slim|Speed|Twister|Ultra) Line|Rainbow)','^Intenso','Intenso',''], + ['^(I-?O Data|HDCL)','^I-?O Data','I-O Data',''], + ['^(INO-|i\.?norys)','^i\.?norys','i.norys',''], + ['^(Integrated[\s-]?Technology|IT\d+)','^Integrated[\s-]?Technology','Integrated Technology',''], + ['^(Iomega|ZIP\b|Clik!)','^Iomega','Iomega',''], + ['^(i[\s_-]?portable\b|ATCS)','^i[\s_-]?portable','i-Portable',''], + ['^ISOCOM','^ISOCOM','ISOCOM (Shenzhen Longsys Electronics)',''], + ['^iTE[\s-]*Tech','^iTE[\s-]*Tech(nology)?','iTE Tech',''], + ['^(James[\s-]?Donkey|JD\d)','^James[\s-]?Donkey','James Donkey',''], + ['^(Jaster|JS\d)','^Jaster','Jaster',''], + ['^JingX','^JingX','JingX',''], #JingX 120G SSD - not confirmed, but guessing + ['^Jingyi','^Jingyi','Jingyi',''], + # NOTE: ITY2 120GB hard to find + ['^JMicron','^JMicron(\s?Tech(nology)?)?','JMicron Tech',''], #JMicron H/W raid + ['^JSYERA','^JSYERA','Jsyera',''], + ['^(Jual|RX7)','^Jual','Jual',''], + ['^(J\.?ZAO|JZ)','^J\.?ZAO','J.ZAO',''], + ['^Kazuk','^Kazuk','Kazuk',''], + ['(\bKDI\b|^OM3P)','\bKDI\b','KDI',''], + ['^KEEPDATA','^KEEPDATA','KeepData',''], + ['^KLLISRE','^KLLISRE','KLLISRE',''], + ['^KimMIDI','^KimMIDI','KimMIDI',''], + ['^Kimtigo','^Kimtigo','Kimtigo',''], + ['^Kingbank','^Kingbank','Kingbank',''], + ['^(KingCell|KC\b)','^KingCell','KingCell',''], + ['^Kingchux[\s-]?ing','^Kingchux[\s-]?ing','Kingchuxing',''], + ['^KINGCOMP','^KINGCOMP','KingComp',''], + ['(KingDian|^NGF|S(280|400))','KingDian','KingDian',''], + ['^(Kingfast|TYFS)','^Kingfast','Kingfast',''], + ['^KingMAX','^KingMAX','KingMAX',''], + ['^Kingrich','^Kingrich','Kingrich',''], + ['^Kingsand','^Kingsand','Kingsand',''], + ['KING\s?SHA\s?RE','KING\s?SHA\s?RE','KingShare',''], + ['^(KingSpec|ACSC|C3000|KS[DQ]|MSH|N[ET]-\d|P3$|P4\b|PA[_-]?(18|25)|Q-180|T-(3260|64|128)|Z(\d\s|F\d))','^KingSpec','KingSpec',''], + ['^KingSSD','^KingSSD','KingSSD',''], + # kingwin docking, not actual drive + ['^(EZD|EZ-Dock)','','Kingwin Docking Station',''], + ['^Kingwin','^Kingwin','Kingwin',''], + ['^KLLISRE','^KLLISRE','KLLISRE',''], + ['(KIOXIA|^K[BX]G\d)','KIOXIA','KIOXIA',''], # company name comes after product ID + ['^(KLEVV|NEO\sN|CRAS)','^KLEVV','KLEVV',''], + ['^(Kodak|Memory\s?Saver)','^Kodak','Kodak',''], + ['^(KOOTION)','^KOOTION','KOOTION',''], + ['^(KUAIKAI|MSAM)','^KUAIKAI','KuaKai',''], + ['(KUIJIA|DAHUA)','^KUIJIA','KUIJIA',''], + ['^KUNUP','^KUNUP','KUNUP',''], + ['^KUU','^KUU\b','KUU',''], # KUU-128GB + ['^(Lacie|P92|itsaKey|iamaKey)','^Lacie','LaCie',''], + ['^LANBO','^LANBO','LANBO',''], + ['^LANTIC','^LANTIC','Lantic',''], + ['^Lapcare','^Lapcare','Lapcare',''], + ['^(Lazos|L-?ISS)','^Lazos','Lazos',''], + ['^LDLC','^LDLC','LDLC',''], + # LENSE30512GMSP34MEAT3TA / UMIS RPITJ256PED2MWX + ['^(LEN|UMIS|Think)','^Lenovo','Lenovo',''], + ['^RPFT','','Lenovo O.E.M.',''], + # JAJS300M120C JAJM600M256C JAJS600M1024C JAJS600M256C JAJMS600M128G + ['^(Leven|JAJ[MS])','^Leven','Leven',''], + ['^(LEQIXIANG)','^LEQIXIANG','Leqixiang',''], + ['^(LG\b|Xtick)','^LG','LG',''], + ['(LITE[-\s]?ON[\s-]?IT)','LITE[-]?ON[\s-]?IT','LITE-ON IT',''], # LITEONIT_LSS-24L6G + # PH6-CE240-L; CL1-3D256-Q11 NVMe LITEON 256GB + ['(LITE[-\s]?ON|^PH[1-9]|^DMT|^CV\d-|L(8[HT]|AT|C[HST]|JH|M[HST]|S[ST])-|^S900)','LITE[-]?ON','LITE-ON',''], + ['^LONDISK','^LONDISK','LONDISK',''], + ['^Longline','^Longline','Longline',''], + ['^LuminouTek','^LuminouTek','LuminouTek',''], + ['^(LSI|MegaRAID)','^LSI\b','LSI',''], + ['^(M-Systems|DiskOnKey)','^M-Systems','M-Systems',''], + ['^(Mach\s*Xtreme|MXSSD|MXU|MX[\s-])','^Mach\s*Xtreme','Mach Xtreme',''], + ['^(MacroVIP|MV(\d|GLD))','^MacroVIP','MacroVIP',''], # maybe MV alone + ['^Mainic','^Mainic','Mainic',''], + ['^(MARSHAL\b|MAL\d)','^MARSHAL','Marshal',''], + ['^Maxell','^Maxell','Maxell',''], + ['^Maximus','^Maximus','Maximus',''], + ['^MAXIO','^MAXIO','Maxio',''], + ['^Maxone','^Maxone','Maxone',''], + ['^MARVELL','^MARVELL','Marvell',''], + ['^Maxsun','^Maxsun','Maxsun',''], + ['^MDT\b','^MDT','MDT (rebuilt WD/Seagate)',''], # mdt rebuilds wd/seagate hdd + # MD1TBLSSHD, careful with this MD starter!! + ['^MD[1-9]','^Max\s*Digital','MaxDigital',''], + ['^Medion','^Medion','Medion',''], + ['^(MEDIAMAX|WL\d{2})','^MEDIAMAX','MediaMax',''], + ['^(Memorex|TravelDrive|TD\s?Classic)','^Memorex','Memorex',''], + ['^Mengmi','^Mengmi','Mengmi',''], + ['^MicroFrom','^MicroFrom','MicroFrom',''], + ['^MGTEC','^MGTEC','MGTEC',''], + # must come before micron + ['^(Mtron|MSP)','^Mtron','Mtron',''], + # note: C300/400 can be either micron or crucial, but C400 is M4 from crucial + ['(^(Micron|2200[SV]|MT|M5|(\d+|[CM]\d+)\sMTF)|00-MT)','^Micron','Micron',''],# C400-MTFDDAK128MAM + ['^(Microsoft|S31)','^Microsoft','Microsoft',''], + ['^MidasForce','^MidasForce','MidasForce',''], + ['^Milan','^Milan','Milan',''], + ['^(Mimoco|Mimobot)','^Mimoco','Mimoco',''], + ['^MINIX','^MINIX','MINIX',''], + ['^Miracle','^Miracle','Miracle',''], + ['^MLLSE','^MLLSE','MLLSE',''], + ['^Moba','^Moba','Moba',''], + # Monster MONSTER DIGITAL + ['^(Monster\s)+(Digital)?|OD[\s-]?ADVANCE','^(Monster\s)+(Digital)?','Monster Digital',''], + ['^Morebeck','^Morebeck','Morebeck',''], + ['^(Moser\s?Bear|MBIL)','^Moser\s?Bear','Moser Bear',''], + ['^(Motile|SSM\d)','^Motile','Motile',''], + ['^(Motorola|XT\d{4}|Moto[\s-]?[EG])','^Motorola','Motorola',''], + ['^Moweek','^Moweek','Moweek',''], + ['^Move[\s-]?Speed','^Move[\s-]?Speed','Move Speed',''], + #MRMAD4B128GC9M2C + ['^(MRMA|Memoright)','^Memoright','Memoright',''], + ['^MSI\b','^MSI\b','MSI',''], + ['^MTASE','^MTASE','MTASE',''], + ['^MTRON','^MTRON','MTRON',''], + ['^(MyDigitalSSD|BP[4X])','^MyDigitalSSD','MyDigitalSSD',''], # BP4 = BulletProof4 + ['^(Myson)','^Myson([\s-]?Century)?([\s-]?Inc\.?)?','Myson Century',''], + ['^(Natusun|i-flashdisk)','^Natusun','Natusun',''], + ['^(Neo\s*Forza|NFS\d)','^Neo\s*Forza','Neo Forza',''], + ['^(Netac|NS\d{3}|OnlyDisk|S535N)','^Netac','Netac',''], + ['^Newsmy','^Newsmy','Newsmy',''], + ['^NFHK','^NFHK','NFHK',''], + # NGFF is a type, like msata, sata + ['^Nik','^Nikimi','Nikimi',''], + ['^NOREL','^NOREL(SYS)?','NorelSys',''], + ['^(N[\s-]?Tech|NT\d)','^N[\s-]?Tec','N Tech',''], # coudl be ^NT alone + ['^ODYS','^ODYS','ODYS',''], + ['^Olympus','^Olympus','Olympus',''], + ['^Orico','^Orico','Orico',''], + ['^Ortial','^Ortial','Ortial',''], + ['^OSC','^OSC\b','OSC',''], + ['^(Ovation)','^Ovation','Ovation',''], + ['^oyunkey','^oyunkey','Oyunkey',''], + ['^PALIT','PALIT','Palit',''], # ssd + ['^Panram','^Panram','Panram',''], # ssd + ['^(Parker|TP00)','^Parker','Parker',''], + ['^(Pasoul|OASD)','^Pasoul','Pasoul',''], + ['^(Patriot|PS[8F]|P2\d{2}|PBT|VPN|Viper|Burst|Blast|Blaze|Pyro|Ignite)','^Patriot([-\s]?Memory)?','Patriot',''],#Viper M.2 VPN100 + ['^PERC\b','','Dell PowerEdge RAID Card',''], # ssd + ['(PHISON[\s-]?|ESR\d)','PHISON[\s-]?','Phison',''],# E12-256G-PHISON-SSD-B3-BB1 + ['^(Pichau[\s-]?Gaming|PG\d{2})','^Pichau[\s-]?Gaming','Pichau Gaming',''], + ['^Pioneer','Pioneer','Pioneer',''], + ['^Platinet','Platinet','Platinet',''], + ['^(PLEXTOR|PX-)','^PLEXTOR','Plextor',''], + ['^(Polion)','^Polion','Polion',''], + ['^(PQI|Intelligent\s?Stick|Cool\s?Drive)','^PQI','PQI',''], + ['^(Premiertek|QSSD|Quaroni)','^Premiertek','Premiertek',''], + ['^(-?Pretec|UltimateGuard)','-?Pretec','Pretec',''], + ['^(Prolific)','^Prolific( Technolgy Inc\.)?','Prolific',''], + # PS3109S9 is the result of an error condition with ssd controller: Phison PS3109 + ['^PUSKILL','^PUSKILL','Puskill',''], + ['QEMU','^\d*QEMU( QEMU)?','QEMU',''], # 0QUEMU QEMU HARDDISK + ['(^Quantum|Fireball)','^Quantum','Quantum',''], + ['(^QOOTEC|QMT)','^QOOTEC','QOOTEC',''], + ['^(QUMO|Q\dDT)','^QUMO','Qumo',''], + ['^QOPP','^QOPP','Qopp',''], + ['^Qunion','^Qunion','Qunion',''], + ['^(R[3-9]|AMD\s?(RADEON)?|Radeon)','AMD\s?(RADEON)?','AMD Radeon',''], # ssd + ['^(Ramaxel|RT|RM|RPF|RDM)','^Ramaxel','Ramaxel',''], + ['^RAMOS','^RAMOS','RAmos',''], + ['^(Ramsta|R[1-9])','^Ramsta','Ramsta',''], + ['^RCESSD','^RCESSD','RCESSD',''], + ['^(Realtek|RTL)','^Realtek','Realtek',''], + ['^(Reletech)','^Reletech','Reletech',''], # id: P400 but that's too short + ['^RENICE','^RENICE','Renice',''], + ['^RevuAhn','^RevuAhn','RevuAhn',''], + ['^(Ricoh|R5)','^Ricoh','Ricoh',''], + ['^RIM[\s]','^RIM','RIM',''], + ['^(Rococo|ITE\b|IT\d{4})','^Rococo','Rococo',''], + #RTDMA008RAV2BWL comes with lenovo but don't know brand + ['^Runcore','^Runcore','Runcore',''], + ['^Rundisk','^Rundisk','RunDisk',''], + ['^RZX','^RZX\b','RZX',''], + ['^(S3Plus|S3\s?SSD)','^S3Plus','S3Plus',''], + ['^(Sabrent|Rocket)','^Sabrent','Sabrent',''], + ['^Sage','^Sage(\s?Micro)?','Sage Micro',''], + ['^SAMSWEET','^SAMSWEET','Samsweet',''], + ['^SandForce','^SandForce','SandForce',''], + ['^Sannobel','^Sannobel','Sannobel',''], + ['^(Sansa|fuse\b)','^Sansa','Sansa',''], + # SATADOM can be innodisk or supermirco: dom == disk on module + # SATAFIRM is an ssd failure message + ['^(Sea\s?Tech|Transformer)','^Sea\s?Tech','Sea Tech',''], + ['^SigmaTel','^SigmaTel','SigmaTel',''], + # DIAMOND_040_GB + ['^(SILICON\s?MOTION|SM\d|090c)','^(SILICON\s?MOTION|090c)','Silicon Motion',''], + ['(Silicon[\s-]?Power|^SP[CP]C|^Silicon|^Diamond|^HasTopSunlightpeed)','Silicon[\s-]?Power','Silicon Power',''], + # simple drive could also maybe be hgst + ['^(Simple\s?Tech|Simple[\s-]?Drive)','^Simple\s?Tech','SimpleTech',''], + ['^(Simmtronics?|S[79]\d{2}|ZipX)','^Simmtronics?','Simmtronics',''], + ['^SINTECHI?','^SINTECHI?','SinTech (adapter)',''], + ['^SiS\b','^SiS','SiS',''], + ['Smartbuy','\s?Smartbuy','Smartbuy',''], # SSD Smartbuy 60GB; mSata Smartbuy 3 + # HFS128G39TND-N210A; seen nvme with name in middle + ['(SK\s?HYNIX|^HF[MS]|^H[BC]G|^BC\d{3}|^SC[234]\d\d\sm?SATA)','\s?SK\s?HYNIX','SK Hynix',''], + ['(hynix|^HAG\d|h[BC]8aP|PC\d{3})','hynix','Hynix',''],# nvme middle of string, must be after sk hynix + ['^SH','','Smart Modular Tech.',''], + ['^Skill','^Skill','Skill',''], + ['^(SMART( Storage Systems)?|TX)','^(SMART( Storage Systems)?)','Smart Storage Systems',''], + ['^Sobetter','^Sobetter','Sobetter',''], + ['^Solidata','^Solidata','Solidata',''], + ['^(SOLIDIGM|SSDPFK)','^SOLIDIGM\b','solidgm',''], + ['^(Sony|IM9|Microvalut|S[FR]-)','^Sony','Sony',''], + ['^(SSSTC|CL1-)','^SSSTC','SSSTC',''], + ['^(SST|SG[AN])','^SST\b','SST',''], + ['^STE[CK]','^STE[CK]','sTec',''], # wd bought this one + ['^STORFLY','^STORFLY','StorFly',''], + ['\dSUN\d','^SUN(\sMicrosystems)?','Sun Microsystems',''], + ['^Sundisk','^Sundisk','Sundisk',''], + ['^SUNEAST','^SUNEAST','SunEast',''], + ['^SuperMicro','^SuperMicro','SuperMicro',''], + ['^Supersonic','^Supersonic','Supersonic',''], + ['^SuperSSpeed','^SuperSSpeed','SuperSSpeed',''], + # NOTE: F[MNETU] not reliable, g.skill starts with FM too: + # Seagate ST skips STT. + ['^(Super\s*Talent|STT|F[HTZ]M\d|PicoDrive|Teranova)','','Super Talent',''], + ['^(SF|Swissbit)','^Swissbit','Swissbit',''], + # ['^(SUPERSPEED)','^SUPERSPEED','SuperSpeed',''], # superspeed is a generic term + ['^(SXMicro|NF8)','^SXMicro','SXMicro',''], + ['^Taisu','^Taisu','Taisu',''], + ['^(TakeMS|ColorLine)','^TakeMS','TakeMS',''], + ['^Tammuz','^Tammuz','Tammuz',''], + ['^TANDBERG','^TANDBERG','Tanberg',''], + ['^(TC[\s-]*SUNBOW|X3\s\d+[GT])','^TC[\s-]*SUNBOW','TCSunBow',''], + ['^(TDK|TF[1-9]\d|LoR)','^TDK','TDK',''], + ['^TEAC','^TEAC','TEAC',''], + ['^(TEAM|T[\s-]?Create|CX[12]\b|L\d\s?Lite|T\d{3,}[A-Z]|TM\d|(Dark\s?)?L3\b|T[\s-]?Force)','^TEAM(\s*Group)?','TeamGroup',''], + ['^(Teclast|CoolFlash)','^Teclast','Teclast',''], + ['^(tecmiyo)','^tecmiyo','TECMIYO',''], + ['^Teelkoou','^Teelkoou','Teelkoou',''], + ['^Tele2','^Tele2','Tele2',''], + ['^Teleplan','^Teleplan','Teleplan',''], + ['^TEUTONS','^TEUTONS','TEUTONS',''], + ['^(Textorm)','^Textorm','Textorm',''], # B5 too short + ['^(T(&|\s?and\s?)?G\d{3})','^T&G\b','T&G',''], + ['^THU','^THU','THU',''], + ['^Tiger[\s_-]?Jet','^Tiger[\s_-]?Jet','TigerJet',''], + ['^Tigo','^Tigo','Tigo',''], + ['^(Timetec|35TT)','^Timetec','Timetec',''], + ['^TKD','^TKD','TKD',''], + ['^TopSunligt','^TopSunligt','TopSunligt',''], # is this a typo? hard to know + ['^TopSunlight','^TopSunlight','TopSunlight',''], + ['^TOROSUS','^TOROSUS','Torosus',''], + ['(Transcend|^((SSD\s|F)?TS|EZEX|USDU)|1307|JetDrive|JetFlash)','\b(Transcend|1307)\b','Transcend',''], + ['^(TrekStor|DS (maxi|pocket)|DataStation)','^TrekStor','TrekStor',''], + ['^Turbox','^Turbox','Turbox',''], + ['^(TwinMOS|TW\d)','^TwinMOS','TwinMOS',''], + # note: udisk means usb disk, it's not a vendor ID + ['^UDinfo','^UDinfo','UDinfo',''], + ['^UMAX','^UMAX','UMAX',''], + ['^(UMIS|RP[IJ]TJ)','^UMIS','UMIS',''], + ['^USBTech','^USBTech','USBTech',''], + ['^(UNIC2)','^UNIC2','UNIC2',''], + ['^(UG|Unigen)','^Unigen','Unigen',''], + ['^(UNITEK)','^UNITEK','UNITEK',''], + ['^(USBest|UT16)','^USBest','USBest',''], + ['^(OOS[1-9]|Utania)','Utania','Utania',''], + ['^U-TECH','U-TECH','U-Tech',''], + ['^(Value\s?Tech|VTP\d)','^Value\s?Tech','ValueTech',''], + ['^VBOX','','VirtualBox',''], + ['^(Veno|Scorp)','^Veno','Veno',''], + ['^(Verbatim|STORE\s?\'?N\'?\s?(FLIP|GO)|Vi[1-9]|OTG\s?Tiny)','^Verbatim','Verbatim',''], + ['^V-GEN','^V-GEN','V-Gen',''], + ['^V[\s-]?(7|Seven)','^V[\s-]?(7|Seven)\b','VSeven',''], + ['^(Victorinox|Swissflash)','^Victorinox','Victorinox',''], + ['^(Visipro|SDVP)','^Visipro','Visipro',''], + ['^VISIONTEK','^VISIONTEK','VisionTek',''], + ['^VMware','^VMware','VMware',''], + ['^(Vseky|Vaseky|V8\d{2})','^Vaseky','Vaseky',''], # ata-Vseky_V880_350G_ + ['^(Walgreen|Infinitive)','^Walgreen','Walgreen',''], + ['^Walram','^Walram','WALRAM',''], + ['^Walton','^Walton','Walton',''], + ['^(Wearable|Air-?Stash)','^Wearable','Wearable',''], + ['^Wellcomm','^Wellcomm','Wellcomm',''], + ['^(wicgtyp|N[V]?900)','^wicgtyp','wicgtyp',''], + ['^Wilk','^Wilk','Wilk',''], + ['^(WinMemory|SWG\d)','^WinMemory','WinMemory',''], + ['^(Winton|WT\d{2})','^Winton','Winton',''], + ['^(WISE)','^WISE','WISE',''], + ['^WPC','^WPC','WPC',''], # WPC-240GB + ['^(Wortmann(\sAG)?|Terra\s?US)','^Wortmann(\sAG)?','Wortmann AG',''], + ['^(XDisk|X9\b)','^XDisk','XDisk',''], + ['^(XinTop|XT-)','^XinTop','XinTop',''], + ['^Xintor','^Xintor','Xintor',''], + ['^XPG','^XPG','XPG',''], + ['^XrayDisk','^XrayDisk','XrayDisk',''], + ['^Xstar','^Xstar','Xstar',''], + ['^(Xtigo)','^Xtigo','Xtigo',''], + ['^(XUM|HX\d)','^XUM','XUM',''], + ['^XUNZHE','^XUNZHE','XUNZHE',''], + ['^(Yangtze|ZhiTai|PC00[5-9]|SC00[1-9])','^Yangtze(\s*Memory)?','Yangtze Memory',''], + ['^(Yeyian|valk)','^Yeyian','Yeyian',''], + ['^(YingChu|YGC)','^YingChu','YingChu',''], + ['^(YUCUN|R880)','^YUCUN','YUCUN',''], + ['^(ZALMAN|ZM\b)','^ZALMAN','Zalman',''], + # Zao/J.Zau: marvell ssd controller + ['^ZXIC','^ZXIC','ZXIC',''], + ['^(Zebronics|ZEB)','^Zebronics','Zebronics',''], + ['^Zenfast','^Zenfast','Zenfast',''], + ['^Zenith','^Zenith','Zenith',''], + ['^ZEUSLAP','^ZEUSLAP','ZEUSLAP',''], + ['^ZEUSS','^ZEUSS','Zeuss',''], + ['^(Zheino|CHN|CNM)','^Zheino','Zheino',''], + ['^(Zotac|ZTSSD)','^Zotac','Zotac',''], + ['^ZSPEED','^ZSPEED','ZSpeed',''], + ['^ZTC','^ZTC','ZTC',''], + ['^ZTE','^ZTE','ZTE',''], + ['^(ZY|ZhanYao)','^ZhanYao([\s-]?data)','ZhanYao',''], + ['^(ASMT|2115)','^ASMT','ASMT (case)',''], + ]; + eval $end if $b_log; +} +## END DISK VENDOR BLOCK ## + +# receives space separated string that may or may not contain vendor data +sub disk_vendor { + eval $start if $b_log; + my ($model,$serial) = @_; + my ($vendor) = (''); + return if !$model; + # 0 - match pattern; 1 - replace pattern; 2 - vendor print; 3 - serial pattern + # Data URLs: inxi-resources.txt Section: DriveItem device_vendor() + # $model = 'H10 HBRPEKNX0202A NVMe INTEL 512GB'; + # $model = 'SD Ultra 3D 1TB'; + set_disk_vendors() if !$vendors; + # prefilter this one, some usb enclosurs and wrong master/slave hdd show default + $model =~ s/^Initio[\s_]//i; + foreach my $row (@$vendors){ + if ($model =~ /$row->[0]/i || ($row->[3] && $serial && $serial =~ /$row->[3]/)){ + $vendor = $row->[2]; + # Usually we want to assign N/A at output phase, maybe do this logic there? + if ($row->[1]){ + if ($model !~ m/$row->[1]$/i){ + $model =~ s/$row->[1]//i; + } + else { + $model = 'N/A'; + } + } + $model =~ s/^[\/\[\s_-]+|[\/\s_-]+$//g; + $model =~ s/\s\s/ /g; + last; + } + } + eval $end if $b_log; + return [$vendor,$model]; +} + +# Normally hddtemp requires root, but you can set user rights in /etc/sudoers. +# args: 0: /dev/ to be tested for +sub hdd_temp { + eval $start if $b_log; + my ($device) = @_; + my ($path) = (''); + my (@data,$hdd_temp); + $hdd_temp = hdd_temp_sys($device) if !$force{'hddtemp'} && -e "/sys/block/$device"; + if (!$hdd_temp){ + $device = "/dev/$device"; + if ($device =~ /nvme/i){ + if (!$b_nvme){ + $b_nvme = 1; + if ($path = main::check_program('nvme')){ + $nvme = $path; + } + } + if ($nvme){ + $device =~ s/n[0-9]//; + @data = main::grabber("$sudoas$nvme smart-log $device 2>/dev/null"); + foreach (@data){ + my @row = split(/\s*:\s*/, $_); + next if !$row[0]; + # other rows may have: Temperature sensor 1 : + if ($row[0] eq 'temperature'){ + $row[1] =~ s/\s*C//; + $hdd_temp = $row[1]; + last; + } + } + } + } + else { + if (!$b_hddtemp){ + $b_hddtemp = 1; + if ($path = main::check_program('hddtemp')){ + $hddtemp = $path; + } + } + if ($hddtemp){ + $hdd_temp = (main::grabber("$sudoas$hddtemp -nq -u C $device 2>/dev/null"))[0]; + } + } + $hdd_temp =~ s/\s?(Celsius|C)$// if $hdd_temp; + } + eval $end if $b_log; + return $hdd_temp; +} + +sub hdd_temp_sys { + eval $start if $b_log; + my ($device) = @_; + my ($hdd_temp,$hdd_temp_alt,%sensors,@data,@working); + my ($holder,$index) = ('',''); + my $path = "/sys/block/$device/device"; + my $path_trimmed = Cwd::abs_path("/sys/block/$device"); + # slice out the part of path that gives us hwmon in earlier kernel drivetemp + $path_trimmed =~ s%/(block|nvme)/.*$%% if $path_trimmed; + print "device: $device path: $path\n path_trimmed: $path_trimmed\n" if $dbg[21]; + return if ! -e $path && (!$path_trimmed || ! -e "$path_trimmed/hwmon"); + # first type, trimmed block,nvme (ata and nvme), 5.9 kernel: + # /sys/devices/pci0000:10/0000:10:08.1/0000:16:00.2/ata8/host7/target7:0:0/7:0:0:0/hwmon/hwmon5/ + # /sys/devices/pci0000:10/0000:10:01.2/0000:13:00.0/hwmon/hwmon0/ < nvme + # /sys/devices/pci0000:00/0000:00:01.3/0000:01:00.1/ata2/host1/target1:0:0/1:0:0:0/hwmon/hwmon3/ + # second type, 5.10+ kernel: + # /sys/devices/pci0000:20/0000:20:03.1/0000:21:00.0/nvme/nvme0/nvme0n1/device/hwmon1 + # /sys/devices/pci0000:00/0000:00:08.1/0000:0b:00.2/ata12/host11/target11:0:0/11:0:0:0/block/sdd/device/hwmon/hwmon1 + # we don't want these items: crit|max|min|lowest|highest + # original kernel 5.8/9 match for nvme and sd, 5.10+ match for sd + if (-e "$path_trimmed/hwmon/"){ + @data = main::globber("$path_trimmed/hwmon/hwmon*/temp*_{input,label}"); + } + # this case only happens if path_trimmed case isn't there, but leave in case + elsif (-e "$path/hwmon/"){ + @data = main::globber("$path/hwmon/hwmon*/temp*_{input,label}"); + } + # current match for nvme, but fails for 5.8/9 kernel nvme + else { + @data = main::globber("$path/hwmon*/temp*_{input,label}"); + } + # seeing long lag to read temp input files for some reason + foreach (sort @data){ + # print "file: $_\n"; + # print(main::reader($_,'',0),"\n"); + $path = $_; + # cleanup everything in front of temp, the path + $path =~ s/^.*\///; + @working = split('_', $path); + if ($holder ne $working[0]){ + $holder = $working[0]; + } + $sensors{$holder}->{$working[1]} = main::reader($_,'strip',0); + } + return if !%sensors; + if (keys %sensors == 1){ + if ($sensors{$holder}->{'input'} && main::is_numeric($sensors{$holder}->{'input'})){ + $hdd_temp = $sensors{$holder}->{'input'}; + } + } + else { + # nvme drives can have > 1 temp types, but composite is the one we want if there + foreach (keys %sensors){ + next if !$sensors{$_}->{'input'} || !main::is_numeric($sensors{$_}->{'input'}); + if ($sensors{$_}->{'label'} && $sensors{$_}->{'label'} eq 'Composite'){ + $hdd_temp = $sensors{$_}->{'input'}; + last; + } + else{ + $hdd_temp_alt = $sensors{$_}->{'input'}; + } + } + $hdd_temp = $hdd_temp_alt if !defined $hdd_temp && defined $hdd_temp_alt; + } + $hdd_temp = sprintf("%.1f", $hdd_temp/1000) if $hdd_temp; + main::log_data('data',"device: $device temp: $hdd_temp") if $b_log; + main::log_data('dump','%sensors',\%sensors) if $b_log; + print Data::Dumper::Dumper \%sensors if $dbg[21]; + eval $end if $b_log; + return $hdd_temp; +} + +# args: 0: block id +sub block_data { + eval $start if $b_log; + my ($id) = @_; + # 0: logical block size 1: disk physical block size/partition block size; + my ($block_log,$block_size) = (0,0); + # my $path_size = "/sys/block/$id/size"; + my $path_log_block = "/sys/block/$id/queue/logical_block_size"; + my $path_phy_block = "/sys/block/$id/queue/physical_block_size"; + # legacy system path + if (! -e $path_phy_block && -e "/sys/block/$id/queue/hw_sector_size"){ + $path_phy_block = "/sys/block/$id/queue/hw_sector_size"; + } + $block_log = main::reader($path_log_block,'',0) if -r $path_log_block; + $block_size = main::reader($path_phy_block,'',0) if -r $path_phy_block; + # print "l-b: $block_log p-b: $block_size raw: $size_raw\n"; + my $blocks = [$block_log,$block_size]; + main::log_data('dump','@blocks',$blocks) if $b_log; + eval $end if $b_log; + return $blocks; +} + +sub drive_speed { + eval $start if $b_log; + my ($device) = @_; + my ($b_nvme,$lanes,$speed); + my $working = Cwd::abs_path("/sys/class/block/$device"); + # print "$working\n"; + if ($working){ + my ($id); + # slice out the ata id: + # /sys/devices/pci0000:00:11.0/ata1/host0/target0: + if ($working =~ /^.*\/ata([0-9]+)\/.*/){ + $id = $1; + } + # /sys/devices/pci0000:00/0000:00:05.0/virtio1/block/vda + elsif ($working =~ /^.*\/virtio([0-9]+)\/.*/){ + $id = $1; + } + # /sys/devices/pci0000:10/0000:10:01.2/0000:13:00.0/nvme/nvme0/nvme0n1 + elsif ($working =~ /^.*\/(nvme[0-9]+)\/.*/){ + $id = $1; + $b_nvme = 1; + } + # do host last because the strings above might have host as well as their search item + # 0000:00:1f.2/host3/target3: increment by 1 sine ata starts at 1, but host at 0 + elsif ($working =~ /^.*\/host([0-9]+)\/.*/){ + $id = $1 + 1 if defined $1; + } + # print "$working $id\n"; + if (defined $id){ + if ($b_nvme){ + $working = "/sys/class/nvme/$id/device/max_link_speed"; + $speed = main::reader($working,'',0) if -r $working; + if (defined $speed && $speed =~ /([0-9\.]+)\sGT\/s/){ + $speed = $1; + # pcie1: 2.5 GT/s; pcie2: 5.0 GT/s; pci3: 8 GT/s + # NOTE: PCIe 3 stopped using the 8b/10b encoding but a sample pcie3 nvme has + # rated speed of GT/s * .8 anyway. GT/s * (128b/130b) + $speed = ($speed <= 5) ? $speed * .8 : $speed * 128/130; + $speed = sprintf("%.1f",$speed) if $speed; + $working = "/sys/class/nvme/$id/device/max_link_width"; + $lanes = main::reader($working,'',0) if -r $working; + $lanes ||= 1; + # https://www.edn.com/electronics-news/4380071/What-does-GT-s-mean-anyway- + # https://www.anandtech.com/show/2412/2 + # http://www.tested.com/tech/457440-theoretical-vs-actual-bandwidth-pci-express-and-thunderbolt/ + # PCIe 1,2 use “8b/10b” encoding: eight bits are encoded into a 10-bit symbol + # PCIe 3,4,5 use "128b/130b" encoding: 128 bits are encoded into a 130 bit symbol + $speed = ($speed * $lanes) . " Gb/s"; + } + } + else { + $working = "/sys/class/ata_link/link$id/sata_spd"; + $speed = main::reader($working,'',0) if -r $working; + $speed = main::clean_disk($speed) if $speed; + $speed =~ s/Gbps/Gb\/s/ if $speed; + } + } + } + # print "$working $speed\n"; + eval $end if $b_log; + return [$speed,$lanes]; +} +} + +## GraphicItem +{ +package GraphicItem; +my ($b_primary,$b_wayland_data,%graphics,%mesa_drivers, +$monitor_ids,$monitor_map); +my ($gpu_amd,$gpu_intel,$gpu_nv); + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + if (%risc && !$use{'soc-gfx'} && !$use{'pci-tool'}){ + my $key = 'Message'; + @$rows = ({ + main::key($num++,0,1,$key) => main::message('risc-pci',$risc{'id'}) + }); + } + else { + device_output($rows); + ($gpu_amd,$gpu_intel,$gpu_nv) = (); + if (!@$rows){ + my $key = 'Message'; + my $message = ''; + my $type = 'pci-card-data'; + if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){ + $type = 'pci-card-data-root'; + } + elsif (!$bsd_type && !%risc && !$pci_tool && + $alerts{'lspci'}->{'action'} && + $alerts{'lspci'}->{'action'} eq 'missing'){ + $message = $alerts{'lspci'}->{'message'}; + } + $message = main::message($type,'') if !$message; + @$rows = ({ + main::key($num++,0,1,$key) => $message + }); + } + } + # note: not perfect, but we need usb gfx to show for all types, soc, pci, etc + usb_output($rows); + display_output($rows); + display_api($rows); + (%graphics,$monitor_ids,$monitor_map) = (); + eval $end if $b_log; + return $rows; +} + +## DEVICE OUTPUT ## +sub device_output { + eval $start if $b_log; + return if !$devices{'graphics'}; + my $rows = $_[0]; + my ($j,$num) = (0,1); + my ($bus_id); + set_monitors_sys() if !$monitor_ids && -e '/sys/class/drm'; + foreach my $row (@{$devices{'graphics'}}){ + $num = 1; + # print "$row->[0] $row->[3]\n"; + # not using 3D controller yet, needs research: |3D controller |display controller + # note: this is strange, but all of these can be either a separate or the same + # card. However, by comparing bus id, say: 00:02.0 we can determine that the + # cards are either the same or different. We want only the .0 version as a valid + # card. .1 would be for example: Display Adapter with bus id x:xx.1, not the right one + next if $row->[3] != 0; + # print "$row->[0] $row->[3]\n"; + $j = scalar @$rows; + my $device = main::trimmer($row->[4]); + ($bus_id) = (); + $device = ($device) ? main::clean_pci($device,'output') : 'N/A'; + # have seen absurdly verbose card descriptions, with non related data etc + if (length($device) > 85 || $size{'max-cols'} < 110){ + $device = main::filter_pci_long($device); + } + push(@$rows, { + main::key($num++,1,1,'Device') => $device, + },); + if ($extra > 0 && $use{'pci-tool'} && $row->[12]){ + my $item = main::get_pci_vendor($row->[4],$row->[12]); + $rows->[$j]{main::key($num++,0,2,'vendor')} = $item if $item; + } + push(@{$graphics{'gpu-drivers'}},$row->[9]) if $row->[9]; + my $driver = ($row->[9]) ? $row->[9]:'N/A'; + $rows->[$j]{main::key($num++,1,2,'driver')} = $driver; + if ($row->[9] && !$bsd_type){ + my $version = main::get_module_version($row->[9]); + $version ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'v')} = $version; + } + if ($b_admin && $row->[10]){ + $row->[10] = main::get_driver_modules($row->[9],$row->[10]); + $rows->[$j]{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10]; + } + if ($extra > 0 && $row->[5] && $row->[6] && + $row->[5] =~ /^(1002|10de|12d2|8086)$/){ + # legacy: 1180 0df7 0029 current: 13bc 1c8d 24b1 regex: H100, RTX 4000 + # ($row->[5],$row->[6],$row->[4]) = ('12de','0029',''); + my ($gpu_data,$b_nv) = gpu_data($row->[5],$row->[6],$row->[4]); + if (!$bsd_type && $b_nv && $b_admin){ + if ($gpu_data->{'legacy'}){ + $rows->[$j]{main::key($num++,1,3,'non-free')} = ''; + $rows->[$j]{main::key($num++,0,4,'series')} = $gpu_data->{'series'}; + $rows->[$j]{main::key($num++,0,4,'status')} = $gpu_data->{'status'}; + if ($gpu_data->{'xorg'}){ + $rows->[$j]{main::key($num++,1,4,'last')} = ''; + $rows->[$j]{main::key($num++,0,5,'release')} = $gpu_data->{'release'}; + $rows->[$j]{main::key($num++,0,5,'kernel')} = $gpu_data->{'kernel'}; + $rows->[$j]{main::key($num++,0,5,'xorg')} = $gpu_data->{'xorg'}; + } + } + else { + $gpu_data->{'series'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,3,'non-free')} = $gpu_data->{'series'}; + $rows->[$j]{main::key($num++,0,4,'status')} = $gpu_data->{'status'}; + } + } + if ($gpu_data->{'arch'}){ + $rows->[$j]{main::key($num++,1,2,'arch')} = $gpu_data->{'arch'}; + # we don't need to see repeated values here, but usually code is different. + if ($b_admin && $gpu_data->{'code'} && + $gpu_data->{'code'} ne $gpu_data->{'arch'}){ + $rows->[$j]{main::key($num++,0,3,'code')} = $gpu_data->{'code'}; + } + if ($b_admin && $gpu_data->{'process'}){ + $rows->[$j]{main::key($num++,0,3,'process')} = $gpu_data->{'process'}; + } + if ($b_admin && $gpu_data->{'years'}){ + $rows->[$j]{main::key($num++,0,3,'built')} = $gpu_data->{'years'}; + } + } + } + if ($extra > 0){ + $bus_id = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]"; + if ($extra > 1 && $bus_id ne 'N/A'){ + main::get_pcie_data($bus_id,$j,$rows,\$num,'gpu'); + } + if ($extra > 1 && $monitor_ids){ + port_output($bus_id,$j,$rows,\$num); + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + } + if ($extra > 1){ + my $chip_id = main::get_chip_id($row->[5],$row->[6]); + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $chip_id; + } + if ($extra > 2 && $row->[1]){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = $row->[1]; + } + if (!$bsd_type && $extra > 0 && $bus_id ne 'N/A' && $bus_id =~ /\.0$/){ + my $temp = main::get_device_temp($bus_id); + if ($temp){ + $rows->[$j]{main::key($num++,0,2,'temp')} = $temp . ' C'; + } + } + # print "$row->[0]\n"; + } + eval $end if $b_log; +} + +sub usb_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@ids,$driver,$path_id,$product,@temp2); + my ($j,$num) = (0,1); + return if !$usb{'graphics'}; + foreach my $row (@{$usb{'graphics'}}){ + # these tests only work for /sys based usb data for now + $num = 1; + $j = scalar @$rows; + # make sure to reset, or second device trips last flag + ($driver,$path_id,$product) = ('','',''); + $product = main::clean($row->[13]) if $row->[13]; + $driver = $row->[15] if $row->[15]; + $path_id = $row->[2] if $row->[2]; + $product ||= 'N/A'; + # note: for real usb video out, no generic drivers? webcams may have one though + if (!$driver){ + if ($row->[14] eq 'audio-video'){ + $driver = 'N/A'; + } + else { + $driver = 'N/A'; + } + } + push(@$rows, { + main::key($num++,1,1,'Device') => $product, + main::key($num++,0,2,'driver') => $driver, + main::key($num++,1,2,'type') => 'USB', + },); + if ($extra > 0){ + if ($extra > 1){ + $row->[8] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'rev')} = $row->[8]; + if ($row->[17]){ + $rows->[$j]{main::key($num++,0,3,'speed')} = $row->[17]; + } + if ($row->[24]){ + $rows->[$j]{main::key($num++,0,3,'lanes')} = $row->[24]; + } + if ($b_admin && $row->[22]){ + $rows->[$j]{main::key($num++,0,3,'mode')} = $row->[22]; + } + } + my $bus_id = "$path_id:$row->[1]"; + if ($monitor_ids){ + port_output($bus_id,$j,$rows,\$num); + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + if ($extra > 1){ + $row->[7] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $row->[7]; + } + if ($extra > 2){ + if (defined $row->[5] && $row->[5] ne ''){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]"; + } + if ($row->[16]){ + $rows->[$j]{main::key($num++,0,2,'serial')} = main::filter($row->[16]); + } + } + } + } + eval $end if $b_log; +} + +# args: $rows, $num by ref +sub port_output { + my ($bus_id,$j,$rows,$num) = @_; + my (@connected,@disabled,@empty); + foreach my $id (keys %$monitor_ids){ + next if !$monitor_ids->{$id}{'status'}; + if ($monitor_ids->{$id}{'path'} =~ m|\Q$bus_id/drm/\E|){ + # status can be: connected|disconnected|unknown + if ($monitor_ids->{$id}{'status'} eq 'connected'){ + if ($monitor_ids->{$id}{'enabled'} eq 'enabled'){ + push(@connected,$id); + } + else { + push(@disabled,$id); + } + } + else { + push(@empty,$id); + } + } + } + if (@connected || @empty || @disabled){ + my ($off,$active,$unused); + my $split = ','; # add space if many to allow for wrapping + $rows->[$j]{main::key($$num++,1,2,'ports')} = ''; + $split = ', ' if scalar @connected > 3; + $active = (@connected) ? join($split,sort @connected) : 'none'; + $rows->[$j]{main::key($$num++,0,3,'active')} = $active; + if (@disabled){ + $split = (scalar @disabled > 3) ? ', ' : ','; + $off = join($split,sort @disabled); + $rows->[$j]{main::key($$num++,0,3,'off')} = $off; + } + $split = (scalar @empty > 3) ? ', ' : ','; + $unused = (@empty) ? join($split,sort @empty) : 'none'; + $rows->[$j]{main::key($$num++,0,3,'empty')} = $unused; + } +} + +## DISPLAY OUTPUT ## +sub display_output(){ + eval $start if $b_log; + my $rows = $_[0]; + my ($num,$j) = (0,scalar @$rows); + # note: these may not always be set, they won't be out of X, for example + display_protocol(); + # get rid of all inactive or disabled monitor port ids + set_active_monitors() if $monitor_ids; + $graphics{'protocol'} = 'wayland' if $force{'wayland'}; + # note, since the compositor is the server with wayland, always show it + if ($extra > 1 || $graphics{'protocol'} eq 'wayland'){ + set_compositor_data(); + } + if ($b_display){ + # Add compositors as data sources found + if ($graphics{'protocol'} eq 'wayland'){ + display_data_wayland(); + } + if (!$b_wayland_data){ + display_data_x() if !$force{'wayland'}; + } + } + else { + $graphics{'tty'} = tty_data(); + } + # no xdpyinfo installed + # undef $graphics{'x-server'}; + # Completes X server data if no previous detections, tests/adds xwayland + display_server_data(); + if (!defined $graphics{'display-id'} && defined $ENV{'DISPLAY'}){ + $graphics{'display-id'} = $ENV{'DISPLAY'}; + } + # print Data::Dumper::Dumper $graphics{'x-server'}; + # print Data::Dumper::Dumper \%graphics; + if (%graphics){ + my ($driver_note,$resolution,$server_string) = ('','',''); + my ($b_screen_monitors,$x_drivers); + $x_drivers = display_drivers_x() if !$force{'wayland'}; + # print 'result: ', Data::Dumper::Dumper $x_drivers; + # print "$graphics{'x-server'} $graphics{'x-version'} $graphics{'x-vendor-release'}","\n"; + if ($graphics{'x-server'}){ + $server_string = $graphics{'x-server'}->[0][0]; + # print "$server_string\n"; + } + if (!$graphics{'protocol'} && !$server_string && !$graphics{'x-server'} && + !@$x_drivers){ + $server_string = main::message('display-server'); + push(@$rows,{ + main::key($num++,1,1,'Display') => '', + main::key($num++,0,2,'server') => $server_string, + }); + } + else { + $server_string ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Display') => $graphics{'protocol'}, + main::key($num++,1,2,'server') => $server_string, + }); + if ($graphics{'x-server'} && $graphics{'x-server'}->[0][1]){ + $rows->[$j]{main::key($num++,0,3,'v')} = $graphics{'x-server'}->[0][1]; + } + if ($graphics{'x-server'} && $graphics{'x-server'}->[1][0]){ + $rows->[$j]{main::key($num++,1,3,'with')} = $graphics{'x-server'}->[1][0]; + if ($graphics{'x-server'}->[1][1]){ + $rows->[$j]{main::key($num++,0,4,'v')} = $graphics{'x-server'}->[1][1]; + } + } + if ($graphics{'compositors'}){ + if (scalar @{$graphics{'compositors'}} == 1){ + $rows->[$j]{main::key($num++,1,2,'compositor')} = $graphics{'compositors'}->[0][0]; + if ($graphics{'compositors'}->[0][1]){ + $rows->[$j]{main::key($num++,0,3,'v')} = $graphics{'compositors'}->[0][1]; + } + } + else { + my $i =1; + $rows->[$j]{main::key($num++,1,2,'compositors')} = ''; + foreach (@{$graphics{'compositors'}}){ + $rows->[$j]{main::key($num++,1,3,$i)} = $_->[0]; + if ($_->[1]){ + $rows->[$j]{main::key($num++,0,4,'v')} = $_->[1]; + } + $i++; + } + } + } + # note: if no xorg log, and if wayland, there will be no xorg drivers, + # obviously, so we use the driver(s) found in the card section. + # Those come from lspci kernel drivers so should be no xorg/wayland issues. + if (!@$x_drivers || !$x_drivers->[0]){ + # Fallback: specific case: in Arch/Manjaro gdm run systems, Xorg.0.log is + # located inside this directory, which is not readable unless you are root + # Normally Arch gdm log is here: ~/.local/share/xorg/Xorg.1.log + if (!$graphics{'protocol'} || $graphics{'protocol'} ne 'wayland'){ + # Problem: as root, wayland has no info anyway, including wayland detection. + if (-e '/var/lib/gdm' && !$b_root){ + if ($graphics{'gpu-drivers'}){ + $driver_note = main::message('display-driver-na-try-root'); + } + else { + $driver_note = main::message('root-suggested'); + } + } + } + } + # if xvesa, will always have display-driver set + if ($graphics{'xvesa'} && $graphics{'display-driver'}){ + $rows->[$j]{main::key($num++,0,2,'driver')} = join(',',@{$graphics{'display-driver'}}); + } + else { + my $gpu_drivers = gpu_drivers_sys('all'); + my $note_indent = 4; + if (@$gpu_drivers || $graphics{'dri-drivers'} || @$x_drivers){ + $rows->[$j]{main::key($num++,1,2,'driver')} = ''; + # The only wayland setups with x drivers have xorg, transitional that is. + if (@$x_drivers){ + $rows->[$j]{main::key($num++,1,3,'X')} = ''; + my $driver = ($x_drivers->[0]) ? join(',',@{$x_drivers->[0]}) : 'N/A'; + $rows->[$j]{main::key($num++,1,4,'loaded')} = $driver; + if ($x_drivers->[1]){ + $rows->[$j]{main::key($num++,0,4,'unloaded')} = join(',',@{$x_drivers->[1]}); + } + if ($x_drivers->[2]){ + $rows->[$j]{main::key($num++,0,4,'failed')} = join(',',@{$x_drivers->[2]}); + } + if ($extra > 1 && $x_drivers->[3]){ + $rows->[$j]{main::key($num++,0,4,'alternate')} = join(',',@{$x_drivers->[3]}); + } + } + if ($graphics{'dri-drivers'}){ + # note: if want to exclude if matches gpu/x driver, loop through and test. + # Here using all dri drivers found. + $rows->[$j]{main::key($num++,1,3,'dri')} = join(',',@{$graphics{'dri-drivers'}}); + } + my $drivers; + if (@$gpu_drivers){ + $drivers = join(',',@$gpu_drivers); + } + else { + $drivers = ($graphics{'gpu-drivers'}) ? join(',',@{$graphics{'gpu-drivers'}}): 'N/A'; + } + $rows->[$j]{main::key($num++,1,3,'gpu')} = $drivers; + } + else { + $note_indent = 3; + $rows->[$j]{main::key($num++,1,2,'driver')} = 'N/A'; + } + if ($driver_note){ + $rows->[$j]{main::key($num++,0,$note_indent,'note')} = $driver_note; + } + } + } + if (!$show{'graphic-basic'} && $extra > 1 && $graphics{'display-rect'}){ + $rows->[$j]{main::key($num++,0,2,'d-rect')} = $graphics{'display-rect'}; + } + if (!$show{'graphic-basic'} && $extra > 1){ + if (defined $graphics{'display-id'}){ + $rows->[$j]{main::key($num++,0,2,'display-ID')} = $graphics{'display-id'}; + } + if (defined $graphics{'display-screens'}){ + $rows->[$j]{main::key($num++,0,2,'screens')} = $graphics{'display-screens'}; + } + if (defined $graphics{'display-default-screen'} && + $graphics{'display-screens'} && $graphics{'display-screens'} > 1){ + $rows->[$j]{main::key($num++,0,2,'default screen')} = $graphics{'display-default-screen'}; + } + } + if ($graphics{'no-screens'}){ + my $res = (!$show{'graphic-basic'} && $extra > 1 && !$graphics{'xvesa'}) ? 'note' : 'resolution'; + $rows->[$j]{main::key($num++,0,2,$res)} = $graphics{'no-screens'}; + } + elsif ($graphics{'screens'}){ + my ($diag,$dpi,$hz,$size); + my ($m_count,$basic_count,$screen_count) = (0,0,0); + my $s_count = ($graphics{'screens'}) ? scalar @{$graphics{'screens'}}: 0; + foreach my $main (@{$graphics{'screens'}}){ + $m_count = scalar keys %{$main->{'monitors'}} if $main->{'monitors'}; + $screen_count++; + ($diag,$dpi,$hz,$resolution,$size) = (); + $j++ if !$show{'graphic-basic'}; + if (!$show{'graphic-basic'} || $m_count == 0){ + if (!$show{'graphic-basic'} && defined $main->{'screen'}){ + $rows->[$j]{main::key($num++,1,2,'Screen')} = $main->{'screen'}; + } + if ($main->{'res-x'} && $main->{'res-y'}){ + $resolution = $main->{'res-x'} . 'x' . $main->{'res-y'}; + if ($main->{'hz'} && $show{'graphic-basic'}){ + $resolution .= '~' . $main->{'hz'} . 'Hz'; + } + } + $resolution ||= 'N/A'; + if ($s_count == 1 || !$show{'graphic-basic'}){ + $rows->[$j]{main::key($num++,0,3,'s-res')} = $resolution; + } + elsif ($show{'graphic-basic'}){ + $rows->[$j]{main::key($num++,0,3,'s-res')} = '' if $screen_count == 1; + $rows->[$j]{main::key($num++,0,3,$screen_count)} = $resolution; + } + if ($main->{'s-dpi'} && (!$show{'graphic-basic'} && $extra > 1)){ + $rows->[$j]{main::key($num++,0,3,'s-dpi')} = $main->{'s-dpi'}; + } + if (!$show{'graphic-basic'} && $extra > 2){ + if ($main->{'size-missing'}){ + $rows->[$j]{main::key($num++,0,3,'s-size')} = $main->{'size-missing'}; + } + else { + if ($main->{'size-x'} && $main->{'size-y'}){ + $size = $main->{'size-x'} . 'x' . $main->{'size-y'} . + 'mm ('. $main->{'size-x-i'} . 'x' . $main->{'size-y-i'} . '")'; + $rows->[$j]{main::key($num++,0,3,'s-size')} = $size; + } + if ($main->{'diagonal'}){ + $diag = $main->{'diagonal-m'} . 'mm ('. $main->{'diagonal'} . '")'; + $rows->[$j]{main::key($num++,0,3,'s-diag')} = $diag; + } + } + } + } + if ($main->{'monitors'}){ + # print $basic_count . '::' . $m_count, "\n"; + $b_screen_monitors = 1; + if ($show{'graphic-basic'}){ + monitors_output_basic('screen',$main->{'monitors'}, + $main->{'s-dpi'},$j,$rows,\$num); + } + else { + monitors_output_full('screen',$main->{'monitors'}, + \$j,$rows,\$num); + } + } + elsif (!$show{'graphic-basic'} && $graphics{'no-monitors'}){ + $rows->[$j]{main::key($num++,0,4,'monitors')} = $graphics{'no-monitors'}; + } + } + } + elsif (!$b_display){ + $graphics{'tty'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'tty')} = $graphics{'tty'}; + } + # fallback, if no xrandr/xdpyinfo, if wayland, if console. Note we've + # deleted each key used in advanced_monitor_data() so those won't show again + if (!$b_screen_monitors && $monitor_ids && %$monitor_ids){ + if ($show{'graphic-basic'}){ + monitors_output_basic('monitor',$monitor_ids,'',$j,$rows,\$num); + } + else { + monitors_output_full('monitor',$monitor_ids,\$j,$rows,\$num); + } + } + } + eval $end if $b_log; +} + +sub monitors_output_basic { + eval $start if $b_log; + my ($type,$monitors,$s_dpi,$j,$row,$num) = @_; + my ($dpi,$resolution); + my ($basic_count,$m_count) = (0,scalar keys %{$monitors}); + foreach my $key (sort keys %{$monitors}){ + if ($type eq 'monitor' && (!$monitors->{$key}{'res-x'} || + !$monitors->{$key}{'res-y'})){ + next; + } + ($dpi,$resolution) = (); + $basic_count++; + if ($monitors->{$key}{'res-x'} && $monitors->{$key}{'res-y'}){ + $resolution = $monitors->{$key}{'res-x'} . 'x' . $monitors->{$key}{'res-y'}; + } + # using main, not monitor, dpi because we want xorg dpi, not physical screen dpi + $dpi = $s_dpi if $resolution && $extra > 1 && $s_dpi; + if ($monitors->{$key}{'hz'} && $resolution){ + $resolution .= '~' . $monitors->{$key}{'hz'} . 'Hz'; + } + $resolution ||= 'N/A'; + if ($basic_count == 1 && $m_count == 1){ + $row->[$j]{main::key($$num++,0,2,'resolution')} = $resolution; + } + else { + if ($basic_count == 1){ + $row->[$j]{main::key($$num++,1,2,'resolution')} = ''; + } + $row->[$j]{main::key($$num++,0,3,$basic_count)} = $resolution; + } + if (!$show{'graphic-basic'} && $m_count == $basic_count && $dpi){ + $row->[$j]{main::key($$num++,0,2,'s-dpi')} = $dpi; + } + } + eval $end if $b_log; +} + +# args: $j, $row, $num passed by ref +sub monitors_output_full { + eval $start if $b_log; + my ($type,$monitors,$j,$rows,$num) = @_; + my ($b_no_size,$resolution); + my ($m1,$m2,$m3,$m4) = ($type eq 'screen') ? (3,4,5,6) : (2,3,4,5); + # note: in case where mapped id != sys id, the key will not match 'monitor' + foreach my $key (sort keys %{$monitors}){ + $$j++; + $rows->[$$j]{main::key($$num++,1,$m1,'Monitor')} = $monitors->{$key}{'monitor'}; + if ($monitors->{$key}{'monitor-mapped'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'mapped')} = $monitors->{$key}{'monitor-mapped'}; + } + if ($monitors->{$key}{'disabled'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'note')} = $monitors->{$key}{'disabled'}; + } + if ($monitors->{$key}{'position'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'pos')} = $monitors->{$key}{'position'}; + } + if ($monitors->{$key}{'model'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'model')} = $monitors->{$key}{'model'}; + } + elsif ($monitors->{$key}{'model-id'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'model-id')} = $monitors->{$key}{'model-id'}; + } + if ($extra > 2 && $monitors->{$key}{'serial'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'serial')} = main::filter($monitors->{$key}{'serial'}); + } + if ($b_admin && $monitors->{$key}{'build-date'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'built')} = $monitors->{$key}{'build-date'}; + } + if ($monitors->{$key}{'res-x'} || $monitors->{$key}{'res-y'} || + $monitors->{$key}{'hz'} || $monitors->{$key}{'size-x'} || + $monitors->{$key}{'size-y'}){ + if ($monitors->{$key}{'res-x'} && $monitors->{$key}{'res-y'}){ + $resolution = $monitors->{$key}{'res-x'} . 'x' . $monitors->{$key}{'res-y'}; + } + $resolution ||= 'N/A'; + $rows->[$$j]{main::key($$num++,0,$m2,'res')} = $resolution; + } + else { + if ($b_display){ + $resolution = main::message('monitor-na'); + } + else { + $resolution = main::message('monitor-console'); + } + $b_no_size = 1; + $rows->[$$j]{main::key($$num++,0,$m2,'size-res')} = $resolution; + } + if ($extra > 2 && $monitors->{$key}{'hz'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'hz')} = $monitors->{$key}{'hz'}; + } + if ($monitors->{$key}{'dpi'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'dpi')} = $monitors->{$key}{'dpi'}; + } + if ($b_admin && $monitors->{$key}{'gamma'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'gamma')} = $monitors->{$key}{'gamma'}; + } + if ($show{'edid'} && $monitors->{$key}{'colors'}){ + $rows->[$$j]{main::key($$num++,1,$m2,'chroma')} = ''; + $rows->[$$j]{main::key($$num++,1,$m3,'red')} = ''; + $rows->[$$j]{main::key($$num++,0,$m4,'x')} = $monitors->{$key}{'colors'}{'red_x'}; + $rows->[$$j]{main::key($$num++,0,$m4,'y')} = $monitors->{$key}{'colors'}{'red_y'}; + $rows->[$$j]{main::key($$num++,1,$m3,'green')} = ''; + $rows->[$$j]{main::key($$num++,0,$m4,'x')} = $monitors->{$key}{'colors'}{'green_x'}; + $rows->[$$j]{main::key($$num++,0,$m4,'y')} = $monitors->{$key}{'colors'}{'green_y'}; + $rows->[$$j]{main::key($$num++,1,$m3,'blue')} = ''; + $rows->[$$j]{main::key($$num++,0,$m4,'x')} = $monitors->{$key}{'colors'}{'blue_x'}; + $rows->[$$j]{main::key($$num++,0,$m4,'y')} = $monitors->{$key}{'colors'}{'blue_y'}; + $rows->[$$j]{main::key($$num++,1,$m3,'white')} = ''; + $rows->[$$j]{main::key($$num++,0,$m4,'x')} = $monitors->{$key}{'colors'}{'white_x'}; + $rows->[$$j]{main::key($$num++,0,$m4,'y')} = $monitors->{$key}{'colors'}{'white_y'}; + } + if ($extra > 2 && $monitors->{$key}{'scale'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'scale')} = $monitors->{$key}{'scale'}; + } + if ($extra > 2 && $monitors->{$key}{'size-x'} && $monitors->{$key}{'size-y'}){ + my $size = $monitors->{$key}{'size-x'} . 'x' . $monitors->{$key}{'size-y'} . + 'mm ('. $monitors->{$key}{'size-x-i'} . 'x' . $monitors->{$key}{'size-y-i'} . '")'; + $rows->[$$j]{main::key($$num++,0,$m2,'size')} = $size; + } + if ($monitors->{$key}{'diagonal'}){ + my $diag = $monitors->{$key}{'diagonal-m'} . 'mm ('. $monitors->{$key}{'diagonal'} . '")'; + $rows->[$$j]{main::key($$num++,0,$m2,'diag')} = $diag; + } + elsif ($b_display && !$b_no_size && !$monitors->{$key}{'size-x'} && + !$monitors->{$key}{'size-y'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'size')} = main::message('monitor-na');; + } + if ($b_admin && $monitors->{$key}{'ratio'}){ + $rows->[$$j]{main::key($$num++,0,$m2,'ratio')} = $monitors->{$key}{'ratio'}; + } + if ($extra > 2){ + if (!$monitors->{$key}{'modes'} || !@{$monitors->{$key}{'modes'}}){ + $monitors->{$key}{'modes'} = ['N/A']; + } + my $cnt = scalar @{$monitors->{$key}{'modes'}}; + if ($cnt == 1 || ($cnt > 2 && $show{'edid'})){ + $rows->[$$j]{main::key($$num++,0,$m2,'modes')} = join(', ', @{$monitors->{$key}{'modes'}}); + } + else { + $rows->[$$j]{main::key($$num++,1,$m2,'modes')} = ''; + $rows->[$$j]{main::key($$num++,0,$m3,'max')} = ${$monitors->{$key}{'modes'}}[0]; + $rows->[$$j]{main::key($$num++,0,$m3,'min')} = ${$monitors->{$key}{'modes'}}[-1]; + } + } + if ($show{'edid'}){ + if ($monitors->{$key}{'edid-errors'}){ + $$j++; + my $cnt = 1; + $rows->[$$j]{main::key($$num++,1,$m2,'EDID-Errors')} = ''; + foreach my $err (@{$monitors->{$key}{'edid-errors'}}){ + $rows->[$$j]{main::key($$num++,0,$m3,$cnt)} = $err; + $cnt++; + } + } + if ($monitors->{$key}{'edid-warnings'}){ + $$j++; + my $cnt = 1; + $rows->[$$j]{main::key($$num++,1,$m2,'EDID-Warnings')} = ''; + foreach my $warn (@{$monitors->{$key}{'edid-warnings'}}){ + $rows->[$$j]{main::key($$num++,0,$m3,$cnt)} = $warn; + $cnt++; + } + } + } + } + # we only want to see gpu drivers for wayland since otherwise it's x drivers. +# if ($b_display && $b_admin && $graphics{'protocol'} && +# $graphics{'protocol'} eq 'wayland' && $monitors->{$key}{'drivers'}){ +# $driver = join(',',@{$monitors->{$key}{'drivers'}}); +# $rows->[$j]{main::key($$num++,0,$m2,'driver')} = $driver; +# } + eval $end if $b_log; +} + +## DISPLAY API ## + +# API Output # + +# GLX/OpenGL EGL Vulkan XVesa +sub display_api { + eval $start if $b_log; + my $rows = $_[0]; + # print ("$b_display : $b_root\n"); + # xvesa is absolute, if it's there, it works in or out of display + if ($graphics{'xvesa'}){ + xvesa_output($rows); + return; + } + my ($b_egl,$b_egl_print,$b_glx,$b_glx_print,$b_vulkan,$api,$program,$type); + my $gl = {}; + if ($fake{'egl'} || ($program = main::check_program('eglinfo'))){ + gl_data('egl',$program,$rows,$gl); + $b_egl = 1; + } + if ($fake{'glx'} || ($program = main::check_program('glxinfo'))){ + gl_data('glx',$program,$rows,$gl) if $b_display; + $b_glx = 1; + } + # Note: we let gl/egl output handle null or root null data issues + if ($gl->{'glx'}){ + process_glx_data($gl->{'glx'},$b_glx); + } + # egl/vulkan give data out of display, and for root + # if ($b_egl}){ + if ($b_egl && ($show{'graphic-full'} || !$gl->{'glx'})){ + egl_output($rows,$gl); + $b_egl_print = 1; + } + # fill in whatever was missing from eglinfo, or if legacy system/no eglinfo + # if ($b_glx || $gl->{'glx'}){ + if (($show{'graphic-full'} && ($b_glx || $gl->{'glx'})) || + (!$show{'graphic-full'} && !$b_egl_print && ($b_glx || $gl->{'glx'}))){ + opengl_output($rows,$gl); + $b_glx = 1; + $b_glx_print = 1; + } + # if ($fake{'vulkan'} || ($program = main::check_program('vulkaninfo'))){ + if (($fake{'vulkan'} || ($program = main::check_program('vulkaninfo'))) && + ($show{'graphic-full'} || (!$b_egl_print && !$b_glx_print))){ + vulkan_output($program,$rows); + $b_vulkan = 1; + } + if ($show{'graphic-full'} || (!$b_egl_print && !$b_glx_print)){ + # remember, sudo/root usually has empty $DISPLAY as well + if ($b_display){ + # first do positive tests, won't be set for sudo/root + if (!$b_glx && $graphics{'protocol'} eq 'x11'){ + $api = 'OpenGL'; + $type = 'glxinfo-missing'; + } + elsif (!$b_egl && $graphics{'protocol'} eq 'wayland'){ + $api = 'EGL'; # /GBM + $type = 'egl-missing'; + } + elsif (!$b_glx && + (main::check_program('X') || main::check_program('Xorg'))){ + $api = 'OpenGL'; + $type = 'glxinfo-missing'; + } + elsif (!$b_egl && main::check_program('Xwayland')){ + $api = 'EGL'; + $type = 'egl-missing'; + } + elsif (!$b_egl && !$b_glx && !$b_vulkan) { + $api = 'N/A'; + $type = 'gfx-api'; + } + } + else { + if (!$b_glx && + (main::check_program('X') || main::check_program('Xorg'))){ + $api = 'OpenGL'; + $type = 'glx-console-glxinfo-missing'; + } + elsif (!$b_egl && main::check_program('Xwayland')){ + $api = 'EGL'; + $type = 'egl-console-missing'; + } + # we don't know what it is, headless system, non xwayland wayland + elsif (!$b_egl && !$b_glx && !$b_vulkan) { + $api = 'N/A'; + $type = 'gfx-api-console'; + } + } + no_data_output($api,$type,$rows) if $type; + } + eval $end if $b_log; +} + +sub no_data_output { + eval $start if $b_log; + my ($api,$type,$rows) = @_; + my $num = 0; + push(@$rows, { + main::key($num++,1,1,'API') => $api, + main::key($num++,0,2,'Message') => main::message($type) + }); + eval $end if $b_log; +} + +sub egl_output { + eval $start if $b_log; + my ($rows,$gl) = @_; + if (!$gl->{'egl'}){ + my $api = 'EGL'; + my $type = 'egl-null'; + no_data_output($api,$type,$rows); + return 0; + } + my ($i,$j,$num) = (0,scalar @$rows,0); + my ($value); + my $ref; + my $data = $gl->{'egl'}{'data'}; + my $plat = $gl->{'egl'}{'platforms'}; + push(@$rows, { + main::key($num++,1,1,'API') => 'EGL', + }); + if ($extra < 2){ + $value = ($data->{'versions'}) ? join(',',sort keys %{$data->{'versions'}}): 'N/A'; + } + else { + $value = ($data->{'version'}) ? $data->{'version'}: 'N/A'; + } + $rows->[$j]{main::key($num++,0,2,'v')} = $value; + if ($extra < 2){ + $value = ($data->{'drivers'}) ? join(',',sort keys %{$data->{'drivers'}}): 'N/A'; + $rows->[$j]{main::key($num++,0,2,'drivers')} = $value; + $value = ($data->{'platforms'}{'active'}) ? join(',',@{$data->{'platforms'}{'active'}}) : 'N/A'; + if ($extra < 1){ + $rows->[$j]{main::key($num++,0,2,'platforms')} = $value; + } + else { + $rows->[$j]{main::key($num++,1,2,'platforms')} = ''; + $rows->[$j]{main::key($num++,0,3,'active')} = $value; + $value = ($data->{'platforms'}{'inactive'}) ? join(',',@{$data->{'platforms'}{'inactive'}}) : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'inactive')} = $value; + } + } + else { + if ($extra > 2 && $data->{'hw'}){ + $i = 0; + $rows->[$j]{main::key($num++,1,2,'hw')} = ''; + foreach my $key (sort keys %{$data->{'hw'}}){ + $value = ($key ne $data->{'hw'}{$key}) ? $data->{'hw'}{$key} . ' ' . $key: $key; + $rows->[$j]{main::key($num++,0,3,'drv')} = $value; + } + } + $rows->[$j]{main::key($num++,1,2,'platforms')} = ''; + $data->{'version'} ||= 0; + $i = 0; + foreach my $key (sort keys %$plat){ + next if !$plat->{$key}{'status'} || $plat->{$key}{'status'} eq 'inactive'; + if ($key eq 'device'){ + foreach my $id (sort keys %{$plat->{$key}}){ + next if ref $plat->{$key}{$id} ne 'HASH'; + $rows->[$j]{main::key($num++,1,3,$key)} = $id; + $ref = $plat->{$key}{$id}{'egl'}; + egl_advanced_output($rows,$ref,\$num,$j,4,$data->{'version'}); + } + } + else { + $rows->[$j]{main::key($num++,1,3,$key)} = ''; + $ref = $plat->{$key}{'egl'}; + egl_advanced_output($rows,$ref,\$num,$j,4,$data->{'version'}); + } + } + if (!$data->{'platforms'}{'active'}){ + $rows->[$j]{main::key($num++,0,3,'active')} = 'N/A'; + } + if ($data->{'platforms'}{'inactive'}){ + $rows->[$j]{main::key($num++,0,3,'inactive')} = join(',',@{$data->{'platforms'}{'inactive'}}); + } + } + eval $end if $b_log; +} + +# args: 0: $rows; 1: data ref; 2: \$num; 3: $j; 4: indent; 5: $b_plat_v +sub egl_advanced_output { + my ($rows,$ref,$num,$j,$ind,$version) = @_; + my $value; + # version is set to 0 for math + if ($version && (!$ref->{'version'} || $version != $ref->{'version'})){ + $value = ($ref->{'version'}) ? $ref->{'version'} : 'N/A'; + $rows->[$j]{main::key($$num++,0,$ind,'egl')} = $value; + undef $value; + } + if ($ref->{'driver'}){ + $value = $ref->{'driver'}; + } + else { + if ($ref->{'vendor'} && $ref->{'vendor'} ne 'mesa'){ + $value = $ref->{'vendor'}; + } + $value ||= 'N/A'; + } + $rows->[$j]{main::key($$num++,0,$ind,'drv')} = $value; +} + +sub opengl_output { + eval $start if $b_log; + my ($rows,$gl) = @_; + # egl will have set $glx if present + if (!$gl->{'glx'}){ + my $api = 'OpenGL'; + my $type; + if ($b_display){ + $type = ($b_root) ? 'glx-display-root': 'glx-null'; + } + else { + $type = ($b_root) ? 'glx-console-root' : 'glx-console-try'; + } + no_data_output($api,$type,$rows); + return 0; + } + my ($j,$num) = (scalar @$rows,0); + my $value; + # print join("\n", %$gl),"\n"; + my $glx = $gl->{'glx'}; + $glx->{'opengl'}{'version'} ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'API') => 'OpenGL', + main::key($num++,0,2,'v') => $glx->{'opengl'}{'version'}, + }); + if ($glx->{'opengl'}{'compatibility'}{'version'}){ + $rows->[$j]{main::key($num++,0,2,'compat-v')} = $glx->{'opengl'}{'compatibility'}{'version'}; + } + if ($glx->{'opengl'}{'vendor'}){ + $rows->[$j]{main::key($num++,1,2,'vendor')} = $glx->{'opengl'}{'vendor'}; + $glx->{'opengl'}{'driver'}{'version'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'v')} = $glx->{'opengl'}{'driver'}{'version'}; + } + if ($extra > 0 && $glx->{'glx-version'}){ + $rows->[$j]{main::key($num++,0,2,'glx-v')} = $glx->{'glx-version'}; + } + if ($extra > 1 && $glx->{'es'}{'version'}){ + $rows->[$j]{main::key($num++,0,2,'es-v')} = $glx->{'es'}{'version'};; + } + if ($glx->{'note'}){ + $rows->[$j]{main::key($num++,0,2,'note')} = $glx->{'note'}; + } + if ($extra > 0 && (!$glx->{'note'} || $glx->{'direct-render'})){ + $glx->{'direct-render'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'direct-render')} = $glx->{'direct-render'}; + } + if (!$glx->{'note'} || $glx->{'opengl'}{'renderer'}){ + $glx->{'opengl'}{'renderer'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'renderer')} = $glx->{'opengl'}{'renderer'}; + } + if ($extra > 1 && $glx->{'info'}){ + if ($glx->{'info'}{'vendor-id'} && $glx->{'info'}{'device-id'}){ + $value = $glx->{'info'}{'vendor-id'} . ':' . $glx->{'info'}{'device-id'}; + $rows->[$j]{main::key($num++,0,2,'device-ID')} = $value; + } + if ($b_admin && $glx->{'info'}{'device-memory'}){ + $rows->[$j]{main::key($num++,1,2,'memory')} = $glx->{'info'}{'device-memory'}; + if ($glx->{'info'}{'unified-memory'}){ + $rows->[$j]{main::key($num++,0,3,'unified')} = $glx->{'info'}{'unified-memory'}; + } + } + # display id depends on xdpyinfo in Display line, which may not be present, + if (!$graphics{'display-id'} && $glx->{'display-id'} && $extra > 1){ + $rows->[$j]{main::key($num++,0,2,'display-ID')} = $glx->{'display-id'}; + } + } + eval $end if $b_log; +} + +sub vulkan_output { + eval $start if $b_log; + my ($program,$rows) = @_; + my $vulkan = {}; + vulkan_data($program,$vulkan); + if (!%$vulkan){ + my $api = 'Vulkan'; + my $type = 'vulkan-null'; + no_data_output($api,$type,$rows); + return 0; + } + my $num = 0; + my $j = scalar @$rows; + my ($value); + my $data = $vulkan->{'data'}; + my $devices = $vulkan->{'devices'}; + $data->{'version'} ||= 'N/A'; + push(@$rows,{ + main::key($num++,1,1,'API') => 'Vulkan', + main::key($num++,0,2,'v') => $data->{'version'}, + }); + # this will be expanded with -a to a full device report + if ($extra < 2){ + $value = ($data->{'drivers'}) ? join(',',@{$data->{'drivers'}}): 'N/A'; + $rows->[$j]{main::key($num++,0,2,'drivers')} = $value; + } + if ($extra > 2){ + $data->{'layers'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'layers')} = $data->{'layers'}; + } + if (!$b_admin){ + $value = ($data->{'surfaces'}) ? join(',',@{$data->{'surfaces'}}) : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'surfaces')} = $value; + } + if ($extra > 0){ + if (!$devices){ + $rows->[$j]{main::key($num++,0,2,'devices')} = 'N/A'; + } + else { + if ($extra < 2){ + $value = scalar keys %{$devices}; + $rows->[$j]{main::key($num++,0,2,'devices')} = $value; + } + else { + foreach my $id (sort keys %$devices){ + $rows->[$j]{main::key($num++,1,2,'device')} = $id; + $devices->{$id}{'device-type'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'type')} = $devices->{$id}{'device-type'}; + if ((($extra == 3 && !$b_admin) || + ($extra > 2 && !$devices->{$id}{'device-name'})) && + $devices->{$id}{'hw'} && $devices->{$id}{'hw'} ne 'nvidia'){ + $rows->[$j]{main::key($num++,0,3,'hw')} = $devices->{$id}{'hw'}; + } + if ($b_admin){ + $value = ($devices->{$id}{'device-name'}) ? + $devices->{$id}{'device-name'}: 'N/A'; + $rows->[$j]{main::key($num++,0,3,'name')} = $value; + } + if ($extra > 1){ + if ($devices->{$id}{'driver-name'}){ + $value = $devices->{$id}{'driver-name'}; + if ($devices->{$id}{'mesa'} && $value ne 'mesa'){ + $value = 'mesa ' . $value; + } + $rows->[$j]{main::key($num++,1,3,'driver')} = $value; + if ($b_admin && $devices->{$id}{'driver-info'}){ + $rows->[$j]{main::key($num++,0,4,'v')} = $devices->{$id}{'driver-info'}; + } + } + else { + $rows->[$j]{main::key($num++,0,3,'driver')} = 'N/A'; + } + $value = ($devices->{$id}{'device-id'} && $devices->{$id}{'vendor-id'}) ? + $devices->{$id}{'vendor-id'} . ':' . $devices->{$id}{'device-id'} : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'device-ID')} = $value; + if ($b_admin){ + $value = ($devices->{$id}{'surfaces'}) ? + join(',',@{$devices->{$id}{'surfaces'}}): 'N/A'; + $rows->[$j]{main::key($num++,0,3,'surfaces')} = $value; + } + } + } + } + } + } + eval $end if $b_log; +} + +sub xvesa_output { + eval $start if $b_log; + my ($rows) = @_; + my ($controller,$dac,$interface,$ram,$source,$version); + # note: goes to stderr, not stdout + my @data = main::grabber($graphics{'xvesa'} . ' -listmodes 2>&1'); + my $j = scalar @$rows; + my $num = 0; + # gop replaced uga, both for uefi + # WARNING! Never seen a GOP type UEFI, needs more data + if ($data[0] && $data[0] =~ /^(VBE|GOP|UGA)\s+version\s+(\S+)\s\(([^)]+)\)/i){ + $interface = $1; + $version = $2; + $source = $3; + } + if ($data[1] && $data[1] =~ /^DAC is ([^,]+), controller is ([^,]+)/i){ + $dac = $1; + $controller = $2; + } + if ($data[2] && $data[2] =~ /^Total memory:\s+(\d+)\s/i){ + $ram = $1; + $ram = main::get_size($ram,'string'); + } + if (!$interface){ + $rows->[$j]{main::key($num++,1,1,'API')} = 'VBE/GOP'; + $rows->[$j]{main::key($num++,0,2,'Message')} = main::message('xvesa-null'); + } + else { + $rows->[$j]{main::key($num++,1,1,'API')} = $interface; + $rows->[$j]{main::key($num++,0,2,'v')} = ($version) ? $version : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'source')} = ($source) ? $source : 'N/A'; + if ($dac){ + $rows->[$j]{main::key($num++,0,2,'dac')} = $dac; + $rows->[$j]{main::key($num++,0,2,'controller')} = $controller; + } + if ($ram){ + $rows->[$j]{main::key($num++,0,2,'ram')} = $ram; + } + } + eval $end if $b_log; +} + +# API Data # +sub gl_data { + eval $start if $b_log; + my ($source,$program,$rows,$gl) = @_; + my ($b_opengl,$msg); + my ($gl_data,$results) = ([],[]); + # only check these if no eglinfo or eglinfo had no opengl data + $b_opengl = 1 if ($source eq 'egl' || !$gl->{'glx'}); + # NOTE: glxinfo -B is not always available, unfortunately + if ($dbg[56] || $b_log){ + $msg = "${line1}GL Source: $source\n${line3}"; + print $msg if $dbg[56]; + push(@$results,$msg) if $b_log; + } + if ($source eq 'glx'){ + if (!$fake{'glx'}){ + $gl_data = main::grabber("$program $display_opt 2>/dev/null",'','','ref'); + } + else { + my $file; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-2012-nvidia-glx1.4.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-ssh-centos.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxiinfo-t420-intel-1.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-mali-allwinner-lima-1.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-partial-intel-5500-1.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-vbox-debian-etch-1.txt"; + $file = "$fake_data_dir/graphics/glxinfo/glxinfo-x11-neomagic-lenny-1.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-nvidia-gl4.6-chr.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-intel-atom-dell_studio-bm.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-asus_1025c-atom-bm.txt"; + # $file = "$fake_data_dir/graphics/glxinfo/glxinfo-2011-nvidia-glx1.4.txt"; + $gl_data= main::reader($file,'','ref'); + } + } + else { + if (!$fake{'egl'}){ + $gl_data = main::grabber("$program 2>/dev/null",'','','ref'); + } + else { + my $file; + $file = "$fake_data_dir/graphics/egl-es/eglinfo-x11-3.txt"; + # $file = "$fake_data_dir/graphics/egl-es/eglinfo-wayland-intel-c30.txt"; + # $file = "$fake_data_dir/grapOhics/egl-es/eglinfo-2022-x11-nvidia-egl1.5.txt"; + # $file = "$fake_data_dir/graphics/egl-es/eglinfo-wayland-intel-nvidia-radu.txt"; + $file = "$fake_data_dir/graphics/egl-es/eglinfo-intel-atom-dell_studio-bm.txt"; + $file = "$fake_data_dir/graphics/egl-es/eglinfo-asus_1025c-atom-bm.txt"; + $gl_data = main::reader($file,'','ref'); + } + } + # print join("\n", @$gl_data),"\n"; + if (!$gl_data || !@$gl_data){ + if ($dbg[56] || $b_log){ + $msg = "No data found for GL Source: $source" if $dbg[56]; + print "$msg\n" if $dbg[56]; + push(@$results,$msg) if $b_log; + } + return 0; + } + # some error cases have only a few top value but not empty + elsif ($source eq 'glx' && scalar @$gl_data > 5){ + $gl->{'glx'}{'source'} = $source; + } + set_mesa_drivers() if $source eq 'egl' && !%mesa_drivers; + my ($b_device,$b_platform,$b_mem_info,$b_rend_info,$device,$platform, + $value,$value2,@working); + foreach my $line (@$gl_data){ + next if (!$b_rend_info && !$b_mem_info) && $line =~ /^(\s|0x)/; + if (($b_rend_info || $b_mem_info) && $line =~ /^\S/){ + ($b_mem_info,$b_rend_info) = (); + } + @working = split(/\s*:\s*/,$line,2); + next if !@working; + if ($dbg[56] || $b_log){ + $msg = $line; + print "$msg\n" if $dbg[56]; + push(@$results,$msg) if $b_log; + } + if ($source eq 'egl'){ + # eglinfo: eglInitialize failed + # This is first line after platform fail for devices, but for Device + # it would be the second or later line. The Device platform can fail, or + # specific device can fail + if ($b_platform){ + $value = ($line =~ /Initialize failed/) ? 'inactive': 'active'; + push(@{$gl->{'egl'}{'data'}{'platforms'}{$value}},$platform); + $gl->{'egl'}{'platforms'}{$platform}{'status'} = $value; + $b_platform = 0; + } + # note: can be sub item: Platform Device platform:; Platform Device: + elsif ($working[0] =~ /^(\S+) platform/i){ + $platform = lc($1); + undef $device; + $b_platform = 1; + } + if ($platform && defined $device && $working[0] eq 'eglinfo'){ + push(@{$gl->{'egl'}{'data'}{'platforms'}{'inactive'}},"$platform-$device"); + undef $device; + } + if ($platform && $platform eq 'device' && $working[0] =~ /^Device #(\d+)/){ + $device = $1; + } + if ($working[0] eq 'EGL API version'){ + if (!defined $platform){ + $gl->{'egl'}{'data'}{'api-version'} = $working[1]; + } + elsif (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'api-version'} = $working[1]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'api-version'} = $working[1]; + } + } + elsif ($working[0] eq 'EGL version string'){ + if (!defined $platform){ + $gl->{'egl'}{'data'}{'version'} = $working[1]; + } + elsif (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'version'} = $working[1]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'version'} = $working[1]; + } + $value = (defined $device) ? "$platform-$device": $platform; + push(@{$gl->{'egl'}{'data'}{'versions'}{$working[1]}},$value); + if (!$gl->{'egl'}{'data'}{'version'} || + $working[1] > $gl->{'egl'}{'data'}{'version'}){ + $gl->{'egl'}{'data'}{'version'} = $working[1]; + } + } + elsif ($working[0] eq 'EGL vendor string'){ + $working[1] = lc($working[1]); + $working[1] =~ s/^(\S+)(\s.+|$)/$1/; + if (!defined $platform){ + $gl->{'egl'}{'data'}{'vendor'} = $working[1]; + } + elsif (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'vendor'} = $working[1]; + if ($working[1] eq 'nvidia'){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'driver'} = $working[1]; + } + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'vendor'} = $working[1]; + if ($working[1] eq 'nvidia'){ + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'driver'} = $working[1]; + } + } + push(@{$gl->{'egl'}{'data'}{'vendors'}},$working[1]); + if ($working[1] eq 'nvidia'){ + $value = (defined $device) ? "$platform-$device": $platform; + push(@{$gl->{'egl'}{'data'}{'drivers'}{$working[1]}},$value); + $gl->{'egl'}{'data'}{'hw'}{$working[1]} = $working[1]; + } + } + elsif ($working[0] eq 'EGL driver name'){ + if (!defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'driver'} = $working[1]; + if ($mesa_drivers{$working[1]}){ + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'hw'} = $mesa_drivers{$working[1]}; + } + } + else { + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'driver'} = $working[1]; + if ($mesa_drivers{$working[1]}){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'hw'} = $mesa_drivers{$working[1]}; + } + } + $value = (defined $device) ? "$platform-$device": $platform; + push(@{$gl->{'egl'}{'data'}{'drivers'}{$working[1]}},$value); + if ($mesa_drivers{$working[1]}){ + $gl->{'egl'}{'data'}{'hw'}{$working[1]} = $mesa_drivers{$working[1]}; + } + } + if ($working[0] eq 'EGL client APIs'){ + if (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'egl'}{'client-apis'} = [split(/\s+/,$working[1])]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'egl'}{'client-apis'} = [split(/\s+/,$working[1])]; + } + } + } + # glx specific values, only found in glxinfo + else { + if (lc($working[0]) eq 'direct rendering'){ + $working[1] = lc($working[1]); + if (!$gl->{'glx'}{'direct-renderers'} || + !(grep {$_ eq $working[1]} @{$gl->{'glx'}{'direct-renders'}})){ + push(@{$gl->{'glx'}{'direct-renders'}}, $working[1]); + } + } + # name of display: does not always list the screen number + elsif (lc($working[0]) eq 'display'){ + if ($working[1] =~ /^(:\d+)\s+screen:\s+(\d+)/){ + $gl->{'glx'}{'display-id'} = $1 . '.' . $2; + } + } + elsif (lc($working[0]) eq 'glx version'){ + if (!$gl->{'glx'}{'glx-version'}){ + $gl->{'glx'}{'glx-version'} = $working[1]; + } + } + elsif (!$b_rend_info && $working[0] =~ /^Extended renderer info/i){ + $b_rend_info = 1; + } + # only check Memory info if no prior device memory found + elsif (!$b_mem_info && $working[0] =~ /^Memory info/i){ + $b_mem_info = (!$gl->{'glx'}{'info'} || !$gl->{'glx'}{'info'}{'device-memory'}) ? 1 : 0; + } + elsif ($b_rend_info){ + if ($line =~ /^\s+Vendor:\s+.*?\(0x([\da-f]+)\)$/){ + $gl->{'glx'}{'info'}{'vendor-id'} = sprintf("%04s",$1); + } + elsif ($line =~ /^\s+Device:\s+.*?\(0x([\da-f]+)\)$/){ + $gl->{'glx'}{'info'}{'device-id'} = sprintf("%04s",$1); + } + elsif ($line =~ /^\s+Video memory:\s+(\d+\s?[MG]B)$/){ + my $size = main::translate_size($1); + $gl->{'glx'}{'info'}{'device-memory'} = main::get_size($size,'string'); + } + elsif ($line =~ /^\s+Unified memory:\s+(\S+)$/){ + $gl->{'glx'}{'info'}{'unified-memory'} = lc($1); + } + } + elsif ($b_mem_info){ + # fallback, nvidia does not seem to have Extended renderer info + if ($line =~ /^\s+Dedicated video memory:\s+(\d+\s?[MG]B)$/){ + my $size = main::translate_size($1); + $gl->{'glx'}{'info'}{'device-memory'} = main::get_size($size,'string'); + $b_mem_info = 0; + } + # we're in the wrong memory block! + elsif ($line =~ /^\s+(VBO|Texture)/){ + $b_mem_info = 0; + } + } + elsif (lc($working[0]) eq 'opengl vendor string'){ + if ($working[1] =~ /^([^\s]+)(\s+\S+)?/){ + my $vendor = lc($1); + $vendor =~ s/(^mesa\/|[\.,]$)//; # Seen Mesa/X.org + if (!$gl->{'glx'}{'opengl'}{'vendor'}){ + $gl->{'glx'}{'opengl'}{'vendor'} = $vendor; + } + } + } + elsif (lc($working[0]) eq 'opengl renderer string'){ + if ($working[1]){ + $working[1] = main::clean($working[1]); + } + # note: seen cases where gl drivers are missing, with empty field value. + else { + $gl->{'glx'}{'no-gl'} = 1; + $working[1] = main::message('glx-value-empty'); + } + if (!$gl->{'glx'}{'opengl'}{'renderers'} || + !(grep {$_ eq $working[1]} @{$gl->{'glx'}{'opengl'}{'renderers'}})){ + push(@{$gl->{'glx'}{'opengl'}{'renderers'}}, $working[1]) ; + } + } + # Dropping all conditions from this test to just show full mesa information + # there is a user case where not f and mesa apply, atom mobo + # This can be the compatibility version, or just the version the hardware + # supports. Core version will override always if present. + elsif (lc($working[0]) eq 'opengl version string'){ + if ($working[1]){ + # first grab the actual gl version + # non free drivers like nvidia may only show their driver version info + if ($working[1] =~ /^(\S+)(\s|$)/){ + push(@{$gl->{'glx'}{'opengl'}{'versions'}}, $1); + } + # handle legacy format: 1.2 (1.5 Mesa 6.5.1) as well as more current: + # 4.5 (Compatibility Profile) Mesa 22.3.6 + # Note: legacy: fglrx starting adding compat strings but they don't + # change this result: + # 4.5 Compatibility Profile Context Mesa 15.3.6 + if ($working[1] =~ /(Mesa|NVIDIA)\s(\S+?)\)?$/i){ + if ($1 && $2 && !$gl->{'glx'}{'opengl'}{'driver'}){ + $gl->{'glx'}{'opengl'}{'driver'}{'vendor'} = lc($1); + $gl->{'glx'}{'opengl'}{'driver'}{'version'} = $2; + } + } + } + elsif (!$gl->{'glx'}{'no-gl'}){ + $gl->{'glx'}{'no-gl'} = 1; + push(@{$gl->{'glx'}{'opengl'}{'versions'}},main::message('glx-value-empty')); + } + } + # if -B was always available, we could skip this, but it is not + elsif ($line =~ /GLX Visuals/){ + last; + } + } + # eglinfo/glxinfo share these + if ($b_opengl){ + if ($working[0] =~ /^OpenGL (compatibility|core) profile version( string)?$/){ + $value = lc($1); + # note: no need to apply empty message here since we don't have the data + # anyway + if ($working[1]){ + # non free drivers like nvidia only show their driver version info + if ($working[1] =~ /^(\S+)(\s|$)/){ + push(@{$gl->{'glx'}{'opengl'}{$value}{'versions'}}, $1); + } + # fglrx started appearing with this extra string, does not appear + # to communicate anything of value + if ($working[1] =~ /\s+(Mesa|NVIDIA)\s+(\S+)$/){ + if ($1 && $2 && !$gl->{'glx'}{'opengl'}{$value}{'vendor'}){ + $gl->{'glx'}{'opengl'}{$value}{'driver'}{'vendor'} = lc($1); + $gl->{'glx'}{'opengl'}{$value}{'driver'}{'version'} = $2; + } + if ($source eq 'egl' && $platform){ + if (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{$value}{'vendor'} = lc($1); + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{$value}{'version'} = $2; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{$value}{'vendor'} = lc($1); + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{$value}{'version'} = $2; + } + } + } + } + } + elsif ($working[0] =~ /^OpenGL (compatibility|core) profile renderer?$/){ + $value = lc($1); + if ($working[1]){ + $working[1] = main::clean($working[1]); + } + # note: seen cases where gl drivers are missing, with empty field value. + else { + $gl->{'glx'}{'no-gl'} = 1; + $working[1] = main::message('glx-value-empty'); + } + if (!$gl->{'glx'}{'opengl'}{$value}{'renderers'} || + !(grep {$_ eq $working[1]} @{$gl->{'glx'}{'opengl'}{$value}{'renderers'}})){ + push(@{$gl->{'glx'}{'opengl'}{$value}{'renderers'}}, $working[1]) ; + } + if ($source eq 'egl' && $platform){ + if ($value eq 'core'){ + $value2 = (defined $device) ? "$platform-$device": $platform; + push(@{$gl->{'egl'}{'data'}{'renderers'}{$working[1]}},$value2); + } + if (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{$value}{'renderer'} = $working[1]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{$value}{'renderer'} = $working[1]; + } + } + } + elsif ($working[0] =~ /^OpenGL (compatibility|core) profile vendor$/){ + $value = lc($1); + if (!$gl->{'glx'}{'opengl'}{$value}{'vendors'} || + !(grep {$_ eq $working[1]} @{$gl->{'glx'}{'opengl'}{$value}{'vendors'}})){ + push(@{$gl->{'glx'}{'opengl'}{$value}{'vendors'}}, $working[1]) ; + } + if ($source eq 'egl' && $platform){ + if (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{$value}{'vendor'} = $working[1]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{$value}{'vendor'} = $working[1]; + } + + } + } + elsif (lc($working[0]) eq 'opengl es profile version string'){ + if ($working[1] && !$gl->{'glx'}{'es-version'}){ + # OpenGL ES 3.2 Mesa 23.0.3 + if ($working[1] =~ /^OpenGL ES (\S+) Mesa (\S+)/){ + $gl->{'glx'}{'es'}{'version'} = $1; + if ($2 && !$gl->{'glx'}{'es'}{'mesa-version'}){ + $gl->{'glx'}{'es'}{'mesa-version'} = $2; + } + if ($source eq 'egl' && $platform){ + if (defined $device){ + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{'es'}{'vendor'} = 'mesa'; + $gl->{'egl'}{'platforms'}{$platform}{$device}{'opengl'}{'es'}{'version'} = $working[1]; + } + else { + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{'es'}{'vendor'} = 'mesa'; + $gl->{'egl'}{'platforms'}{$platform}{'opengl'}{'es'}{'version'} = $working[1]; + } + } + } + } + } + } + } + main::log_data('dump',"$source \$results",$results) if $b_log; + if ($source eq 'egl'){ + print "GL Data: $source: ", Data::Dumper::Dumper $gl if $dbg[57]; + main::log_data('dump',"GL data: $source:",$gl) if $b_log; + } + else { + print "GL Data: $source: ", Data::Dumper::Dumper $gl->{'glx'} if $dbg[57]; + main::log_data('dump',"GLX data: $source:",$gl->{'glx'}) if $b_log; + } + eval $end if $b_log; +} + +sub process_glx_data { + eval $start if $b_log; + my ($glx,$b_glx) = @_; + my $value; + # Remember: if you test for a hash ref hash ref, you create the first hash ref! + if ($glx->{'direct-renders'}){ + $glx->{'direct-render'} = join(', ', @{$glx->{'direct-renders'}}); + } + if (!$glx->{'opengl'}{'renderers'} && $glx->{'opengl'}{'compatibility'} && + $glx->{'opengl'}{'compatibility'}{'renderers'}){ + $glx->{'opengl'}{'renderers'} = $glx->{'opengl'}{'compatibility'}{'renderers'}; + } + # This is tricky, GLX OpenGL version string can be compatibility version, + # but usually they are the same. Just in case, try this. Note these are + # x.y.z type numbering formats generally so use string compare + if ($glx->{'opengl'}{'core'} && $glx->{'opengl'}{'core'}{'versions'}){ + $glx->{'opengl'}{'version'} = (sort @{$glx->{'opengl'}{'core'}{'versions'}})[-1]; + } + elsif ($glx->{'opengl'}{'versions'}){ + $glx->{'opengl'}{'version'} = (sort @{$glx->{'opengl'}{'versions'}})[-1]; + } + if ($glx->{'opengl'}{'version'} && + ($glx->{'opengl'}{'compatibility'} || $glx->{'opengl'}{'versions'})){ + # print "v: $glx->{'opengl'}{'version'}\n"; + # print Data::Dumper::Dumper $glx->{'opengl'}{'versions'}; + # print 'v1: ', (sort @{$glx->{'opengl'}{'versions'}})[0], "\n"; + # here we look for different versions, and determine most likely compat one + if ($glx->{'opengl'}{'compatibility'} && + $glx->{'opengl'}{'compatibility'}{'versions'} && + (sort @{$glx->{'opengl'}{'compatibility'}{'versions'}})[0] ne $glx->{'opengl'}{'version'}){ + $value = (sort @{$glx->{'opengl'}{'compatibility'}{'versions'}})[0]; + $glx->{'opengl'}{'compatibility'}{'version'} = $value; + } + elsif ($glx->{'opengl'}{'versions'} && + (sort @{$glx->{'opengl'}{'versions'}})[0] ne $glx->{'opengl'}{'version'}){ + $value = (sort @{$glx->{'opengl'}{'versions'}})[0]; + $glx->{'opengl'}{'compatibility'}{'version'} = $value; + } + } + if ($glx->{'opengl'}{'renderers'}){ + $glx->{'opengl'}{'renderer'} = join(', ', @{$glx->{'opengl'}{'renderers'}}); + } + # likely eglinfo or advanced glxinfo + if ($glx->{'opengl'}{'vendor'} && + $glx->{'opengl'}{'core'} && + $glx->{'opengl'}{'core'}{'driver'} && + $glx->{'opengl'}{'core'}{'driver'}{'vendor'} && + $glx->{'opengl'}{'core'}{'driver'}{'vendor'} eq 'mesa' && + $glx->{'opengl'}{'vendor'} ne $glx->{'opengl'}{'core'}{'driver'}{'vendor'}){ + $value = $glx->{'opengl'}{'vendor'} . ' '; + $value .= $glx->{'opengl'}{'core'}{'driver'}{'vendor'}; + $glx->{'opengl'}{'vendor'} = $value; + } + # this can be glxinfo only case, no eglinfo + elsif ($glx->{'opengl'}{'vendor'} && + $glx->{'opengl'}{'driver'} && + $glx->{'opengl'}{'driver'}{'vendor'} && + $glx->{'opengl'}{'driver'}{'vendor'} eq 'mesa' && + $glx->{'opengl'}{'vendor'} ne $glx->{'opengl'}{'driver'}{'vendor'}){ + $value = $glx->{'opengl'}{'vendor'} . ' '; + $value .= $glx->{'opengl'}{'driver'}{'vendor'}; + $glx->{'opengl'}{'vendor'} = $value; + } + elsif (!$glx->{'opengl'}{'vendor'} && + $glx->{'opengl'}{'core'} && $glx->{'opengl'}{'core'}{'driver'} && + $glx->{'opengl'}{'core'}{'driver'}{'vendor'}){ + $glx->{'opengl'}{'vendor'} = $glx->{'opengl'}{'core'}{'driver'}{'vendor'}; + } + if ((!$glx->{'opengl'}{'driver'} || + !$glx->{'opengl'}{'driver'}{'version'}) && + $glx->{'opengl'}{'core'} && + $glx->{'opengl'}{'core'}{'driver'} && + $glx->{'opengl'}{'core'}{'driver'}{'version'}){ + $value = $glx->{'opengl'}{'core'}{'driver'}{'version'}; + $glx->{'opengl'}{'driver'}{'version'} = $value; + } + # only tripped when glx filled by eglinfo + if (!$glx->{'source'}){ + my $type; + if (!$b_glx){ + $type = 'glx-egl-missing'; + } + elsif ($b_display){ + $type = 'glx-egl'; + } + else { + $type = 'glx-egl-console'; + } + $glx->{'note'} = main::message($type); + } + print "GLX Data: ", Data::Dumper::Dumper $glx if $dbg[57]; + main::log_data('dump',"GLX data:",$glx) if $b_log; + eval $end if $b_log; +} + +sub vulkan_data { + eval $start if $b_log; + my ($program,$vulkan) = @_; + my ($data,$msg,@working); + my ($results) = ([]); + if ($dbg[56] || $b_log){ + $msg = "${line1}Vulkan Data\n${line3}"; + print $msg if $dbg[56]; + push(@$results,$msg) if $b_log; + } + if (!$fake{'vulkan'}){ + $data = main::grabber("$program 2>/dev/null",'','','ref'); + } + else { + my $file; + $file = "$fake_data_dir/graphics/vulkan/vulkaninfo-intel-llvm-1.txt"; + $file = "$fake_data_dir/graphics/vulkan/vulkaninfo-nvidia-1.txt"; + $file = "$fake_data_dir/graphics/vulkan/vulkaninfo-intel-1.txt"; + $file = "$fake_data_dir/graphics/vulkan/vulkaninfo-amd-dz.txt"; + $file = "$fake_data_dir/graphics/vulkan/vulkaninfo-mali-3.txt"; + $data = main::reader($file,'','ref'); + } + if (!$data){ + if ($dbg[56] || $b_log){ + $msg = "No Vulkan data found" if $dbg[56]; + print "$msg\n" if $dbg[56]; + push(@$results,$msg) if $b_log; + } + return 0; + } + set_mesa_drivers() if !%mesa_drivers; + my ($id,%active); + foreach my $line (@$data){ + next if $line =~ /^(\s*|-+|=+)$/; + @working = split(/\s*:\s*/,$line,2); + next if !@working; + if ($line =~ /^\S/){ + if ($active{'start'}){undef $active{'start'}} + if ($active{'layers'}){undef $active{'layers'}} + if ($active{'groups'}){undef $active{'groups'}} + if ($active{'limits'}){undef $active{'limits'}} + if ($active{'features'}){undef $active{'features'}} + if ($active{'extensions'}){undef $active{'extensions'}} + if ($active{'format'}){undef $active{'format'}} + if ($active{'driver'}){($active{'driver'},$id) = ()} + } + next if $active{'start'}; + next if $active{'groups'}; + next if $active{'limits'}; + next if $active{'features'}; + next if $active{'extensions'}; + next if $active{'format'}; + if ($dbg[56] || $b_log){ + $msg = $line; + print "$msg\n" if $dbg[56]; + push(@$results,$msg) if $b_log; + } + if ($working[0] eq 'Vulkan Instance Version'){ + $vulkan->{'data'}{'version'} = $working[1]; + $active{'start'} = 1; + } + elsif ($working[0] eq 'Layers'){ + if ($working[1] =~ /count\s*=\s*(\d+)/){ + $vulkan->{'data'}{'layers'} = $1; + } + $active{'layers'} = 1; + } + # note: can't close this because Intel didn't use proper indentation + elsif ($working[0] eq 'Presentable Surfaces'){ + $active{'surfaces'} = 1; + } + elsif ($working[0] eq 'Device Groups'){ + $active{'groups'} = 1; + $active{'surfaces'} = 0; + } + elsif ($working[0] eq 'Device Properties and Extensions'){ + $active{'devices'} = 1; + $active{'surfaces'} = 0; + undef $id; + } + elsif ($working[0] eq 'VkPhysicalDeviceProperties'){ + $active{'props'} = 1; + } + elsif ($working[0] eq 'VkPhysicalDeviceDriverProperties'){ + $active{'driver'} = 1; + } + elsif ($working[0] =~ /^\S+Features/i){ + $active{'features'} = 1; + } + # seen as line starter string or inner VkPhysicalDeviceProperties + elsif ($working[0] =~ /^\s*\S+Limits/i){ + $active{'limits'} = 1; + } + elsif ($working[0] =~ /^FORMAT_/){ + $active{'format'} = 1; + } + elsif ($working[0] =~ /^(Device|Instance) Extensions/){ + $active{'extensions'} = 1; + } + if ($active{'surfaces'}){ + if ($working[0] eq 'GPU id'){ + if ($working[1] =~ /^(\d+)\s+\((.*?)\):?$/){ + $id = $1; + $vulkan->{'devices'}{$id}{'model'} = main::clean($2); + } + } + if (defined $id){ + # seen leading space, no leading space + if ($line =~ /^\s*Surface type/){ + $active{'surface-type'} = 1; + } + if ($active{'surface-type'} && $line =~ /\S+_(\S+)_surface$/){ + if (!$vulkan->{'devices'}{$id}{'surfaces'} || + !(grep {$_ eq $1} @{$vulkan->{'devices'}{$id}{'surfaces'}})){ + push(@{$vulkan->{'devices'}{$id}{'surfaces'}},$1); + } + if (!$vulkan->{'data'}{'surfaces'} || + !(grep {$_ eq $1} @{$vulkan->{'data'}{'surfaces'}})){ + push(@{$vulkan->{'data'}{'surfaces'}},$1); + } + } + if ($working[0] =~ /^\s*Formats/){ + undef $active{'surface-type'}; + } + } + } + if ($active{'devices'}){ + if ($working[0] =~ /^GPU(\d+)/){ + $id = $1; + } + elsif (defined $id){ + # apiVersion=4194528 (1.0.224); 1.3.246 (4206838); 79695971 (0x4c01063) + if ($line =~ /^\s+apiVersion\s*=\s*(\S+)(\s+\(([^)]+)\))?/i){ + my ($a,$b) = ($1,$3); + my $api = (!$b || $b =~ /^(0x)?\d+$/) ? $a : $b; + $vulkan->{'devices'}{$id}{'device-api-version'} = $api; + } + elsif ($line =~ /^\s+driverVersion\s*=\s*(\S+)/i){ + $vulkan->{'devices'}{$id}{'device-driver-version'} = $1; + } + elsif ($line =~ /^\s+vendorID\s*=\s*0x(\S+)/i){ + $vulkan->{'devices'}{$id}{'vendor-id'} = $1; + } + elsif ($line =~ /^\s+deviceID\s*=\s*0x(\S+)/i){ + $vulkan->{'devices'}{$id}{'device-id'} = $1; + } + # deviceType=DISCRETE_GPU; PHYSICAL_DEVICE_TYPE_DISCRETE_GPU + elsif ($line =~ /^\s+deviceType\s*=\s*(\S+?_TYPE_)?(\S+)$/i){ + $vulkan->{'devices'}{$id}{'device-type'} = lc($2); + $vulkan->{'devices'}{$id}{'device-type'} =~ s/_/-/g; + } + # deviceName=AMD Radeon RX 6700 XT (RADV NAVI22); AMD RADV HAWAII + # lvmpipe (LLVM 15.0.6, 256 bits); NVIDIA GeForce GTX 1650 Ti + elsif ($line =~ /^\s+deviceName\s*=\s*(\S+)(\s.*|$)/i){ + $vulkan->{'devices'}{$id}{'device-vendor'} = main::clean(lc($1)); + $vulkan->{'devices'}{$id}{'device-name'} = main::clean($1 . $2); + } + } + } + if ($active{'driver'}){ + if (defined $id){ + # driverName=llvmpipe; radv; + if ($line =~ /^\s+driverName\s*=\s*(\S+)(\s|$)/i){ + my $driver = lc($1); + if ($mesa_drivers{$driver}){ + $vulkan->{'devices'}{$id}{'hw'} = $mesa_drivers{$driver}; + } + $vulkan->{'devices'}{$id}{'driver-name'} = $driver; + if (!$vulkan->{'data'}{'drivers'} || + !(grep {$_ eq $driver} @{$vulkan->{'data'}{'drivers'}})){ + push(@{$vulkan->{'data'}{'drivers'}},$driver); + } + } + # driverInfo=Mesa 23.1.3 (LLVM 15.0.7); 525.89.02; Mesa 23.1.3 + elsif ($line =~ /^\s+driverInfo\s*=\s*((Mesa)\s)?(.*)/i){ + $vulkan->{'devices'}{$id}{'mesa'} = lc($2) if $2; + $vulkan->{'devices'}{$id}{'driver-info'} = $3; + } + } + } + } + main::log_data('dump','$results',$results) if $b_log; + print 'Vulkan Data: ', Data::Dumper::Dumper $vulkan if $dbg[57]; + main::log_data('dump','$vulkan',$vulkan) if $b_log; + eval $end if $b_log; +} + +## DISPLAY DATA WAYLAND ## +sub display_data_wayland { + eval $start if $b_log; + my ($b_skip_pos,$program); + if ($ENV{'WAYLAND_DISPLAY'}){ + $graphics{'display-id'} = $ENV{'WAYLAND_DISPLAY'}; + # return as wayland-0 or 0? + $graphics{'display-id'} =~ s/wayland-?//i; + } + if ($fake{'swaymsg'} || ($program = main::check_program('swaymsg'))){ + swaymsg_data($program); + } + # until we get data proving otherwise, assuming these have same output + elsif ($fake{'wl-info'} || (($program = main::check_program('wayland-info')) || + ($program = main::check_program('weston-info')))){ + wlinfo_data($program); + } + elsif ($fake{'wlr-randr'} || ($program = main::check_program('wlr-randr'))){ + wlrrandr_data($program); + } + # make sure we got enough for advanced position data, might be from /sys + if ($extra > 1 && $monitor_ids){ + $b_skip_pos = check_wayland_data(); + } + if ($extra > 1 && $monitor_ids && $b_wayland_data){ + # map_monitor_ids([keys %$monitors]); # not required, but leave in case. + wayland_data_advanced($b_skip_pos); + } + print 'Wayland monitors: ', Data::Dumper::Dumper $monitor_ids if $dbg[17]; + main::log_data('dump','$monitor_ids',$monitor_ids) if $b_log; + eval $end if $b_log; +} + +# If we didn't get explicit tool for wayland data, check to see if we got most +# of the data from /sys/class/drm edid and then skip xrandr to avoid gunking up +# the data, in that case, all we get from xrandr would be the position, which is +# nice but not a must-have. We've already cleared out all disabled ports. +sub check_wayland_data { + eval $start if $b_log; + my ($b_skip_pos,$b_invalid); + foreach my $key (keys %$monitor_ids){ + # we need these 4 items to construct the grid rectangle + if (!defined $monitor_ids->{$key}{'pos-x'} || + !defined $monitor_ids->{$key}{'pos-y'} || + !$monitor_ids->{$key}{'res-x'} || !$monitor_ids->{$key}{'res-y'}){ + $b_skip_pos = 1; + } + if (!$monitor_ids->{$key}{'res-x'} || !$monitor_ids->{$key}{'res-y'}){ + $b_invalid = 1; + } + } + # ok, we have enough, we don't need to do fallback xrandr checks + $b_wayland_data = 1 if !$b_invalid; + eval $end if $b_log; + return $b_skip_pos; +} + +# Set Display rect size for > 1 monitors, monitor positions, size-i, diag +sub wayland_data_advanced { + eval $start if $b_log; + my ($b_skip_pos) = @_; + my (%x_pos,%y_pos); + my ($x_max,$y_max) = (0,0); + my @keys = keys %$monitor_ids; + foreach my $key (@keys){ + if (!$b_skip_pos){ + if ($monitor_ids->{$key}{'res-x'} && $monitor_ids->{$key}{'res-x'} > $x_max){ + $x_max = $monitor_ids->{$key}{'res-x'}; + } + if ($monitor_ids->{$key}{'res-y'} && $monitor_ids->{$key}{'res-y'} > $y_max){ + $y_max = $monitor_ids->{$key}{'res-y'}; + } + # Now we'll add the detected x, y res to the trackers + if (!defined $x_pos{$monitor_ids->{$key}{'pos-x'}}){ + $x_pos{$monitor_ids->{$key}{'pos-x'}} = $monitor_ids->{$key}{'res-x'}; + } + if (!defined $y_pos{$monitor_ids->{$key}{'pos-y'}}){ + $y_pos{$monitor_ids->{$key}{'pos-y'}} += $monitor_ids->{$key}{'res-y'}; + } + } + # this means we failed to get EDID real data, and are using just the wayland + # tool to get this info, eg. with BSD without compositor data. + if ($monitor_ids->{$key}{'size-x'} && $monitor_ids->{$key}{'size-y'} && + (!$monitor_ids->{$key}{'size-x-i'} || !$monitor_ids->{$key}{'size-y-i'} || + !$monitor_ids->{$key}{'dpi'} || !$monitor_ids->{$key}{'diagonal'})){ + my $size_x = $monitor_ids->{$key}{'size-x'}; + my $size_y = $monitor_ids->{$key}{'size-y'}; + $monitor_ids->{$key}{'size-x-i'} = sprintf("%.2f", ($size_x/25.4)) + 0; + $monitor_ids->{$key}{'size-y-i'} = sprintf("%.2f", ($size_y/25.4)) + 0; + $monitor_ids->{$key}{'diagonal'} = sprintf("%.2f", (sqrt($size_x**2 + $size_y**2)/25.4)) + 0; + $monitor_ids->{$key}{'diagonal-m'} = sprintf("%.0f", (sqrt($size_x**2 + $size_y**2))); + if ($monitor_ids->{$key}{'res-x'}){ + my $res_x = $monitor_ids->{$key}{'res-x'}; + $monitor_ids->{$key}{'dpi'} = sprintf("%.0f", $res_x * 25.4 / $size_x); + } + } + } + if (!$b_skip_pos){ + if (scalar @keys > 1 && %x_pos && %y_pos){ + my ($x,$y) = (0,0); + foreach (keys %x_pos){$x += $x_pos{$_}} + foreach (keys %y_pos){$y += $y_pos{$_}} + # handle cases with one tall portrait mode > 2 short landscapes, etc. + $x = $x_max if $x_max > $x; + $y = $y_max if $y_max > $y; + $graphics{'display-rect'} = $x . 'x' . $y; + } + my $layouts = []; + set_monitor_layouts($layouts); + # only update position, we already have all the rest of the data + advanced_monitor_data($monitor_ids,$layouts); + undef $layouts; + } + eval $end if $b_log; +} + +## WAYLAND COMPOSITOR DATA TOOLS ## +# NOTE: These patterns are VERY fragile, and depend on no changes at all to +# the data structure, and more important, the order. Something I would put +# almost no money on being able to count on. +sub wlinfo_data { + eval $start if $b_log; + my ($program) = @_; + my ($data,%mon,@temp,$ref); + my ($b_iwlo,$b_izxdg,$file,$hz,$id,$pos_x,$pos_y,$res_x,$res_y,$scale); + if (!$fake{'wl-info'}){ + undef $monitor_ids; + $data = main::grabber("$program 2>/dev/null",'','strip','ref'); + } + else { + $file = "$fake_data_dir/graphics/wayland/weston-info-2-mon-1.txt"; + $data = main::reader($file,'strip','ref'); + } + print 'wayland/weston-info raw: ', Data::Dumper::Dumper $data if $dbg[46]; + main::log_data('dump','@$data', $data) if $b_log; + foreach (@$data){ + # print 'l: ', $_,"\n"; + if (/^interface: 'wl_output', version: \d+, name: (\d+)$/){ + $b_iwlo = 1; + $id = $1; + } + elsif (/^interface: 'zxdg_output/){ + $b_izxdg = 1; + $b_iwlo = 0; + } + if ($b_iwlo){ + if (/^x: (\d+), y: (\d+), scale: ([\d\.]+)/){ + $mon{$id}->{'pos-x'} = $1; + $mon{$id}->{'pos-y'} = $2; + $mon{$id}->{'scale'} = $3; + } + elsif (/^physical_width: (\d+) mm, physical_height: (\d+) mm/){ + $mon{$id}->{'size-x'} = $1 if $1; # can be 0 if edid data n/a + $mon{$id}->{'size-y'} = $2 if $2; # can be 0 if edid data n/a + } + elsif (/^make: '([^']+)', model: '([^']+)'/){ + my $make = main::clean($1); + my $model = main::clean($2); + $mon{$id}->{'model'} = $make; + if ($make && $model){ + $mon{$id}->{'model'} = $make . ' ' . $model; + } + elsif ($model) { + $mon{$id}->{'model'} = $model; + } + elsif ($make) { + $mon{$id}->{'model'} = $make; + } + # includes remove duplicates and remove unset + if ($mon{$id}->{'model'}){ + $mon{$id}->{'model'} = main::clean_dmi($mon{$id}->{'model'}); + } + } + elsif (/^width: (\d+) px, height: (\d+) px, refresh: ([\d\.]+) Hz,/){ + $mon{$id}->{'res-x'} = $1; + $mon{$id}->{'res-y'} = $2; + $mon{$id}->{'hz'} = sprintf('%.0f',$3); + } + } + # note: we don't want to use the 'description' field because that doesn't + # always contain make/model data, sometimes it's: Built-in/Unknown Display + elsif ($b_izxdg){ + if (/^output: (\d+)/){ + $id = $1; + } + elsif (/^name: '([^']+)'$/){ + $mon{$id}->{'monitor'} = $1; + } + elsif (/^logical_x: (\d+), logical_y: (\d+)/){ + $mon{$id}->{'log-pos-x'} = $1; + $mon{$id}->{'log-pos-y'} = $2; + } + elsif (/^logical_width: (\d+), logical_height: (\d+)/){ + $mon{$id}->{'log-x'} = $1; + $mon{$id}->{'log-y'} = $2; + } + } + if ($b_izxdg && /^interface: '(?!zxdg_output)/){ + last; + } + } + # now we need to map %mon back to $monitor_ids + if (%mon){ + $b_wayland_data = 1; + foreach my $key (keys %mon){ + next if !$mon{$key}->{'monitor'}; # no way to know what it is, sorry + $id = $mon{$key}->{'monitor'}; + $monitor_ids->{$id}{'monitor'} = $id; + $monitor_ids->{$id}{'log-x'} = $mon{$key}->{'log-x'} if defined $mon{$key}->{'log-x'}; + $monitor_ids->{$id}{'log-y'} = $mon{$key}->{'log-y'} if defined $mon{$key}->{'log-y'}; + $monitor_ids->{$id}{'pos-x'} = $mon{$key}->{'pos-x'} if defined $mon{$key}->{'pos-x'}; + $monitor_ids->{$id}{'pos-y'} = $mon{$key}->{'pos-y'} if defined $mon{$key}->{'pos-y'}; + $monitor_ids->{$id}{'res-x'} = $mon{$key}->{'res-x'} if defined $mon{$key}->{'res-x'}; + $monitor_ids->{$id}{'res-y'} = $mon{$key}->{'res-y'} if defined $mon{$key}->{'res-y'}; + $monitor_ids->{$id}{'size-x'} = $mon{$key}->{'size-x'} if defined $mon{$key}->{'size-x'}; + $monitor_ids->{$id}{'size-y'} = $mon{$key}->{'size-y'} if defined $mon{$key}->{'size-y'}; + $monitor_ids->{$id}{'hz'} = $mon{$key}->{'hz'} if defined $mon{$key}->{'hz'}; + if (defined $mon{$key}->{'model'} && !$monitor_ids->{$id}{'model'}){ + $monitor_ids->{$id}{'model'} = $mon{$key}->{'model'}; + } + $monitor_ids->{$id}{'scale'} = $mon{$key}->{'scale'} if defined $mon{$key}->{'scale'}; + # fallbacks in case wl_output block is not present, which happens + if (!defined $mon{$key}->{'pos-x'} && defined $mon{$key}->{'log-pos-x'}){ + $monitor_ids->{$id}{'pos-x'} = $mon{$key}->{'log-pos-x'}; + } + if (!defined $mon{$key}->{'pos-y'} && defined $mon{$key}->{'log-pos-y'}){ + $monitor_ids->{$id}{'pos-y'} = $mon{$key}->{'log-pos-y'}; + } + if (!defined $mon{$key}->{'res-x'} && defined $mon{$key}->{'log-x'}){ + $monitor_ids->{$id}{'res-x'} = $mon{$key}->{'log-x'}; + } + if (!defined $mon{$key}->{'res-y'} && defined $mon{$key}->{'log-y'}){ + $monitor_ids->{$id}{'res-y'} = $mon{$key}->{'log-y'}; + } + } + } + print '%mon: ', Data::Dumper::Dumper \%mon if $dbg[46]; + main::log_data('dump','%mon', \%mon) if $b_log; + print 'wayland/weston-info: monitor_ids: ', Data::Dumper::Dumper $monitor_ids if $dbg[46]; + eval $end if $b_log; +} + +# Note; since not all systems will have /sys data, we'll repack it if it's +# missing here. +sub swaymsg_data { + eval $start if $b_log; + my ($program) = @_; + my (@data,%json,@temp,$ref); + my ($b_json,$file,$hz,$id,$model,$pos_x,$pos_y,$res_x,$res_y,$scale,$serial); + if (!$fake{'swaymsg'}){ + main::load_json() if !$loaded{'json'}; + if ($use{'json'}){ + my $result = qx($program -t get_outputs -r 2>/dev/null); + # returns array of monitors found + @data = &{$use{'json'}->{'decode'}}($result) if $result; + $b_json = 1; + print "$use{'json'}->{'type'}: " if $dbg[46]; + # print "using: $use{'json'}->{'type'}\n"; + } + else { + @data = main::grabber("$program -t get_outputs -p 2>/dev/null",'','strip'); + } + } + else { + undef $monitor_ids; + $file = "$fake_data_dir/graphics/wayland/swaymsg-2-monitor-1.txt"; + @data = main::reader($file,'strip'); + } + print 'swaymsg: ', Data::Dumper::Dumper \@data if $dbg[46]; + main::log_data('dump','@data', \@data) if $b_log; + # print Data::Dumper::Dumper \@data; + if ($b_json){ + $b_wayland_data = 1 if scalar @data > 0; + foreach my $display (@data){ + foreach my $mon (@$display){ + ($hz,$pos_x,$pos_y,$res_x,$res_y,$scale) = (); + $id = $mon->{'name'}; + if (!$monitor_ids->{$id}{'monitor'}){ + $monitor_ids->{$id}{'monitor'} = $mon->{'name'}; + } + # we don't want to overwrite good edid model data if we already got it + if (!$monitor_ids->{$id}{'model'} && $mon->{'make'}){ + $monitor_ids->{$id}{'model'} = main::clean($mon->{'make'}); + if ($mon->{'model'}){ + $monitor_ids->{$id}{'model'} .= ' ' . main::clean($mon->{'model'}); + } + $monitor_ids->{$id}{'model'} = main::remove_duplicates($monitor_ids->{$id}{'model'}); + } + if ($monitor_ids->{$id}{'primary'}){ + if ($monitor_ids->{$id}{'primary'} ne 'false'){ + $monitor_ids->{$id}{'primary'} = $id; + $b_primary = 1; + } + else { + $monitor_ids->{$id}{'primary'} = undef; + } + } + if (!$monitor_ids->{$id}{'serial'}){ + $monitor_ids->{$id}{'serial'} = main::clean_dmi($mon->{'serial'}); + } + # sys data will only have edid type info, not active state res/pos/hz + if ($mon->{'current_mode'}){ + if ($hz = $mon->{'current_mode'}{'refresh'}){ + $hz = sprintf('%.0f',($mon->{'current_mode'}{'refresh'}/1000)); + $monitor_ids->{$id}{'hz'} = $hz; + } + $monitor_ids->{$id}{'res-x'} = $mon->{'current_mode'}{'width'}; + $monitor_ids->{$id}{'res-y'} = $mon->{'current_mode'}{'height'}; + } + if ($mon->{'rect'}){ + $monitor_ids->{$id}{'pos-x'} = $mon->{'rect'}{'x'}; + $monitor_ids->{$id}{'pos-y'} = $mon->{'rect'}{'y'}; + } + if ($mon->{'scale'}){ + $monitor_ids->{$id}{'scale'} =$mon->{'scale'}; + } + } + } + } + else { + foreach (@data){ + push(@temp,'~~') if /^Output/i; + push(@temp,$_); + } + push(@temp,'~~') if @temp; + @data = @temp; + $b_wayland_data = 1 if scalar @data > 8; + foreach (@data){ + if ($_ eq '~~' && $id){ + $monitor_ids->{$id}{'hz'} = $hz; + $monitor_ids->{$id}{'model'} = $model if $model; + $monitor_ids->{$id}{'monitor'} = $id; + $monitor_ids->{$id}{'pos-x'} = $pos_x; + $monitor_ids->{$id}{'pos-y'} = $pos_y; + $monitor_ids->{$id}{'res-x'} = $res_x; + $monitor_ids->{$id}{'res-y'} = $res_y; + $monitor_ids->{$id}{'scale'} = $scale; + $monitor_ids->{$id}{'serial'} = $serial if $serial; + ($hz,$model,$pos_x,$pos_y,$res_x,$res_y,$scale,$serial) = (); + $b_wayland_data = 1; + } + # Output VGA-1 ' ' (focused) + # unknown how 'primary' is shown, if it shows in this output + if (/^Output (\S+) '([^']+)'/i){ + $id = $1; + if ($2 && !$monitor_ids->{$id}{'model'}){ + ($model,$serial) = get_model_serial($2); + } + } + elsif (/^Current mode:\s+(\d+)x(\d+)\s+\@\s+([\d\.]+)\s+Hz/i){ + $res_x = $1; + $res_y = $2; + $hz = (sprintf('%.0f',($3/1000)) + 0) if $3; + } + elsif (/^Position:\s+(\d+),(\d+)/i){ + $pos_x = $1; + $pos_y = $2; + } + elsif (/^Scale factor:\s+([\d\.]+)/i){ + $scale = $1 + 0; + } + } + } + print 'swaymsg: ', Data::Dumper::Dumper $monitor_ids if $dbg[46]; + eval $end if $b_log; +} + +# Like a basic stripped down swaymsg -t get_outputs -p, less data though +# This is EXTREMELY LIKELY TO FAIL! Any tiny syntax change will break this. +sub wlrrandr_data { + eval $start if $b_log; + my ($program) = @_; + my ($file,$hz,$id,$info,$model,$pos_x,$pos_y,$res_x,$res_y,$scale,$serial); + my ($data,@temp); + if (!$fake{'wlr-randr'}){ + $data = main::grabber("$program 2>/dev/null",'','strip','ref'); + } + else { + undef $monitor_ids; + $file = "$fake_data_dir/graphics/wayland/wlr-randr-2-monitor-1.txt"; + $data = main::reader($file,'strip','ref'); + } + foreach (@$data){ + push(@temp,'~~') if /^([A-Z]+-[ABID\d-]+)\s['"]/i; + push(@temp,$_); + } + push(@temp,'~~') if @temp; + @$data = @temp; + $b_wayland_data = 1 if scalar @$data > 4; + print 'wlr-randr: ', Data::Dumper::Dumper $data if $dbg[46]; + main::log_data('dump','@$data', $data) if $b_log; + foreach (@$data){ + if ($_ eq '~~' && $id){ + $monitor_ids->{$id}{'hz'} = $hz; + $monitor_ids->{$id}{'model'} = $model if $model && !$monitor_ids->{$id}{'model'}; + $monitor_ids->{$id}{'monitor'} = $id; + $monitor_ids->{$id}{'pos-x'} = $pos_x; + $monitor_ids->{$id}{'pos-y'} = $pos_y; + $monitor_ids->{$id}{'res-x'} = $res_x; + $monitor_ids->{$id}{'res-y'} = $res_y; + $monitor_ids->{$id}{'scale'} = $scale; + $monitor_ids->{$id}{'serial'} = $serial if $serial && !$monitor_ids->{$id}{'serial'}; + ($hz,$info,$model,$pos_x,$pos_y,$res_x,$res_y,$scale,$serial) = (); + $b_wayland_data = 1; + } + # Output: VGA-1 ' ' (focused) + # DVI-I-1 'Samsung Electric Company SyncMaster H9NX843762' (focused) + # unknown how 'primary' is shown, if it shows in this output + if (/^([A-Z]+-[ABID\d-]+)\s([']([^']+)['])?/i){ + $id = $1; + # if model is set, we got edid data + if ($3 && !$monitor_ids->{$id}{'model'}){ + ($model,$serial) = get_model_serial($3); + } + } + elsif (/^(\d+)x(\d+)\s+px,\s+([\d\.]+)\s+Hz \([^\)]*?current\)/i){ + $res_x = $1; + $res_y = $2; + $hz = sprintf('%.0f',$3) if $3; + } + elsif (/^Position:\s+(\d+),(\d+)/i){ + $pos_x = $1; + $pos_y = $2; + } + elsif (/^Scale:\s+([\d\.]+)/i){ + $scale = $1 + 0; + } + } + print 'wlr-randr: ', Data::Dumper::Dumper $monitor_ids if $dbg[46]; + eval $end if $b_log; +} + +# Return model/serial for those horrible string type values we have to process +# in swaymsg -t get_outputs -p and wlr-randr default output +sub get_model_serial { + eval $start if $b_log; + my $info = $_[0]; + my ($model,$serial); + $info = main::clean($info); + return if !$info; + my @parts = split(/\s+/, $info); + # Perl Madness, lol: the last just checks how many integers in string + if (scalar @parts > 1 && (length($parts[-1]) > 7) && + (($parts[-1] =~ tr/[0-9]//) > 4)){ + $serial = pop @parts; + $serial = main::clean_dmi($serial); # clears out 0x00000 type non data + } + # we're assuming that we'll never get a serial without make/model data too. + $model = join(' ',@parts) if @parts; + $model = main::remove_duplicates($model) if $model && scalar @parts > 1; + eval $end if $b_log; + return ($model,$serial); +} + +# DISPLAY DATA X.org ## +sub display_data_x { + eval $start if $b_log; + my ($prog_xdpyinfo,$prog_xdriinfo,$prog_xrandr); + if ($prog_xdpyinfo = main::check_program('xdpyinfo')){ + xdpyinfo_data($prog_xdpyinfo); + } + # print Data::Dumper::Dumper $graphics{'screens'}; + if ($prog_xrandr = main::check_program('xrandr')){ + xrandr_data($prog_xrandr); + } + # if tool not installed, falls back to testing Xorg log file + if ($prog_xdriinfo = main::check_program('xdriinfo')){ + xdriinfo_data($prog_xdriinfo); + } + if (!$graphics{'screens'}){ + $graphics{'tty'} = tty_data(); + } + if (!$prog_xrandr){ + $graphics{'no-monitors'} = main::message('tool-missing-basic','xrandr'); + if (!$prog_xdpyinfo){ + if ($graphics{'protocol'} eq 'wayland'){ + $graphics{'no-screens'} = main::message('screen-wayland'); + } + else { + $graphics{'no-screens'} = main::message('tool-missing-basic','xdpyinfo/xrandr'); + } + } + } + print 'Final display x: ', Data::Dumper::Dumper $graphics{'screens'} if $dbg[17]; + main::log_data('dump','$graphics{screens}',$graphics{'screens'}) if $b_log; + eval $end if $b_log; +} + +sub xdriinfo_data { + eval $start if $b_log; + my $program = $_[0]; + my (%dri_drivers,$screen,$xdriinfo); + if (!$fake{'xdriinfo'}){ + $xdriinfo = main::grabber("$program $display_opt 2>/dev/null",'','strip','ref'); + } + else { + # $xdriinfo = main::reader("$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-test-1.txt",'strip','ref'); + } + foreach $screen (@$xdriinfo){ + if ($screen =~ /^Screen (\d+):\s+(\S+)/){ + $dri_drivers{$1} = $2 if $2 !~ /^not\b/; + } + } + if ($graphics{'screens'}){ + # assign to the screen if it's found + foreach $screen (@{$graphics{'screens'}}){ + if (defined $dri_drivers{$screen->{'screen'}} ){ + $screen->{'dri-driver'} = $dri_drivers{$screen->{'screen'}}; + } + } + } + # now the display drivers + foreach $screen (sort keys %dri_drivers){ + if (!$graphics{'dri-drivers'} || + !(grep {$dri_drivers{$screen} eq $_} @{$graphics{'dri-drivers'}})){ + push (@{$graphics{'dri-drivers'}},$dri_drivers{$screen}); + } + } + print 'x dri driver: ', Data::Dumper::Dumper \%dri_drivers if $dbg[17]; + main::log_data('dump','%dri_drivers',\%dri_drivers) if $b_log; + eval $end if $b_log; +} + +sub xdpyinfo_data { + eval $start if $b_log; + my ($program) = @_; + my ($diagonal,$diagonal_m,$dpi) = ('','',''); + my ($screen_id,$xdpyinfo,@working); + my ($res_x,$res_y,$size_x,$size_x_i,$size_y,$size_y_i); + if (!$fake{'xdpyinfo'}){ + $xdpyinfo = main::grabber("$program $display_opt 2>/dev/null","\n",'strip','ref'); + } + else { + # my $file; + # $file = "$ENV{HOME}/bin/scripts/inxi/data/xdpyinfo/xdpyinfo-1-screen-2-in-inxi.txt"; + # $xdpyinfo = main::reader($file,'strip','ref'); + } + # @$xdpyinfo = map {s/^\s+//;$_} @$xdpyinfo if @$xdpyinfo; + # print join("\n",@$xdpyinfo), "\n"; + # X vendor and version detection. + # new method added since radeon and X.org and the disappearance of + # version : ...etc. Later on, the normal textual version string + # returned, e.g. like: X.Org version: 6.8.2 + # A failover mechanism is in place: if $version empty, release number parsed instead + foreach (@$xdpyinfo){ + @working = split(/:\s+/, $_); + next if (($graphics{'screens'} && $working[0] !~ /^(dimensions$|screen\s#)/) || !$working[0]); + # print "$_\n"; + if ($working[0] eq 'vendor string'){ + $working[1] =~ s/The\s|\sFoundation//g; + # some distros, like fedora, report themselves as the xorg vendor, + # so quick check here to make sure the vendor string includes Xorg in string + if ($working[1] !~ /x/i){ + $working[1] .= ' X.org'; + } + $graphics{'x-server'} = [[$working[1]]]; + } + elsif ($working[0] eq 'name of display'){ + $graphics{'display-id'} = $working[1]; + } + # this is the x protocol version + elsif ($working[0] eq 'version number'){ + $graphics{'x-protocol-version'} = $working[1]; + } + # not used, but might be good for something? + elsif ($working[0] eq 'vendor release number'){ + $graphics{'x-vendor-release'} = $working[1]; + } + # the real X.org version string + elsif ($working[0] eq 'X.Org version'){ + push(@{$graphics{'x-server'}->[0]},$working[1]); + } + elsif ($working[0] eq 'default screen number'){ + $graphics{'display-default-screen'} = $working[1]; + } + elsif ($working[0] eq 'number of screens'){ + $graphics{'display-screens'} = $working[1]; + } + elsif ($working[0] =~ /^screen #([0-9]+):/){ + $screen_id = $1; + } + elsif ($working[0] eq 'resolution'){ + $working[1] =~ s/^([0-9]+)x/$1/; + $graphics{'s-dpi'} = $working[1]; + } + # This is Screen, not monitor: dimensions: 2560x1024 pixels (677x270 millimeters) + elsif ($working[0] eq 'dimensions'){ + ($dpi,$res_x,$res_y,$size_x,$size_y) = (); + if ($working[1] =~ /([0-9]+)\s*x\s*([0-9]+)\s+pixels\s+\(([0-9]+)\s*x\s*([0-9]+)\s*millimeters\)/){ + $res_x = $1; + $res_y = $2; + $size_x = $3; + $size_y = $4; + # flip size x,y if don't roughly match res x/y ratio + if ($size_x && $size_y && $res_y){ + flip_size_x_y(\$size_x,\$size_y,\$res_x,\$res_y); + } + $size_x_i = ($size_x) ? sprintf("%.2f", ($size_x/25.4)) : 0; + $size_y_i = ($size_y) ? sprintf("%.2f", ($size_y/25.4)) : 0; + $dpi = ($res_x && $size_x) ? sprintf("%.0f", ($res_x*25.4/$size_x)) : ''; + $diagonal = ($size_x && $size_y) ? sprintf("%.2f", (sqrt($size_x**2 + $size_y**2)/25.4)) + 0 : ''; + $diagonal_m = ($size_x && $size_y) ? sprintf("%.0f", (sqrt($size_x**2 + $size_y**2))) : ''; + } + push(@{$graphics{'screens'}}, { + 'diagonal' => $diagonal, + 'diagonal-m' => $diagonal_m, + 'res-x' => $res_x, + 'res-y' => $res_y, + 'screen' => $screen_id, + 's-dpi' => $dpi, + 'size-x' => $size_x, + 'size-x-i' => $size_x_i, + 'size-y' => $size_y, + 'size-y-i' => $size_y_i, + 'source' => 'xdpyinfo', + }); + } + } + print 'Data: xdpyinfo: ', Data::Dumper::Dumper $graphics{'screens'} if $dbg[17]; + main::log_data('dump','$graphics{screens}',$graphics{'screens'}) if $b_log; + eval $end if $b_log; +} + +sub xrandr_data { + eval $end if $b_log; + my ($program) = @_; + my ($diagonal,$diagonal_m,$dpi,$monitor_id,$pos_x,$pos_y,$primary); + my ($res_x,$res_x_max,$res_y,$res_y_max); + my ($screen_id,$set_as,$size_x,$size_x_i,$size_y,$size_y_i); + my (@ids,%monitors,@xrandr_screens,$xrandr); + if (!$fake{'xrandr'}){ + $xrandr = main::grabber("$program $display_opt 2>/dev/null",'','strip','ref'); + } + else { + # my $file; + # $file = ""$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-4-displays-1.txt"; + # $file = "$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-3-display-primary-issue.txt"; + # $file = "$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-test-1.txt"; + # $file = "$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-test-2.txt"; + # $file = "$ENV{HOME}/bin/scripts/inxi/data/xrandr/xrandr-1-screen-2-in-inxi.txt"; + # $xrandr = main::reader($file,'strip','ref'); + } + # $graphics{'dimensions'} = (\@dimensions); + # we get a bit more info from xrandr than xdpyinfo, but xrandr fails to handle + # multiple screens from different video cards + # $graphics{'screens'} = undef; + foreach (@$xrandr){ + # note: no mm as with xdpyinfo + # Screen 0: minimum 320 x 200, current 2560 x 1024, maximum 8192 x 8192 + if (/^Screen ([0-9]+):/){ + $screen_id = $1; + # handle no xdpyinfo Screen data, multiple xscreens, etc + if (check_screens($screen_id) && + /:\s.*?current\s+(\d+)\s*x\s*(\d+),\smaximum\s+(\d+)\s*x\s*(\d+)/){ + $res_x = $1; + $res_y = $2; + $res_x_max = $3; + $res_y_max = $4; + push(@{$graphics{'screens'}}, { + 'diagonal' => undef, + 'diagonal-m' => undef, + 'res-x' => $res_x, + 'res-y' => $res_y, + 'screen' => $screen_id, + 's-dpi' => undef, + 'size-x' => undef, + 'size-x-i' => undef, + 'size-y' => undef, + 'size-y-i' => undef, + 'source' => 'xrandr', + }); + } + if (%monitors){ + push(@xrandr_screens,{%monitors}); + %monitors = (); + } + } + # HDMI-2 connected 1920x1200+1080+0 (normal left inverted right x axis y axis) 519mm x 324mm + # DP-1 connected primary 2560x1440+1080+1200 (normal left inverted right x axis y axis) 598mm x 336mm + # HDMI-1 connected 1080x1920+0+0 left (normal left inverted right x axis y axis) 160mm x 90mm + # disabled but connected: VGA-1 connected (normal left inverted right x axis y axis) + elsif (/^([^\s]+)\s+connected\s(primary\s)?/){ + $monitor_id = $1; + $set_as = $2; + if (/^[^\s]+\s+connected\s(primary\s)?([0-9]+)\s*x\s*([0-9]+)\+([0-9]+)\+([0-9]+)(\s[^(]*\([^)]+\))?(\s([0-9]+)mm\sx\s([0-9]+)mm)?/){ + $res_x = $2; + $res_y = $3; + $pos_x = $4; + $pos_y = $5; + $size_x = $8; + $size_y = $9; + # flip size x,y if don't roughly match res x/y ratio + if ($size_x && $size_y && $res_y){ + flip_size_x_y(\$size_x,\$size_y,\$res_x,\$res_y); + } + $size_x_i = ($size_x) ? sprintf("%.2f", ($size_x/25.4)) + 0 : 0; + $size_y_i = ($size_y) ? sprintf("%.2f", ($size_y/25.4)) + 0 : 0; + $dpi = ($res_x && $size_x) ? sprintf("%.0f", $res_x * 25.4 / $size_x) : ''; + $diagonal = ($res_x && $size_x) ? sprintf("%.2f", (sqrt($size_x**2 + $size_y**2)/25.4)) + 0 : ''; + $diagonal_m = ($res_x && $size_x) ? sprintf("%.0f", (sqrt($size_x**2 + $size_y**2))) : ''; + } + else { + ($res_x,$res_y,$pos_x,$pos_y,$size_x,$size_x_i,$size_y,$size_y_i,$dpi,$diagonal,$diagonal_m) = () + } + undef $primary; + push(@ids,$monitor_id); + if ($set_as){ + $primary = $monitor_id; + $set_as =~ s/\s$//; + $b_primary = 1; + } + $monitors{$monitor_id} = { + 'screen' => $screen_id, + 'monitor' => $monitor_id, + 'pos-x' => $pos_x, + 'pos-y' => $pos_y, + 'primary' => $primary, + 'res-x' => $res_x, + 'res-y' => $res_y, + 'size-x' => $size_x, + 'size-x-i' => $size_x_i, + 'size-y' => $size_y, + 'size-y-i' => $size_y_i, + 'dpi' => $dpi, + 'diagonal' => $diagonal, + 'diagonal-m' => $diagonal_m, + 'position' => $set_as, + }; + # print "x:$size_x y:$size_y rx:$res_x ry:$res_y dpi:$dpi\n"; + ($res_x,$res_y,$size_x,$size_x_i,$size_y,$size_y_i,$set_as) = (0,0,0,0,0,0,0,0,undef); + } + my @working = split(/\s+/,$_); + # this is the monitor current dimensions + # 5120x1440 59.98* 29.98 + if ($working[1] =~ /\*/){ + $working[1] =~ s/\*|\+//g; + $working[1] = sprintf("%.0f",$working[1]); + if ($monitor_id && %monitors){ + $monitors{$monitor_id}->{'hz'} = $working[1]; + } + ($diagonal,$dpi) = ('',''); + # print Data::Dumper::Dumper \@monitors; + } + } + if (%monitors){ + push(@xrandr_screens,{%monitors}); + } + my $i = 0; + my $layouts; + # corner cases, xrandr screens > xdpyinfo screen, no xdpyinfo counts + if ($graphics{'screens'} && (!defined $graphics{'display-screens'} || + $graphics{'display-screens'} < scalar @{$graphics{'screens'}})){ + $graphics{'display-screens'} = scalar @{$graphics{'screens'}}; + } + map_monitor_ids(\@ids) if @ids; + # print "xrandr_screens 1: " . Data::Dumper::Dumper \@xrandr_screens; + foreach my $main (@{$graphics{'screens'}}){ + # print "h: " . Data::Dumper::Dumper $main; + # print "h: " . Data::Dumper::Dumper @xrandr_screens; + # print $main->{'screen'}, "\n"; + foreach my $x_screen (@xrandr_screens){ + # print "d: " . Data::Dumper::Dumper $x_screen; + my @keys = sort keys %$x_screen; + if ($x_screen->{$keys[0]}{'screen'} eq $main->{'screen'} && + !defined $graphics{'screens'}->[$i]{'monitors'}){ + $graphics{'screens'}->[$i]{'monitors'} = $x_screen; + } + if ($extra > 1){ + if (!$layouts){ + $layouts = []; + set_monitor_layouts($layouts); + } + advanced_monitor_data($x_screen,$layouts); + } + if (!defined $main->{'size-x'}){ + $graphics{'screens'}->[$i]{'size-missing'} = main::message('tool-missing-basic','xdpyinfo'); + } + } + $i++; + } + undef $layouts; + # print "xrandr_screens 2: " . Data::Dumper::Dumper \@xrandr_screens; + print 'Data: xrandr: ', Data::Dumper::Dumper $graphics{'screens'} if $dbg[17]; + main::log_data('dump','$graphics{screens}',$graphics{'screens'}) if $b_log; + eval $end if $b_log; +} + +# Handle some strange corner cases with more robust testing +sub check_screens { + my ($id) = @_; + my $b_use; + # used: scalar @{$graphics{'screens'}} != (scalar @$xrandr_screens + 1) + # before but that test can fail in some cases. + # no screens set in xdpyinfo. If xrandr has > 1 xscreen, this would be false + if (!$graphics{'screens'}){ + $b_use = 1; + } + # verify that any xscreen set so far does not exist in $graphics{'screens'} + else { + my $b_detected; + foreach my $screen (@{$graphics{'screens'}}){ + if ($screen->{'screen'} eq $id){ + $b_detected = 1; + last; + } + } + $b_use = 1 if !$b_detected; + } + return $b_use; +} + +# Case where no xpdyinfo display server/version data exists, or to set Wayland +# Xwayland version, or Xvesa data. +sub display_server_data { + eval $start if $b_log; + my ($program); + # load the extra X paths, it's important that these are first, because + # later Xorg versions show error if run in console or ssh if the true path + # is not used. + @paths = (qw(/usr/lib /usr/lib/xorg /usr/lib/xorg-server /usr/libexec), @paths); + my (@data,$server,$version); + if (!$graphics{'x-server'} || !$graphics{'x-server'}->[0][1]){ + # IMPORTANT: both commands send version data to stderr! + if ($program = main::check_program('Xorg')){ + @data = main::grabber("$program -version 2>&1",'','strip'); + $server = 'X.org'; + } + elsif ($program = main::check_program('X')){ + @data = main::grabber("$program -version 2>&1",'','strip'); + $server = 'X.org'; + } + elsif ($program = main::check_program('Xvesa')){ + @data = main::grabber("$program -version 2>&1",'','strip'); + $server = 'Xvesa'; + $graphics{'display-driver'} = ['vesa']; + $graphics{'xvesa'} = $program; + if (!$graphics{'screens'}){ + $graphics{'no-screens'} = main::message('screen-xvesa'); + } + } + # print join('^ ', @paths), " :: $program\n"; + # print Data::Dumper::Dumper \@data; + if ($data[0]){ + if ($data[0] =~ /X.org X server (\S+)/i){ + $version = $1; + } + elsif ($data[0] =~ /XFree86 Version (\S+)/i){ + $version = $1; + $server = 'XFree86'; + } + elsif ($data[0] =~ /X Window System Version (\S+)/i){ + $version = $1; + } + elsif ($data[0] =~ /Xvesa from tinyx (\S+)/i){ + $version = $1; + $server = 'TinyX Xvesa'; + } + } + $graphics{'x-server'} = [[$server,$version]] if $server; + } + if ($program = main::check_program('Xwayland')){ + undef $version; + @data = main::grabber("$program -version 2>&1",'','strip'); + # Slackware Linux Project Xwayland Version 21.1.4 (12101004) + # The X.Org Foundation Xwayland Version 21.1.4 (12101004) + if (@data){ + $data[0] =~ /Xwayland Version (\S+)/; + $version = $1; + } + $graphics{'x-server'} = [] if !$graphics{'x-server'}; + push(@{$graphics{'x-server'}},['Xwayland',$version]); + } + # remove extra X paths from global @paths + @paths = grep { !/^\/usr\/lib|xorg|libexec/ } @paths; + eval $end if $b_log; +} + +sub display_protocol { + eval $start if $b_log; + $graphics{'protocol'} = ''; + if ($ENV{'XDG_SESSION_TYPE'}){ + $graphics{'protocol'} = $ENV{'XDG_SESSION_TYPE'}; + } + if (!$graphics{'protocol'} && $ENV{'WAYLAND_DISPLAY'}){ + $graphics{'protocol'} = $ENV{'WAYLAND_DISPLAY'}; + } + # can show as wayland-0 + if ($graphics{'protocol'} && $graphics{'protocol'} =~ /wayland/i){ + $graphics{'protocol'} = 'wayland'; + } + # yes, I've seen this in 2019 distros, sigh + elsif ($graphics{'protocol'} eq 'tty'){ + $graphics{'protocol'} = ''; + } + # If no other source, get user session id, then grab session type. + # loginctl also results in the session id + # undef $graphics{'protocol'}; + if (!$graphics{'protocol'}){ + if (my $program = main::check_program('loginctl')){ + my $id = ''; + # $id = $ENV{'XDG_SESSION_ID'}; # returns tty session in console + my @data = main::grabber("$program --no-pager --no-legend 2>/dev/null",'','strip'); + foreach (@data){ + # some systems show empty or ??? for TTY field, but whoami should do ok + next if /(ttyv?\d|pts\/)/; # freebsd: ttyv3 + # in display, root doesn't show in the logins + next if $client{'whoami'} && $client{'whoami'} ne 'root' && !/\b$client{'whoami'}\b/; + $id = (split(/\s+/, $_))[0]; + # multiuser? too bad, we'll go for the first one that isn't a tty/pts + last; + } + if ($id){ + my $temp = (main::grabber("$program show-session $id -p Type --no-pager --no-legend 2>/dev/null"))[0]; + $temp =~ s/Type=// if $temp; + # ssh will not show /dev/ttyx so would have passed the first test + $graphics{'protocol'} = $temp if $temp && $temp ne 'tty'; + } + } + } + $graphics{'protocol'} = lc($graphics{'protocol'}) if $graphics{'protocol'}; + eval $end if $b_log; +} + +## DRIVER DATA ## +# for wayland display/monitor drivers, or if no display drivers found for x +sub gpu_drivers_sys { + eval $start if $b_log; + my ($id) = @_; + my ($driver); + my $drivers = []; + # we only want list of drivers for cards with a connected monitor, and inactive + # ports are already removed by the 'all' stage. + foreach my $port (keys %{$monitor_ids}){ + if (!$monitor_ids->{$port}{'drivers'} || + ($id ne 'all' && $id ne $port) || + !$monitor_ids->{$port}{'status'} || + $monitor_ids->{$port}{'status'} ne 'connected'){ + next; + } + else { + foreach $driver (@{$monitor_ids->{$port}{'drivers'}}){ + push(@$drivers,$driver); + } + } + } + if (@$drivers){ + @$drivers = sort(@$drivers); + main::uniq($drivers); + } + eval $end if $b_log; + return $drivers; +} + +sub display_drivers_x { + eval $start if $b_log; + my $driver_data = []; + # print 'x-log: ' . $system_files{'xorg-log'} . "\n"; + if (my $log = $system_files{'xorg-log'}){ + if ($fake{'xorg-log'}){ + # $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/Xorg.0-voyager-serena.log"; + # $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/loading-unload-failed-all41-mint.txt"; + # $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/loading-unload-failed-phd21-mint.txt"; + # $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/Xorg.0-gm10.log"; + # $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/xorg-multi-driver-1.log"; + } + my $x_log = main::reader($log,'','ref'); + # list is from sgfxi plus non-free drivers, plus ARM drivers. + # Don't use ati. It's just a wrapper for: r128, mach64, radeon + my $list = join('|', qw(amdgpu apm ark armsoc atimisc + chips cirrus cyrix etnaviv fbdev fbturbo fglrx geode glide glint + i128 i740 i810-dec100 i810e i810 i815 i830 i845 i855 i865 i915 i945 i965 + iftv imstt intel ivtv mach64 mesa mga m68k modesetting neomagic newport + nouveau nsc nvidia nv openchrome r128 radeonhd radeon rendition + s3virge s3 savage siliconmotion sisimedia sisusb sis + sunbw2 suncg14 suncg3 suncg6 sunffb sunleo suntcx tdfx tga trident tseng + unichrome v4l vboxvideo vesa vga via vmware vmwgfx voodoo)); + # $list = qr/$list/i; # qr/../i only added perl 5.14, fails on older perls + my ($b_use_dri,$dri,$driver,%drivers); + my ($alternate,$failed,$loaded,$unloaded); + my $pattern = 'Failed|Unload|Loading'; + # preferred source xdriinfo because it's current and accurate, but fallback here + if (!$graphics{'dri-drivers'}){ + $b_use_dri = 1; + $pattern .= '|DRI driver:'; + } + # $pattern = qr/$pattern/i; # qr/../i only added perl 5.14, fails on older perls + # it's much cheaper to grab the simple pattern match then do the expensive one + # in the main loop. + # @$x_log = grep {/Failed|Unload|Loading/} @$x_log; + foreach my $line (@$x_log){ + next if $line !~ /$pattern/i; + # print "$line\n"; + # note that in file names, driver is always lower case. Legacy _drv.o + if ($line =~ /\sLoading.*($list)_drv\.s?o$/i){ + $driver=lc($1); + # we get all the actually loaded drivers first, we will use this to compare the + # failed/unloaded, which have not always actually been truly loaded + $drivers{$driver}='loaded'; + } + # openbsd uses UnloadModule: + elsif ($line =~ /(Unloading\s|UnloadModule).*\"?($list)(_drv\.s?o)?\"?$/i){ + $driver=lc($2); + # we get all the actually loaded drivers first, we will use this to compare the + # failed/unloaded, which have not always actually been truly loaded + if (exists $drivers{$driver} && $drivers{$driver} ne 'alternate'){ + $drivers{$driver}='unloaded'; + } + } + # verify that the driver actually started the desktop, even with false failed messages + # which can occur. This is the driver that is actually driving the display. + # note that xorg will often load several modules, like modesetting,fbdev,nouveau + # NOTE: + # (II) UnloadModule: "nouveau" + # (II) Unloading nouveau + # (II) Failed to load module "nouveau" (already loaded, 0) + # (II) LoadModule: "modesetting" + elsif ($line =~ /Failed.*($list)\"?.*$/i){ + # Set driver to lower case because sometimes it will show as + # RADEON or NVIDIA in the actual x start + $driver=lc($1); + # we need to make sure that the driver has already been truly loaded, + # not just discussed + if (exists $drivers{$driver} && $drivers{$driver} ne 'alternate'){ + if ($line !~ /\(already loaded/){ + $drivers{$driver}='failed'; + } + # reset the previous line's 'unloaded' to 'loaded' as well + else { + $drivers{$driver}='loaded'; + } + } + elsif ($line =~ /module does not exist/){ + $drivers{$driver}='alternate'; + } + } + elsif ($b_use_dri && $line =~ /DRI driver:\s*(\S+)/i){ + $dri = $1; + if (!$graphics{'dri-drivers'} || + !(grep {$dri eq $_} @{$graphics{'dri-drivers'}})){ + push(@{$graphics{'dri-drivers'}},$dri); + } + } + } + # print 'drivers: ', Data::Dumper::Dumper \%drivers; + foreach (sort keys %drivers){ + if ($drivers{$_} eq 'loaded'){ + push(@$loaded,$_); + } + elsif ($drivers{$_} eq 'unloaded'){ + push(@$unloaded,$_); + } + elsif ($drivers{$_} eq 'failed'){ + push(@$failed,$_); + } + elsif ($drivers{$_} eq 'alternate'){ + push(@$alternate,$_); + } + } + if ($loaded || $unloaded || $failed || $alternate){ + $driver_data = [$loaded,$unloaded,$failed,$alternate]; + } + } + eval $end if $b_log; + # print 'source: ', Data::Dumper::Dumper $driver_data; + return $driver_data; +} +sub set_mesa_drivers { + %mesa_drivers = ( + 'anv' => 'intel', + 'crocus' => 'intel', + 'etnaviv' => 'vivante', + 'freedreno' => 'qualcomm', + 'i915' => 'intel', + 'i965' => 'intel', + 'iris' => 'intel', + 'lima' => 'mali', + 'nouveau' => 'nvidia', + 'panfrost' => 'mali/bifrost', + 'r200' => 'amd', + 'r300' => 'amd', + 'r600' => 'amd', + 'radeonsi' => 'amd', + 'radv' => 'amd', + 'svga3d' => 'vmware', + 'v3d' => 'broadcom', + 'v3dv' => 'broadcom', + 'vc4' => 'broadcom', + ); +} + +## GPU DATA ## +sub set_amd_data { + $gpu_amd = [ + # no ids + {'arch' => 'Wonder', + 'ids' => '', + 'code' => 'Wonder', + 'process' => 'NEC 800nm', + 'years' => '1986-92', + }, + {'arch' => 'Mach', + 'ids' => '4158|4354|4358|4554|4654|4754|4755|4758|4c42|4c49|4c50|4c54|5354|' . + '5654|5655|5656', + 'code' => 'Mach64', + 'process' => 'TSMC 500-600nm', + 'years' => '1992-97', + }, + {'arch' => 'Rage-2', + 'ids' => '4756|4757|4759|475a|4c47', + 'code' => 'Rage-2', + 'process' => 'TSMC 500nm', + 'years' => '1996', + }, + {'arch' => 'Rage-3', + 'ids' => '4742|4744|4749|474d|474f|4750|4752', + 'code' => 'Rage-3', + 'process' => 'TSMC 350nm', + 'years' => '1997-99', + }, + {'arch' => 'Rage-4', + 'ids' => '474e|4753|4c46|4c4d|4c4e|4c52|4d46|5044|5046|5050|5052|5245|5246|' . + '524b|524c|534d|5446|5452', + 'code' => 'Rage-4', + 'process' => 'TSMC 250-350nm', + 'years' => '1998-99', + }, + # vendor 1014 IBM, subvendor: 1092 + # 0172|0173|0174|0184 + # {'arch' => 'IBM', + # 'code' => 'Fire GL', + # 'process' => 'IBM 156-250nm', + # 'years' => '1999-2001', + # }, + # rage 5 was game cube flipper chip +# rage 5 was game cube flipper chip 2000 + {'arch' => 'Rage-6', + 'ids' => '4137|4337|4437|4c59|5144|5159|515e', + 'code' => 'R100', + 'process' => 'TSMC 180nm', + 'years' => '2000-07', + }, + # |Radeon (7[3-9]{2}|8d{3}|9[5-9]d{2} + {'arch' => 'Rage-7', + 'ids' => '4136|4150|4152|4170|4172|4242|4336|4966|496e|4c57|4c58|4c66|4c6e|' . + '4e51|4f72|4f73|5148|514c|514d|5157|5834|5835|5940|5941|5944|5960|5961|5962|' . + '5964|5965|5b63|5b72|5b73|5c61|5c63|5d44|5d45|7100|7101|7102|7109|710a|710b|' . + '7120|7129|7140|7142|7143|7145|7146|7147|7149|714a|715f|7162|7163|7166|7167|' . + '7181|7183|7186|7187|718b|718c|718d|7193|7196|719f|71a0|71a1|71a3|71a7|71c0|' . + '71c1|71c2|71c3|71c5|71c6|71c7|71ce|71d5|71d6|71de|71e0|71e1|71e2|71e6|71e7|' . + '7240|7244|7248|7249|724b|7269|726b|7280|7288|7291|7293|72a0|72a8|72b1|72b3|' . + '7834|7835|791e', + 'code' => 'R200', + 'process' => 'TSMC 150nm', + 'years' => '2001-06', + }, + {'arch' => 'Rage-8', + 'ids' => '4144|4146|4147|4148|4151|4153|4154|4155|4157|4164|4165|4166|4168|' . + '4171|4173|4e44|4e45|4e46|4e47|4e48|4e49|4e4b|4e50|4e52|4e54|4e64|4e65|4e66|' . + '4e67|4e68|4e69|4e6a|4e71|5a41|5a42|5a61|5a62', + 'code' => 'R300', + 'process' => 'TSMC 130nm', + 'years' => '2002-07', + }, + {'arch' => 'Rage-9', + 'ids' => '3150|3151|3152|3154|3155|3171|3e50|3e54|3e70|4e4a|4e56|5460|5461|' . + '5462|5464|5657|5854|5874|5954|5955|5974|5975|5b60|5b62|5b64|5b65|5b66|5b70|' . + '5b74|5b75', + 'code' => 'Radeon IGP', + 'process' => 'TSMC 110nm', + 'years' => '2003-08', + }, + {'arch' => 'R400', + 'ids' => '4a49|4a4a|4a4b|4a4d|4a4e|4a4f|4a50|4a54|4a69|4a6a|4a6b|4a70|4a74|' . + '4b49|4b4b|4b4c|4b69|4b6b|4b6c|5549|554a|554b|554d|554e|554f|5550|5551|5569|' . + '556b|556d|556f|5571|564b|564f|5652|5653|5d48|5d49|5d4a|5d4d|5d4e|5d4f|5d50|' . + '5d52|5d57|5d6d|5d6f|5d72|5d77|5e48|5e49|5e4a|5e4b|5e4c|5e4d|5e4f|5e6b|5e6d|' . + '5f57|791f|793f|7941|7942|796e', + 'code' => 'R400', + 'process' => 'TSMC 55-130nm', + 'years' => '2004-08', + }, + {'arch' => 'R500', + 'ids' => '7104|710e|710f|7124|712e|712f|7152|7153|7172|7173|7188|718a|719b|' . + '71bb|71c4|71d2|71d4|71f2|7210|7211|724e|726e|940f|94c8|94c9|9511|9581|9583|' . + '958b|958d', + 'code' => 'R500', + 'process' => 'TSMC 90nm', + 'years' => '2005-07', + }, + # process: tsmc 55nm, 65nm, xbox 360s at 40nm + {'arch' => 'TeraScale', + 'ids' => '4346|4630|4631|9400|9401|9403|9405|940a|940b|9440|9441|9442|9443|' . + '9444|9446|944a|944b|944c|944e|9450|9452|9456|945a|9460|9462|946a|9480|9488|' . + '9489|9490|9491|9495|9498|949c|949e|949f|94a0|94a1|94a3|94b3|94b4|94c1|94c3|' . + '94c4|94c5|94c7|94cb|94cc|9500|9501|9504|9505|9506|9507|9508|9509|950f|9513|' . + '9515|9519|9540|954f|9552|9553|9555|9557|955f|9580|9586|9587|9588|9589|958a|' . + '958c|9591|9593|9595|9596|9597|9598|9599|95c0|95c2|95c4|95c5|95c6|95c9|95cc|' . + '95cd|95cf|9610|9611|9612|9613|9614|9615|9616|9710|9712|9713|9714|9715', + 'code' => 'R6xx/RV6xx/RV7xx', + 'process' => 'TSMC 55-65nm', + 'years' => '2005-13', + }, + {'arch' => 'TeraScale-2', + 'ids' => '6720|6738|6739|673e|6740|6741|6742|6743|6749|674a|6750|6751|6758|' . + '6759|675b|675d|675f|6760|6761|6763|6764|6765|6766|6767|6768|6770|6771|6772|' . + '6778|6779|677b|6840|6841|6842|6843|6880|6888|6889|688a|688c|688d|6898|6899|' . + '689b|689c|689d|689e|68a0|68a1|68a8|68a9|68b8|68b9|68ba|68be|68bf|68c0|68c1|' . + '68c7|68c8|68c9|68d8|68d9|68da|68de|68e0|68e1|68e4|68e5|68e8|68e9|68f1|68f2|' . + '68f8|68f9|68fa|68fe|9640|9641|9642|9643|9644|9645|9647|9648|9649|964a|964b|' . + '964c|964e|964f|9802|9803|9804|9805|9806|9807|9808|9809|980a|9925|9926', + 'code' => 'Evergreen', + 'process' => 'TSMC 32-40nm', + 'years' => '2009-15', + }, + {'arch' => 'TeraScale-3', + 'ids' => '6704|6707|6718|6719|671c|671d|671f|9900|9901|9903|9904|9905|9906|' . + '9907|9908|9909|990a|990b|990c|990d|990e|990f|9910|9913|9917|9918|9919|9990|' . + '9991|9992|9993|9994|9995|9996|9997|9998|9999|999a|999b|999c|999d|99a0|99a2|' . + '99a4', + 'code' => 'Northern Islands', + 'process' => 'TSMC 32nm', + 'years' => '2010-13', + }, + {'arch' => 'GCN-1', + 'ids' => '154c|6600|6601|6604|6605|6606|6607|6608|6609|6610|6611|6613|6617|' . + '6631|6660|6663|6664|6665|6666|6667|666f|6780|6784|6788|678a|6798|6799|679a|' . + '679b|679e|679f|6800|6801|6802|6806|6808|6809|6810|6811|6816|6817|6818|6819|' . + '6820|6821|6822|6823|6825|6826|6827|6828|6829|682a|682b|682c|682d|682f|6830|' . + '6831|6835|6837|683d|683f|684c', + 'code' => 'Southern Islands', + 'process' => 'TSMC 28nm', + 'years' => '2011-20', + }, + # process: both TSMC and GlobalFoundries + {'arch' => 'GCN-2', + 'ids' => '1304|1305|1306|1307|1309|130a|130b|130c|130d|130e|130f|1310|1311|' . + '1312|1313|1315|1316|1317|1318|131b|131c|131d|6640|6641|6646|6647|6649|664d|' . + '6650|6651|6658|665c|665d|665f|67a0|67a1|67a2|67a8|67a9|67aa|67b0|67b1|67b8|' . + '67b9|67be|9830|9831|9832|9833|9834|9835|9836|9837|9838|9839|983d|9850|9851|' . + '9852|9853|9854|9855|9856|9857|9858|9859|985a|985b|985c|985d|985e|985f|991e|' . + '9920|9922', + 'code' => 'Sea Islands', + 'process' => 'GF/TSMC 16-28nm', + 'years' => '2013-17', + }, + {'arch' => 'GCN-3', + 'ids' => '6900|6901|6902|6907|6920|6921|6929|692b|692f|6930|6938|6939|693b|' . + '7300|730f|9874|98c0|98e4', + 'code' => 'Volcanic Islands', + 'process' => 'TSMC 28nm', + 'years' => '2014-19', + }, + {'arch' => 'GCN-4', + 'ids' => '154e|1551|1552|1561|67c0|67c1|67c2|67c4|67c7|67ca|67cc|67cf|67d0|' . + '67d4|67d7|67df|67e0|67e1|67e3|67e8|67e9|67eb|67ef|67ff|694c|694e|694f|6980|' . + '6981|6984|6985|6986|6987|698f|6995|6997|699f|6fdf|9924|9925', + 'code' => 'Arctic Islands', + 'process' => 'GF 14nm', + 'years' => '2016-20', + }, + {'arch' => 'GCN-5.1', + 'ids' => '15d8|15dd|15df|15e7|1636|1638|164c|66a0|66a1|66a2|66a3|66a7|66af|' . + '69af', + 'code' => 'Vega-2', + 'process' => 'TSMC n7 (7nm)', + 'years' => '2018-22+', + }, + {'arch' => 'GCN-5', + 'ids' => '15d8|15d9|15dd|15e7|15ff|1636|1638|164c|66a0|66a1|66a2|66a3|66a4|' . + '66a7|66af|6860|6861|6862|6863|6864|6867|6868|6869|686a|686b|686c|686d|686e|' . + '687f|69a0|69a1|69a2|69a3|69af', + 'code' => 'Vega', + 'process' => 'GF 14nm', + 'years' => '2017-20', + }, + {'arch' => 'RDNA-1', + 'ids' => '13e9|13f9|13fe|1478|1479|1607|7310|7312|7318|7319|731a|731b|731e|' . + '731f|7340|7341|7343|7347|734f|7360|7362', + 'code' => 'Navi-1x', + 'process' => 'TSMC n7 (7nm)', + 'years' => '2019-20', + }, + {'arch' => 'RDNA-2', + 'ids' => '1506|163f|164d|164e|1681|73a0|73a1|73a2|73a3|73a5|73ab|73ae|73af|' . + '73bf|73c0|73c1|73c3|73ce|73df|73e0|73e1|73e3|73ef|73ff|7420|7421|7422|7423|' . + '7424|743f', + 'code' => 'Navi-2x', + 'process' => 'TSMC n7 (7nm)', + 'years' => '2020-22', + }, + {'arch' => 'RDNA-3', + 'ids' => '73a8|73c4|73c5|73c8|7448|744c|745e|7460|7461|7470|7478|747e', + 'code' => 'Navi-3x', + 'process' => 'TSMC n5 (5nm)', + 'years' => '2022+', + }, + {'arch' => 'RDNA-3', + 'ids' => '73f0|7480|7481|7483|7487|7489|748b|749f', + 'code' => 'Navi-33', + 'process' => 'TSMC n6 (6nm)', + 'years' => '2023+', + }, + {'arch' => 'RDNA-3', + 'ids' => '15bf|15c8|164f|1900|1901', + 'code' => 'Phoenix', + 'process' => 'TSMC n4 (4nm)', + 'years' => '2023+', + }, + {'arch' => 'CDNA-1', + 'ids' => '7388|738c|738e', + 'code' => 'Instinct-MI1xx', + 'process' => 'TSMC n7 (7nm)', + 'years' => '2020', + }, + {'arch' => 'CDNA-2', + 'ids' => '7408|740c|740f', + 'code' => 'Instinct-MI2xx', + 'process' => 'TSMC n6 (6nm)', + 'years' => '2021-22+', + }, + {'arch' => 'CDNA-3', + 'ids' => '', + 'code' => 'Instinct-MI3xx', + 'pattern' => 'Instinct MI3\d{2}X?', + 'process' => 'TSMC n5 (5nm)', + 'years' => '2023+', + }, + ]; +} + +sub set_intel_data { + $gpu_intel = [ + {'arch' => 'Gen-1', + 'ids' => '1132|7120|7121|7122|7123|7124|7125|7126|7128|712a', + 'code' => '', + 'process' => 'Intel 150nm', + 'years' => '1998-2002', + }, + # ill-fated standalone gfx card + {'arch' => 'i740', + 'ids' => '7800', + 'code' => '', + 'process' => 'Intel 150nm', + 'years' => '1998', + }, + {'arch' => 'Gen-2', + 'ids' => '2562|2572|3577|3582|358e', + 'code' => '', + 'process' => 'Intel 130nm', + 'years' => '2002-03', + }, + {'arch' => 'Gen-3', + 'ids' => '2582|2592|2780|2782|2792', + 'code' => 'Intel 130nm', + 'process' => '', + 'years' => '2004-05', + }, + {'arch' => 'Gen-3.5', + 'ids' => '2772|2776|27a2|27a6|27ae|2972|2973', + 'code' => '', + 'process' => 'Intel 90nm', + 'years' => '2005-06', + }, + {'arch' => 'Gen-4', + 'ids' => '2982|2983|2992|2993|29a2|29a3|29b2|29b3|29c2|29c3|29d2|29d3|2a02|' . + '2a03|2a12|2a13', + 'code' => '', + 'process' => 'Intel 65n', + 'years' => '2006-07', + }, + {'arch' => 'PowerVR SGX535', + 'ids' => '4100|8108|8109|a001|a002|a011|a012', + 'code' => '', + 'process' => 'Intel 45-130nm', + 'year' => '2008-10', + }, + {'arch' => 'Gen-5', + 'ids' => '2a41|2a42|2a43|2e02|2e03|2e12|2e13|2e22|2e23|2e32|2e33|2e42|2e43|' . + '2e92|2e93', + 'code' => '', + 'process' => 'Intel 45nm', + 'years' => '2008', + }, + {'arch' => 'PowerVR SGX545', + 'ids' => '0be0|0be1|0be2|0be3|0be4|0be5|0be6|0be7|0be8|0be9|0bea|0beb|0bec|' . + '0bed|0bee|0bef', + 'code' => '', + 'process' => 'Intel 65nm', + 'years' => '2008-10', + }, + {'arch' => 'Gen-5.75', + 'ids' => '0042|0046|004a|0402|0412|0416', + 'code' => '', + 'process' => 'Intel 45nm', + 'years' => '2010', + }, + {'arch' => 'Knights', + 'ids' => '', + 'code' => '', + 'process' => 'Intel 22nm', + 'years' => '2012-13', + }, + {'arch' => 'Gen-6', + 'ids' => '0102|0106|010a|010b|010e|0112|0116|0122|0126|08cf', + 'code' => 'Sandybridge', + 'process' => 'Intel 32nm', + 'years' => '2011', + }, + {'arch' => 'Gen-7.5', + 'ids' => '0402|0406|040a|040b|040e|0412|0416|041a|041b|041e|0422|0426|042a|' . + '042b|042e|0a02|0a06|0a0a|0a0b|0a0e|0a12|0a16|0a1a|0a1b|0a1e|0a22|0a26|0a2a|' . + '0a2b|0a2e|0c02|0c06|0c0a|0c0b|0c0e|0c12|0c16|0c1a|0c1b|0c1e|0c22|0c26|0c2a|' . + '0c2b|0c2e|0d02|0d06|0d0a|0d0b|0d0e|0d12|0d16|0d1a|0d1b|0d1e|0d22|0d26|0d2a|' . + '0d2b|0d2e', + 'code' => '', + 'process' => 'Intel 22nm', + 'years' => '2013', + }, + {'arch' => 'Gen-7', + 'ids' => '0152|0155|0156|0157|015a|015e|0162|0166|016a|0172|0176|0f31|0f32|' . + '0f33', + 'code' => '', + 'process' => 'Intel 22nm', + 'years' => '2012-13', + }, + {'arch' => 'Gen-8', + 'ids' => '1602|1606|160a|160b|160d|160e|1612|1616|161a|161b|161d|161e|1622|' . + '1626|162a|162b|162d|162e|1632|1636|163a|163b|163d|163e|22b0|22b1|22b2|22b3', + 'code' => '', + 'process' => 'Intel 14nm', + 'years' => '2014-15', + }, + {'arch' => 'Gen-9.5', + 'ids' => '3184|3185|3e90|3e91|3e92|3e93|3e94|3e96|3e98|3e99|3e9a|3e9b|3e9c|' . + '3ea0|3ea1|3ea2|3ea3|3ea4|3ea5|3ea6|3ea7|3ea8|3ea9|5902|5906|5908|590a|590b|' . + '590e|5912|5913|5915|5916|5917|591a|591b|591c|591d|591e|5921|5923|5926|5927|' . + '593b|87c0|87ca|9b21|9b41|9ba0|9ba2|9ba4|9ba5|9ba8|9baa|9bab|9bac|9bc0|9bc2|' . + '9bc4|9bc5|9bc6|9bc8|9bca|9bcb|9bcc|9be6|9bf6', + 'code' => '', + 'process' => 'Intel 14nm', + 'years' => '2016-20', + }, + {'arch' => 'Gen-9', + 'ids' => '0a84|1902|1906|190a|190b|190e|1912|1913|1915|1916|1917|191a|191b|' . + '191d|191e|1921|1923|1926|1927|192a|192b|192d|1932|193a|193b|193d|1a84|1a85|' . + '5a84|5a85', + 'code' => '', + 'process' => 'Intel 14n', + 'years' => '2015-16', + }, + # gen10 was cancelled., + {'arch' => 'Gen-11', + 'ids' => '0d16|0d26|0d36|4541|4551|4555|4557|4571|4e51|4e55|4e57|4e61|4e71|' . + '8a50|8a51|8a52|8a53|8a54|8a56|8a57|8a58|8a59|8a5a|8a5b|8a5c|8a5d|8a70|8a71|' . + '9840|9841', + 'code' => '', + 'process' => 'Intel 10nm', + 'years' => '2019-21', + }, + {'arch' => 'Gen-12.1', + 'ids' => '4905|4907|4908|4c80|4c8a|4c8b|4c8c|4c90|4c9a|9a40|9a49|9a59|9a60|' . + '9a68|9a70|9a78|9ac0|9ac9|9ad9|9af8', + 'code' => '', + 'process' => 'Intel 10nm', + 'years' => '2020-21', + }, + {'arch' => 'Gen-12.2', + 'ids' => '4626|4628|462a|4636|4638|463a|4682|4688|468a|468b|4690|4692|4693|' . + '46a3|46a6|46a8|46aa|46b0|46b1|46b3|46b6|46b8|46ba|46c1|46c3|46d0|46d1|46d2', + 'code' => '', + 'process' => 'Intel 10nm', + 'years' => '2021-22+', + }, + {'arch' => 'Gen-12.5', + 'ids' => '0bd0|0bd5|0bd6|0bd7|0bd9|0bda|0bdb', + 'code' => '', + 'process' => 'Intel 10nm', + 'years' => '2021-23+', + }, + # Jupiter Sound cancelled? + {'arch' => 'Gen-12.7', + 'ids' => '5690|5691|5692|5693|5694|5695|5696|5697|5698|56a0|56a1|56a3|56a4|' . + '56a5|56a6|56a7|56a8|56a9|56b0|56b1|56b2|56b3', + 'code' => 'Alchemist', + 'process' => 'TSMC n6 (7nm)', + 'years' => '2022+', + }, + {'arch' => 'Gen-12.7', + 'ids' => '56c0|56c1', + 'code' => '', + 'process' => 'TSMC n6 (7nm)', + 'years' => '2022+', + }, + {'arch' => 'Gen-13', + 'ids' => 'a720|a721|a74d|a780|a781|a782|a783|a788|a789|a78a|a78b|a7a0|a7a1|' . + 'a7a8|a7a9|a7aa|a7ab|a7ac|a7ad', + 'code' => '', + 'process' => 'Intel 7 (10nm)', + 'years' => '2022+', + }, + {'arch' => 'Gen-14', + 'ids' => '7d40|7d45|7d55|7d60|7dd5', + 'code' => '', + 'process' => 'Intel 4 (7nm+)', + 'years' => '2023+', + }, + + ]; +} + +sub set_nv_data { + # this is vendor id: 12d2, nv1/riva/tnt type cards + # 0008|0009|0010|0018|0019 + # and these are vendor id: 10de for 73.14 + # 0020|0028|0029|002c|002d|00a0|0100|0101|0103|0150|0151|0152|0153 + # generic fallback if we don't have the actual EOL termination date + my $date = $self_date; + $date =~ s/-\d+$//; + my $status_current = main::message('nv-current',$date); + # load legacy data, note, if there are 2 or more arch in 1 legacy, it has 1 + # item per arch. kernel/last/xorg support either from nvidia or sgfxi + ## Legacy 71.86.xx + $gpu_nv = [ + {'arch' => 'Fahrenheit', + 'ids' => '0008|0009|0010|0018|0019|0020|0028|0029|002c|002d|00a0', + 'code' => 'NVx', + 'kernel' => '2.6.38', + 'legacy' => 1, + 'process' => 'TSMC 220-350nm', + 'release' => '71.86.15', + 'series' => '71.86.xx', + 'status' => main::message('nv-legacy-eol','2011-08-xx'), + 'xorg' => '1.7', + 'years' => '1998-2000', + }, + {'arch' => 'Celsius', + 'ids' => '0100|0101|0103|0150|0151|0152|0153', + 'code' => 'NV1x', + 'kernel' => '2.6.38', + 'legacy' => 1, + 'process' => 'TSMC 150-220nm', + 'release' => '71.86.15', + 'series' => '71.86.xx', + 'status' => main::message('nv-legacy-eol','2011-08-xx'), + 'xorg' => '1.7', + 'years' => '1999-2005', + }, + ## Legacy 96.43.xx + {'arch' => 'Celsius', + 'ids' => '0110|0111|0112|0113|01a0', + 'code' => 'NV1x', + 'kernel' => '3.6', + 'legacy' => 1, + 'process' => 'TSMC 150-220nm', + 'release' => '96.43.23', + 'series' => '96.43.xx', + 'status' => main::message('nv-legacy-eol','2012-09-xx'), + 'xorg' => '1.12', + 'years' => '1999-2005', + }, + {'arch' => 'Kelvin', + 'ids' => '0170|0171|0172|0173|0174|0175|0176|0177|0178|0179|017a|017c|017d|' . + '0181|0182|0183|0185|0188|018a|018b|018c|01f0|0200|0201|0202|0203|0250|0251|' . + '0253|0258|0259|025b|0280|0281|0282|0286|0288|0289|028c', + 'code' => 'NV2x', + 'kernel' => '3.6', + 'legacy' => 1, + 'process' => 'TSMC 150nm', + 'release' => '96.43.23', + 'series' => '96.43.xx', + 'status' => main::message('nv-legacy-eol','2012-09-xx'), + 'xorg' => '1.12', + 'years' => '2001-2003', + }, + ## Legacy 173.14.xx + # process: IBM 130, TSMC 130-150 + {'arch' => 'Rankine', + 'ids' => '00fa|00fb|00fc|00fd|00fe|0301|0302|0308|0309|0311|0312|0314|031a|' . + '031b|031c|0320|0321|0322|0323|0324|0325|0326|0327|0328|032a|032b|032c|032d|' . + '0330|0331|0332|0333|0334|0338|033f|0341|0342|0343|0344|0347|0348|034c|034e', + 'code' => 'NV3x', + 'kernel' => '3.12', + 'legacy' => 1, + 'process' => '130-150nm', + 'release' => '173.14.39', + 'series' => '173.14.xx', + 'status' => main::message('nv-legacy-eol','2013-12-xx'), + 'xorg' => '1.15', + 'years' => '2003-2005', + }, + ## Legacy 304.xx + # code: hard to get these, roughly MCP[567]x/NV4x/G7x + # process: IBM 130, TSMC 90-110 + {'arch' => 'Curie', + 'ids' => '0040|0041|0042|0043|0044|0045|0046|0047|0048|004e|0090|0091|0092|' . + '0093|0095|0098|0099|009d|00c0|00c1|00c2|00c3|00c8|00c9|00cc|00cd|00ce|00f1|' . + '00f2|00f3|00f4|00f5|00f6|00f8|00f9|0140|0141|0142|0143|0144|0145|0146|0147|' . + '0148|0149|014a|014c|014d|014e|014f|0160|0161|0162|0163|0164|0165|0166|0167|' . + '0168|0169|016a|01d0|01d1|01d2|01d3|01d6|01d7|01d8|01da|01db|01dc|01dd|01de|' . + '01df|0211|0212|0215|0218|0221|0222|0240|0241|0242|0244|0245|0247|0290|0291|' . + '0292|0293|0294|0295|0297|0298|0299|029a|029b|029c|029d|029e|029f|02e0|02e1|' . + '02e2|02e3|02e4|038b|0390|0391|0392|0393|0394|0395|0397|0398|0399|039c|039e|' . + '03d0|03d1|03d2|03d5|03d6|0531|0533|053a|053b|053e|07e0|07e1|07e2|07e3|07e5', + 'code' => '', + 'kernel' => '4.13', + 'legacy' => 1, + 'process' => '90-130nm', + 'release' => '304.137', + 'series' => '304.xx', + 'status' => main::message('nv-legacy-eol','2017-09-xx'), + 'xorg' => '1.19', + 'years' => '2003-2013', + }, + ## Legacy 340.xx + # these are both Tesla and Tesla 2.0 + # code: not clear, 8800/GT2xx/maybe G7x + # years: 2006-2010 Tesla 2007-2013 Tesla 2.0 + {'arch' => 'Tesla', + 'ids' => '0191|0193|0194|0197|019d|019e|0400|0401|0402|0403|0404|0405|0406|' . + '0407|0408|0409|040a|040b|040c|040d|040e|040f|0410|0420|0421|0422|0423|0424|' . + '0425|0426|0427|0428|0429|042a|042b|042c|042d|042e|042f|05e0|05e1|05e2|05e3|' . + '05e6|05e7|05ea|05eb|05ed|05f8|05f9|05fd|05fe|05ff|0600|0601|0602|0603|0604|' . + '0605|0606|0607|0608|0609|060a|060b|060c|060d|060f|0610|0611|0612|0613|0614|' . + '0615|0617|0618|0619|061a|061b|061c|061d|061e|061f|0621|0622|0623|0625|0626|' . + '0627|0628|062a|062b|062c|062d|062e|0630|0631|0632|0635|0637|0638|063a|0640|' . + '0641|0643|0644|0645|0646|0647|0648|0649|064a|064b|064c|0651|0652|0653|0654|' . + '0655|0656|0658|0659|065a|065b|065c|06e0|06e1|06e2|06e3|06e4|06e5|06e6|06e7|' . + '06e8|06e9|06ea|06eb|06ec|06ef|06f1|06f8|06f9|06fa|06fb|06fd|06ff|0840|0844|' . + '0845|0846|0847|0848|0849|084a|084b|084c|084d|084f|0860|0861|0862|0863|0864|' . + '0865|0866|0867|0868|0869|086a|086c|086d|086e|086f|0870|0871|0872|0873|0874|' . + '0876|087a|087d|087e|087f|08a0|08a2|08a3|08a4|08a5|0a20|0a22|0a23|0a26|0a27|' . + '0a28|0a29|0a2a|0a2b|0a2c|0a2d|0a32|0a34|0a35|0a38|0a3c|0a60|0a62|0a63|0a64|' . + '0a65|0a66|0a67|0a68|0a69|0a6a|0a6c|0a6e|0a6f|0a70|0a71|0a72|0a73|0a74|0a75|' . + '0a76|0a78|0a7a|0a7c|0ca0|0ca2|0ca3|0ca4|0ca5|0ca7|0ca8|0ca9|0cac|0caf|0cb0|' . + '0cb1|0cbc|10c0|10c3|10c5|10d8', + 'code' => '', + 'kernel' => '5.4', + 'legacy' => 1, + 'process' => '40-80nm', + 'release' => '340.108', + 'series' => '340.xx', + 'status' => main::message('nv-legacy-eol','2019-12-xx'), + 'xorg' => '1.20', + 'years' => '2006-2013', + }, + ## Legacy 367.xx + {'arch' => 'Kepler', + 'ids' => '0fef|0ff2|11bf', + 'code' => 'GKxxx', + 'kernel' => '5.4', + 'legacy' => 1, + 'process' => 'TSMC 28nm', + 'release' => '', + 'series' => '367.xx', + 'status' => main::message('nv-legacy-eol','2017'), + 'xorg' => '1.20', + 'years' => '2012-2018', + }, + ## Legacy 390.xx + # this is Fermi, Fermi 2.0 + {'arch' => 'Fermi', + 'ids' => '06c0|06c4|06ca|06cd|06d1|06d2|06d8|06d9|06da|06dc|06dd|06de|06df|' . + '0dc0|0dc4|0dc5|0dc6|0dcd|0dce|0dd1|0dd2|0dd3|0dd6|0dd8|0dda|0de0|0de1|0de2|' . + '0de3|0de4|0de5|0de7|0de8|0de9|0dea|0deb|0dec|0ded|0dee|0def|0df0|0df1|0df2|' . + '0df3|0df4|0df5|0df6|0df7|0df8|0df9|0dfa|0dfc|0e22|0e23|0e24|0e30|0e31|0e3a|' . + '0e3b|0f00|0f01|0f02|0f03|1040|1042|1048|1049|104a|104b|104c|1050|1051|1052|' . + '1054|1055|1056|1057|1058|1059|105a|105b|107c|107d|1080|1081|1082|1084|1086|' . + '1087|1088|1089|108b|1091|1094|1096|109a|109b|1140|1200|1201|1203|1205|1206|' . + '1207|1208|1210|1211|1212|1213|1241|1243|1244|1245|1246|1247|1248|1249|124b|' . + '124d|1251', + 'code' => 'GF1xx', + 'kernel' => '6.0', + 'legacy' => 1, + 'process' => '40/28nm', + 'release' => '390.157', + 'series' => '390.xx+', + 'status' => main::message('nv-legacy-eol','2022-11-22'), + 'xorg' => '1.21', + 'years' => '2010-2016', + }, + ## Legacy 470.xx + {'arch' => 'Fermi 2', + 'ids' => '0fec|1281|1289|128b|1295|1298', + 'code' => 'GF119/GK208', + 'kernel' => '', + 'legacy' => 1, + 'process' => 'TSMC 28nm', + 'release' => '', + 'series' => '470.xx+', + 'status' => main::message('nv-legacy-active','2024-09-xx'), + 'xorg' => '', + 'years' => '2010-2016', + }, + # GT 720M and 805A/810A are the same cpu id. + # years: 2012-2018 Kepler 2013-2015 Kepler 2.0 + {'arch' => 'Kepler', + 'ids' => '0fc6|0fc8|0fc9|0fcd|0fce|0fd1|0fd2|0fd3|0fd4|0fd5|0fd8|0fd9|0fdf|' . + '0fe0|0fe1|0fe2|0fe3|0fe4|0fe9|0fea|0fed|0fee|0ff6|0ff8|0ff9|0ffa|0ffb|0ffc|' . + '0ffd|0ffe|0fff|1001|1004|1005|1007|1008|100a|100c|1021|1022|1023|1024|1026|' . + '1027|1028|1029|102a|102d|103a|103c|1180|1183|1184|1185|1187|1188|1189|118a|' . + '118e|118f|1193|1194|1195|1198|1199|119a|119d|119e|119f|11a0|11a1|11a2|11a3|' . + '11a7|11b4|11b6|11b7|11b8|11ba|11bc|11bd|11be|11c0|11c2|11c3|11c4|11c5|11c6|' . + '11c8|11cb|11e0|11e1|11e2|11e3|11fa|11fc|1280|1282|1284|1286|1287|1288|1290|' . + '1291|1292|1293|1295|1296|1299|129a|12b9|12ba', + 'code' => 'GKxxx', + 'kernel' => '', + 'legacy' => 1, + 'process' => 'TSMC 28nm', + 'release' => '', + 'series' => '470.xx+', + 'status' => main::message('nv-legacy-active','2024-09-xx'), + 'xorg' => '', + 'years' => '2012-2018', + }, + ## Current Active Series + # load microarch data, as stuff goes legacy, these will form new legacy items. + {'arch' => 'Maxwell', + 'ids' => '1340|1341|1344|1346|1347|1348|1349|134b|134d|134e|134f|137a|137b|' . + '1380|1381|1382|1390|1391|1392|1393|1398|1399|139a|139b|139c|139d|13b0|13b1|' . + '13b2|13b3|13b4|13b6|13b9|13ba|13bb|13bc|13c0|13c2|13d7|13d8|13d9|13da|13f0|' . + '13f1|13f2|13f3|13f8|13f9|13fa|13fb|1401|1402|1406|1407|1427|1430|1431|1436|' . + '1617|1618|1619|161a|1667|174d|174e|179c|17c8|17f0|17f1|17fd|1c90|1d10|1d12', + 'code' => 'GMxxx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC 28nm', + 'release' => '', + 'series' => '545.xx+', + 'status' => main::message('nv-current-eol',$date,'2026-12-xx'), + 'xorg' => '', + 'years' => '2014-2019', + }, + {'arch' => 'Pascal', + 'ids' => '15f0|15f7|15f8|15f9|17c2|1b00|1b02|1b06|1b30|1b38|1b80|1b81|1b82|' . + '1b83|1b84|1b87|1ba0|1ba1|1ba2|1bb0|1bb1|1bb3|1bb4|1bb5|1bb6|1bb7|1bb8|1bb9|' . + '1bbb|1bc7|1be0|1be1|1c02|1c03|1c04|1c06|1c07|1c09|1c20|1c21|1c22|1c23|1c30|' . + '1c31|1c60|1c61|1c62|1c81|1c82|1c83|1c8c|1c8d|1c8f|1c90|1c91|1c92|1c94|1c96|' . + '1cb1|1cb2|1cb3|1cb6|1cba|1cbb|1cbc|1cbd|1cfa|1cfb|1d01|1d02|1d11|1d13|1d16|' . + '1d33|1d34|1d52', + 'code' => 'GP10x', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC 16nm', + 'release' => '', + 'series' => '545.xx+', + 'status' => main::message('nv-current-eol',$date,'2026-12-xx'), + 'xorg' => '', + 'years' => '2016-2021', + }, + {'arch' => 'Volta', + 'ids' => '1d81|1db1|1db3|1db4|1db5|1db6|1db7|1db8|1dba|1df0|1df2|1df6|1fb0', + 'code' => 'GV1xx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC 12nm', + 'release' => '', + 'series' => '545.xx+', + 'status' => main::message('nv-current-eol',$date,'2026-12-xx'), + 'xorg' => '', + 'years' => '2017-2020', + }, + {'arch' => 'Turing', + 'ids' => '1e02|1e04|1e07|1e09|1e30|1e36|1e78|1e81|1e82|1e84|1e87|1e89|1e90|' . + '1e91|1e93|1eb0|1eb1|1eb5|1eb6|1ec2|1ec7|1ed0|1ed1|1ed3|1ef5|1f02|1f03|1f06|' . + '1f07|1f08|1f0a|1f0b|1f10|1f11|1f12|1f14|1f15|1f36|1f42|1f47|1f50|1f51|1f54|' . + '1f55|1f76|1f82|1f83|1f91|1f95|1f96|1f97|1f98|1f99|1f9c|1f9d|1f9f|1fa0|1fb0|' . + '1fb1|1fb2|1fb6|1fb7|1fb8|1fb9|1fba|1fbb|1fbc|1fdd|1ff0|1ff2|1ff9|2182|2184|' . + '2187|2188|2189|2191|2192|21c4|21d1|25a6|25a7|25a9|25aa|25ad|25ed|28b8|28f8', + 'code' => 'TUxxx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC 12nm FF', + 'release' => '', + 'series' => '545.xx+', + 'status' => main::message('nv-current-eol',$date,'2026-12-xx'), + 'xorg' => '', + 'years' => '2018-2022', + }, + {'arch' => 'Ampere', + 'ids' => '20b0|20b2|20b3|20b5|20b6|20b7|20bd|20f1|20f3|20f5|20f6|2203|2204|' . + '2206|2207|2208|220a|220d|2216|2230|2231|2232|2233|2235|2236|2237|2238|2414|' . + '2420|2438|2460|2482|2484|2486|2487|2488|2489|248a|249c|249d|24a0|24b0|24b1|' . + '24b6|24b7|24b8|24b9|24ba|24bb|24c7|24c9|24dc|24dd|24e0|24fa|2503|2504|2507|' . + '2508|2520|2521|2523|2531|2544|2560|2563|2571|2582|25a0|25a2|25a5|25ab|25ac|' . + '25b6|25b8|25b9|25ba|25bb|25bc|25bd|25e0|25e2|25e5|25ec|25f9|25fa|25fb|2838', + 'code' => 'GAxxx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC n7 (7nm)', + 'release' => '', + 'series' => '545.xx+', + 'status' => main::message('nv-current-eol',$date,'2026-12-xx'), + 'xorg' => '', + 'years' => '2020-2023', + }, + {'arch' => 'Hopper', + 'ids' => '2321|2322|2324|2330|2331|2339|233a|2342', + 'code' => 'GH1xx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC n4 (5nm)', + 'release' => '', + 'series' => '545.xx+', + 'status' => $status_current, + 'xorg' => '', + 'years' => '2022+', + }, + {'arch' => 'Lovelace', + 'ids' => '2684|26b1|26b2|26b5|26b9|2704|2717|2730|2757|2770|2782|2786|27a0|' . + '27b0|27b1|27b2|27b8|27ba|27bb|27e0|27fb|2803|2805|2820|2860|2882|28a0|28a1|' . + '28e0|28e1', + 'code' => 'AD1xx', + 'kernel' => '', + 'legacy' => 0, + 'process' => 'TSMC n4 (5nm)', + 'release' => '', + 'series' => '545.xx+', + 'status' => $status_current, + 'xorg' => '', + 'years' => '2022+', + }, + + ], +} + +sub gpu_data { + eval $start if $b_log; + my ($v_id,$p_id,$name) = @_; + my ($gpu,$gpu_data,$b_nv); + if ($v_id eq '1002'){ + set_amd_data() if !$gpu_amd; + $gpu = $gpu_amd; + } + elsif ($v_id eq '8086'){ + set_intel_data() if !$gpu_intel; + $gpu = $gpu_intel; + } + else { + set_nv_data() if !$gpu_nv; + $gpu = $gpu_nv; + $b_nv = 1; + } + $gpu_data = get_gpu_data($gpu,$p_id,$name); + eval $end if $b_log; + return ($gpu_data,$b_nv); +} + +sub get_gpu_data { + eval $start if $b_log; + my ($gpu,$p_id,$name) = @_; + my ($info); + # Don't use reverse because if product ID is matched, we want that, not a looser + # regex match. Tried with reverse and led to false matches. + foreach my $item (reverse @$gpu){ + next if !$item->{'ids'} && (!$item->{'pattern'} || !$name); + if (($item->{'ids'} && $p_id =~ /^($item->{'ids'})$/) || + (!$item->{'ids'} && $item->{'pattern'} && + $name =~ /\b($item->{'pattern'})\b/)){ + $info = { + 'arch' => $item->{'arch'}, + 'code' => $item->{'code'}, + 'kernel' => $item->{'kernel'}, + 'legacy' => $item->{'legacy'}, + 'process' => $item->{'process'}, + 'release' => $item->{'release'}, + 'series' => $item->{'series'}, + 'status' => $item->{'status'}, + 'xorg' => $item->{'xorg'}, + 'years' => $item->{'years'}, + }; + last; + } + } + if (!$info){ + $info->{'status'} = main::message('unknown-device-id'); + } + main::log_data('dump','%info',$info) if $b_log; + print "Raw \$info data: ", Data::Dumper::Dumper $info if $dbg[49]; + eval $end if $b_log; + return $info; +} + +## MONITOR DATA ## +sub set_monitors_sys { + eval $start if $b_log; + my $pattern = '/sys/class/drm/card[0-9]/device/driver/module/drivers/*'; + my @cards_glob = main::globber($pattern); + $pattern = '/sys/class/drm/card*-*/{edid,enabled,status,modes}'; + my @ports_glob = main::globber($pattern); + # print Data::Dumper::Dumper \@cards_glob; + # print Data::Dumper::Dumper \@ports_glob; + my ($card,%cards,@data,$file,$item,$path,$port); + foreach $file (@cards_glob){ + next if ! -e $file; + if ($file =~ m|^/sys/class/drm/(card\d+)/.+?/drivers/(\S+):(\S+)$|){ + push(@{$cards{$1}},[$2,$3]); + } + } + # print Data::Dumper::Dumper \%cards; + foreach $file (sort @ports_glob){ + next if ! -r $file; + $item = $file; + $item =~ s|(/.*/(card\d+)-([^/]+))/(.+)||; + $path = $1; + $card = $2; + $port = $3; + $item = $4; + next if !$1; + $monitor_ids = {} if !$monitor_ids; + $monitor_ids->{$port}{'monitor'} = $port; + if (!$monitor_ids->{$port}{'drivers'} && $cards{$card}){ + foreach my $info (@{$cards{$card}}){ + push(@{$monitor_ids->{$port}{'drivers'}},$info->[1]); + } + } + $monitor_ids->{$port}{'path'} = readlink($path); + $monitor_ids->{$port}{'path'} =~ s|^\.\./\.\.|/sys|; + if ($item eq 'status' || $item eq 'enabled'){ + # print "$file\n"; + $monitor_ids->{$port}{$item} = main::reader($file,'strip',0); + } + # arm: U:1680x1050p-0 + elsif ($item eq 'modes'){ + @data = main::reader($file,'strip'); + next if !@data; + # modes has repeat values, probably because kernel doesn't show hz + main::uniq(\@data); + $monitor_ids->{$port}{'modes'} = [@data]; + } + elsif ($item eq 'edid'){ + next if -s $file; + monitor_edid_data($file,$port); + } + } + main::log_data('dump','$ports ref',$monitor_ids) if $b_log; + print 'monitor_sys_data(): ', Data::Dumper::Dumper $monitor_ids if $dbg[44]; + eval $end if $b_log; +} + +sub monitor_edid_data { + eval $start if $b_log; + my ($file,$port) = @_; + my (@data); + open my $fh, '<:raw', $file or return; # it failed, give up, we don't care why + my $edid_raw = do { local $/; <$fh> }; + return if !$edid_raw; + my $edid = ParseEDID::parse_edid($edid_raw,$dbg[47]); + main::log_data('dump','Parse::EDID',$edid) if $b_log; + print 'parse_edid(): ', Data::Dumper::Dumper $edid if $dbg[44]; + return if !$edid || ref $edid ne 'HASH' || !%$edid; + $monitor_ids->{$port}{'build-date'} = $edid->{'year'}; + if ($edid->{'color_characteristics'}){ + $monitor_ids->{$port}{'colors'} = $edid->{'color_characteristics'}; + } + if ($edid->{'gamma'}){ + $monitor_ids->{$port}{'gamma'} = ($edid->{'gamma'}/100 + 0); + } + if ($edid->{'monitor_name'} || $edid->{'manufacturer_name_nice'}){ + my $model = ''; + if ($edid->{'manufacturer_name_nice'}){ + $model = $edid->{'manufacturer_name_nice'}; + } + if ($edid->{'monitor_name'}){ + $model .= ' ' if $model; + $model .= $edid->{'monitor_name'}; + } + elsif ($model && $edid->{'product_code_h'}){ + $model .= ' ' . $edid->{'product_code_h'}; + } + $monitor_ids->{$port}{'model'} = main::remove_duplicates(main::clean($model)); + } + elsif ($edid->{'manufacturer_name'} && $edid->{'product_code_h'}){ + $monitor_ids->{$port}{'model-id'} = $edid->{'manufacturer_name'} . ' '; + $monitor_ids->{$port}{'model-id'} .= $edid->{'product_code_h'}; + } + # construct to match xorg values + if ($edid->{'manufacturer_name'} && $edid->{'product_code'}){ + my $id = $edid->{'manufacturer_name'} . sprintf('%x',$edid->{'product_code'}); + $monitor_ids->{$port}{$id} = ($edid->{'serial_number'}) ? $edid->{'serial_number'}: ''; + } + if ($edid->{'diagonal_size'}){ + $monitor_ids->{$port}{'diagonal-m'} = sprintf('%.0f',($edid->{'diagonal_size'}*25.4)) + 0; + $monitor_ids->{$port}{'diagonal'} = sprintf('%.1f',$edid->{'diagonal_size'}) + 0; + } + if ($edid->{'ratios'}){ + $monitor_ids->{$port}{'ratio'} = join(', ', @{$edid->{'ratios'}}); + } + if ($edid->{'detailed_timings'}){ + $monitor_ids->{$port}{'res-x'} = $edid->{'detailed_timings'}[0]{'horizontal_active'}; + $monitor_ids->{$port}{'res-y'} = $edid->{'detailed_timings'}[0]{'vertical_active'}; + if ($edid->{'detailed_timings'}[0]{'horizontal_image_size'}){ + $monitor_ids->{$port}{'size-x'} = $edid->{'detailed_timings'}[0]{'horizontal_image_size'}; + $monitor_ids->{$port}{'size-x-i'} = $edid->{'detailed_timings'}[0]{'horizontal_image_size_i'}; + } + if ($edid->{'detailed_timings'}[0]{'vertical_image_size'}){ + $monitor_ids->{$port}{'size-y'} = $edid->{'detailed_timings'}[0]{'vertical_image_size'}; + $monitor_ids->{$port}{'size-y-i'} = $edid->{'detailed_timings'}[0]{'vertical_image_size_i'}; + } + if ($edid->{'detailed_timings'}[0]{'horizontal_dpi'}){ + $monitor_ids->{$port}{'dpi'} = sprintf('%.0f',$edid->{'detailed_timings'}[0]{'horizontal_dpi'}) + 0; + } + } + if ($edid->{'serial_number'} || $edid->{'serial_number2'}){ + # this looks much more like a real serial than the default: serial_number + if ($edid->{'serial_number2'} && @{$edid->{'serial_number2'}}){ + $monitor_ids->{$port}{'serial'} = main::clean_dmi($edid->{'serial_number2'}[0]); + } + elsif ($edid->{'serial_number'}){ + $monitor_ids->{$port}{'serial'} = main::clean_dmi($edid->{'serial_number'}); + } + } + # this will be an array reference of one or more edid errors + if ($edid->{'edid_errors'}){ + $monitor_ids->{$port}{'edid-errors'} = $edid->{'edid_errors'}; + } + # this will be an array reference of one or more edid warnings + if ($edid->{'edid_warnings'}){ + $monitor_ids->{$port}{'edid-warnings'} = $edid->{'edid_warnings'}; + } + eval $end if $b_log; +} + +sub advanced_monitor_data { + eval $start if $b_log; + my ($monitors,$layouts) = @_; + my (@horiz,@vert); + my $position = ''; + # then see if we can locate a default position primary monitor + foreach my $key (keys %$monitors){ + next if !defined $monitors->{$key}{'pos-x'} || !defined $monitors->{$key}{'pos-y'}; + # this is the only scenario we can guess at if no primary detected + if (!$b_primary && !$monitors->{$key}{'primary'} && + $monitors->{$key}{'pos-x'} == 0 && $monitors->{$key}{'pos-y'} == 0){ + $monitors->{$key}{'position'} = 'primary'; + $monitors->{$key}{'primary'} = $monitors->{$key}{'monitor'}; + } + if (!grep {$monitors->{$key}{'pos-x'} == $_} @horiz){ + push(@horiz,$monitors->{$key}{'pos-x'}); + } + if (!grep {$monitors->{$key}{'pos-y'} == $_} @vert){ + push(@vert,$monitors->{$key}{'pos-y'}); + } + } + # we need NUMERIC sort, because positions can be less than 1000! + @horiz = sort {$a <=> $b} @horiz; + @vert =sort {$a <=> $b} @vert; + my ($h,$v) = (scalar(@horiz),scalar(@vert)); + # print Data::Dumper::Dumper \@horiz; + # print Data::Dumper::Dumper \@vert; + # print Data::Dumper::Dumper $layouts; + # print 'mon advanced monitor_map: ', Data::Dumper::Dumper $monitor_map; + foreach my $key (keys %$monitors){ + # disabled monitor may not have pos-x/pos-y, so skip + if (@horiz && @vert && (scalar @horiz > 1 || scalar @vert > 1) && + defined $monitors->{$key}{'pos-x'} && defined $monitors->{$key}{'pos-y'}){ + $monitors->{$key}{'position'} ||= ''; + $position = ''; + $position = get_monitor_position($monitors->{$key},\@horiz,\@vert); + $position = $layouts->[$v][$h]{$position} if $layouts->[$v][$h]{$position}; + $monitors->{$key}{'position'} .= ',' if $monitors->{$key}{'position'}; + $monitors->{$key}{'position'} .= $position; + } + my $mon_mapped = ($monitor_map) ? $monitor_map->{$monitors->{$key}{'monitor'}} : undef; + # these are already set for monitor_ids, only need this for Xorg data. + if ($mon_mapped && $monitor_ids->{$mon_mapped}){ + # note: xorg drivers can be different than gpu drivers + $monitors->{$key}{'drivers'} = gpu_drivers_sys($mon_mapped); + $monitors->{$key}{'build-date'} = $monitor_ids->{$mon_mapped}{'build-date'}; + $monitors->{$key}{'colors'} = $monitor_ids->{$mon_mapped}{'colors'}; + $monitors->{$key}{'diagonal'} = $monitor_ids->{$mon_mapped}{'diagonal'}; + $monitors->{$key}{'diagonal-m'} = $monitor_ids->{$mon_mapped}{'diagonal-m'}; + $monitors->{$key}{'gamma'} = $monitor_ids->{$mon_mapped}{'gamma'}; + $monitors->{$key}{'modes'} = $monitor_ids->{$mon_mapped}{'modes'}; + $monitors->{$key}{'model'} = $monitor_ids->{$mon_mapped}{'model'}; + $monitors->{$key}{'color-characteristics'} = $monitor_ids->{$mon_mapped}{'color-characteristics'}; + if (!defined $monitors->{$key}{'size-x'} && $monitor_ids->{$mon_mapped}{'size-x'}){ + $monitors->{$key}{'size-x'} = $monitor_ids->{$mon_mapped}{'size-x'}; + $monitors->{$key}{'size-x-i'} = $monitor_ids->{$mon_mapped}{'size-x-i'}; + } + if (!defined $monitors->{$key}{'size-y'} && $monitor_ids->{$mon_mapped}{'size-y'}){ + $monitors->{$key}{'size-y'} = $monitor_ids->{$mon_mapped}{'size-y'}; + $monitors->{$key}{'size-y-i'} = $monitor_ids->{$mon_mapped}{'size-y-i'}; + } + if (!defined $monitors->{$key}{'dpi'} && $monitor_ids->{$mon_mapped}{'dpi'}){ + $monitors->{$key}{'dpi'} = $monitor_ids->{$mon_mapped}{'dpi'}; + } + if ($monitor_ids->{$mon_mapped}{'model-id'}){ + $monitors->{$key}{'model-id'} = $monitor_ids->{$mon_mapped}{'model-id'}; + } + if ($monitor_ids->{$mon_mapped}{'edid-errors'}){ + $monitors->{$key}{'edid-errors'} = $monitor_ids->{$mon_mapped}{'edid-errors'}; + } + if ($monitor_ids->{$mon_mapped}{'edid-warnings'}){ + $monitors->{$key}{'edid-warnings'} = $monitor_ids->{$mon_mapped}{'edid-warnings'}; + } + if ($monitor_ids->{$mon_mapped}{'enabled'} && + $monitor_ids->{$mon_mapped}{'enabled'} eq 'disabled'){ + $monitors->{$key}{'disabled'} = $monitor_ids->{$mon_mapped}{'enabled'}; + } + $monitors->{$key}{'ratio'} = $monitor_ids->{$mon_mapped}{'ratio'}; + $monitors->{$key}{'serial'} = $monitor_ids->{$mon_mapped}{'serial'}; + } + # now swap the drm id for the display server id if they don't match + if ($mon_mapped && $mon_mapped ne $monitors->{$key}{'monitor'}){ + $monitors->{$key}{'monitor-mapped'} = $monitors->{$key}{'monitor'}; + $monitors->{$key}{'monitor'} = $mon_mapped; + } + } + # not printing out primary if Screen has only 1 Monitor + if (scalar keys %$monitors == 1){ + my @keys = keys %$monitors; + $monitors->{$keys[0]}{'position'} = undef; + } + print Data::Dumper::Dumper $monitors if $dbg[45]; + eval $end if $b_log; +} + +# Clear out all disabled or not connected monitor ports +sub set_active_monitors { + eval $start if $b_log; + foreach my $key (keys %$monitor_ids){ + if (!$monitor_ids->{$key}{'status'} || + $monitor_ids->{$key}{'status'} ne 'connected'){ + delete $monitor_ids->{$key}; + } + } + # print 'active monitors: ', Data::Dumper::Dumper $monitor_ids; + eval $end if $b_log; +} + +sub get_monitor_position { + eval $start if $b_log; + my ($monitor,$horiz,$vert) = @_; + my ($i,$position) = (1,''); + foreach (@$vert){ + if ($_ == $monitor->{'pos-y'}){ + $position = $i . '-'; + last; + } + $i++; + } + $i = 1; + foreach (@$horiz){ + if ($_ == $monitor->{'pos-x'}){ + $position .= $i; + last; + } + $i++; + } + main::log_data('data','pos-raw: ' . $position) if $b_log; + eval $end if $b_log; + return $position; +} + +sub set_monitor_layouts { + my ($layouts) = @_; + $layouts->[1][2] = {'1-1' => 'left','1-2' => 'right'}; + $layouts->[1][3] = {'1-1' => 'left','1-2' => 'center','1-3' => 'right'}; + $layouts->[1][4] = {'1-1' => 'left','1-2' => 'center-l','1-3' => 'center-r', + '1-4' => 'right'}; + $layouts->[2][1] = {'1-1' => 'top','2-1' => 'bottom'}; + $layouts->[2][2] = {'1-1' => 'top-left','1-2' => 'top-right', + '2-1' => 'bottom-l','2-2' => 'bottom-r'}; + $layouts->[2][3] = {'1-1' => 'top-left','1-2' => 'top-center','1-3' => 'top-right', + '2-1' => 'bottom-l','2-2' => 'bottom-c','2-3' => 'bottom-r'}; + $layouts->[3][1] = {'1-1' => 'top','2-1' => 'middle','3-1' => 'bottom'}; + $layouts->[3][2] = {'1-1' => 'top-left','1-2' => 'top-right', + '2-1' => 'middle-l','2-2' => 'middle-r', + '3-1' => 'bottom-l','3-2' => 'bottom-r'}; + $layouts->[3][3] = {'1-1' => 'top-left','1-2' => 'top-center',,'1-3' => 'top-right', + '2-1' => 'middle-l','2-2' => 'middle-c','2-3' => 'middle-r', + '3-1' => 'bottom-l','3-2' => 'bottom-c','3-3' => 'bottom-r'}; +} + +# This is required to resolve the situation where some xorg drivers change +# the kernel ID for the port to something slightly different, amdgpu in particular. +sub map_monitor_ids { + eval $start if $b_log; + my ($display_ids) = @_; + return if !$monitor_ids; + my (@sys_ids,@unmatched_display,@unmatched_sys); + @unmatched_display = @$display_ids = sort { lc($a) cmp lc($b) } @$display_ids; + foreach my $key (keys %$monitor_ids){ + if ($monitor_ids->{$key}{'status'} eq 'connected'){ + push(@sys_ids,$key); + } + } + # @sys_ids = ('DVI-I-1','eDP-1','VGA-1'); + main::log_data('dump','@sys_ids',\@sys_ids) if $b_log; + main::log_data('dump','$xrandr_ids ref',$display_ids) if $b_log; + print 'sys: ', Data::Dumper::Dumper \@sys_ids if $dbg[45]; + print 'display: ', Data::Dumper::Dumper $display_ids if $dbg[45]; + return if scalar @sys_ids != scalar @$display_ids; + @unmatched_sys = @sys_ids = sort { lc($a) cmp lc($b) } @sys_ids; + $monitor_map = {}; + # known patterns: s: DP-1 d: DisplayPort-0; s: DP-1 d: DP1-1; s: DP-2 d: DP1-2; + # s: HDMI-A-2 d: HDMI-A-1; s: HDMI-A-2 d: HDMI-2; s: DVI-1 d: DVI1; s: HDMI-1 d: HDMI1 + # s: DVI-I-1 d: DVI0; s: VGA-1 d: VGA1; s: DP-1-1; d: DP-1-1; + # s: eDP-1 d: eDP-1-1 (yes, reversed from normal deviation!); s: eDP-1 d: eDP + # worst: s: DP-6 d: DP-2-3 (2 banks of 3 according to X); s: eDP-1 d: DP-4; + # s: DP-3 d: DP-1-1; s: DP-4 d: DP-1-2 + # s: DP-3 d: DP-4 [yes, +1, not -]; + my ($d_1,$d_2,$d_m,$s_1,$s_2,$s_m); + my $b_single = (scalar @sys_ids == 1) ? 1 : 0; + my $pattern = '([A-Z]+)(-[A-Z]-\d+-\d+|-[A-Z]-\d+|-?\d+-\d+|-?\d+|)'; + for (my $i=0; $i < scalar @$display_ids; $i++){ + print "s: $sys_ids[$i] d: $display_ids->[$i]\n" if $dbg[45]; + # try 1: /^([A-Z]+)(-[AB]|-[ADI]|-[ADI]-\d+?|-\d+?)?(-)?(\d+)$/i + if ($display_ids->[$i] =~ /^$pattern$/i){ + $d_1 = $1; + $d_2 = ($2) ? $2 : ''; + $d_2 =~ /(\d+)?$/; + $d_m = ($1) ? $1 : 0; + $d_1 =~ s/^DisplayPort/DP/i; # amdgpu... + print " d1: $d_1 d2: $d_2 d3: $d_m\n" if $dbg[45]; + if ($sys_ids[$i] =~ /^$pattern$/i){ + $s_1 = $1; + $s_2 = ($2) ? $2 : ''; + $s_2 =~ /(\d+)?$/; + $s_m = ($1) ? $1 : 0; + $d_1 = $s_1 if uc($d_1) eq 'XWAYLAND'; + print " d1: $d_1 s1: $s_1 dm: $d_m sm: $s_m \n" if $dbg[45]; + if ($d_1 eq $s_1 && ($d_m == $s_m || $d_m == ($s_m - 1))){ + $monitor_map->{$display_ids->[$i]} = $sys_ids[$i]; + @unmatched_display = grep {$_ ne $display_ids->[$i]} @unmatched_display; + @unmatched_sys = grep {$_ ne $sys_ids[$i]} @unmatched_sys; + } + } + } + # in case of one unmatched, we'll dump this, and use the actual unmatched + if (!$monitor_map->{$display_ids->[$i]}){ + # we're not even going to try, if there's 1 sys and 1 display, just use it! + if ($b_single){ + $monitor_map->{$display_ids->[$i]} = $sys_ids[$i]; + (@unmatched_display,@unmatched_sys) = (); + } + else { + $monitor_map->{$display_ids->[$i]} = main::message('monitor-id'); + } + } + } + # we don't care at all what the pattern is, if there is 1 unmatched display + # out of 1 sys ids, we'll assume that is the one. This can only be assumed in + # cases where only 1 monitor was not matched, otherwise it's just a guess. + # obviously, if one of the matches was wrong, this will also be wrong, but + # thats' life when dealing with irrational data. DP is a particular problem. + if (scalar @unmatched_sys == 1){ + $monitor_map->{$unmatched_display[0]} = $unmatched_sys[0]; + } + main::log_data('dump','$monitor_map ref',$monitor_map) if $b_log; + print Data::Dumper::Dumper $monitor_map if $dbg[45]; + eval $end if $b_log; +} + +# Handle case of monitor on left or right edge, vertical that is. +# mm dimensiions are based on the default position of monitor as sold. +# very old systems may not have non 0 value for size x or y +# size, res x,y by reference +sub flip_size_x_y { + eval $start if $b_log; + my ($size_x,$size_y,$res_x,$res_y) = @_; + if ((($$res_x/$$res_y > 1 && $$size_x/$$size_y < 1) || + ($$res_x/$$res_y < 1 && $$size_x/$$size_y > 1))){ + ($$size_x,$$size_y) = ($$size_y,$$size_x); + } + eval $end if $b_log; +} + +## COMPOSITOR DATA ## +sub set_compositor_data { + eval $start if $b_log; + my $compositors = get_compositors(); + if (@$compositors){ + my @data; + foreach my $compositor (@$compositors){ + # gnome-shell is incredibly slow to return version + if (($extra > 1 || $graphics{'protocol'} eq 'wayland') && + (!$show{'system'} || $compositor ne 'gnome-shell')){ + $graphics{'compositors'} = [] if !$graphics{'compositors'}; + push(@{$graphics{'compositors'}},[main::program_data($compositor,$compositor)]); + } + else { + $graphics{'compositors'} = [] if !$graphics{'compositors'}; + push(@{$graphics{'compositors'}},[(main::program_values($compositor))[3]]); + } + } + } + eval $end if $b_log; +} + +sub get_compositors { + eval $start if $b_log; + my $found = []; + main::set_ps_gui() if !$loaded{'ps-gui'}; + if (@ps_gui){ + # ORDER MATTES! + # notes: compiz: debian package compiz-core; + # enlightenment: as of version 20 wayland compositor + my @compositors = qw(budgie-wm compiz compton enlightenment gnome-shell + kwin_wayland kwin_x11 kwinft marco muffin mutter); + # these are more obscure, so check for them after primary common ones + push (@compositors,qw(3dwm cosmic-comp dcompmgr gala kmscon + metisse mir moblin monsterwm picom ukwm unagi unity-system-compositor + xcompmgr xfwm4 xfwm5 xfwm)); + my $matches = join('|',@compositors) . $wl_compositors; + foreach my $psg (@ps_gui){ + if ($psg =~ /^($matches)$/){ + push(@$found,$1); + } + } + } + main::log_data('dump','$found compositors:', $found) if $b_log; + eval $end if $b_log; + return $found; +} + +## UTILITIES ## +sub tty_data { + eval $start if $b_log; + my ($tty); + if ($size{'term-cols'}){ + $tty = "$size{'term-cols'}x$size{'term-lines'}"; + } + # this is broken + elsif ($b_irc && $client{'console-irc'}){ + ShellData::console_irc_tty() if !$loaded{'con-irc-tty'}; + my $tty_working = $client{'con-irc-tty'}; + if ($tty_working ne '' && (my $program = main::check_program('stty'))){ + my $tty_arg = ($bsd_type) ? '-f' : '-F'; + # handle vtnr integers, and tty ID with letters etc. + $tty_working = "tty$tty_working" if -e "/dev/tty$tty_working"; + $tty = (main::grabber("$program $tty_arg /dev/$tty_working size 2>/dev/null"))[0]; + if ($tty){ + my @temp = split(/\s+/, $tty); + $tty = "$temp[1]x$temp[0]"; + } + } + } + eval $end if $b_log; + return $tty; +} +} + +## LogicalItem +{ +package LogicalItem; + +sub get { + eval $start if $b_log; + my ($key1,$val1); + my $rows = []; + my $num = 0; + if ($bsd_type){ + $key1 = 'Message'; + $val1 = main::message('logical-data-bsd',$uname[0]); + push(@$rows,{main::key($num++,0,1,$key1) => $val1}); + } + else { + LsblkData::set() if !$loaded{'lsblk'}; + if ($fake{'logical'} || $alerts{'lvs'}->{'action'} eq 'use'){ + lvm_data() if !$loaded{'logical-data'}; + if (!@lvm){ + my $key = 'Message'; + # note: arch linux has a bug where lvs returns 0 if non root start + my $message = ($use{'logical-lvm'}) ? main::message('tool-permissions','lvs') : main::message('logical-data',''); + push(@$rows, { + main::key($num++,0,1,$key) => $message, + }); + } + else { + lvm_output($rows,process_lvm_data()); + } + } + elsif ($use{'logical-lvm'} && $alerts{'lvs'}->{'action'} eq 'permissions'){ + my $key = 'Message'; + push(@$rows, { + main::key($num++,0,1,$key) => $alerts{'lvs'}->{'message'}, + }); + } + elsif (@lsblk && !$use{'logical-lvm'} && ($alerts{'lvs'}->{'action'} eq 'permissions' || + $alerts{'lvs'}->{'action'} eq 'missing')){ + my $key = 'Message'; + push(@$rows, { + main::key($num++,0,1,$key) => main::message('logical-data',''), + }); + } + elsif ($alerts{'lvs'}->{'action'} ne 'use'){ + $key1 = $alerts{'lvs'}->{'action'}; + $val1 = $alerts{'lvs'}->{'message'}; + $key1 = ucfirst($key1); + push(@$rows, {main::key($num++,0,1,$key1) => $val1}); + } + if ($use{'logical-general'}){ + my $general_data = general_data(); + general_output($rows,$general_data) if @$general_data; + } + } + eval $end if $b_log; + return $rows; +} + +sub general_output { + eval $start if $b_log; + my ($rows,$general_data) = @_; + my ($size); + my ($j,$num) = (0,0); + # cryptsetup status luks-a00baac5-44ff-4b48-b303-3bedb1f623ce + foreach my $item (sort {$a->{'type'} cmp $b->{'type'}} @$general_data){ + $j = scalar @$rows; + $size = ($item->{'size'}) ? main::get_size($item->{'size'}, 'string') : 'N/A'; + push(@$rows,{ + main::key($num++,1,1,'Device') => $item->{'name'}, + }); + if ($b_admin){ + $item->{'name'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'maj-min')} = $item->{'maj-min'}; + } + $rows->[$j]{main::key($num++,0,2,'type')} = $item->{'type'}; + if ($extra > 0 && $item->{'dm'}){ + $rows->[$j]{main::key($num++,0,2,'dm')} = $item->{'dm'}; + } + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + my $b_fake; + components_output('general',\$j,\$num,$rows,\@{$item->{'components'}},\$b_fake); + } + eval $end if $b_log; +} + +sub lvm_output { + eval $start if $b_log; + my ($rows,$lvm_data) = @_; + my ($size); + my ($j,$num) = (0,0); + foreach my $vg (sort keys %$lvm_data){ + $j = scalar @$rows; + # print Data::Dumper::Dumper $lvm_data->{$vg}; + $size = main::get_size($lvm_data->{$vg}{'vg-size'},'string','N/A'); + push(@$rows,{ + main::key($num++,1,1,'Device') => '', + main::key($num++,0,2,'VG') => $vg, + main::key($num++,0,2,'type') => uc($lvm_data->{$vg}{'vg-format'}), + main::key($num++,0,2,'size') => $size, + },); + $size = main::get_size($lvm_data->{$vg}{'vg-free'},'string','N/A'); + $rows->[$j]{main::key($num++,0,2,'free')} = $size; + foreach my $lv (sort keys %{$lvm_data->{$vg}{'lvs'}}){ + next if $extra < 2 && $lv =~ /^\[/; # it's an internal vg lv, raid meta/image + $j = scalar @$rows; + my $b_raid; + $size = main::get_size($lvm_data->{$vg}{'lvs'}{$lv}{'lv-size'},'string','N/A'); + $rows->[$j]{main::key($num++,1,2,'LV')} = $lv; + if ($b_admin && $lvm_data->{$vg}{'lvs'}{$lv}{'maj-min'}){ + $rows->[$j]{main::key($num++,0,3,'maj-min')} = $lvm_data->{$vg}{'lvs'}{$lv}{'maj-min'}; + } + $rows->[$j]{main::key($num++,0,3,'type')} = $lvm_data->{$vg}{'lvs'}{$lv}{'lv-type'}; + if ($extra > 0 && $lvm_data->{$vg}{'lvs'}{$lv}{'dm'}){ + $rows->[$j]{main::key($num++,0,3,'dm')} = $lvm_data->{$vg}{'lvs'}{$lv}{'dm'}; + } + $rows->[$j]{main::key($num++,0,3,'size')} = $size; + if ($extra > 1 && !($show{'raid'} || $show{'raid-basic'}) && $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}){ + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,3,'RAID')} = ''; + $rows->[$j]{main::key($num++,0,4,'stripes')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'stripes'}; + $rows->[$j]{main::key($num++,0,4,'sync')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'sync'}; + my $copied = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'copied'}; + $copied = (defined $copied) ? ($copied + 0) . '%': 'N/A'; + $rows->[$j]{main::key($num++,0,4,'copied')} = $copied; + $rows->[$j]{main::key($num++,0,4,'mismatches')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'mismatches'}; + $b_raid = 1; + } + components_output('lvm',\$j,\$num,$rows,\@{$lvm_data->{$vg}{'lvs'}{$lv}{'components'}},\$b_raid); + } + } + eval $end if $b_log; +} + +sub components_output { + my ($type,$j,$num,$rows,$components,$b_raid) = @_; + my ($l1); + $$j = scalar @$rows if $$b_raid || $extra > 1; + $$b_raid = 0; + if ($type eq 'general'){ + ($l1) = (2); + } + elsif ($type eq 'lvm'){ + ($l1) = (3); + } + my $status = (!@$components) ? 'N/A': ''; + $rows->[$$j]{main::key($$num++,1,$l1,'Components')} = $status; + components_recursive_output($type,$j,$num,$rows,$components,0,'c','p'); +} + +sub components_recursive_output { + my ($type,$j,$num,$rows,$components,$indent,$c,$p) = @_; + my ($l,$m,$size) = (1,1,0); + my ($l2,$l3); + if ($type eq 'general'){ + ($l2,$l3) = (3+$indent,4+$indent) ; + } + elsif ($type eq 'lvm'){ + ($l2,$l3) = (4+$indent,5+$indent); + } + # print 'outside: ', scalar @$component, "\n", Data::Dumper::Dumper $component; + foreach my $component (@$components){ + # print "inside: -n", Data::Dumper::Dumper $component->[$i]; + $$j = scalar @$rows if $b_admin; + my $id; + if ($component->[0] =~ /^(bcache|dm-|md)[0-9]/){ + $id = $c .'-' . $m; + $m++; + } + else { + $id = $p . '-' . $l; + $l++; + } + $rows->[$$j]{main::key($$num++,1,$l2,$id)} = $component->[0]; + if ($extra > 1){ + if ($b_admin){ + $component->[1] ||= 'N/A'; + $rows->[$$j]{main::key($$num++,0,$l3,'maj-min')} = $component->[1]; + $rows->[$$j]{main::key($$num++,0,$l3,'mapped')} = $component->[3] if $component->[3]; + $size = main::get_size($component->[2],'string','N/A'); + $rows->[$$j]{main::key($$num++,0,$l3,'size')} = $size; + } + #next if !$component->[$i][4]; + for (my $i = 4; $i < scalar @$component; $i++){ + components_recursive_output($type,$j,$num,$rows,$component->[$i],$indent+1,$c.'c',$p.'p'); + } + } + } +} + +# Note: type dm is seen in only one dataset, but it's a start +sub general_data { + eval $start if $b_log; + my (@found,$parent,$parent_fs); + my $general_data = []; + PartitionData::set('proc') if !$loaded{'partition-data'}; + main::set_mapper() if !$loaded{'mapper'}; + foreach my $row (@lsblk){ + # bcache doesn't have mapped name: !$mapper{$row->{'name'}} || + next if !$row->{'parent'}; + $parent = LsblkData::get($row->{'parent'}); + next if !$parent->{'fs'}; + if ($row->{'type'} && (($row->{'type'} eq 'crypt' || + $row->{'type'} eq 'mpath' || $row->{'type'} eq 'multipath') || + ($row->{'type'} eq 'dm' && $row->{'name'} =~ /veracrypt/i) || + ($parent->{'fs'} eq 'bcache'))){ + my (@full_components,$mapped,$type); + $mapped = $mapper{$row->{'name'}} if %mapper; + next if grep(/^$row->{'name'}$/, @found); + push(@found,$row->{'name'}); + if ($parent->{'fs'} eq 'crypto_LUKS'){ + $type = 'LUKS'; + } + # note, testing name is random user string, and there is no other + # ID known, the parent FS is '', empty. + elsif ($row->{'type'} eq 'dm' && $row->{'name'} =~ /veracrypt/i){ + $type = 'VeraCrypt'; + } + elsif ($row->{'type'} eq 'crypt'){ + $type = 'Crypto'; + } + elsif ($parent->{'fs'} eq 'bcache'){ + $type = 'bcache'; + } + # probably only seen on older Redhat servers, LVM probably replaces + elsif ($row->{'type'} eq 'mpath' || $row->{'type'} eq 'multipath'){ + $type = 'MultiPath'; + } + elsif ($row->{'type'} eq 'crypt'){ + $type = 'Crypt'; + } + # my $name = ($use{'filter-uuid'}) ? "luks-$filter_string" : $row->{'name'}; + component_data($row->{'maj-min'},\@full_components); + # print "$row->{'name'}\n", Data::Dumper::Dumper \@full_components; + push(@$general_data, { + 'components' => \@full_components, + 'dm' => $mapped, + 'maj-min' => $row->{'maj-min'}, + 'name' => $row->{'name'}, + 'size' => $row->{'size'}, + 'type' => $type, + }); + } + } + main::log_data('dump','luks @$general_data', $general_data); + print Data::Dumper::Dumper $general_data if $dbg[23]; + eval $end if $b_log; + return $general_data; +} + +# Note: called for disk totals, raid, and logical +sub lvm_data { + eval $start if $b_log; + $loaded{'logical-data'} = 1; + my (@args,@data,%totals); + @args = qw(vg_name vg_fmt vg_size vg_free lv_name lv_layout lv_size + lv_kernel_major lv_kernel_minor segtype seg_count seg_start_pe seg_size_pe + stripes devices raid_mismatch_count raid_sync_action raid_write_behind + copy_percent); + my $num = 0; + PartitionData::set() if !$loaded{'partition-data'}; + main::set_mapper() if !$loaded{'mapper'}; + if ($fake{'logical'}){ + # my $file = "$fake_data_dir/raid-logical/lvm/lvs-test-1.txt"; + # @data = main::reader($file,'strip'); + } + else { + # lv_full_name: ar0-home; lv_dm_path: /dev/mapper/ar0-home + # seg_size: unit location on volume where segement starts + # 2>/dev/null -unit k ---separator ^: + my $cmd = $alerts{'lvs'}->{'path'}; + $cmd .= ' -aPv --unit k --separator "^:" --segments --noheadings -o '; + # $cmd .= ' -o +lv_size,pv_major,pv_minor 2>/dev/null'; + $cmd .= join(',', @args); + $cmd .= ' 2>/dev/null'; + @data = main::grabber("$cmd",'','strip'); + main::log_data('dump','lvm @data', \@data) if $b_log; + print "command: $cmd\n" if $dbg[22]; + } + my $j = 0; + foreach (@data){ + my @line = split(/\^:/, $_); + next if $_ =~ /^Partial mode/i; # sometimes 2>/dev/null doesn't catch this + for (my $i = 0; $i < scalar @args; $i++){ + $line[$i] =~ s/k$// if $args[$i] =~ /_(free|size|used)$/; + $lvm[$j]->{$args[$i]} = $line[$i]; + } + if (!$totals{'vgs'}->{$lvm[$j]->{'vg_name'}}){ + $totals{'vgs'}->{$lvm[$j]->{'vg_name'}} = $lvm[$j]->{'vg_size'}; + $raw_logical[2] += $lvm[$j]->{'vg_free'} if $lvm[$j]->{'vg_free'}; + } + $j++; + } + # print Data::Dumper::Dumper \%totals, \@raw_logical; + main::log_data('dump','lvm @lvm', \@lvm) if $b_log; + print Data::Dumper::Dumper \@lvm if $dbg[22]; + eval $end if $b_log; +} + +sub process_lvm_data { + eval $start if $b_log; + my $processed = {}; + foreach my $item (@lvm){ + my (@components,@devices,$dm,$dm_tmp,$dm_mm,@full_components,$maj_min,%raid,@temp); + if (!$processed->{$item->{'vg_name'}}){ + $processed->{$item->{'vg_name'}}->{'vg-size'} = $item->{'vg_size'}; + $processed->{$item->{'vg_name'}}->{'vg-free'} = $item->{'vg_free'}; + $processed->{$item->{'vg_name'}}->{'vg-format'} = $item->{'vg_fmt'}; + } + if (!$processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}){ + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'lv-size'} = $item->{'lv_size'}; + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'lv-type'} = $item->{'segtype'}; + $maj_min = $item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'}; + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'maj-min'} = $maj_min; + $dm_tmp = $item->{'vg_name'} . '-' . $item->{'lv_name'}; + $dm_tmp =~ s/\[|\]$//g; + $dm = $mapper{$dm_tmp} if %mapper; + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'dm'} = $dm; + if ($item->{'segtype'} && $item->{'segtype'} ne 'linear' && $item->{'segtype'} =~ /^raid/){ + $raid{'copied'} = $item->{'copy_percent'}; + $raid{'mismatches'} = $item->{'raid_mismatch_count'}; + $raid{'stripes'} = $item->{'stripes'}; + $raid{'sync'} = $item->{'raid_sync_action'}; + $raid{'type'} = $item->{'segtype'}; + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'raid'} = \%raid; + } + component_data($maj_min,\@full_components); + # print "$item->{'lv_name'}\n", Data::Dumper::Dumper \@full_components; + $processed->{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'components'} = \@full_components; + } + } + main::log_data('dump','lvm %$processed', $processed) if $b_log; + print Data::Dumper::Dumper $processed if $dbg[23]; + eval $end if $b_log; + return $processed; +} + +sub component_data { + my ($maj_min,$full_components) = @_; + push(@$full_components, component_recursive_data($maj_min)); +} + +sub component_recursive_data { + eval $start if $b_log; + my ($maj_min) = @_; + my (@components,@devices); + @devices = main::globber("/sys/dev/block/$maj_min/slaves/*") if -e "/sys/dev/block/$maj_min/slaves"; + @devices = map {$_ =~ s|^/.*/||; $_;} @devices if @devices; + # return @devices if !$b_admin; + foreach my $device (@devices){ + my ($mapped,$mm2,$part); + $part = PartitionData::get($device) if @proc_partitions; + $mm2 = $part->[0] . ':' . $part->[1] if @$part; + if ($device =~ /^(bcache|dm-|md)[0-9]+$/){ + $mapped = $dmmapper{$device}; + $raw_logical[1] += $part->[2] if $mapped && $mapped =~ /_(cdata|cmeta)$/; + push(@components, [$device,$mm2,$part->[2],$mapped,[component_recursive_data($mm2)]]); + } + else { + push(@components,[$device,$mm2,$part->[2]]); + } + } + eval $end if $b_log; + return @components; +} +} + +## MachineItem +# Public: get(), is_vm() +{ +my $b_vm; +package MachineItem; + +sub get { + eval $start if $b_log; + my (%soc_machine,$data,@rows,$key1,$val1,$which); + my $rows = []; + my $num = 0; + if ($bsd_type && $sysctl{'machine'} && !$force{'dmidecode'}){ + $data = machine_data_sysctl(); + if (%$data){ + machine_output($rows,$data); + } + elsif (!$key1){ + $key1 = 'Message'; + $val1 = main::message('machine-data-force-dmidecode',''); + } + } + elsif ($bsd_type || $force{'dmidecode'}){ + if (!$fake{'dmidecode'} && $alerts{'dmidecode'}->{'action'} ne 'use'){ + $key1 = $alerts{'dmidecode'}->{'action'}; + $val1 = $alerts{'dmidecode'}->{'message'}; + $key1 = ucfirst($key1); + } + else { + $data = machine_data_dmi(); + if (%$data){ + machine_output($rows,$data); + } + elsif (!$key1){ + $key1 = 'Message'; + $val1 = main::message('machine-data'); + } + } + } + elsif (-d '/sys/class/dmi/id/'){ + $data = machine_data_sys(); + if (%$data){ + machine_output($rows,$data); + } + else { + $key1 = 'Message'; + if ($alerts{'dmidecode'}->{'action'} eq 'missing'){ + $val1 = main::message('machine-data-dmidecode'); + } + else { + $val1 = main::message('machine-data'); + } + } + } + elsif ($fake{'elbrus'} || $cpu_arch eq 'elbrus'){ + if ($fake{'elbrus'} || (my $program = main::check_program('fruid_print'))){ + $data = machine_data_fruid($program); + if (%$data){ + machine_output($rows,$data); + } + elsif (!$key1){ + $key1 = 'Message'; + $val1 = main::message('machine-data-fruid'); + } + } + } + elsif (!$bsd_type){ + # this uses /proc/cpuinfo so only GNU/Linux + if (%risc){ + $data = machine_data_soc(); + machine_soc_output($rows,$data) if %$data; + } + if (!$data || !%$data){ + $key1 = 'Message'; + $val1 = main::message('machine-data-force-dmidecode',''); + } + } + # if error case, null data, whatever + if ($key1){ + push(@$rows,{main::key($num++,0,1,$key1) => $val1,}); + } + eval $end if $b_log; + return $rows; +} + +sub is_vm { + return $b_vm; +} + +## keys for machine data are: +# 0: sys_vendor; 1: product_name; 2: product_version; 3: product_serial; +# 4: product_uuid; 5: board_vendor; 6: board_name; 7: board_version; +# 8: board_serial; 9: bios_vendor; 10: bios_version; 11: bios_date; +## with extra data: +# 12: chassis_vendor; 13: chassis_type; 14: chassis_version; 15: chassis_serial; +## unused: 16: bios_rev; 17: bios_romsize; 18: firmware type +sub machine_output { + eval $start if $b_log; + my ($rows,$data) = @_; + my $firmware = 'BIOS'; + my $num = 0; + my $j = 0; + my ($b_chassis,$b_skip_chassis,$b_skip_system); + my ($bios_date,$bios_rev,$bios_romsize,$bios_vendor,$bios_version,$chassis_serial, + $chassis_type,$chassis_vendor,$chassis_version,$mobo_model,$mobo_serial,$mobo_vendor, + $mobo_version,$product_name,$product_serial,$product_version,$system_vendor); + # foreach my $key (keys %data){ + # print "$key: $data->{$key}\n"; + # } + if (!$data->{'sys_vendor'} || + ($data->{'board_vendor'} && $data->{'sys_vendor'} eq $data->{'board_vendor'} && + !$data->{'product_name'} && !$data->{'product_version'} && + !$data->{'product_serial'})){ + $b_skip_system = 1; + } + # The goal here is to not show laptop/mobile devices + # found a case of battery existing but having nothing in it on desktop mobo + # not all laptops show the first. /proc/acpi/battery is deprecated. + elsif (!glob('/proc/acpi/battery/*') && !glob('/sys/class/power_supply/*')){ + # ibm / ibm can be true; dell / quantum is false, so in other words, only do this + # in case where the vendor is the same and the version is the same and not null, + # otherwise the version information is going to be different in all cases I think + if (($data->{'sys_vendor'} && $data->{'board_vendor'} && + $data->{'sys_vendor'} eq $data->{'board_vendor'}) && + (($data->{'product_version'} && $data->{'board_version'} && + $data->{'product_version'} eq $data->{'board_version'}) || + (!$data->{'product_version'} && $data->{'product_name'} && $data->{'board_name'} && + $data->{'product_name'} eq $data->{'board_name'}))){ + $b_skip_system = 1; + } + } + $data->{'device'} ||= 'N/A'; + $j = scalar @$rows; + push(@$rows, { + main::key($num++,0,1,'Type') => ucfirst($data->{'device'}), + },); + if (!$b_skip_system){ + # this has already been tested for above so we know it's not null + $system_vendor = main::clean($data->{'sys_vendor'}); + $product_name = ($data->{'product_name'}) ? $data->{'product_name'}:'N/A'; + $product_version = ($data->{'product_version'}) ? $data->{'product_version'}:'N/A'; + $product_serial = main::filter($data->{'product_serial'}); + $rows->[$j]{main::key($num++,1,1,'System')} = $system_vendor; + $rows->[$j]{main::key($num++,1,2,'product')} = $product_name; + $rows->[$j]{main::key($num++,0,3,'v')} = $product_version; + $rows->[$j]{main::key($num++,0,3,'serial')} = $product_serial; + # no point in showing chassis if system isn't there, it's very unlikely that + # would be correct + if ($extra > 1){ + if ($data->{'board_version'} && $data->{'chassis_version'} && + $data->{'chassis_version'} eq $data->{'board_version'}){ + $b_skip_chassis = 1; + } + if (!$b_skip_chassis && $data->{'chassis_vendor'}){ + if ($data->{'chassis_vendor'} ne $data->{'sys_vendor'}){ + $chassis_vendor = $data->{'chassis_vendor'}; + } + # dmidecode can have these be the same + if ($data->{'chassis_type'} && $data->{'device'} ne $data->{'chassis_type'}){ + $chassis_type = $data->{'chassis_type'}; + } + if ($data->{'chassis_version'}){ + $chassis_version = $data->{'chassis_version'}; + $chassis_version =~ s/^v([0-9])/$1/i; + } + $chassis_serial = main::filter($data->{'chassis_serial'}); + $chassis_vendor ||= ''; + $chassis_type ||= ''; + $rows->[$j]{main::key($num++,1,1,'Chassis')} = $chassis_vendor; + if ($chassis_type){ + $rows->[$j]{main::key($num++,0,2,'type')} = $chassis_type; + } + if ($chassis_version){ + $rows->[$j]{main::key($num++,0,2,'v')} = $chassis_version; + } + $rows->[$j]{main::key($num++,0,2,'serial')} = $chassis_serial; + } + } + $j++; # start new row + } + if ($data->{'firmware'}){ + $firmware = $data->{'firmware'}; + } + $mobo_vendor = ($data->{'board_vendor'}) ? main::clean($data->{'board_vendor'}) : 'N/A'; + $mobo_model = ($data->{'board_name'}) ? $data->{'board_name'}: 'N/A'; + $mobo_version = ($data->{'board_version'})? $data->{'board_version'} : ''; + $mobo_serial = main::filter($data->{'board_serial'}); + $bios_vendor = ($data->{'bios_vendor'}) ? main::clean($data->{'bios_vendor'}) : 'N/A'; + if ($data->{'bios_version'}){ + $bios_version = $data->{'bios_version'}; + $bios_version =~ s/^v([0-9])/$1/i; + if ($data->{'bios_rev'}){ + $bios_rev = $data->{'bios_rev'}; + } + } + $bios_version ||= 'N/A'; + if ($data->{'bios_date'}){ + $bios_date = $data->{'bios_date'}; + } + $bios_date ||= 'N/A'; + if ($extra > 1 && $data->{'bios_romsize'}){ + $bios_romsize = $data->{'bios_romsize'}; + } + $rows->[$j]{main::key($num++,1,1,'Mobo')} = $mobo_vendor; + $rows->[$j]{main::key($num++,1,2,'model')} = $mobo_model; + if ($mobo_version){ + $rows->[$j]{main::key($num++,0,3,'v')} = $mobo_version; + } + $rows->[$j]{main::key($num++,0,3,'serial')} = $mobo_serial; + if ($extra > 2 && $data->{'board_uuid'}){ + $rows->[$j]{main::key($num++,0,3,'uuid')} = $data->{'board_uuid'}; + } + if ($extra > 1 && $data->{'board_mfg_date'}){ + $rows->[$j]{main::key($num++,0,3,'mfg-date')} = $data->{'board_mfg_date'}; + } + $rows->[$j]{main::key($num++,1,1,$firmware)} = $bios_vendor; + $rows->[$j]{main::key($num++,0,2,'v')} = $bios_version; + if ($bios_rev){ + $rows->[$j]{main::key($num++,0,2,'rev')} = $bios_rev; + } + $rows->[$j]{main::key($num++,0,2,'date')} = $bios_date; + if ($bios_romsize){ + $rows->[$j]{main::key($num++,0,2,'rom size')} = $bios_romsize; + } + eval $end if $b_log; +} + +sub machine_soc_output { + my ($rows,$soc_machine) = @_; + my ($key); + my ($cont_sys,$ind_sys,$j,$num) = (1,1,0,0); + # print Data::Dumper::Dumper \%soc_machine; + # this is sketchy, /proc/device-tree/model may be similar to Hardware value from /proc/cpuinfo + # raspi: Hardware : BCM2835 model: Raspberry Pi Model B Rev 2 + if ($soc_machine->{'device'} || $soc_machine->{'model'}){ + $rows->[$j]{main::key($num++,0,1,'Type')} = uc($risc{'id'}); + my $system = 'System'; + if (defined $soc_machine->{'model'}){ + $rows->[$j]{main::key($num++,1,1,'System')} = $soc_machine->{'model'}; + $system = 'details'; + ($cont_sys,$ind_sys) = (0,2); + } + $soc_machine->{'device'} ||= 'N/A'; + $rows->[$j]{main::key($num++,$cont_sys,$ind_sys,$system)} = $soc_machine->{'device'}; + } + if ($soc_machine->{'mobo'}){ + $rows->[$j]{main::key($num++,1,1,'mobo')} = $soc_machine->{'mobo'}; + } + # we're going to print N/A for 0000 values sine the item was there. + if ($soc_machine->{'firmware'}){ + # most samples I've seen are like: 0000 + $soc_machine->{'firmware'} =~ s/^[0]+$//; + $soc_machine->{'firmware'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'rev')} = $soc_machine->{'firmware'}; + } + # sometimes has value like: 0000 + if (defined $soc_machine->{'serial'}){ + # most samples I've seen are like: 0000 + $soc_machine->{'serial'} =~ s/^[0]+$//; + $rows->[$j]{main::key($num++,0,2,'serial')} = main::filter($soc_machine->{'serial'}); + } + eval $end if $b_log; +} + +sub machine_data_fruid { + eval $start if $b_log; + my ($program) = @_; + my ($b_start,@fruid); + my $data = {}; + if (!$fake{'elbrus'}){ + @fruid = main::grabber("$program 2>/dev/null",'','strip'); + } + else { + # my $file; + # $file = "$fake_data_dir/machine/fruid/fruid-e904-1_full.txt"; + # $file = "$fake_data_dir/machine/fruid/fruid-e804-1_full.txt"; + # @fruid = main::reader($file,'strip'); + } + # print Data::Dumper::Dumper \@fruid; + foreach (@fruid){ + $b_start = 1 if /^Board info/; + next if !$b_start; + my @split = split(/\s*:\s+/,$_,2); + if ($split[0] eq 'Mfg. Date/Time'){ + $data->{'board_mfg_date'} = $split[1]; + $data->{'board_mfg_date'} =~ s/^(\d+:\d+)\s//; + } + elsif ($split[0] eq 'Board manufacturer'){ + $data->{'board_vendor'} = $split[1]; + } + elsif ($split[0] eq 'Board part number'){ + $data->{'board_part_nu'} = $split[1]; + } + elsif ($split[0] eq 'Board product name'){ + $data->{'board_name'} = $split[1]; + } + elsif ($split[0] eq 'Board serial number'){ + $data->{'board_serial'} = $split[1]; + } + elsif ($split[0] eq 'Board product version'){ + $data->{'board_version'} = $split[1]; + } + } + print Data::Dumper::Dumper $data if $dbg[28]; + main::log_data('dump','%data',$data) if $b_log; + return $data; +} + +sub machine_data_sys { + eval $start if $b_log; + my ($path,$vm); + my $data = {}; + my $sys_dir = '/sys/class/dmi/id/'; + my $sys_dir_alt = '/sys/devices/virtual/dmi/id/'; + my @sys_files = qw(bios_vendor bios_version bios_date + board_name board_serial board_vendor board_version chassis_type + product_name product_serial product_uuid product_version sys_vendor + ); + if ($extra > 1){ + splice(@sys_files, 0, 0, qw(chassis_serial chassis_vendor chassis_version)); + } + $data->{'firmware'} = 'BIOS'; + # print Data::Dumper::Dumper \@sys_files; + if (!-d $sys_dir){ + if (-d $sys_dir_alt){ + $sys_dir = $sys_dir_alt; + } + else { + return 0; + } + } + if (-d '/sys/firmware/efi'){ + $data->{'firmware'} = 'UEFI'; + } + elsif (glob('/sys/firmware/acpi/tables/UEFI*')){ + $data->{'firmware'} = 'UEFI-[Legacy]'; + } + foreach (@sys_files){ + $path = "$sys_dir$_"; + if (-r $path){ + $data->{$_} = main::reader($path,'',0); + $data->{$_} = ($data->{$_}) ? main::clean_dmi($data->{$_}) : ''; + } + elsif (!$b_root && -e $path && !-r $path){ + $data->{$_} = main::message('root-required'); + } + else { + $data->{$_} = ''; + } + } + if ($data->{'chassis_type'}){ + if ($data->{'chassis_type'} == 1){ + $data->{'device'} = check_vm($data->{'sys_vendor'},$data->{'product_name'}); + $data->{'device'} ||= 'other-vm?'; + } + else { + $data->{'device'} = get_device_sys($data->{'chassis_type'}); + } + } + # print "sys:\n"; + # foreach (keys %data){ + # print "$_: $data->{$_}\n"; + # } + print Data::Dumper::Dumper $data if $dbg[28]; + main::log_data('dump','%data',$data) if $b_log; + eval $end if $b_log; + return $data; +} + +# This will create an alternate machine data source +# which will be used for alt ARM machine data in cases +# where no dmi data present, or by cpu data to guess at +# certain actions for arm only. +sub machine_data_soc { + eval $end if $b_log; + my $data = {}; + if (my $file = $system_files{'proc-cpuinfo'}){ + CpuItem::cpuinfo_data_grabber($file) if !$loaded{'cpuinfo'}; + # grabber sets keys to lower case to avoid error here + if ($cpuinfo_machine{'hardware'} || $cpuinfo_machine{'machine'}){ + $data->{'device'} = main::get_defined($cpuinfo_machine{'hardware'}, + $cpuinfo_machine{'machine'}); + $data->{'device'} = main::clean_arm($data->{'device'}); + $data->{'device'} = main::clean_dmi($data->{'device'}); + $data->{'device'} = main::clean($data->{'device'}); + } + if (defined $cpuinfo_machine{'system type'} || $cpuinfo_machine{'model'}){ + $data->{'model'} = main::get_defined($cpuinfo_machine{'system type'}, + $cpuinfo_machine{'model'}); + $data->{'model'} = main::clean_dmi($data->{'model'}); + $data->{'model'} = main::clean($data->{'model'}); + } + # seen with PowerMac PPC + if (defined $cpuinfo_machine{'motherboard'}){ + $data->{'mobo'} = $cpuinfo_machine{'motherboard'}; + } + if (defined $cpuinfo_machine{'revision'}){ + $data->{'firmware'} = $cpuinfo_machine{'revision'}; + } + if (defined $cpuinfo_machine{'serial'}){ + $data->{'serial'} = $cpuinfo_machine{'serial'}; + } + undef %cpuinfo_machine; # we're done with it, don't need it anymore + } + if (!$data->{'model'} && $b_android){ + main::set_build_prop() if !$loaded{'build-prop'}; + if ($build_prop{'product-manufacturer'} && $build_prop{'product-model'}){ + my $brand = ''; + if ($build_prop{'product-brand'} && + $build_prop{'product-brand'} ne $build_prop{'product-manufacturer'}){ + $brand = $build_prop{'product-brand'} . ' '; + } + $data->{'model'} = $brand . $build_prop{'product-manufacturer'} . ' ' . $build_prop{'product-model'}; + } + elsif ($build_prop{'product-device'}){ + $data->{'model'} = $build_prop{'product-device'}; + } + elsif ($build_prop{'product-name'}){ + $data->{'model'} = $build_prop{'product-name'}; + } + } + if (!$data->{'model'} && -r '/proc/device-tree/model'){ + my $model = main::reader('/proc/device-tree/model','',0); + main::log_data('data',"device-tree-model: $model") if $b_log; + if ($model){ + $model = main::clean_dmi($model); + $model = (split(/\x01|\x02|\x03|\x00/, $model))[0] if $model; + my $device_temp = main::clean_regex($data->{'device'}); + if (!$data->{'device'} || ($model && $model !~ /\Q$device_temp\E/i)){ + $model = main::clean_arm($model); + $data->{'model'} = $model; + } + } + } + if (!$data->{'serial'} && -f '/proc/device-tree/serial-number'){ + my $serial = main::reader('/proc/device-tree/serial-number','',0); + $serial = (split(/\x01|\x02|\x03|\x00/, $serial))[0] if $serial; + main::log_data('data',"device-tree-serial: $serial") if $b_log; + $data->{'serial'} = $serial if $serial; + } + print Data::Dumper::Dumper $data if $dbg[28]; + main::log_data('dump','%data',$data) if $b_log; + eval $end if $b_log; + return $data; +} + +# bios_date: 09/07/2010 +# bios_romsize: dmi only +# bios_vendor: American Megatrends Inc. +# bios_version: P1.70 +# bios_rev: 8.14: dmi only +# board_name: A770DE+ +# board_serial: +# board_vendor: ASRock +# board_version: +# chassis_serial: +# chassis_type: 3 +# chassis_vendor: +# chassis_version: +# firmware: +# product_name: +# product_serial: +# product_uuid: +# product_version: +# sys_uuid: dmi/sysctl only +# sys_vendor: +sub machine_data_dmi { + eval $start if $b_log; + return if !@dmi; + my ($vm); + my $data = {}; + $data->{'firmware'} = 'BIOS'; + # dmi types: + # 0 bios; 1 system info; 2 board|base board info; 3 chassis info; + # 4 processor info, use to check for hypervisor + foreach my $row (@dmi){ + # bios/firmware + if ($row->[0] == 0){ + # skip first three row, we don't need that data + foreach my $item (@$row[3 .. $#$row]){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split(/:\s+/, $item); + if ($value[0] eq 'Release Date'){ + $data->{'bios_date'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Vendor'){ + $data->{'bios_vendor'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Version'){ + $data->{'bios_version'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'ROM Size'){ + $data->{'bios_romsize'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'BIOS Revision'){ + $data->{'bios_rev'} = main::clean_dmi($value[1]) } + } + else { + if ($item eq '~UEFI is supported'){ + $data->{'firmware'} = 'UEFI';} + } + } + next; + } + # system information + elsif ($row->[0] == 1){ + # skip first three row, we don't need that data + foreach my $item (@$row[3 .. $#$row]){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split(/:\s+/, $item); + if ($value[0] eq 'Product Name'){ + $data->{'product_name'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Version'){ + $data->{'product_version'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Serial Number'){ + $data->{'product_serial'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Manufacturer'){ + $data->{'sys_vendor'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'UUID'){ + $data->{'sys_uuid'} = main::clean_dmi($value[1]) } + } + } + next; + } + # baseboard information + elsif ($row->[0] == 2){ + # skip first three row, we don't need that data + foreach my $item (@$row[3 .. $#$row]){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split(/:\s+/, $item); + if ($value[0] eq 'Product Name'){ + $data->{'board_name'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Serial Number'){ + $data->{'board_serial'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Manufacturer'){ + $data->{'board_vendor'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Version'){ + $data->{'board_version'} = main::clean_dmi($value[1]) } + } + } + next; + } + # chassis information + elsif ($row->[0] == 3){ + # skip first three row, we don't need that data + foreach my $item (@$row[3 .. $#$row]){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split(/:\s+/, $item); + if ($value[0] eq 'Serial Number'){ + $data->{'chassis_serial'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Type'){ + $data->{'chassis_type'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Manufacturer'){ + $data->{'chassis_vendor'} = main::clean_dmi($value[1]) } + elsif ($value[0] eq 'Version'){ + $data->{'chassis_version'} = main::clean_dmi($value[1]) } + } + } + if ($data->{'chassis_type'} && $data->{'chassis_type'} ne 'Other'){ + $data->{'device'} = $data->{'chassis_type'}; + } + next; + } + # this may catch some BSD and fringe Linux cases + # processor information: check for hypervisor + elsif ($row->[0] == 4){ + # skip first three row, we don't need that data + if (!$data->{'device'}){ + if (grep {/hypervisor/i} @$row){ + $data->{'device'} = 'virtual-machine'; + $b_vm = 1; + } + } + last; + } + elsif ($row->[0] > 4){ + last; + } + } + if (!$data->{'device'}){ + $data->{'device'} = check_vm($data->{'sys_vendor'},$data->{'product_name'}); + $data->{'device'} ||= 'other-vm?'; + } + # print "dmi:\n"; + # foreach (keys %data){ + # print "$_: $data->{$_}\n"; + # } + print Data::Dumper::Dumper $data if $dbg[28]; + main::log_data('dump','%data',$data) if $b_log; + eval $end if $b_log; + return $data; +} + +# As far as I know, only OpenBSD supports this method. +# it uses hw. info from sysctl -a and bios info from dmesg.boot +sub machine_data_sysctl { + eval $start if $b_log; + my ($product,$vendor,$vm); + my $data = {}; + # ^hw\.(vendor|product|version|serialno|uuid) + foreach (@{$sysctl{'machine'}}){ + next if !$_; + my @item = split(':', $_); + next if !$item[1]; + if ($item[0] eq 'hw.vendor' || $item[0] eq 'machdep.dmi.board-vendor'){ + $data->{'board_vendor'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'hw.product' || $item[0] eq 'machdep.dmi.board-product'){ + $data->{'board_name'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'hw.version' || $item[0] eq 'machdep.dmi.board-version'){ + $data->{'board_version'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'hw.serialno' || $item[0] eq 'machdep.dmi.board-serial'){ + $data->{'board_serial'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'hw.serial'){ + $data->{'board_serial'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'hw.uuid'){ + $data->{'board_uuid'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.system-vendor'){ + $data->{'sys_vendor'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.system-product'){ + $data->{'product_name'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.system-version'){ + $data->{'product_version'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.system-serial'){ + $data->{'product_serial'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.system-uuid'){ + $data->{'sys_uuid'} = main::clean_dmi($item[1]); + } + # bios0:at mainbus0: AT/286+ BIOS, date 06/30/06, BIOS32 rev. 0 @ 0xf2030, SMBIOS rev. 2.4 @ 0xf0000 (47 entries) + # bios0:vendor Phoenix Technologies, LTD version "3.00" date 06/30/2006 + elsif ($item[0] =~ /^bios[0-9]/){ + if ($_ =~ /^^bios[0-9]:at\s.*?\srev\.\s([\S]+)\s@.*/){ + $data->{'bios_rev'} = $1; + $data->{'firmware'} = 'BIOS' if $_ =~ /BIOS/; + } + elsif ($item[1] =~ /^vendor\s(.*?)\sversion\s(.*?)\sdate\s([\S]+)/){ + $data->{'bios_vendor'} = $1; + $data->{'bios_version'} = $2; + $data->{'bios_date'} = $3; + $data->{'bios_version'} =~ s/^v//i if $data->{'bios_version'} && $data->{'bios_version'} !~ /vi/i; + } + } + elsif ($item[0] eq 'machdep.dmi.bios-vendor'){ + $data->{'bios_vendor'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.bios-version'){ + $data->{'bios_version'} = main::clean_dmi($item[1]); + } + elsif ($item[0] eq 'machdep.dmi.bios-date'){ + $data->{'bios_date'} = main::clean_dmi($item[1]); + } + } + if ($data->{'board_vendor'} || $data->{'sys_vendor'} || $data->{'board_name'} || $data->{'product_name'}){ + $vendor = $data->{'sys_vendor'}; + $vendor = $data->{'board_vendor'} if !$vendor; + $product = $data->{'product_name'}; + $product = $data->{'board_name'} if !$product; + } + # detections can be from other sources. + $data->{'device'} = check_vm($vendor,$product); + print Data::Dumper::Dumper $data if $dbg[28]; + main::log_data('dump','%data',$data) if $b_log; + eval $end if $b_log; + return $data; +} + +sub get_device_sys { + eval $start if $b_log; + my ($chasis_id) = @_; + my ($device) = (''); + my @chassis; + # See inxi-resources MACHINE DATA for data sources + $chassis[2] = 'unknown'; + $chassis[3] = 'desktop'; + $chassis[4] = 'desktop'; + # 5 - pizza box was a 1 U desktop enclosure, but some old laptops also id this way + $chassis[5] = 'pizza-box'; + $chassis[6] = 'desktop'; + $chassis[7] = 'desktop'; + $chassis[8] = 'portable'; + $chassis[9] = 'laptop'; + # note: lenovo T420 shows as 10, notebook, but it's not a notebook + $chassis[10] = 'laptop'; + $chassis[11] = 'portable'; + $chassis[12] = 'docking-station'; + # note: 13 is all-in-one which we take as a mac type system + $chassis[13] = 'desktop'; + $chassis[14] = 'notebook'; + $chassis[15] = 'desktop'; + $chassis[16] = 'laptop'; + $chassis[17] = 'server'; + $chassis[18] = 'expansion-chassis'; + $chassis[19] = 'sub-chassis'; + $chassis[20] = 'bus-expansion'; + $chassis[21] = 'peripheral'; + $chassis[22] = 'RAID'; + $chassis[23] = 'server'; + $chassis[24] = 'desktop'; + $chassis[25] = 'multimount-chassis'; # blade? + $chassis[26] = 'compact-PCI'; + $chassis[27] = 'blade'; + $chassis[28] = 'blade'; + $chassis[29] = 'blade-enclosure'; + $chassis[30] = 'tablet'; + $chassis[31] = 'convertible'; + $chassis[32] = 'detachable'; + $chassis[33] = 'IoT-gateway'; + $chassis[34] = 'embedded-pc'; + $chassis[35] = 'mini-pc'; + $chassis[36] = 'stick-pc'; + $device = $chassis[$chasis_id] if $chassis[$chasis_id]; + eval $end if $b_log; + return $device; +} + +sub check_vm { + eval $start if $b_log; + my ($manufacturer,$product_name) = @_; + $manufacturer ||= ''; + $product_name ||= ''; + my $vm; + if (my $program = main::check_program('systemd-detect-virt')){ + my $vm_test = (main::grabber("$program 2>/dev/null"))[0]; + if ($vm_test){ + # kvm vbox reports as oracle, usually, unless they change it + if (lc($vm_test) eq 'oracle'){ + $vm = 'virtualbox'; + } + elsif ($vm_test ne 'none'){ + $vm = $vm_test; + } + } + } + if (!$vm || lc($vm) eq 'bochs'){ + if (-e '/proc/vz'){$vm = 'openvz'} + elsif (-e '/proc/xen'){$vm = 'xen'} + elsif (-e '/dev/vzfs'){$vm = 'virtuozzo'} + elsif (my $program = main::check_program('lsmod')){ + my @vm_data = main::grabber("$program 2>/dev/null"); + if (@vm_data){ + if (grep {/kqemu/i} @vm_data){$vm = 'kqemu'} + elsif (grep {/kvm|qumranet/i} @vm_data){$vm = 'kvm'} + elsif (grep {/qemu/i} @vm_data){$vm = 'qemu'} + } + } + } + # this will catch many Linux systems and some BSDs + if (!$vm || lc($vm) eq 'bochs'){ + # $device_vm is '' if nothing detected + my @vm_data = ($device_vm); + push(@vm_data,@{$dboot{'machine-vm'}}) if $dboot{'machine-vm'}; + if (-e '/dev/disk/by-id'){ + my @dev = glob('/dev/disk/by-id/*'); + push(@vm_data,@dev); + } + if (grep {/innotek|vbox|virtualbox/i} @vm_data){ + $vm = 'virtualbox'; + } + elsif (grep {/vmware/i} @vm_data){ + $vm = 'vmware'; + } + # needs to be first, because contains virtio;qumranet, grabber only gets + # first instance then stops, so make sure patterns are right. + elsif (grep {/(openbsd[\s-]vmm)/i} @vm_data){ + $vm = 'vmm'; + } + elsif (grep {/(\bhvm\b)/i} @vm_data){ + $vm = 'hvm'; + } + elsif (grep {/(qemu)/i} @vm_data){ + $vm = 'qemu'; + } + elsif (grep {/(\bkvm\b|qumranet|virtio)/i} @vm_data){ + $vm = 'kvm'; + } + elsif (grep {/Virtual HD|Microsoft.*Virtual Machine/i} @vm_data){ + $vm = 'hyper-v'; + } + if (!$vm && (my $file = $system_files{'proc-cpuinfo'})){ + my @info = main::reader($file); + $vm = 'virtual-machine' if grep {/^flags.*hypervisor/} @info; + } + # this may be wrong, confirm it + if (!$vm && -e '/dev/vda' || -e '/dev/vdb' || -e '/dev/xvda' || -e '/dev/xvdb'){ + $vm = 'virtual-machine'; + } + } + if (!$vm && $product_name){ + if ($product_name eq 'VMware'){ + $vm = 'vmware'; + } + elsif ($product_name eq 'VirtualBox'){ + $vm = 'virtualbox'; + } + elsif ($product_name eq 'KVM'){ + $vm = 'kvm'; + } + elsif ($product_name eq 'Bochs'){ + $vm = 'qemu'; + } + } + if (!$vm && $manufacturer && $manufacturer eq 'Xen'){ + $vm = 'xen'; + } + $b_vm = 1 if $vm; + eval $end if $b_log; + return $vm; +} +} + +## NetworkItem +{ +package NetworkItem; +my ($b_ip_run,@ifs_found); + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + if (%risc && !$use{'soc-network'} && !$use{'pci-tool'}){ + # do nothing, but keep the test conditions to force + # the non arm case to always run + } + else { + device_output($rows); + } + # note: raspberry pi uses usb networking only + if (!@$rows){ + if (%risc){ + my $key = 'Message'; + @$rows = ({ + main::key($num++,0,1,$key) => main::message('risc-pci',$risc{'id'}) + }); + } + else { + my $key = 'Message'; + my $message = ''; + my $type = 'pci-card-data'; + # for some reason, this was in device_output too redundantly + if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){ + $type = 'pci-card-data-root'; + } + elsif (!$bsd_type && !%risc && !$pci_tool && + $alerts{'lspci'}->{'action'} && + $alerts{'lspci'}->{'action'} eq 'missing'){ + $message = $alerts{'lspci'}->{'message'}; + } + $message = main::message($type,'') if !$message; + @$rows = ({ + main::key($num++,0,1,$key) => $message + }); + } + } + usb_output($rows); + if ($show{'network-advanced'}){ + # @ifs_found = (); + # shift @ifs_found; + # pop @ifs_found; + if (!$bsd_type){ + advanced_data_sys($rows,'check','',0,'','',''); + } + else { + advanced_data_bsd($rows,'check'); + } + } + if ($show{'ip'}){ + wan_ip($rows); + } + eval $end if $b_log; + return $rows; +} + +sub device_output { + eval $start if $b_log; + return if !$devices{'network'}; + my $rows = $_[0]; + my ($b_wifi,%holder); + my ($j,$num) = (0,1); + foreach my $row (@{$devices{'network'}}){ + $num = 1; + # print "$row->[0] $row->[3]\n"; + # print "$row->[0] $row->[3]\n"; + $j = scalar @$rows; + my $driver = $row->[9]; + my $chip_id = main::get_chip_id($row->[5],$row->[6]); + # working around a virtuo bug same chip id is used on two nics + if (!defined $holder{$chip_id}){ + $holder{$chip_id} = 0; + } + else { + $holder{$chip_id}++; + } + # first check if it's a known wifi id'ed card, if so, no print of duplex/speed + $b_wifi = check_wifi($row->[4]); + my $device = $row->[4]; + $device = ($device) ? main::clean_pci($device,'output') : 'N/A'; + #$device ||= 'N/A'; + $driver ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Device') => $device, + },); + if ($extra > 0 && $use{'pci-tool'} && $row->[12]){ + my $item = main::get_pci_vendor($row->[4],$row->[12]); + $rows->[$j]{main::key($num++,0,2,'vendor')} = $item if $item; + } + if ($row->[1] eq '0680'){ + $rows->[$j]{main::key($num++,0,2,'type')} = 'network bridge'; + } + $rows->[$j]{main::key($num++,1,2,'driver')} = $driver; + my $bus_id = 'N/A'; + # note: for arm/mips we want to see the single item bus id, why not? + # note: we can have bus id: 0002 / 0 which is valid, but 0 / 0 is invalid + if (defined $row->[2] && $row->[2] ne '0' && defined $row->[3]){ + $bus_id = "$row->[2].$row->[3]"} + elsif (defined $row->[2] && $row->[2] ne '0'){ + $bus_id = $row->[2]} + elsif (defined $row->[3] && $row->[3] ne '0'){ + $bus_id = $row->[3]} + if ($extra > 0){ + if ($row->[9] && !$bsd_type){ + my $version = main::get_module_version($row->[9]); + $version ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'v')} = $version; + } + if ($b_admin && $row->[10]){ + $row->[10] = main::get_driver_modules($row->[9],$row->[10]); + $rows->[$j]{main::key($num++,0,3,'modules')} = $row->[10] if $row->[10]; + } + $row->[8] ||= 'N/A'; + if ($extra > 1 && $bus_id ne 'N/A'){ + main::get_pcie_data($bus_id,$j,$rows,\$num); + } + # as far as I know, wifi has no port, but in case it does in future, use it + if (!$b_wifi || ($b_wifi && $row->[8] ne 'N/A')){ + $rows->[$j]{main::key($num++,0,2,'port')} = $row->[8]; + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + } + if ($extra > 1){ + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $chip_id; + } + if ($extra > 2 && $row->[1]){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = $row->[1]; + } + if (!$bsd_type && $extra > 0 && $bus_id ne 'N/A' && $bus_id =~ /\.0$/){ + my $temp = main::get_device_temp($bus_id); + if ($temp){ + $rows->[$j]{main::key($num++,0,2,'temp')} = $temp . ' C'; + } + } + if ($show{'network-advanced'}){ + my @data; + if (!$bsd_type){ + advanced_data_sys($rows,$row->[5],$row->[6],$holder{$chip_id},$b_wifi,'',$bus_id); + } + else { + if (defined $row->[9] && defined $row->[11]){ + advanced_data_bsd($rows,"$row->[9]$row->[11]",$b_wifi); + } + } + } + # print "$row->[0]\n"; + } + # @rows = (); + eval $end if $b_log; +} + +sub usb_output { + eval $start if $b_log; + return if !$usb{'network'}; + my $rows = $_[0]; + my (@temp2,$b_wifi,$driver,$path,$path_id,$product,$type); + my ($j,$num) = (0,1); + foreach my $row (@{$usb{'network'}}){ + $num = 1; + ($driver,$path,$path_id,$product,$type) = ('','','','',''); + $product = main::clean($row->[13]) if $row->[13]; + $driver = $row->[15] if $row->[15]; + $path = $row->[3] if $row->[3]; + $path_id = $row->[2] if $row->[2]; + $type = $row->[14] if $row->[14]; + $driver ||= 'N/A'; + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'Device') => $product, + main::key($num++,0,2,'driver') => $driver, + main::key($num++,1,2,'type') => 'USB', + },); + $b_wifi = check_wifi($product); + if ($extra > 0){ + if ($extra > 1){ + $row->[8] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'rev')} = $row->[8]; + if ($row->[17]){ + $rows->[$j]{main::key($num++,0,3,'speed')} = $row->[17]; + } + if ($row->[24]){ + $rows->[$j]{main::key($num++,0,3,'lanes')} = $row->[24]; + } + if ($b_admin && $row->[22]){ + $rows->[$j]{main::key($num++,0,3,'mode')} = $row->[22]; + } + } + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]"; + if ($extra > 1){ + $row->[7] ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $row->[7]; + } + if ($extra > 2){ + if (defined $row->[5] && $row->[5] ne ''){ + $rows->[$j]{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]"; + } + if ($row->[16]){ + $rows->[$j]{main::key($num++,0,2,'serial')} = main::filter($row->[16]); + } + } + } + if ($show{'network-advanced'}){ + if (!$bsd_type){ + my (@temp,$vendor,$chip); + @temp = split(':', $row->[7]) if $row->[7]; + ($vendor,$chip) = ($temp[0],$temp[1]) if @temp; + advanced_data_sys($rows,$vendor,$chip,0,$b_wifi,$path,''); + } + # NOTE: we need the driver + driver nu, like wlp0 to get a match, + else { + $driver .= $row->[21] if defined $row->[21]; + advanced_data_bsd($rows,$driver,$b_wifi); + } + } + } + eval $end if $b_log; +} + +sub advanced_data_sys { + eval $start if $b_log; + return if ! -d '/sys/class/net'; + my ($rows,$vendor,$chip,$count,$b_wifi,$path_usb,$bus_id) = @_; + my ($cont_if,$ind_if,$j,$num) = (2,3,0,0); + my $key = 'IF'; + my ($b_check,$b_usb,$if,$path,@paths); + # ntoe: we've already gotten the base path, now we + # we just need to get the IF path, which is one level in: + # usb1/1-1/1-1:1.0/net/enp0s20f0u1/ + if ($path_usb){ + $b_usb = 1; + @paths = main::globber("${path_usb}*/net/*"); + } + else { + @paths = main::globber('/sys/class/net/*'); + } + @paths = grep {!/\/lo$/} @paths; + # push(@paths,'/sys/class/net/ppp0'); # fake IF if needed to match test data + if ($count > 0 && $count < scalar @paths){ + @paths = splice(@paths, $count, scalar @paths); + } + if ($vendor eq 'check'){ + $b_check = 1; + $key = 'IF-ID'; + ($cont_if,$ind_if) = (1,2); + } + # print join('; ', @paths), $count, "\n"; + foreach (@paths){ + my ($data1,$data2,$duplex,$mac,$speed,$state); + $j = scalar @$rows; + # for usb, we already know where we are + if (!$b_usb){ + # pi mmcnr has pcitool and also these vendor/device paths. + if (!%risc || $use{'pci-tool'}){ + $path = "$_/device/vendor"; + $data1 = main::reader($path,'',0) if -r $path; + $data1 =~ s/^0x// if $data1; + $path = "$_/device/device"; + $data2 = main::reader($path,'',0) if -r $path; + $data2 =~ s/^0x// if $data2; + # this is a fix for a redhat bug in virtio + $data2 = (defined $data2 && $data2 eq '0001' && defined $chip && $chip eq '1000') ? '1000' : $data2; + } + # there are cases where arm devices have a small pci bus + # or, with mmcnr devices, will show device/vendor info in data1/2 + # which won't match with the path IDs + if (%risc && $chip && Cwd::abs_path($_) =~ /\b$chip\b/){ + $data1 = $vendor; + $data2 = $chip; + } + } + # print "d1:$data1 v:$vendor d2:$data2 c:$chip bus_id: $bus_id\n"; + # print Cwd::abs_path($_), "\n" if $bus_id; + if ($b_usb || $b_check || ($data1 && $data2 && $data1 eq $vendor && $data2 eq $chip && + (%risc || check_bus_id($_,$bus_id)))){ + $if = $_; + $if =~ s/^\/.+\///; + # print "top: if: $if ifs: @ifs_found\n"; + next if ($b_check && grep {/$if/} @ifs_found); + $path = "$_/duplex"; + $duplex = main::reader($path,'',0) if -r $path; + $duplex ||= 'N/A'; + $path = "$_/address"; + $mac = main::reader($path,'',0) if -r $path; + $mac = main::filter($mac); + $path = "$_/speed"; + $speed = main::reader($path,'',0) if -r $path; + $speed ||= 'N/A'; + $path = "$_/operstate"; + $state = main::reader($path,'',0) if -r $path; + $state ||= 'N/A'; + # print "$speed \n"; + push(@$rows,{ + main::key($num++,1,$cont_if,$key) => $if, + main::key($num++,0,$ind_if,'state') => $state + }); + # my $j = scalar @row - 1; + push(@ifs_found, $if) if (!$b_check && (! grep {/$if/} @ifs_found)); + # print "push: if: $if ifs: @ifs_found\n"; + # no print out for wifi since it doesn't have duplex/speed data available + # note that some cards show 'unknown' for state, so only testing explicitly + # for 'down' string in that to skip showing speed/duplex + # /sys/class/net/$if/wireless : not always there, but worth a try: wlan/wl/ww/wlp + $b_wifi = 1 if !$b_wifi && (-e "$_$if/wireless" || $if =~ /^(wl|ww)/); + if (!$b_wifi && $state ne 'down' && $state ne 'no'){ + # make sure the value is strictly numeric before appending Mbps + $speed = (main::is_int($speed)) ? "$speed Mbps" : $speed; + $rows->[$j]{main::key($num++,0,$ind_if,'speed')} = $speed; + $rows->[$j]{main::key($num++,0,$ind_if,'duplex')} = $duplex; + } + $rows->[$j]{main::key($num++,0,$ind_if,'mac')} = $mac; + # if ($b_check){ + # push(@rows,@row); + # } + # else { + # @rows = @row; + # } + if ($show{'ip'}){ + if_ip($rows,$key,$if); + } + last if !$b_check; + } + } + eval $end if $b_log; +} + +sub advanced_data_bsd { + eval $start if $b_log; + return if ! @ifs_bsd; + my ($rows,$if,$b_wifi) = @_; + my ($data,$working_if); + my ($b_check,$state,$speed,$duplex,$mac); + my ($cont_if,$ind_if,$j,$num) = (2,3,0,0); + my $key = 'IF'; + if ($if eq 'check'){ + $b_check = 1; + $key = 'IF-ID'; + ($cont_if,$ind_if) = (1,2); + } + foreach my $item (@ifs_bsd){ + if (ref $item ne 'ARRAY'){ + $working_if = $item; + # print "$working_if\n"; + next; + } + else { + $data = $item; + } + if ($b_check || $working_if eq $if){ + $if = $working_if if $b_check; + # print "top1: if: $if ifs: wif: $working_if @ifs_found\n"; + next if ($b_check && grep {/$if/} @ifs_found); + # print "top2: if: $if wif: $working_if ifs: @ifs_found\n"; + # print Data::Dumper::Dumper $data; + # ($state,$speed,$duplex,$mac) + $duplex = $data->[2]; + $duplex ||= 'N/A'; + $mac = main::filter($data->[3]); + $speed = $data->[1]; + $speed ||= 'N/A'; + $state = $data->[0]; + $state ||= 'N/A'; + $j = scalar @$rows; + # print "$speed \n"; + push(@$rows, { + main::key($num++,1,$cont_if,$key) => $if, + main::key($num++,0,$ind_if,'state') => $state, + }); + push(@ifs_found, $if) if (!$b_check && (!grep {/$if/} @ifs_found)); + # print "push: if: $if ifs: @ifs_found\n"; + # no print out for wifi since it doesn't have duplex/speed data available + # note that some cards show 'unknown' for state, so only testing explicitly + # for 'down' string in that to skip showing speed/duplex + if (!$b_wifi && $state ne 'down' && $state ne 'no network'){ + # make sure the value is strictly numeric before appending Mbps + $speed = (main::is_int($speed)) ? "$speed Mbps" : $speed; + $rows->[$j]{main::key($num++,0,$ind_if,'speed')} = $speed; + $rows->[$j]{main::key($num++,0,$ind_if,'duplex')} = $duplex; + } + $rows->[$j]{main::key($num++,0,$ind_if,'mac')} = $mac; + if ($show{'ip'} && $if){ + if_ip($rows,$key,$if); + } + } + } + eval $end if $b_log; +} + +## Result values: +# 0: ipv +# 1: ip +# 2: broadcast, if found +# 3: scope, if found +# 4: scope IF, if different from IF +sub if_ip { + eval $start if $b_log; + my ($rows,$type,$if) = @_; + my ($working_if); + my ($cont_ip,$ind_ip,$if_cnt) = (3,4,0); + my ($j,$num) = (0,0); + $b_ip_run = 1; + if ($type eq 'IF-ID'){ + ($cont_ip,$ind_ip) = (2,3); + } + OUTER: + foreach my $item (@ifs){ + if (ref $item ne 'ARRAY'){ + $working_if = $item; + # print "if:$if wif:$working_if\n"; + next; + } + if ($working_if eq $if){ + $if_cnt = 0; + # print "if $if item:\n", Data::Dumper::Dumper $item; + foreach my $data2 (@$item){ + $j = scalar @$rows; + $num = 1; + $if_cnt++; + if ($limit > 0 && $if_cnt > $limit){ + push(@$rows, { + main::key($num++,0,$cont_ip,'Message') => main::message('output-limit',scalar @$item), + }); + last OUTER; + } + # print "$data2->[0] $data2->[1]\n"; + my ($ipv,$ip,$broadcast,$scope,$scope_id); + $ipv = ($data2->[0])? $data2->[0]: 'N/A'; + $ip = main::filter($data2->[1]); + $scope = ($data2->[3])? $data2->[3]: 'N/A'; + # note: where is this ever set to 'all'? Old test condition? + if ($if ne 'all'){ + if (defined $data2->[4] && $working_if ne $data2->[4]){ + # scope global temporary deprecated dynamic + # scope global dynamic + # scope global temporary deprecated dynamic + # scope site temporary deprecated dynamic + # scope global dynamic noprefixroute enx403cfc00ac68 + # scope global eth0 + # scope link + # scope site dynamic + # scope link + # trim off if at end of multi word string if found + $data2->[4] =~ s/\s$if$// if $data2->[4] =~ /[^\s]+\s$if$/; + my $key = ($data2->[4] =~ /deprecated|dynamic|temporary|noprefixroute/) ? 'type' : 'virtual'; + push(@$rows, { + main::key($num++,1,$cont_ip,"IP v$ipv") => $ip, + main::key($num++,0,$ind_ip,$key) => $data2->[4], + main::key($num++,0,$ind_ip,'scope') => $scope, + }); + } + else { + push(@$rows, { + main::key($num++,1,$cont_ip,"IP v$ipv") => $ip, + main::key($num++,0,$ind_ip,'scope') => $scope, + }); + } + } + else { + push(@$rows, { + main::key($num++,1,($cont_ip - 1),'IF') => $if, + main::key($num++,1,$cont_ip,"IP v$ipv") => $ip, + main::key($num++,0,$ind_ip,'scope') => $scope, + }); + } + if ($extra > 1 && $data2->[2]){ + $broadcast = main::filter($data2->[2]); + $rows->[$j]{main::key($num++,0,$ind_ip,'broadcast')} = $broadcast; + } + } + } + } + eval $end if $b_log; +} + +# Get ip using downloader to stdout. This is a clean, text only IP output url, +# single line only, ending in the ip address. May have to modify this in the future +# to handle ipv4 and ipv6 addresses but should not be necessary. +# ip=$(echo 2001:0db8:85a3:0000:0000:8a2e:0370:7334 | gawk --re-interval ' +# ip=$(wget -q -O - $WAN_IP_URL | gawk --re-interval ' +# this generates a direct dns based ipv4 ip address, but if opendns.com goes down, +# the fall backs will still work. +# note: consistently slower than domain based: +# dig +short +time=1 +tries=1 myip.opendns.com. A @208.67.222.222 +sub wan_ip { + eval $start if $b_log; + my $rows = $_[0]; + my ($b_dig,$b_html,$ip,$ua); + my $num = 0; + # time: 0.06 - 0.07 seconds + # Cisco opendns.com may be terminating supporting this one, sometimes works, sometimes not: + # use -4/6 to force ipv 4 or 6, but generally we want the 'natural' native ip returned. + # dig +short +time=1 +tries=1 myip.opendns.com @resolver1.opendns.com :: 0.021s + # Works but is slow: + # dig +short @ns1-1.akamaitech.net ANY whoami.akamai.net :: 0.156s + # This one can take forever, and sometimes requires explicit -4 or -6 + # dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com :: 0.026s; 1.087ss + if (!$force{'no-dig'} && (my $program = main::check_program('dig'))){ + $ip = (main::grabber("$program +short +time=1 +tries=1 \@ns1-1.akamaitech.net ANY whoami.akamai.net 2>/dev/null"))[0]; + $ip =~ s/"//g if $ip; # some return IP in quotes, when using TXT + $b_dig = 1; + } + if (!$ip && !$force{'no-html-wan'}){ + # if dig failed or is not installed, set downloader data if unset + if (!defined $dl{'no-ssl'}){ + main::set_downloader(); + } + # note: tests: akamai: 0.015 - 0.025 icanhazip.com: 0.020 0.030 + # smxi: 0.230, so ~10x slower. Dig is not as fast as you'd expect + # dig: 0.167s 0.156s + # leaving smxi as last test because I know it will always be up. + # --wan-ip-url replaces values with user supplied arg + # 0.020s: http://whatismyip.akamai.com/ + # 0.136s: https://get.geojs.io/v1/ip + # 0.024s: http://icanhazip.com/ + # 0.027s: ifconfig.io + # 0.230s: https://smxi.org/opt/ip.php + # 0.023s: https://api.ipify.org :: NOTE: hangs, widely variable times, don't use + my @urls = (!$wan_url) ? qw(http://whatismyip.akamai.com/ + http://icanhazip.com/ https://smxi.org/opt/ip.php) : ($wan_url); + foreach (@urls){ + $ua = 'ip' if $_ =~ /smxi/; + $ip = main::download_file('stdout',$_,'',$ua); + if ($ip){ + # print "$_\n"; + chomp($ip); + $ip = (split(/\s+/, $ip))[-1]; + last; + } + } + $b_html = 1; + } + if ($ip && $use{'filter'}){ + $ip = $filter_string; + } + if (!$ip){ + # true case trips + if (!$b_dig){ + $ip = main::message('IP-no-dig', 'WAN IP'); + } + elsif ($b_dig && !$b_html){ + $ip = main::message('IP-dig', 'WAN IP'); + } + else { + $ip = main::message('IP', 'WAN IP'); + } + } + push(@$rows, { + main::key($num++,0,1,'WAN IP') => $ip, + }); + eval $end if $b_log; +} + +sub check_bus_id { + eval $start if $b_log; + my ($path,$bus_id) = @_; + my ($b_valid); + if ($bus_id){ + # legacy, not link, but uevent has path: + # PHYSDEVPATH=/devices/pci0000:00/0000:00:0a.1/0000:05:00.0 + if (Cwd::abs_path($path) =~ /$bus_id\// || + (-r "$path/uevent" && -s "$path/uevent" && + (grep {/$bus_id/} main::reader("$path/uevent")))){ + $b_valid = 1; + } + } + eval $end if $b_log; + return $b_valid; +} + +sub check_wifi { + my ($item) = @_; + my $b_wifi = ($item =~ /wireless|wi-?fi|wlan|802\.11|centrino/i) ? 1 : 0; + return $b_wifi; +} +} + +## OpticalItem +{ +package OpticalItem; + +sub get { + eval $start if $b_log; + my $rows = $_[0]; + my $start = scalar @$rows; + my ($data,$val1); + my $num = 0; + if ($bsd_type){ + $val1 = main::message('optical-data-bsd'); + if ($dboot{'optical'}){ + $data = drive_data_bsd(); + drive_output($rows,$data) if %$data; + } + else{ + my $file = $system_files{'dmesg-boot'}; + if ($file && ! -r $file){ + $val1 = main::message('dmesg-boot-permissions'); + } + elsif (!$file){ + $val1 = main::message('dmesg-boot-missing'); + } + } + } + else { + $val1 = main::message('optical-data'); + $data = drive_data_linux(); + drive_output($rows,$data) if %$data; + } + # if none of the above increased the row count, show the error message + if ($start == scalar @$rows){ + push(@$rows,{main::key($num++,0,1,'Message') => $val1}); + } + eval $end if $b_log; + return $rows; +} + +sub drive_output { + eval $start if $b_log; + my ($rows,$drives) = @_; + my $num = 0; + my $j = 0; + # build floppy if any + foreach my $key (sort keys %$drives){ + if ($drives->{$key}{'type'} eq 'floppy'){ + push(@$rows, { + main::key($num++,0,1,ucfirst($drives->{$key}{'type'})) => "/dev/$key", + }); + delete $drives->{$key}; + } + } + foreach my $key (sort keys %$drives){ + $j = scalar @$rows; + $num = 1; + my $vendor = $drives->{$key}{'vendor'}; + $vendor ||= 'N/A'; + my $model = $drives->{$key}{'model'}; + $model ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,ucfirst($drives->{$key}{'type'})) => "/dev/$key", + main::key($num++,0,2,'vendor') => $vendor, + main::key($num++,0,2,'model') => $model, + }); + if ($extra > 0){ + my $rev = $drives->{$key}{'rev'}; + $rev ||= 'N/A'; + $rows->[$j]{ main::key($num++,0,2,'rev')} = $rev; + } + if ($extra > 1 && $drives->{$key}{'serial'}){ + $rows->[$j]{ main::key($num++,0,2,'serial')} = main::filter($drives->{$key}{'serial'}); + } + my $links = (@{$drives->{$key}{'links'}}) ? join(',', sort @{$drives->{$key}{'links'}}) : 'N/A' ; + $rows->[$j]{ main::key($num++,0,2,'dev-links')} = $links; + if ($show{'optical'}){ + $j = scalar @$rows; + my $speed = $drives->{$key}{'speed'}; + $speed ||= 'N/A'; + my ($audio,$multisession) = ('',''); + if (defined $drives->{$key}{'multisession'}){ + $multisession = ($drives->{$key}{'multisession'} == 1) ? 'yes' : 'no' ; + } + $multisession ||= 'N/A'; + if (defined $drives->{$key}{'audio'}){ + $audio = ($drives->{$key}{'audio'} == 1) ? 'yes' : 'no' ; + } + $audio ||= 'N/A'; + my $dvd = 'N/A'; + my (@rw,$rws); + if (defined $drives->{$key}{'dvd'}){ + $dvd = ($drives->{$key}{'dvd'} == 1) ? 'yes' : 'no' ; + } + if ($drives->{$key}{'cdr'}){ + push(@rw, 'cd-r'); + } + if ($drives->{$key}{'cdrw'}){ + push(@rw, 'cd-rw'); + } + if ($drives->{$key}{'dvdr'}){ + push(@rw, 'dvd-r'); + } + if ($drives->{$key}{'dvdram'}){ + push(@rw, 'dvd-ram'); + } + $rws = (@rw) ? join(',', @rw) : 'none' ; + push(@$rows, { + main::key($num++,1,2,'Features') => '', + main::key($num++,0,3,'speed') => $speed, + main::key($num++,0,3,'multisession') => $multisession, + main::key($num++,0,3,'audio') => $audio, + main::key($num++,0,3,'dvd') => $dvd, + main::key($num++,0,3,'rw') => $rws, + }); + if ($extra > 0){ + my $state = $drives->{$key}{'state'}; + $state ||= 'N/A'; + $rows->[$j]{ main::key($num++,0,3,'state')} = $state; + } + } + } + # print Data::Dumper::Dumper $drives; + eval $end if $b_log; +} + +sub drive_data_bsd { + eval $start if $b_log; + my (@rows,@temp); + my $drives = {}; + my ($count,$i,$working) = (0,0,''); + foreach (@{$dboot{'optical'}}){ + $_ =~ s/(cd[0-9]+)\(([^:]+):([0-9]+):([0-9]+)\):/$1:$2-$3.$4,/; + my @row = split(/:\s*/, $_); + next if ! defined $row[1]; + if ($working ne $row[0]){ + # print "$id_holder $row[0]\n"; + $working = $row[0]; + } + # no dots, note: ada2: 2861588MB BUT: ada2: 600.000MB/s + if (!exists $drives->{$working}){ + $drives->{$working}{'links'} = []; + $drives->{$working}{'model'} = ''; + $drives->{$working}{'rev'} = ''; + $drives->{$working}{'state'} = ''; + $drives->{$working}{'vendor'} = ''; + $drives->{$working}{'temp'} = ''; + $drives->{$working}{'type'} = ($working =~ /^cd/) ? 'optical' : 'unknown'; + } + # print "$_\n"; + if ($bsd_type !~ /^(net|open)bsd$/){ + if ($row[1] && $row[1] =~ /^<([^>]+)>/){ + $drives->{$working}{'model'} = $1; + $count = ($drives->{$working}{'model'} =~ tr/ //); + if ($count && $count > 1){ + @temp = split(/\s+/, $drives->{$working}{'model'}); + $drives->{$working}{'vendor'} = $temp[0]; + my $index = ($#temp > 2) ? ($#temp - 1): $#temp; + $drives->{$working}{'model'} = join(' ', @temp[1..$index]); + $drives->{$working}{'rev'} = $temp[-1] if $count > 2; + } + if ($show{'optical'}){ + if (/\bDVD\b/){ + $drives->{$working}{'dvd'} = 1; + } + if (/\bRW\b/){ + $drives->{$working}{'cdrw'} = 1; + $drives->{$working}{'dvdr'} = 1 if $drives->{$working}{'dvd'}; + } + } + } + if ($row[1] && $row[1] =~ /^Serial/){ + @temp = split(/\s+/,$row[1]); + $drives->{$working}{'serial'} = $temp[-1]; + } + if ($show{'optical'}){ + if ($row[1] =~ /^([0-9\.]+[MGTP][B]?\/s)/){ + $drives->{$working}{'speed'} = $1; + $drives->{$working}{'speed'} =~ s/\.[0-9]+//; + } + if (/\bDVD[-]?RAM\b/){ + $drives->{$working}{'cdr'} = 1; + $drives->{$working}{'dvdram'} = 1; + } + if ($row[2] && $row[2] =~ /,\s(.*)$/){ + $drives->{$working}{'state'} = $1; + $drives->{$working}{'state'} =~ s/\s+-\s+/, /; + } + } + } + else { + if ($row[2] && $row[2] =~ /<([^>]+)>/){ + $drives->{$working}{'model'} = $1; + $count = ($drives->{$working}{'model'} =~ tr/,//); + # print "c: $count $row[2]\n"; + if ($count && $count > 1){ + @temp = split(/,\s*/, $drives->{$working}{'model'}); + $drives->{$working}{'vendor'} = $temp[0]; + $drives->{$working}{'model'} = $temp[1]; + $drives->{$working}{'rev'} = $temp[2]; + } + if ($show{'optical'}){ + if (/\bDVD\b/){ + $drives->{$working}{'dvd'} = 1; + } + if (/\bRW\b/){ + $drives->{$working}{'cdrw'} = 1; + $drives->{$working}{'dvdr'} = 1 if $drives->{$working}{'dvd'}; + } + if (/\bDVD[-]?RAM\b/){ + $drives->{$working}{'cdr'} = 1; + $drives->{$working}{'dvdram'} = 1; + } + } + } + if ($show{'optical'}){ + # print "$row[1]\n"; + if (($row[1] =~ tr/,//) > 1){ + @temp = split(/,\s*/, $row[1]); + $drives->{$working}{'speed'} = $temp[2]; + } + } + } + } + main::log_data('dump','%$drives',$drives) if $b_log; + # print Data::Dumper::Dumper $drives; + eval $end if $b_log; + return $drives; +} + +sub drive_data_linux { + eval $start if $b_log; + my (@data,@info,@rows); + my $drives = {}; + @data = main::globber('/dev/dvd* /dev/cdr* /dev/scd* /dev/sr* /dev/fd[0-9]'); + # Newer kernel is NOT linking all optical drives. Some, but not all. + # Get the actual disk dev location, first try default which is easier to run, + # need to preserve line breaks + foreach (@data){ + my $working = readlink($_); + $working = ($working) ? $working: $_; + next if $working =~ /random/; + # possible fix: puppy has these in /mnt not /dev they say + $working =~ s/\/(dev|media|mnt)\///; + $_ =~ s/\/(dev|media|mnt)\///; + if (!defined $drives->{$working}){ + my @temp = ($_ ne $working) ? ($_) : (); + $drives->{$working}{'links'} = \@temp; + $drives->{$working}{'type'} = ($working =~ /^fd/) ? 'floppy' : 'optical' ; + } + else { + push(@{$drives->{$working}{'links'}}, $_) if $_ ne $working; + } + # print "$working\n"; + } + if ($show{'optical'} && -e '/proc/sys/dev/cdrom/info'){ + @info = main::reader('/proc/sys/dev/cdrom/info','strip'); + } + # print join('; ', @data), "\n"; + foreach my $key (keys %$drives){ + next if $drives->{$key}{'type'} eq 'floppy'; + my $device = "/sys/block/$key/device"; + if (-d $device){ + if (-r "$device/vendor"){ + $drives->{$key}{'vendor'} = main::reader("$device/vendor",'',0); + $drives->{$key}{'vendor'} = main::clean($drives->{$key}{'vendor'}); + $drives->{$key}{'state'} = main::reader("$device/state",'',0); + $drives->{$key}{'model'} = main::reader("$device/model",'',0); + $drives->{$key}{'model'} = main::clean($drives->{$key}{'model'}); + $drives->{$key}{'rev'} = main::reader("$device/rev",'',0); + } + } + elsif (-r "/proc/ide/$key/model"){ + $drives->{$key}{'vendor'} = main::reader("/proc/ide/$key/model",'',0); + $drives->{$key}{'vendor'} = main::clean($drives->{$key}{'vendor'}); + } + if ($show{'optical'} && @info){ + my $index = 0; + foreach my $item (@info){ + next if $item =~ /^\s*$/; + my @split = split(/\s+/, $item); + if ($item =~ /^drive name:/){ + foreach my $id (@split){ + last if ($id eq $key); + $index++; + } + last if !$index; # index will be > 0 if it was found + } + elsif ($item =~/^drive speed:/){ + $drives->{$key}{'speed'} = $split[$index]; + } + elsif ($item =~/^Can read multisession:/){ + $drives->{$key}{'multisession'}=$split[$index+1]; + } + elsif ($item =~/^Can read MCN:/){ + $drives->{$key}{'mcn'}=$split[$index+1]; + } + elsif ($item =~/^Can play audio:/){ + $drives->{$key}{'audio'}=$split[$index+1]; + } + elsif ($item =~/^Can write CD-R:/){ + $drives->{$key}{'cdr'}=$split[$index+1]; + } + elsif ($item =~/^Can write CD-RW:/){ + $drives->{$key}{'cdrw'}=$split[$index+1]; + } + elsif ($item =~/^Can read DVD:/){ + $drives->{$key}{'dvd'}=$split[$index+1]; + } + elsif ($item =~/^Can write DVD-R:/){ + $drives->{$key}{'dvdr'}=$split[$index+1]; + } + elsif ($item =~/^Can write DVD-RAM:/){ + $drives->{$key}{'dvdram'}=$split[$index+1]; + } + } + } + } + main::log_data('dump','%$drives',$drives) if $b_log; + # print Data::Dumper::Dumper $drives; + eval $end if $b_log; + return $drives; +} +} + +## PartitionItem +{ +# these will be globally accessible via PartitionItem::filters() +my ($fs_exclude,$fs_skip,$part_filter); +package PartitionItem; + +sub get { + eval $start if $b_log; + my ($key1,$val1); + my $rows = []; + my $num = 0; + set_partitions() if !$loaded{'set-partitions'}; + # Fails in corner case with zram but no other mounted filesystems + if (!@partitions){ + $key1 = 'Message'; + #$val1 = ($bsd_type && $bsd_type eq 'darwin') ? + # main::message('darwin-feature') : main::message('partition-data'); + $val1 = main::message('partition-data'); + @$rows = ({main::key($num++,0,1,$key1) => $val1,}); + } + else { + create_output($rows); + } + eval $end if $b_log; + return $rows; +} + +sub create_output { + eval $start if $b_log; + my $rows = $_[0]; + my $num = 0; + my $j = 0; + my ($dev,$dev_type,$fs,$percent,$raw_size,$size,$used); + # alpha sort for non numerics + if ($show{'partition-sort'} !~ /^(percent-used|size|used)$/){ + @partitions = sort { $a->{$show{'partition-sort'}} cmp $b->{$show{'partition-sort'}} } @partitions; + } + else { + @partitions = sort { $a->{$show{'partition-sort'}} <=> $b->{$show{'partition-sort'}} } @partitions; + } + my $fs_skip = get_filters('fs-skip'); + foreach my $row (@partitions){ + $num = 1; + next if $row->{'type'} eq 'secondary' && $show{'partition'}; + next if $show{'swap'} && $row->{'fs'} && $row->{'fs'} eq 'swap'; + next if $row->{'swap-type'} && $row->{'swap-type'} ne 'partition'; + if (!$row->{'hidden'}){ + $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A'; + $used = main::get_size($row->{'used'},'string','N/A'); # used can be 0 + $percent = (defined $row->{'percent-used'}) ? ' (' . $row->{'percent-used'} . '%)' : ''; + } + else { + $percent = ''; + $used = $size = (!$b_root) ? main::message('root-required') : main::message('partition-hidden'); + } + $fs = ($row->{'fs'}) ? lc($row->{'fs'}): 'N/A'; + $dev_type = ($row->{'dev-type'}) ? $row->{'dev-type'} : 'dev'; + $row->{'dev-base'} = '/dev/' . $row->{'dev-base'} if $dev_type eq 'dev' && $row->{'dev-base'}; + $dev = ($row->{'dev-base'}) ? $row->{'dev-base'} : 'N/A'; + $row->{'id'} =~ s|/home/[^/]+/(.*)|/home/$filter_string/$1| if $use{'filter'}; + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'ID') => $row->{'id'}, + }); + if (($b_admin || $row->{'hidden'}) && $row->{'raw-size'}){ + # It's an error! permissions or missing tool + $raw_size = ($row->{'raw-size'}) ? main::get_size($row->{'raw-size'},'string') : 'N/A'; + $rows->[$j]{main::key($num++,0,2,'raw-size')} = $raw_size; + } + if ($b_admin && $row->{'raw-available'} && $size ne 'N/A'){ + $size .= ' (' . $row->{'raw-available'} . '%)'; + } + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + $rows->[$j]{main::key($num++,0,2,'used')} = $used . $percent; + $rows->[$j]{main::key($num++,0,2,'fs')} = $fs; + if ($b_admin && $fs eq 'swap' && defined $row->{'swappiness'}){ + $rows->[$j]{main::key($num++,0,2,'swappiness')} = $row->{'swappiness'}; + } + if ($b_admin && $fs eq 'swap' && defined $row->{'cache-pressure'}){ + $rows->[$j]{main::key($num++,0,2,'cache-pressure')} = $row->{'cache-pressure'}; + } + if ($extra > 1 && $fs eq 'swap' && defined $row->{'priority'}){ + $rows->[$j]{main::key($num++,0,2,'priority')} = $row->{'priority'}; + } + if ($b_admin && $row->{'block-size'}){ + $rows->[$j]{main::key($num++,0,2,'block-size')} = $row->{'block-size'} . ' B';; + # $rows->[$j]{main::key($num++,0,2,'physical')} = $row->{'block-size'} . ' B'; + # $rows->[$j]{main::key($num++,0,2,'logical')} = $row->{'block-logical'} . ' B'; + } + $rows->[$j]{main::key($num++,1,2,$dev_type)} = $dev; + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,3,'maj-min')} = $row->{'maj-min'}; + } + if ($extra > 0 && $row->{'dev-mapped'}){ + $rows->[$j]{main::key($num++,0,3,'mapped')} = $row->{'dev-mapped'}; + } + # add fs known to not use label/uuid here + if (($show{'label'} || $show{'uuid'}) && $dev_type eq 'dev' && + $fs !~ /^$fs_skip$/){ + if ($show{'label'}){ + if ($use{'filter-label'}){ + $row->{'label'} = main::filter_partition('part', $row->{'label'}, ''); + } + $row->{'label'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'label')} = $row->{'label'}; + } + if ($show{'uuid'}){ + if ($use{'filter-uuid'}){ + $row->{'uuid'} = main::filter_partition('part', $row->{'uuid'}, ''); + } + $row->{'uuid'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'uuid')} = $row->{'uuid'}; + } + } + } + # Corner case, no partitions, but zram swap. + if (!@$rows){ + @$rows = ({main::key($num++,0,1,'Message') => main::message('partition-data')}); + } + eval $end if $b_log; +} + +sub set_partitions { + eval $start if $b_log; + # return if $bsd_type && $bsd_type eq 'darwin'; # darwin has mutated output + my (@data,@rows,@mount,@partitions_working,$part,@working); + my ($back_size,$back_used,$b_fs,$cols) = (4,3,1,6); + my ($b_dfp,$b_fake_map,$b_load,$b_logical,$b_space,); + my ($block_size,$blockdev,$dev_base,$dev_mapped,$dev_type,$fs,$id,$label, + $maj_min,$percent_used,$raw_size,$replace,$size_available,$size,$test, + $type,$uuid,$used); + $loaded{'set-partitions'} = 1; + if ($b_admin){ + # For partition block size + $blockdev = $alerts{'blockdev'}->{'path'} if $alerts{'blockdev'}->{'path'}; + } + # For raw partition sizes, maj_min + if ($bsd_type){ + DiskDataBSD::set() if !$loaded{'disk-data-bsd'}; + } + else { + PartitionData::set() if !$loaded{'partition-data'}; + LsblkData::set() if !$loaded{'lsblk'}; + } + # set @labels, @uuid + if (!$bsd_type){ + set_label_uuid() if !$loaded{'label-uuid'}; + } + # Most current OS support -T and -k, but -P means different things + # in freebsd. However since most use is from linux, we make that default + # android 7 no -T support + if (!$fake{'partitions'}){ + if (@partitions_working = main::grabber("df -P -T -k 2>/dev/null")){ + main::set_mapper() if !$loaded{'mapper'} && !$bsd_type; + $b_dfp = 1; + } + elsif (@partitions_working = main::grabber("df -T -k 2>/dev/null")){ + # Fine, it worked, could be bsd or linux + } + # Busybox supports -k and -P, older openbsd, darwin, solaris don't have -P + else { + if (@partitions_working = main::grabber("df -k -P 2>/dev/null")){ + $b_dfp = 1; + } + else { + @partitions_working = main::grabber("df -k 2>/dev/null"); + } + $b_fs = 0; + if (my $path = main::check_program('mount')){ + @mount = main::grabber("$path 2>/dev/null"); + } + } + } + else { + my $file; + # $file = "$fake_data_dir/block-devices/df/df-kTP-cygwin-1.txt"; + # $file = "$fake_data_dir/block-devices/df/df-kT-wrapped-1.txt"; + # @partitions_working = main::reader($file); + } + # print Data::Dumper::Dumper \@partitions_working; + # Determine positions + if (@partitions_working){ + my $row1 = shift @partitions_working; + $row1 =~ s/Mounted on/Mounted-on/i; + my @temp = split(/\s+/,$row1); + $cols = $#temp; + } + # NOTE: using -P fixes line wraps, otherwise look for hangs and reconnect + if (!$b_dfp){ + my $holder = ''; + my @part_temp; + foreach (@partitions_working){ + my @columns= split(/\s+/,$_); + if ($#columns < $cols){ + $holder = join('^^',@columns[0..$#columns]); + next; + } + if ($holder){ # reconnect hanging lines + $_ = $holder . ' ' . $_; + $holder = ''; + } + push(@part_temp,$_); + } + @partitions_working = @part_temp; + } + if (!$bsd_type){ + # New kernels/df have rootfs and / repeated, creating two entries for the + # same partition so check for two string endings of / then slice out the + # rootfs one, I could check for it before slicing it out, but doing that + # would require the same action twice re code execution. + my $roots = 0; + foreach (@partitions_working){ + $roots++ if /\s\/$/; + } + @partitions_working = grep {!/^rootfs/} @partitions_working if $roots > 1; + } + else { + # turns out freebsd uses this junk too + $b_fake_map = 1; + # darwin k: Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on + # linux kT: Filesystem Type 1K-blocks Used Available Use% Mounted on + # freebsd kT: Filesystem Type 1024-blocks Used Avail Capacity Mounted on + if ($bsd_type eq 'darwin'){ + ($back_size,$back_used) = (7,6); + } + } + my $filters = get_filters('partition'); + # These are local, not remote, iso, or overlay types: + my $fuse_fs = 'adb|apfs(-?fuse)?|archive(mount)?|gphoto|gv|gzip|ifuse|'; + $fuse_fs .= '[^\.]*mtp|ntfs-?3g|[^\.]*ptp|vdfuse|vram|wim(mount)?|xb|xml'; + # Just the common ones desktops might have + my $remote_fs = 'curlftp|gmail|g(oogle-?)?drive|pnfs|\bnfs|rclone|s3fs|smb|ssh'; + # push @partitions_working, '//mafreebox.freebox.fr/Disque dur cifs 239216096 206434016 20607496 91% /freebox/Disque dur'; + # push @partitions_working, '//mafreebox.freebox.fr/AllPG cifs 436616192 316339304 120276888 73% /freebox/AllPG'; + # push(@partitions_working,'/dev/loop0p1 iso9660 3424256 3424256 0 100% /media/jason/d-live nf 11.3.0 gn 6555 9555 amd64'); + # push(@partitions_working,'drvfs 9p 511881212 115074772 396806440 23% /mnt/c'); + # push(@partitions_working,'drivers 9p 511881212 115074772 396806440 23% /usr/lib/wsl/drivers'); + foreach (@partitions_working){ + ($dev_base,$dev_mapped,$dev_type,$fs,$id,$label, + $maj_min,$type,$uuid) = ('','','','','','','','',''); + ($b_load,$b_space,$block_size,$percent_used,$raw_size,$size_available, + $size,$used) = (0,0,0,0,0,0,0,0); + undef $part; + # apple crap, maybe also freebsd? + $_ =~ s/^map\s+([\S]+)/map:\/$1/ if $b_fake_map; + # handle spaces in remote filesystem names + # busybox df shows KM, sigh; note: GoogleDrive Hogne: fuse.rclone 15728640 316339304 120276888 73% + if (/^(.*?)(\s[\S]+)\s+[a-z][a-z0-9\.]+(\s+[0-9]+){3}\s+[0-9]+%\s/){ + $replace = $test = "$1$2"; + if ($test =~ /\s/){ # paranoid test, but better safe than sorry + $b_space = 1; + $replace =~ s/\s/^^/g; + # print ":$replace:\n"; + $_ =~ s/^$test/$replace/; + # print "$_\n"; + } + } + my @row = split(/\s+/, $_); + # print Data::Dumper::Dumper \@row; + $row[0] =~ s/\^\^/ /g if $b_space; # reset spaces in > 1 word fs name + # autofs is a bsd thing, has size 0 + if ($row[0] =~ /^$filters$/ || $row[0] =~ /^ROOT/i || + ($b_fs && ($row[2] == 0 || $row[1] =~ /^(autofs|devtmpfs|iso9660|tmpfs)$/))){ + next; + } + # cygwin C:\cygwin passes this test so has to be handled later + if ($row[0] =~ /^\/dev\/|:\/|\/\//){ + # this could point to by-label or by-uuid so get that first. In theory, abs_path should + # drill down to get the real path, but it isn't always working. + if ($row[0] eq '/dev/root'){ + $row[0] = get_root(); + } + # sometimes paths are set using /dev/disk/by-[label|uuid] so we need to get the /dev/xxx path + if ($row[0] =~ /by-label|by-uuid/){ + $row[0] = Cwd::abs_path($row[0]); + } + elsif ($row[0] =~ /mapper\// && %mapper){ + $dev_mapped = $row[0]; + $dev_mapped =~ s|^/.*/||; + $row[0] = $mapper{$dev_mapped} if $mapper{$dev_mapped}; + } + elsif ($row[0] =~ /\/dm-[0-9]+$/ && %dmmapper){ + my $temp = $row[0]; + $temp =~ s|^/.*/||; + $dev_mapped = $dmmapper{$temp}; + } + $dev_base = $row[0]; + $dev_base =~ s|^/.*/||; + $part = LsblkData::get($dev_base) if @lsblk; + $maj_min = get_maj_min($dev_base) if @proc_partitions; + } + # this handles zfs type devices/partitions, which do not start with / but contain / + # note: Main/jails/transmission_1 path can be > 1 deep + # Main zfs 3678031340 8156 3678023184 0% /mnt/Main + if (!$dev_base && ($row[0] =~ /^([^\/]+\/)(.+)/ || + ($row[0] =~ /^[^\/]+$/ && $row[1] =~ /^(btrfs|hammer[2-9]?|zfs)$/)) || + ($windows{'wsl'} && $row[0] eq 'drivers')){ + $dev_base = $row[0]; + $dev_type = 'logical'; + } + # this handles yet another fredforfaen special case where a mounted drive + # has the search string in its name, includes / (| + if ($row[-1] =~ m%^/(|boot|boot/efi|home|opt|tmp|usr|usr/home|var|var/log|var/tmp)$% || + ($b_android && $row[-1] =~ /^\/(cache|data|firmware|system)$/)){ + $b_load = 1; + # note, older df in bsd do not have file system column + $type = 'main'; + } + # $cols in case where mount point has space in name, we only care about the first part + elsif ($row[$cols] !~ m%^\/(|boot|boot/efi|home|opt|tmp|usr|usr/home|var|var/log|var/tmp)$% && + $row[$cols] !~ /^filesystem/ && + !($b_android && $row[$cols] =~ /^\/(cache|data|firmware|system)$/)){ + $b_load = 1; + $type = 'secondary'; + } + if ($b_load){ + if (!$bsd_type){ + if ($b_fs){ + $fs = ($part->{'fs'}) ? $part->{'fs'} : $row[1]; + } + else { + $fs = get_mounts_fs($row[0],\@mount); + } + if ($show{'label'}){ + if ($part->{'label'}){ + $label = $part->{'label'}; + } + elsif (@labels){ + $label = get_label($row[0]); + } + } + if ($show{'uuid'}){ + if ($part->{'uuid'}){ + $uuid = $part->{'uuid'}; + } + elsif (@uuids){ + $uuid = get_uuid($row[0]); + } + } + } + else { + $fs = ($b_fs) ? $row[1]: get_mounts_fs($row[0],\@mount); + } + # assuming that all null/nullfs are parts of a logical fs + $b_logical = 1 if $fs && $fs =~ /^(btrfs|hammer|null|zfs)/; + $id = join(' ', @row[$cols .. $#row]); + $size = $row[$cols - $back_size]; + if ($b_admin && -e "/sys/block/"){ + @working = admin_data($blockdev,$dev_base,$size); + $raw_size = $working[0]; + $size_available = $working[1]; + $block_size = $working[2]; + } + if (!$dev_type){ + # C:/cygwin64, D: + if ($windows{'cygwin'} && $row[0] =~ /^[A-Z]+:/){ + $dev_type = 'windows'; + $dev_base = $row[0] if !$dev_base; + # looks weird if D:, yes, I know, windows uses \, but cygwin doesn't + $dev_base .= '/' if $dev_base =~ /:$/; + } + elsif ($windows{'wsl'} && $row[0] =~ /^(drvfs)/){ + $dev_type = 'windows'; + if ($id =~ m|^/mnt/([a-z])$|){ + $dev_base = uc($1) . ':'; + } + $dev_base = $row[0] if !$dev_base; + } + # need data set, this could maybe be converted to use + # dev-mapped and abspath but not without testing + elsif ($dev_base =~ /^map:\/(.*)/){ + $dev_type = 'mapped'; + $dev_base = $1; + } + # note: possible: sshfs path: beta:data/; remote: fuse.rclone + elsif ($dev_base =~ /^\/\/|:\// || ($fs && $fs =~ /($remote_fs)/i)){ + $dev_type = 'remote'; + $dev_base = $row[0] if !$dev_base; # only trips in fs test case + } + # a slice bsd system, zfs can't be detected this easily + elsif ($b_logical && $fs && $fs =~ /^null(fs)?$/){ + $dev_type = 'logical'; + $dev_base = $row[0] if !$dev_base; + } + elsif (!$dev_base){ + if ($fs && $fs =~ /^(fuse[\._-]?)?($fuse_fs)(fs)?/i){ + $dev_base = $2; + $dev_type = 'fuse'; + } + # Check dm-crypt, that may be real partition type, but no data. + # We've hit something inxi doesn't know about, or error has occured + else { + $dev_type = 'source'; + $dev_base = main::message('unknown-dev'); + } + } + else { + $dev_type = 'dev'; + } + } + if ($bsd_type && $dev_type eq 'dev' && $row[0] && + ($b_admin || $show{'label'} || $show{'uuid'})){ + my $temp = DiskDataBSD::get($row[0]); + $block_size = $temp->{'logical-block-size'}; + $label = $temp->{'label'}; + $uuid = $temp->{'uuid'}; + } + $used = $row[$cols - $back_used]; + $percent_used = sprintf("%.1f", ($used/$size)*100) if ($size && main::is_numeric($size)); + push(@partitions,{ + 'block-size' => $block_size, + 'dev-base' => $dev_base, + 'dev-mapped' => $dev_mapped, + 'dev-type' => $dev_type, + 'fs' => $fs, + 'id' => $id, + 'label' => $label, + 'maj-min' => $maj_min, + 'percent-used' => $percent_used, + 'raw-available' => $size_available, + 'raw-size' => $raw_size, + 'size' => $size, + 'type' => $type, + 'used' => $used, + 'uuid' => $uuid, + }); + } + } + swap_data() if !$loaded{'set-swap'}; + push(@partitions,@swaps); + print Data::Dumper::Dumper \@partitions if $dbg[16]; + if (!$bsd_type && @lsblk){ + check_partition_data();# updates @partitions + } + main::log_data('dump','@partitions',\@partitions) if $b_log; + print Data::Dumper::Dumper \@partitions if $dbg[16]; + eval $end if $b_log; +} + +sub swap_data { + eval $start if $b_log; + $loaded{'set-swap'} = 1; + my (@data,@working); + my ($block_size,$cache_pressure,$dev_base,$dev_mapped,$dev_type,$label, + $maj_min,$mount,$path,$pattern1,$pattern2,$percent_used,$priority, + $size,$swap_type,$swappiness,$used,$uuid,$zram_comp,$zram_mcs, + $zswap_enabled,$zram_comp_avail,$zswap_comp,$zswap_mpp); + my ($s,$j,$size_id,$used_id) = (1,0,2,3); + if (!$bsd_type){ + # faster, avoid subshell, same as swapon -s + if (-r '/proc/swaps'){ + @working = main::reader("/proc/swaps"); + } + elsif ($path = main::check_program('swapon')){ + # note: while -s is deprecated, --show --bytes is not supported + # on older systems + @working = main::grabber("$path -s 2>/dev/null"); + } + if ($b_admin){ + swap_advanced_data(\$swappiness,\$cache_pressure,\$zswap_enabled, + \$zswap_comp,\$zswap_mpp); + } + if (($show{'label'} || $show{'uuid'}) && !$loaded{'label-uuid'}){ + set_label_uuid(); + } + $pattern1 = 'partition|file|ram'; + $pattern2 = '[^\s].*[^\s]'; + } + else { + if ($path = main::check_program('swapctl')){ + # output in in KB blocks + @working = main::grabber("$path -l -k 2>/dev/null"); + } + ($size_id,$used_id) = (1,2); + $pattern1 = '[0-9]+'; + $pattern2 = '[^\s]+'; + } + # now add the swap partition data, don't want to show swap files, just partitions, + # though this can include /dev/ramzswap0. Note: you can also use /proc/swaps for this + # data, it's the same exact output as swapon -s + foreach (@working){ + #next if ! /^\/dev/ || /^\/dev\/(ramzwap|zram)/; + next if /^(Device|Filename|no swap)/; + ($block_size,$dev_base,$dev_mapped,$dev_type,$label,$maj_min,$mount, + $swap_type,$uuid) = ('','','','','','','','partition',''); + ($priority,$zram_comp_avail,$zram_comp,$zram_mcs) = (); + @data = split(/\s+/, $_); + # /dev/zramX; ramzswapX == compcache, legacy version of zram. + # /run/initramfs/dev/zram0; /dev/ramzswap0 + if (/^\/(dev|run).*?\/((compcache|ramzwap|zram)\d+)/i){ + $dev_base = $2; + $swap_type = 'zram'; + $dev_type = 'dev'; + if ($b_admin){ + zram_data($dev_base,\$zram_comp,\$zram_comp_avail,\$zram_mcs); + } + } + elsif ($data[1] && $data[1] eq 'ram'){ + $swap_type = 'ram'; + } + elsif (m|^/dev|){ + $swap_type = 'partition'; + $dev_base = $data[0]; + $dev_base =~ s|^/dev/||; + if (!$bsd_type){ + if ($dev_base =~ /^dm-/ && %dmmapper){ + $dev_mapped = $dmmapper{$dev_base}; + } + if ($show{'label'} && @labels){ + $label = get_label($data[0]); + } + if ($show{'uuid'} && @uuids){ + $uuid = get_uuid($data[0]); + } + } + else { + if ($show{'label'} || $show{'uuid'}){ + my $temp = DiskDataBSD::get($data[0]); + $block_size = $temp->{'logical-block-size'}; + $label = $temp->{'label'}; + $uuid = $temp->{'uuid'}; + } + } + $dev_type = 'dev'; + $maj_min = get_maj_min($dev_base) if @proc_partitions; + } + elsif ($data[1] && $data[1] eq 'file' || m|^/|){ + $swap_type = 'file'; + } + $priority = $data[-1] if !$bsd_type; + # swpaon -s: /dev/sdb1 partition 16383996 109608 -2 + # swapctl -l -k: /dev/label/swap0.eli 524284 154092 + # users could have space in swapfile name + if (/^($pattern2)\s+($pattern1)\s+/){ + $mount = main::trimmer($1); + } + $size = $data[$size_id]; + $used = $data[$used_id]; + $percent_used = sprintf("%.1f", ($used/$size)*100); + push(@swaps, { + 'block-size' => $block_size, + 'cache-pressure' => $cache_pressure, + 'dev-base' => $dev_base, + 'dev-mapped' => $dev_mapped, + 'dev-type' => $dev_type, + 'fs' => 'swap', + 'id' => "swap-$s", + 'label' => $label, + 'maj-min' => $maj_min, + 'mount' => $mount, + 'percent-used' => $percent_used, + 'priority' => $priority, + 'size' => $size, + 'swappiness' => $swappiness, + 'type' => 'main', + 'swap-type' => $swap_type, + 'used' => $used, + 'uuid' => $uuid, + 'zram-comp' => $zram_comp, + 'zram-comp-avail' => $zram_comp_avail, + 'zram-max-comp-streams' => $zram_mcs, + 'zswap-enabled' => $zswap_enabled, + 'zswap-compressor' => $zswap_comp, + 'zswap-max-pool-percent' => $zswap_mpp, + }); + $s++; + } + main::log_data('dump','@swaps',\@swaps) if $b_log; + print Data::Dumper::Dumper \@swaps if $dbg[15];; + eval $end if $b_log; +} + +# Alll by ref: 0: $swappiness; 1: $cache_pressure; 2: $zswap_enabled; +# 3: $zswap_comp; 4: $zswap_mpp +sub swap_advanced_data { + eval $start if $b_log; + if (-r '/proc/sys/vm/swappiness'){ + ${$_[0]} = main::reader('/proc/sys/vm/swappiness','',0); + if (defined ${$_[0]}){ + ${$_[0]} .= (${$_[0]} == 60) ? ' (default)' : ' (default 60)' ; + } + } + if (-r '/proc/sys/vm/vfs_cache_pressure'){ + ${$_[1]} = main::reader('/proc/sys/vm/vfs_cache_pressure','',0); + if (defined ${$_[1]}){ + ${$_[1]} .= (${$_[1]}== 100) ? ' (default)' : ' (default 100)' ; + } + } + if (-r '/sys/module/zswap/parameters/enabled'){ + ${$_[2]} = main::reader('/sys/module/zswap/parameters/enabled','',0); + if (${$_[2]} =~ /^(Y|yes|true|1)$/){ + ${$_[2]} = 'yes'; + } + elsif (${$_[2]} =~ /^(N|no|false|0)$/){ + ${$_[2]} = 'no'; + } + else { + ${$_[2]} = 'unset'; + } + } + if (-r '/sys/module/zswap/parameters/compressor'){ + ${$_[3]} = main::reader('/sys/module/zswap/parameters/compressor','',0); + } + if (-r '/sys/module/zswap/parameters/max_pool_percent'){ + ${$_[4]} = main::reader('/sys/module/zswap/parameters/max_pool_percent','',0); + } + eval $end if $b_log; +} + +# 0: device id [zram0]; by ref: 1: $zram_comp; 2: $zram_comp_avail; 3: $zram_mcs; +sub zram_data { + if (-r "/sys/block/$_[0]/comp_algorithm"){ + ${$_[2]} = main::reader("/sys/block/$_[0]/comp_algorithm",'',0); + # current is in [..] in list + if (${$_[2]} =~ /\[(\S+)\]/){ + ${$_[1]} = $1; + # dump the active one, and leave the available + ${$_[2]} =~ s/\[${$_[1]}\]//; + ${$_[2]} =~ s/^\s+|\s+$//g; + ${$_[2]} =~ s/\s+/,/g; + } + } + if (-r "/sys/block/$_[0]/max_comp_streams"){ + ${$_[3]} = main::reader("/sys/block/$_[0]/max_comp_streams",'',0); + } +} + +# Handle cases of hidden file systems +sub check_partition_data { + eval $start if $b_log; + my ($b_found,$dev_mapped,$temp); + my $filters = get_filters('partition'); + foreach my $row (@lsblk){ + $b_found = 0; + $dev_mapped = ''; + if (!$row->{'name'} || !$row->{'mount'} || !$row->{'type'} || + ($row->{'fs'} && $row->{'fs'} =~ /^$filters$/) || + ($row->{'type'} =~ /^(disk|loop|rom)$/)){ + next; + } + # unmap so we can match name to dev-base + if (%mapper && $mapper{$row->{'name'}}){ + $dev_mapped = $row->{'name'}; + $row->{'name'} = $mapper{$row->{'name'}}; + } + # print "$row->{'name'} $row->{'mount'}\n"; + foreach my $row2 (@partitions){ + # print "1: n:$row->{'name'} m:$row->{'mount'} db:$row2->{'dev-base'} id:$row2->{'id'}\n"; + next if !$row2->{'id'}; + # note: for swap mount point is [SWAP] in @lsblk, but swap-x in @partitions + if ($row->{'mount'} eq $row2->{'id'} || $row->{'name'} eq $row2->{'dev-base'}){ + $b_found = 1; + last; + } + # print "m:$row->{'mount'} id:$row2->{'id'}\n"; + } + if (!$b_found){ + # print "found: n:$row->{'name'} m:$row->{'mount'}\n"; + $temp = { + 'block-logical' => $row->{'block-logical'}, + 'dev-base' => $row->{'name'}, + 'dev-mapped' => $dev_mapped, + 'fs' => $row->{'fs'}, + 'id' => $row->{'mount'}, + 'hidden' => 1, + 'label' => $row->{'label'}, + 'maj-min' => $row->{'maj-min'}, + 'percent-used' => 0, + 'raw-size' => $row->{'size'}, + 'size' => 0, + 'type' => 'secondary', + 'used' => 0, + 'uuid' => $row->{'uuid'}, + }; + push(@partitions,$temp); + main::log_data('dump','lsblk check: @temp',$temp) if $b_log; + } + } + eval $end if $b_log; +} + +# fs-exclude: Excludes fs size from disk used total; +# fs-skip: do not display label/uuid fields from partition/unmounted/swap. +# partition: do not use this partition in -p output. +# args: 0: [fs-exclude|fs-skip|partition] +sub get_filters { + set_filters() if !$fs_exclude; + if ($_[0] eq 'fs-exclude'){ + return $fs_exclude; + } + elsif ($_[0] eq 'fs-skip'){ + return $fs_skip; + } + elsif ($_[0] eq 'partition'){ + return $part_filter; + } +} + +# See docs/inxi-partitions.txt FILE SYSTEMS for specific fs info. +# The filter string must match /^[regex]$/ exactly. +sub set_filters { + # Notes: appimage/flatpak mount?; astreamfs reads remote http urls; + # avfs == fuse; cgmfs,vramfs in ram, like devfs, sysfs; gfs = googlefs; + # hdfs == hadoop; ifs == integrated fs; pvfs == orangefs; smb == cifs; + # null == hammer fs slice; kfs/kosmosfs == CloudStore; + # snap mounts with squashfs; swap is set in swap_data(); vdfs != vdfuse; + # vramfs == like zram except gpu ram; + # Some can be fuse mounts: fuse.sshfs. + # Distributed/Remote: 9p, (open-)?afs, alluxio, astreamfs, beegfs, + # cephfs, cfs, chironfs, cifs, cloudstore, dfs, davfs, dce, + # gdrivefs, gfarm, gfs\d{0,2}, gitfs, glusterfs, gmailfs, gpfs, + # hdfs, httpdirfs, hubicfuse, ipfs, juice, k(osmos)?fs, .*lafs, lizardfs, + # lustre, magma, mapr, moosefs, nfs[34], objective, ocfs\d{0,2}, onefs, + # orangefs, panfs, pnfs, pvfs\d{0,2}, rclone, restic, rozofs, s3fs, scality, + # sfs, sheepdogfs, spfs, sshfs, smbfs, v9fs, vdfs, vmfs, wekafs, xtreemfs + # Stackable/Union: aufs, e?cryptfs, encfs, erofs, gocryptfs, ifs, lofs, + # mergerfs, mhddfs, overla(id|y)(fs)?, squashfs, unionfs; + # ISO/Archive: archive(mount)?, atlas, avfs. borg, erofs, fuse-archive, + # fuseiso, gzipfs, iso9660, lofs, vdfuse, wimmountfs, xbfuse + # FUSE: adbfs, apfs-fuse, atomfs, gvfs, gvfs-mtp, ifuse, jmtpfs, mtpfs, ptpfs, + # puzzlefs, simple-mtpfs, vramfs, xmlfs + # System fs: cgmfs, configfs, debugfs, devfs, devtmpfs, efivarfs, fdescfs, + # hugetlbfs, kernfs, linprocfs, linsysfs, lxcfs, procfs, ptyfs, run, + # securityfs, shm, swap, sys, sysfs, tmpfs, tracefs, type, udev, vartmp + # System dir: /dev, /dev/loop[0-9]+, /run(/.*)?, /sys/.* + + ## These are global, all filters use these. ISO, encrypted/stacked + my @all = qw%au av e?crypt enc ero gocrypt i (fuse-?)?iso iso9660 lo merger + mhdd overla(id|y) splitview(-?fuse)? squash union xbfuse%; + ## These are fuse/archive/distributed/remote/clustered mostly + my @exclude = (@all,qw%9p (open-?)?a adb archive(mount)? astream atlas atom + beeg borg c ceph chiron ci cloudstore curlftp d dav dce + g gdrive gfarm git gluster gmail gocrypt google-drive-ocaml gp gphoto gv gzip + hd httpd hubic ip juice k(osmos)? .*la lizard lustre magma mapr moose .*mtp + null p?n objective oc one orange pan .*ptp puzzle pv rclone restic rozo + s s3 scality sheepdog sp ssh smb v9 vd vm vram weka wim(mount)? xb xml + xtreem%); + # Various RAM based system FS + my @partition = (@all,qw%cgroup.* cgm config debug dev devtmp efivar fdesc + hugetlb kern linproc linsys lxc none proc pty run security shm swap sys + tmp trace type udev vartmp%); + my $start = '(fuse(blk)?[\._-]?)?('; + my $end = ')([\._-]?fuse)?(fs)?\d{0,2}'; + $fs_exclude = $start . join('|',@exclude) . $end; + $fs_skip = $start . join('|',@exclude,'f') . $end; # apfs?; BSD ffs has no u/l + $part_filter = '((' . join('|',@partition) . ')(fs)?|'; + $part_filter .= '\/dev|\/dev\/loop[0-9]+|\/run(\/.*)?|\/sys\/.*)'; + # print "$part_filter\n"; +} + +sub get_mounts_fs { + eval $start if $b_log; + my ($item,$mount) = @_; + $item =~ s/map:\/(\S+)/map $1/ if $bsd_type && $bsd_type eq 'darwin'; + return 'N/A' if ! @$mount; + my ($fs) = (''); + # linux: /dev/sdb6 on /var/www/m type ext4 (rw,relatime,data=ordered) + # /dev/sda3 on /root.dev/ugw type ext3 (rw,relatime,errors=continue,user_xattr,acl,barrier=1,data=journal) + # bsd: /dev/ada0s1a on / (ufs, local, soft-updates) + # bsd 2: /dev/wd0g on /home type ffs (local, nodev, nosuid) + foreach (@$mount){ + if ($_ =~ /^$item\s+on.*?\s+type\s+([\S]+)\s+\([^\)]+\)/){ + $fs = $1; + last; + } + elsif ($_ =~ /^$item\s+on.*?\s+\(([^,\s\)]+?)[,\s]*.*\)/){ + $fs = $1; + last; + } + } + eval $end if $b_log; + main::log_data('data',"fs: $fs") if $b_log; + return $fs; +} + +sub set_label_uuid { + eval $start if $b_log; + $loaded{'label-uuid'} = 1; + if ($show{'unmounted'} || $show{'label'} || $show{'swap'} || $show{'uuid'}){ + if (-d '/dev/disk/by-label'){ + @labels = main::globber('/dev/disk/by-label/*'); + } + if (-d '/dev/disk/by-uuid'){ + @uuids = main::globber('/dev/disk/by-uuid/*'); + } + main::log_data('dump', '@labels', \@labels) if $b_log; + main::log_data('dump', '@uuids', \@uuids) if $b_log; + } + eval $end if $b_log; +} + +# args: 0: blockdev full path (part only); 1: block id; 2: size (part only) +sub admin_data { + eval $start if $b_log; + my ($blockdev,$id,$size) = @_; + # 0: calc block 1: available percent 2: disk physical block size/partition block size; + my @sizes = (0,0,0); + my ($block_size,$percent,$size_raw) = (0,0,0); + foreach my $row (@proc_partitions){ + if ($row->[-1] eq $id){ + $size_raw = $row->[2]; + last; + } + } + # get the fs block size + $block_size = (main::grabber("$blockdev --getbsz /dev/$id 2>/dev/null"))[0] if $blockdev; + if (!$size_raw){ + $size_raw = 'N/A'; + } + else { + $percent = sprintf("%.2f", ($size/$size_raw) * 100) if $size && $size_raw; + } + # print "$id size: $size %: $percent p-b: $block_size raw: $size_raw\n"; + @sizes = ($size_raw,$percent,$block_size); + main::log_data('dump','@sizes',\@sizes) if $b_log; + eval $end if $b_log; + return @sizes; +} + +sub get_maj_min { + eval $start if $b_log; + my ($id) = @_; + my ($maj_min,@working); + foreach my $row (@proc_partitions){ + if ($id eq $row->[-1]){ + $maj_min = $row->[0] . ':' . $row->[1]; + last; + } + } + eval $end if $b_log; + return $maj_min; +} + +sub get_label { + eval $start if $b_log; + my ($item) = @_; + my $label = ''; + foreach (@labels){ + if ($item eq Cwd::abs_path($_)){ + $label = $_; + $label =~ s/\/dev\/disk\/by-label\///; + $label =~ s/\\x20/ /g; + $label =~ s%\\x2f%/%g; + last; + } + } + $label ||= 'N/A'; + eval $end if $b_log; + return $label; +} + +sub get_root { + eval $start if $b_log; + my ($path) = ('/dev/root'); + # note: the path may be a symbolic link to by-label/by-uuid but not + # sure how far in abs_path resolves the path. + my $temp = Cwd::abs_path($path); + $path = $temp if $temp; + # note: it's a kernel config option to have /dev/root be a sym link + # or not, if it isn't, path will remain /dev/root, if so, then try mount + if ($path eq '/dev/root' && (my $program = main::check_program('mount'))){ + my @data = main::grabber("$program 2>/dev/null"); + # /dev/sda2 on / type ext4 (rw,noatime,data=ordered) + foreach (@data){ + if (/^([\S]+)\son\s\/\s/){ + $path = $1; + # note: we'll be handing off any uuid/label paths to the next + # check tools after get_root() above, so don't trim those. + $path =~ s/.*\/// if $path !~ /by-uuid|by-label/; + last; + } + } + } + eval $end if $b_log; + return $path; +} + +sub get_uuid { + eval $start if $b_log; + my ($item) = @_; + my $uuid = ''; + foreach (@uuids){ + if ($item eq Cwd::abs_path($_)){ + $uuid = $_; + $uuid =~ s/\/dev\/disk\/by-uuid\///; + last; + } + } + $uuid ||= 'N/A'; + eval $end if $b_log; + return $uuid; +} +} + +## ProcessItem +{ +package ProcessItem; + +sub get { + eval $start if $b_log; + my $num = 0; + my $rows = []; + if (@ps_aux){ + if ($show{'ps-cpu'}){ + cpu_processes($rows); + } + if ($show{'ps-mem'}){ + mem_processes($rows); + } + } + else { + my $key = 'Message'; + push(@$rows, { + main::key($num++,0,1,$key) => main::message('ps-data-null','') + }); + } + eval $end if $b_log; + return $rows; +} + +sub cpu_processes { + eval $start if $b_log; + my $rows = $_[0]; + my ($j,$num,$cpu,$cpu_mem,$mem,$pid) = (0,0,'','','',''); + my ($pid_col,@ps_rows); + my $count = ($b_irc)? 5: $ps_count; + if ($ps_cols >= 10){ + @ps_rows = sort { + my @a = split(/\s+/, $a); + my @b = split(/\s+/, $b); + $b[2] <=> $a[2] } @ps_aux; + $pid_col = 1; + } + else { + @ps_rows = @ps_aux; + $pid_col = 0 if $ps_cols == 2; + } + # if there's a count limit, for irc, etc, only use that much of the data + @ps_rows = splice(@ps_rows,0,$count); + $j = scalar @ps_rows; + # $cpu_mem = ' - Memory: MiB / % used' if $extra > 0; + my $throttled = throttled($ps_count,$count,$j); + # my $header = "CPU % used - Command - pid$cpu_mem - top"; + # my $header = "Top $count by CPU"; + push(@$rows,{ + main::key($num++,1,1,'CPU top') => "$count$throttled" . ' of ' . scalar @ps_aux + }); + my $i = 1; + foreach (@ps_rows){ + $num = 1; + $j = scalar @$rows; + my @row = split(/\s+/, $_); + my $command = process_starter(scalar @row, $row[$ps_cols],$row[$ps_cols + 1]); + $cpu = ($ps_cols >= 10) ? $row[2] . '%': 'N/A'; + push(@$rows,{ + main::key($num++,1,2,$i++) => '', + main::key($num++,0,3,'cpu') => $cpu, + main::key($num++,1,3,'command') => $command->[0], + }); + if ($command->[1]){ + $rows->[$j]{main::key($num++,0,4,'started-by')} = $command->[1]; + } + $pid = (defined $pid_col)? $row[$pid_col] : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'pid')} = $pid; + if ($extra > 0 && $ps_cols >= 10){ + my $decimals = ($row[5]/1024 > 10) ? 1 : 2; + $mem = (defined $row[5]) ? sprintf("%.${decimals}f", $row[5]/1024) . ' MiB' : 'N/A'; + $mem .= ' (' . $row[3] . '%)'; + $rows->[$j]{main::key($num++,0,3,'mem')} = $mem; + } + # print Data::Dumper::Dumper \@processes, "i: $i; j: $j "; + } + eval $end if $b_log; +} + +sub mem_processes { + eval $start if $b_log; + my $rows = $_[0]; + my ($j,$num,$cpu,$cpu_mem,$mem,$pid) = (0,0,'','','',''); + my (@data,$pid_col,$memory,@ps_rows); + my $count = ($b_irc)? 5: $ps_count; + if ($ps_cols >= 10){ + @ps_rows = sort { + my @a = split(/\s+/, $a); + my @b = split(/\s+/, $b); + $b[5] <=> $a[5] } @ps_aux; # 5 + #$a[1] <=> $b[1] } @ps_aux; # 5 + $pid_col = 1; + } + else { + @ps_rows = @ps_aux; + $pid_col = 0 if $ps_cols == 2; + } + @ps_rows = splice(@ps_rows,0,$count); + # print Data::Dumper::Dumper \@rows; + if (!$loaded{'memory'}){ + my $row = {}; + main::MemoryData::row('process',$row,\$num,1); + push(@$rows,$row); + $num = 0; + } + $j = scalar @$rows; + my $throttled = throttled($ps_count,$count,$j); + #$cpu_mem = ' - CPU: % used' if $extra > 0; + # my $header = "Memory MiB/% used - Command - pid$cpu_mem - top"; + # my $header = "Top $count by Memory"; + push(@$rows, { + main::key($num++,1,1,'Memory top') => "$count$throttled" . ' of ' . scalar @ps_aux + }); + my $i = 1; + foreach (@ps_rows){ + $num = 1; + $j = scalar @$rows; + my @row = split(/\s+/, $_); + if ($ps_cols >= 10){ + my $decimals = ($row[5]/1024 > 10) ? 1 : 2; + $mem = (main::is_int($row[5])) ? sprintf("%.${decimals}f", $row[5]/1024) . ' MiB' : 'N/A'; + $mem .= " (" . $row[3] . "%)"; + } + else { + $mem = 'N/A'; + } + my $command = process_starter(scalar @row, $row[$ps_cols],$row[$ps_cols + 1]); + push(@$rows,{ + main::key($num++,1,2,$i++) => '', + main::key($num++,0,3,'mem') => $mem, + main::key($num++,1,3,'command') => $command->[0], + }); + if ($command->[1]){ + $rows->[$j]{main::key($num++,0,4,'started-by')} = $command->[1]; + } + $pid = (defined $pid_col)? $row[$pid_col] : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'pid')} = $pid; + if ($extra > 0 && $ps_cols >= 10){ + $cpu = $row[2] . '%'; + $rows->[$j]{main::key($num++,0,3,'cpu')} = $cpu; + } + # print Data::Dumper::Dumper \@processes, "i: $i; j: $j "; + } + eval $end if $b_log; +} + +sub process_starter { + my ($count, $row10, $row11) = @_; + my $return = []; + # note: [migration/0] would clear with a simple basename + if ($count > ($ps_cols + 1) && $row11 =~ /^\// && $row11 !~ /^\/(tmp|temp)/){ + $row11 =~ s/^\/.*\///; + $return->[0] = $row11; + $row10 =~ s/^\/.*\///; + $return->[1] = $row10; + } + else { + $row10 =~ s/^\/.*\///; + $return->[0] = $row10; + $return->[1] = ''; + } + return $return; +} + +sub throttled { + my ($ps_count,$count,$j) = @_; + my $throttled = ''; + if ($count > $j){ + $throttled = " ( $j processes)"; # space to avoid emoji in irc + } + elsif ($count < $ps_count){ + $throttled = " (throttled from $ps_count)"; + } + return $throttled; +} +} + +## RaidItem +{ +package RaidItem; + +sub get { + eval $start if $b_log; + my ($hardware_raid,$key1,$val1); + my $num = 0; + my $rows = []; + $hardware_raid = hw_data() if $use{'hardware-raid'} || $fake{'raid-hw'}; + raid_data() if !$loaded{'raid'}; + # print 'get btrfs: ', Data::Dumper::Dumper \@btrfs_raid; + # print 'get lvm: ', Data::Dumper::Dumper \@lvm_raid; + # print 'get md: ', Data::Dumper::Dumper \@md_raid; + # print 'get zfs: ', Data::Dumper::Dumper \@zfs_raid; + if (!@btrfs_raid && !@lvm_raid && !@md_raid && !@zfs_raid && !@soft_raid && + !$hardware_raid){ + if ($show{'raid-forced'}){ + $key1 = 'Message'; + $val1 = main::message('raid-data'); + } + } + else { + if ($hardware_raid){ + hw_output($rows,$hardware_raid); + } + if (@btrfs_raid){ + btrfs_output($rows); + } + if (@lvm_raid){ + lvm_output($rows); + } + if (@md_raid){ + md_output($rows); + } + if (@soft_raid){ + soft_output($rows); + } + if (@zfs_raid){ + zfs_output($rows); + } + } + if (!@$rows && $key1){ + @$rows = ({main::key($num++,0,1,$key1) => $val1,}); + } + eval $end if $b_log; + return $rows; +} + +sub hw_output { + eval $start if $b_log; + my ($rows,$hardware_raid) = @_; + my ($j,$num) = (0,0); + foreach my $row (@$hardware_raid){ + $num = 1; + my $device = ($row->{'device'}) ? $row->{'device'}: 'N/A'; + my $driver = ($row->{'driver'}) ? $row->{'driver'}: 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Hardware') => $device, + }); + $j = scalar @$rows - 1; + $rows->[$j]{main::key($num++,0,2,'vendor')} = $row->{'vendor'} if $row->{'vendor'}; + $rows->[$j]{main::key($num++,1,2,'driver')} = $driver; + if ($extra > 0){ + $row->{'driver-version'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'v')} = $row->{'driver-version'}; + if ($extra > 2){ + my $port= ($row->{'port'}) ? $row->{'port'}: 'N/A' ; + $rows->[$j]{main::key($num++,0,2,'port')} = $port; + } + my $bus_id = (defined $row->{'bus-id'} && defined $row->{'sub-id'}) ? "$row->{'bus-id'}.$row->{'sub-id'}": 'N/A' ; + $rows->[$j]{main::key($num++,0,2,'bus-ID')} = $bus_id; + } + if ($extra > 1){ + my $chip_id = main::get_chip_id($row->{'vendor-id'},$row->{'chip-id'}); + $rows->[$j]{main::key($num++,0,2,'chip-ID')} = $chip_id; + } + if ($extra > 2){ + $row->{'rev'} = 'N/A' if !defined $row->{'rev'}; # could be 0 + $rows->[$j]{main::key($num++,0,2,'rev')} = $row->{'rev'}; + $rows->[$j]{main::key($num++,0,2,'class-ID')} = $row->{'class-id'} if $row->{'class-id'}; + } + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +sub btrfs_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@components,@good); + my ($size); + my ($j,$num) = (0,0); + foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @btrfs_raid){ + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,2,'Components')} = ''; + my $b_bump; + components_output('lvm','Online',$rows,\@good,\$j,\$num,\$b_bump); + components_output('lvm','Meta',$rows,\@components,\$j,\$num,\$b_bump); + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +sub lvm_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@components,@good,@components_meta); + my ($size); + my ($j,$num) = (0,0); + foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @lvm_raid){ + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'Device') => $row->{'id'}, + }); + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'}; + } + $rows->[$j]{main::key($num++,0,2,'type')} = $row->{'type'}; + $rows->[$j]{main::key($num++,0,2,'level')} = $row->{'level'}; + $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string'): 'N/A'; + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + if ($row->{'raid-sync'}){ + $rows->[$j]{main::key($num++,0,2,'sync')} = $row->{'raid-sync'}; + } + if ($extra > 0){ + $j = scalar @$rows; + $num = 1; + $rows->[$j]{main::key($num++,1,2,'Info')} = ''; + if (defined $row->{'stripes'}){ + $rows->[$j]{main::key($num++,0,3,'stripes')} = $row->{'stripes'}; + } + if (defined $row->{'raid-mismatches'} && ($extra > 1 || $row->{'raid-mismatches'} > 0)){ + $rows->[$j]{main::key($num++,0,3,'mismatches')} = $row->{'raid-mismatches'}; + } + if (defined $row->{'copy-percent'} && ($extra > 1 || $row->{'copy-percent'} < 100)){ + $rows->[$j]{main::key($num++,0,3,'copied')} = ($row->{'copy-percent'} + 0) . '%'; + } + if ($row->{'vg'}){ + $rows->[$j]{main::key($num++,1,3,'v-group')} = $row->{'vg'}; + } + $size = ($row->{'vg-size'}) ? main::get_size($row->{'vg-size'},'string') : 'N/A'; + $rows->[$j]{main::key($num++,0,4,'vg-size')} = $size; + $size = ($row->{'vg-free'}) ? main::get_size($row->{'vg-free'},'string') : 'N/A'; + $rows->[$j]{main::key($num++,0,4,'vg-free')} = $size; + } + @components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : (); + @good = (); + @components_meta = (); + foreach my $item (sort { $a->[0] cmp $b->[0]} @components){ + if ($item->[4] =~ /_rmeta/){ + push(@components_meta, $item); + } + else { + push(@good, $item); + } + } + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,2,'Components')} = ''; + my $b_bump; + components_output('lvm','Online',$rows,\@good,\$j,\$num,\$b_bump); + components_output('lvm','Meta',$rows,\@components_meta,\$j,\$num,\$b_bump); + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +sub md_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@components,@good,@failed,@inactive,@spare,@temp); + my ($blocks,$chunk,$level,$report,$size,$status); + my ($j,$num) = (0,0); + # print Data::Dumper::Dumper \@md_raid; + if ($extra > 2 && $md_raid[0]->{'supported-levels'}){ + push(@$rows, { + main::key($num++,0,1,'Supported mdraid levels') => $md_raid[0]->{'supported-levels'}, + }); + } + foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @md_raid){ + $j = scalar @$rows; + next if !%$row; + $num = 1; + $level = (defined $row->{'level'}) ? $row->{'level'} : 'linear'; + push(@$rows, { + main::key($num++,1,1,'Device') => $row->{'id'}, + }); + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'}; + } + $rows->[$j]{main::key($num++,0,2,'type')} = $row->{'type'}; + $rows->[$j]{main::key($num++,0,2,'level')} = $level; + $rows->[$j]{main::key($num++,0,2,'status')} = $row->{'status'}; + if ($row->{'details'}{'state'}){ + $rows->[$j]{main::key($num++,0,2,'state')} = $row->{'details'}{'state'}; + } + if ($row->{'size'}){ + $size = main::get_size($row->{'size'},'string'); + } + else { + $size = (!$b_root && !@lsblk) ? main::message('root-required'): 'N/A'; + } + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + $report = ($row->{'report'}) ? $row->{'report'}: ''; + $report .= " $row->{'u-data'}" if $report; + $report ||= 'N/A'; + if ($extra == 0){ + # print "here 0\n"; + $rows->[$j]{main::key($num++,0,2,'report')} = $report; + } + if ($extra > 0){ + $j = scalar @$rows; + $num = 1; + $rows->[$j]{main::key($num++,1,2,'Info')} = ''; + #$rows->[$j]{main::key($num++,0,3,'raid')} = $raid; + $rows->[$j]{main::key($num++,0,3,'report')} = $report; + $blocks = ($row->{'blocks'}) ? $row->{'blocks'} : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'blocks')} = $blocks; + $chunk = ($row->{'chunk-size'}) ? $row->{'chunk-size'} : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'chunk-size')} = $chunk; + if ($extra > 1){ + if ($row->{'bitmap'}){ + $rows->[$j]{main::key($num++,0,3,'bitmap')} = $row->{'bitmap'}; + } + if ($row->{'super-block'}){ + $rows->[$j]{main::key($num++,0,3,'super-blocks')} = $row->{'super-block'}; + } + if ($row->{'algorithm'}){ + $rows->[$j]{main::key($num++,0,3,'algorithm')} = $row->{'algorithm'}; + } + } + } + @components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : (); + @good = (); + @failed = (); + @inactive = (); + @spare = (); + # @spare = split(/\s+/, $row->{'unused'}) if $row->{'unused'}; + # print Data::Dumper::Dumper \@components; + foreach my $item (sort { $a->[1] <=> $b->[1]} @components){ + if (defined $item->[2] && $item->[2] =~ /^(F)$/){ + push(@failed,$item); + } + elsif (defined $item->[2] && $item->[2] =~ /(S)$/){ + push(@spare,$item); + } + elsif ($row->{'status'} && $row->{'status'} eq 'inactive'){ + push(@inactive,$item); + } + else { + push(@good,$item); + } + } + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,2,'Components')} = ''; + my $b_bump; + components_output('mdraid','Online',$rows,\@good,\$j,\$num,\$b_bump); + components_output('mdraid','Failed',$rows,\@failed,\$j,\$num,\$b_bump); + components_output('mdraid','Inactive',$rows,\@inactive,\$j,\$num,\$b_bump); + components_output('mdraid','Spare',$rows,\@spare,\$j,\$num,\$b_bump); + if ($row->{'recovery-percent'}){ + $j = scalar @$rows; + $num = 1; + my $percent = $row->{'recovery-percent'}; + if ($extra > 1 && $row->{'progress-bar'}){ + $percent .= " $row->{'progress-bar'}" + } + $rows->[$j]{main::key($num++,1,2,'Recovering')} = $percent; + my $finish = ($row->{'recovery-finish'})?$row->{'recovery-finish'} : 'N/A'; + $rows->[$j]{main::key($num++,0,3,'time-remaining')} = $finish; + if ($extra > 0){ + if ($row->{'sectors-recovered'}){ + $rows->[$j]{main::key($num++,0,3,'sectors')} = $row->{'sectors-recovered'}; + } + } + if ($extra > 1 && $row->{'recovery-speed'}){ + $rows->[$j]{main::key($num++,0,3,'speed')} = $row->{'recovery-speed'}; + } + } + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +sub soft_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@components,@good,@failed,@offline,@rebuild,@temp); + my ($size); + my ($j,$num) = (0,0); + if (@soft_raid && $alerts{'bioctl'}->{'action'} eq 'permissions'){ + push(@$rows,{ + main::key($num++,1,1,'Message') => main::message('root-item-incomplete','softraid'), + }); + } + # print Data::Dumper::Dumper \@soft_raid; + foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @soft_raid){ + $j = scalar @$rows; + next if !%$row; + $num = 1; + push(@$rows, { + main::key($num++,1,1,'Device') => $row->{'id'}, + }); + $row->{'level'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'type')} = $row->{'type'}; + $rows->[$j]{main::key($num++,0,2,'level')} = $row->{'level'}; + $rows->[$j]{main::key($num++,0,2,'status')} = $row->{'status'}; + if ($row->{'state'}){ + $rows->[$j]{main::key($num++,0,2,'state')} = $row->{'state'}; + } + if ($row->{'size'}){ + $size = main::get_size($row->{'size'},'string'); + } + $size ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + @components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : (); + @good = (); + @failed = (); + @offline = (); + @rebuild = (); + foreach my $item (sort { $a->[1] <=> $b->[1]} @components){ + if (defined $item->[2] && $item->[2] eq 'failed'){ + push(@failed,$item); + } + elsif (defined $item->[2] && $item->[2] eq 'offline'){ + push(@offline,$item); + } + elsif (defined $item->[2] && $item->[2] eq 'rebuild'){ + push(@rebuild,$item); + } + else { + push(@good,$item); + } + } + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,2,'Components')} = ''; + my $b_bump; + components_output('softraid','Online',$rows,\@good,\$j,\$num,\$b_bump); + components_output('softraid','Failed',$rows,\@failed,\$j,\$num,\$b_bump); + components_output('softraid','Rebuild',$rows,\@rebuild,\$j,\$num,\$b_bump); + components_output('softraid','Offline',$rows,\@offline,\$j,\$num,\$b_bump); + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +sub zfs_output { + eval $start if $b_log; + my $rows = $_[0]; + my (@arrays,@arrays_holder,@components,@good,@failed,@spare); + my ($allocated,$available,$level,$size,$status); + my ($b_row_1_sizes); + my ($j,$num) = (0,0); + # print Data::Dumper::Dumper \@zfs_raid; + foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @zfs_raid){ + $j = scalar @$rows; + $b_row_1_sizes = 0; + next if !%$row; + $num = 1; + push(@$rows, { + main::key($num++,1,1,'Device') => $row->{'id'}, + main::key($num++,0,2,'type') => $row->{'type'}, + main::key($num++,0,2,'status') => $row->{'status'}, + }); + $size = ($row->{'raw-size'}) ? main::get_size($row->{'raw-size'},'string') : ''; + $available = main::get_size($row->{'raw-free'},'string',''); # could be zero free + if ($extra > 2){ + $allocated = ($row->{'raw-allocated'}) ? main::get_size($row->{'raw-allocated'},'string') : ''; + } + @arrays = @{$row->{'arrays'}}; + @arrays = grep {defined $_} @arrays; + @arrays_holder = @arrays; + my $count = scalar @arrays; + if (!defined $arrays[0]->{'level'}){ + $level = 'linear'; + $rows->[$j]{main::key($num++,0,2,'level')} = $level; + } + elsif ($count < 2 && $arrays[0]->{'level'}){ + $rows->[$j]{main::key($num++,0,2,'level')} = $arrays[0]->{'level'}; + } + if ($size || $available || $allocated){ + $rows->[$j]{main::key($num++,1,2,'raw')} = ''; + if ($size){ + # print "here 0\n"; + $rows->[$j]{main::key($num++,0,3,'size')} = $size; + $size = ''; + $b_row_1_sizes = 1; + } + if ($available){ + $rows->[$j]{main::key($num++,0,3,'free')} = $available; + $available = ''; + $b_row_1_sizes = 1; + } + if ($allocated){ + $rows->[$j]{main::key($num++,0,3,'allocated')} = $allocated; + $allocated = ''; + } + } + if ($row->{'zfs-size'}){ + $rows->[$j]{main::key($num++,1,2,'zfs-fs')} = ''; + $rows->[$j]{main::key($num++,0,3,'size')} = main::get_size($row->{'zfs-size'},'string'); + $rows->[$j]{main::key($num++,0,3,'free')} = main::get_size($row->{'zfs-free'},'string'); + } + foreach my $row2 (@arrays){ + if ($count > 1){ + $j = scalar @$rows; + $num = 1; + $size = ($row2->{'raw-size'}) ? main::get_size($row2->{'raw-size'},'string') : 'N/A'; + $available = ($row2->{'raw-free'}) ? main::get_size($row2->{'raw-free'},'string') : 'N/A'; + $level = (defined $row2->{'level'}) ? $row2->{'level'}: 'linear'; + $status = ($row2->{'status'}) ? $row2->{'status'}: 'N/A'; + push(@$rows, { + main::key($num++,1,2,'Array') => $level, + main::key($num++,0,3,'status') => $status, + main::key($num++,1,3,'raw') => '', + main::key($num++,0,4,'size') => $size, + main::key($num++,0,4,'free') => $available, + }); + } + # items like cache may have one component, with a size on that component + elsif (!$b_row_1_sizes){ + # print "here $count\n"; + $size = ($row2->{'raw-size'}) ? main::get_size($row2->{'raw-size'},'string') : 'N/A'; + $available = ($row2->{'raw-free'}) ? main::get_size($row2->{'raw-free'},'string') : 'N/A'; + $rows->[$j]{main::key($num++,1,2,'raw')} = ''; + $rows->[$j]{main::key($num++,0,3,'size')} = $size; + $rows->[$j]{main::key($num++,0,3,'free')} = $available; + if ($extra > 2){ + $allocated = ($row2->{'raw-allocated'}) ? main::get_size($row2->{'raw-allocated'},'string') : ''; + if ($allocated){ + $rows->[$j]{main::key($num++,0,3,'allocated')} = $allocated; + } + } + } + @components = (ref $row2->{'components'} eq 'ARRAY') ? @{$row2->{'components'}} : (); + @failed = (); + @spare = (); + @good = (); + # @spare = split(/\s+/, $row->{'unused'}) if $row->{'unused'}; + foreach my $item (sort { $a->[0] cmp $b->[0]} @components){ + if (defined $item->[3] && $item->[3] =~ /^(DEGRADED|FAULTED|UNAVAIL)$/){ + push(@failed, $item); + } + elsif (defined $item->[3] && $item->[3] =~ /(AVAIL|OFFLINE|REMOVED)$/){ + push(@spare, $item); + } + # note: spares in use show: INUSE but technically it's still a spare, + # but since it's in use, consider it online. + else { + push(@good, $item); + } + } + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,3,'Components')} = ''; + my $b_bump; + components_output('zfs','Online',$rows,\@good,\$j,\$num,\$b_bump); + components_output('zfs','Failed',$rows,\@failed,\$j,\$num,\$b_bump); + components_output('zfs','Available',$rows,\@spare,\$j,\$num,\$b_bump); + } + } + eval $end if $b_log; + # print Data::Dumper::Dumper $rows; +} + +# Most key stuff passed by ref, and is changed on the fly +sub components_output { + eval $start if $b_log; + my ($type,$item,$rows,$array,$j,$num,$b_bump) = @_; + return if !@$array && $item ne 'Online'; + my ($extra1,$extra2,$f1,$f2,$f3,$f4,$f5,$k,$k1,$key1,$l1,$l2,$l3); + if ($type eq 'btrfs'){ + + } + elsif ($type eq 'lvm'){ + ($f1,$f2,$f3,$f4,$f5,$l1,$l2,$l3) = (1,2,3,4,5,3,4,5); + $k = 1; + $extra1 = 'mapped'; + $extra2 = 'dev'; + } + elsif ($type eq 'mdraid'){ + ($f1,$f2,$f3,$f4,$k1,$l1,$l2,$l3) = (3,4,5,6,1,3,4,5); + $extra1 = 'mapped'; + $k = 1 if $item eq 'Inactive'; + } + elsif ($type eq 'softraid'){ + ($f1,$f2,$f3,$f4,$k1,$l1,$l2,$l3) = (1,10,10,3,5,3,4,5); + $extra1 = 'device'; + $k = 1; + } + elsif ($type eq 'zfs'){ + ($f1,$f2,$f3,$l1,$l2,$l3) = (1,2,3,4,5,6); + $k = 1; + } + # print "item: $item\n"; + $$j++ if $$b_bump; + $$b_bump = 0; + my $good = ($item eq 'Online' && !@$array) ? 'N/A' : ''; + $rows->[$$j]{main::key($$num++,1,$l1,$item)} = $good; + #$$j++ if $b_admin; + # print Data::Dumper::Dumper $array; + foreach my $device (@$array){ + next if ref $device ne 'ARRAY'; + # if ($b_admin && $device->[$f1] && $device->[$f2]){ + if ($b_admin){ + $$j++; + $$b_bump = 1; + $$num = 1; + } + $key1 = (defined $k1 && defined $device->[$k1]) ? $device->[$k1] : $k++; + $rows->[$$j]{main::key($$num++,1,$l2,$key1)} = $device->[0]; + if ($b_admin && $device->[$f2]){ + $rows->[$$j]{main::key($$num++,0,$l3,'maj-min')} = $device->[$f2]; + } + if ($b_admin && $device->[$f1]){ + my $size = main::get_size($device->[$f1],'string'); + $rows->[$$j]{main::key($$num++,0,$l3,'size')} = $size; + } + if ($b_admin && $device->[$f3]){ + $rows->[$$j]{main::key($$num++,0,$l3,'state')} = $device->[$f3]; + } + if ($b_admin && $extra1 && $device->[$f4]){ + $rows->[$$j]{main::key($$num++,0,$l3,$extra1)} = $device->[$f4]; + } + if ($b_admin && $extra2 && $device->[$f5]){ + $rows->[$$j]{main::key($$num++,0,$l3,$extra2)} = $device->[$f5]; + } + } + eval $end if $b_log; +} + +sub raid_data { + eval $start if $b_log; + LsblkData::set() if !$bsd_type && !$loaded{'lsblk'}; + main::set_mapper() if !$bsd_type && !$loaded{'mapper'}; + PartitionData::set() if !$bsd_type && !$loaded{'partition-data'}; + my (@data); + $loaded{'raid'} = 1; + if ($fake{'raid-btrfs'} || + ($alerts{'btrfs'}->{'action'} && $alerts{'btrfs'}->{'action'} eq 'use')){ + @btrfs_raid = btrfs_data(); + } + if ($fake{'raid-lvm'} || + ($alerts{'lvs'}->{'action'} && $alerts{'lvs'}->{'action'} eq 'use')){ + @lvm_raid = lvm_data(); + } + if ($fake{'raid-md'} || (my $file = $system_files{'proc-mdstat'})){ + @md_raid = md_data($file); + } + if ($fake{'raid-soft'} || $sysctl{'softraid'}){ + DiskDataBSD::set() if !$loaded{'disk-data-bsd'}; + @soft_raid = soft_data(); + } + if ($fake{'raid-zfs'} || (my $path = main::check_program('zpool'))){ + DiskDataBSD::set() if $bsd_type && !$loaded{'disk-data-bsd'}; + @zfs_raid = zfs_data($path); + } + eval $end if $b_log; +} + +# 0: type +# 1: type_id +# 2: bus_id +# 3: sub_id +# 4: device +# 5: vendor_id +# 6: chip_id +# 7: rev +# 8: port +# 9: driver +# 10: modules +sub hw_data { + eval $start if $b_log; + return if !$devices{'hwraid'}; + my ($driver,$vendor,$hardware_raid); + foreach my $working (@{$devices{'hwraid'}}){ + $driver = ($working->[9]) ? lc($working->[9]): ''; + $driver =~ s/-/_/g if $driver; + my $driver_version = ($driver) ? main::get_module_version($driver): ''; + if ($extra > 2 && $use{'pci-tool'} && $working->[11]){ + $vendor = main::get_pci_vendor($working->[4],$working->[11]); + } + push(@$hardware_raid, { + 'class-id' => $working->[1], + 'bus-id' => $working->[2], + 'chip-id' => $working->[6], + 'device' => $working->[4], + 'driver' => $driver, + 'driver-version' => $driver_version, + 'port' => $working->[8], + 'rev' => $working->[7], + 'sub-id' => $working->[3], + 'vendor-id' => $working->[5], + 'vendor' => $vendor, + }); + } + # print Data::Dumper::Dumper $hardware_raid; + main::log_data('dump','@$hardware_raid',$hardware_raid) if $b_log; + eval $end if $b_log; + return $hardware_raid; +} + +# Placeholder, if they ever get useful tools +sub btrfs_data { + eval $start if $b_log; + my (@btraid,@working); + if ($fake{'raid-btrfs'}){ + + } + else { + + } + print Data::Dumper::Dumper \@working if $dbg[37]; + print Data::Dumper::Dumper \@btraid if $dbg[37]; + main::log_data('dump','@lvraid',\@btraid) if $b_log; + eval $end if $b_log; + return @btraid; +} + +sub lvm_data { + eval $start if $b_log; + LogicalItem::lvm_data() if !$loaded{'logical-data'}; + return if !@lvm; + my (@lvraid,$maj_min,$vg_used,@working); + foreach my $item (@lvm){ + next if $item->{'segtype'} && $item->{'segtype'} !~ /^raid/; + my (@components,$dev,$maj_min,$vg_used); + # print Data::Dumper::Dumper $item; + if ($item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'}){ + $maj_min = $item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'}; + } + if (defined $item->{'vg_free'} && defined $item->{'vg_size'}){ + $vg_used = ($item->{'vg_size'} - $item->{'vg_free'}); + } + $raw_logical[0] += $item->{'lv_size'} if $item->{'lv_size'}; + @working = main::globber("/sys/dev/block/$maj_min/slaves/*") if $maj_min; + @working = map {$_ =~ s|^/.*/||; $_;} @working if @working; + foreach my $part (@working){ + my ($dev,$maj_min,$mapped,$size); + if (@proc_partitions){ + my $info = PartitionData::get($part); + $maj_min = $info->[0] . ':' . $info->[1] if defined $info->[1]; + $size = $info->[2]; + $raw_logical[1] += $size if $part =~ /^dm-/ && $size; + my @data = main::globber("/sys/dev/block/$maj_min/slaves/*") if $maj_min; + @data = map {$_ =~ s|^/.*/||; $_;} @data if @data; + $dev = join(',', @data) if @data; + } + $mapped = $dmmapper{$part} if %dmmapper; + push(@components, [$part,$size,$maj_min,undef,$mapped,$dev],); + } + if ($item->{'segtype'}){ + if ($item->{'segtype'} eq 'raid1'){$item->{'segtype'} = 'mirror';} + else {$item->{'segtype'} =~ s/^raid([0-9]+)/raid-$1/;} + } + push(@lvraid, { + 'components' => \@components, + 'copy-percent' => $item->{'copy_percent'}, + 'id' => $item->{'lv_name'}, + 'level' => $item->{'segtype'}, + 'maj-min' => $maj_min, + 'raid-mismatches' => $item->{'raid_mismatch_count'}, + 'raid-sync' => $item->{'raid_sync_action'}, + 'size' => $item->{'lv_size'}, + 'stripes' => $item->{'stripes'}, + 'type' => $item->{'vg_fmt'}, + 'vg' => $item->{'vg_name'}, + 'vg-free' => $item->{'vg_free'}, + 'vg-size' => $item->{'vg_size'}, + 'vg-used' => $vg_used, + }); + } + print Data::Dumper::Dumper \@lvraid if $dbg[37]; + main::log_data('dump','@lvraid',\@lvraid) if $b_log; + eval $end if $b_log; + return @lvraid; +} + +sub md_data { + eval $start if $b_log; + my ($mdstat) = @_; + my $j = 0; + if ($fake{'raid-md'}){ + #$mdstat = "$fake_data_dir/raid-logical/md/md-4-device-1.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-rebuild-1.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-2-mirror-fserver2-1.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-2-raid10-abucodonosor.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-2-raid10-ant.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-inactive-weird-syntax.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-inactive-active-syntax.txt"; + #$mdstat = "$fake_data_dir/raid-logical/md/md-inactive-active-spare-syntax.txt"; + } + my @working = main::reader($mdstat,'strip'); + # print Data::Dumper::Dumper \@working; + my (@mdraid,@temp,$b_found,$system,$unused); + # NOTE: a system with empty mdstat will not show these values + if ($working[0] && $working[0] =~ /^Personalities/){ + $system = (split(/:\s*/, $working[0]))[1]; + $system =~ s/\[|\]//g if $system; + shift @working; + } + if ($working[-1] && $working[-1] =~ /^unused\sdevices/){ + $unused = (split(/:\s*/, $working[-1]))[1]; + $unused =~ s/<|>|none//g if $unused; + pop @working; + } + foreach (@working){ + $_ =~ s/\s*:\s*/:/; + # print "$_\n"; + # md0 : active raid1 sdb1[2] sda1[0] + # md126 : active (auto-read-only) raid1 sdq1[0] + # md127 : inactive sda0 + # md1 : inactive sda1[0] sdd1[3] sdc1[2] sdb1[1] + # if (/^(md[0-9]+)\s*:\s*([^\s]+)(\s\([^)]+\))?\s([^\s]+)\s(.*)/){ + if (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?/){ + my ($component_string,$details,$device,$id,$level,$maj_min,$part,$size,$status); + my (@components); + $id = $1; + $status = $2; + if (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?\s((faulty|linear|multipath|raid)[\S]*)\s(.*)/){ + $level = $4; + $component_string = $6; + $level =~ s/^raid1$/mirror/; + $level =~ s/^raid/raid-/; + $level = 'mirror' if $level eq '1'; + } + elsif (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?\s(.*)/){ + $component_string = $4; + $level = 'N/A'; + } + @temp = (); + # cascade of tests, light to cpu intense + if ((!$maj_min || !$size) && @proc_partitions){ + $part = PartitionData::get($id); + if (@$part){ + $maj_min = $part->[0] . ':' . $part->[1]; + $size = $part->[2]; + } + } + if ((!$maj_min || !$size) && @lsblk){ + $device = LsblkData::get($id) if @lsblk; + $maj_min = $device->{'maj-min'} if $device->{'maj-min'}; + $size = $device->{'size'} if $device->{'size'}; + } + if ((!$size || $b_admin) && $alerts{'mdadm'}->{'action'} eq 'use'){ + $details = md_details($id); + $size = $details->{'size'} if $details->{'size'}; + } + $raw_logical[0] += $size if $size; + # remember, these include the [x] id, so remove that for disk/unmounted + foreach my $component (split(/\s+/, $component_string)){ + my (%data,$maj_min,$name,$number,$info,$mapped,$part_size,$state); + if ($component =~ /([\S]+)\[([0-9]+)\]\(?([SF])?\)?/){ + ($name,$number,$info) = ($1,$2,$3); + } + elsif ($component =~ /([\S]+)/){ + $name = $1; + } + next if !$name; + if ($details->{'devices'} && ref $details->{'devices'} eq 'HASH'){ + $maj_min = $details->{'devices'}{$name}{'maj-min'}; + $state = $details->{'devices'}{$name}{'state'}; + } + if ((!$maj_min || !$part_size) && @proc_partitions){ + $part = PartitionData::get($name); + if (@$part){ + $maj_min = $part->[0] . ':' . $part->[1] if !$maj_min; + $part_size = $part->[2] if !$part_size; + } + } + if ((!$maj_min || !$part_size) && @lsblk){ + %data= LsblkData::get($name); + $maj_min = $data{'maj-min'} if !$maj_min; + $part_size = $data{'size'}if !$part_size; + } + $mapped = $dmmapper{$name} if %dmmapper; + $raw_logical[1] += $part_size if $part_size; + $state = $info if !$state && $info; + push(@components,[$name,$number,$info,$part_size,$maj_min,$state,$mapped]); + } + # print "$component_string\n"; + $j = scalar @mdraid; + push(@mdraid, { + 'chunk-size' => $details->{'chunk-size'}, # if we got it, great, if not, further down + 'components' => \@components, + 'details' => $details, + 'id' => $id, + 'level' => $level, + 'maj-min' => $maj_min, + 'size' => $size, + 'status' => $status, + 'type' => 'mdraid', + }); + } + # print "$_\n"; + if ($_ =~ /^([0-9]+)\sblocks/){ + $mdraid[$j]->{'blocks'} = $1; + } + if ($_ =~ /super\s([0-9\.]+)\s/){ + $mdraid[$j]->{'super-block'} = $1; + } + if ($_ =~ /algorithm\s([0-9\.]+)\s/){ + $mdraid[$j]->{'algorithm'} = $1; + } + if ($_ =~ /\[([0-9]+\/[0-9]+)\]\s\[([U_]+)\]/){ + $mdraid[$j]->{'report'} = $1; + $mdraid[$j]->{'u-data'} = $2; + } + if ($_ =~ /resync=([\S]+)/){ + $mdraid[$j]->{'resync'} = $1; + } + if ($_ =~ /([0-9]+[km])\schunk/i){ + $mdraid[$j]->{'chunk-size'} = $1; + } + if ($_ =~ /(\[[=]*>[\.]*\]).*(resync|recovery)\s*=\s*([0-9\.]+%)?(\s\(([0-9\/]+)\))?/){ + $mdraid[$j]->{'progress-bar'} = $1; + $mdraid[$j]->{'recovery-percent'} = $3 if $3; + $mdraid[$j]->{'sectors-recovered'} = $5 if $5; + } + if ($_ =~ /finish\s*=\s*([\S]+)\s+speed\s*=\s*([\S]+)/){ + $mdraid[$j]->{'recovery-finish'} = $1; + $mdraid[$j]->{'recovery-speed'} = $2; + } + # print 'mdraid loop: ', Data::Dumper::Dumper \@mdraid; + } + if (@mdraid){ + $mdraid[0]->{'supported-levels'} = $system if $system; + $mdraid[0]->{'unused'} = $unused if $unused; + } + print Data::Dumper::Dumper \@mdraid if $dbg[37]; + eval $end if $b_log; + return @mdraid; +} + +sub md_details { + eval $start if $b_log; + my ($id) = @_; + my (@working); + my $details = {}; + my $cmd = $alerts{'mdadm'}->{'path'} . " --detail /dev/$id 2>/dev/null"; + my @data = main::grabber($cmd,'','strip'); + main::log_data('dump',"$id raw: \@data",\@data) if $b_log; + foreach (@data){ + @working = split(/\s*:\s*/, $_, 2); + if (scalar @working == 2){ + if ($working[0] eq 'Array Size' && $working[1] =~ /^([0-9]+)\s\(/){ + $details->{'size'} = $1; + } + elsif ($working[0] eq 'Active Devices'){ + $details->{'c-active'} = $working[1]; + } + elsif ($working[0] eq 'Chunk Size'){ + $details->{'chunk-size'} = $working[1]; + } + elsif ($working[0] eq 'Failed Devices'){ + $details->{'c-failed'} = $working[1]; + } + elsif ($working[0] eq 'Raid Devices'){ + $details->{'c-raid'} = $working[1]; + } + elsif ($working[0] eq 'Spare Devices'){ + $details->{'c-spare'} = $working[1]; + } + elsif ($working[0] eq 'State'){ + $details->{'state'} = $working[1]; + } + elsif ($working[0] eq 'Total Devices'){ + $details->{'c-total'} = $working[1]; + } + elsif ($working[0] eq 'Used Dev Size' && $working[1] =~ /^([0-9]+)\s\(/){ + $details->{'dev-size'} = $1; + } + elsif ($working[0] eq 'UUID'){ + $details->{'uuid'} = $working[1]; + } + elsif ($working[0] eq 'Working Devices'){ + $details->{'c-working'} = $working[1]; + } + } + # end component data lines + else { + @working = split(/\s+/,$_); + # 0 8 80 0 active sync /dev/sdf + # 2 8 128 - spare /dev/sdi + next if !@working || $working[0] eq 'Number' || scalar @working < 6; + $working[-1] =~ s|^/dev/(mapper/)?||; + $details->{'devices'}{$working[-1]} = { + 'maj-min' => $working[1] . ':' . $working[2], + 'number' => $working[0], + 'raid-device' => $working[3], + 'state' => join(' ', @working[4..($#working - 1)]), + }; + } + } + # print Data::Dumper::Dumper $details; + main::log_data('dump',$id . ': %$details',$details) if $b_log; + eval $end if $b_log; + return $details; +} + +sub soft_data { + eval $start if $b_log; + my ($cmd,$id,$state,$status,@data,@softraid,@working); + # already been set in DiskDataBSD but we know the device exists + foreach my $device (@{$sysctl{'softraid'}}){ + if ($device =~ /\.drive[\d]+:([\S]+)\s\(([a-z0-9]+)\)[,\s]+(\S+)/){ + my ($level,$size,@components); + $id = $2; + $status = $1; + $state = $3; + if ($alerts{'bioctl'}->{'action'} eq 'use'){ + $cmd = $alerts{'bioctl'}->{'path'} . " $id 2>/dev/null"; + @data = main::grabber($cmd,'','strip'); + main::log_data('dump','softraid @data',\@data) if $b_log; + shift @data if @data; # get rid of headers + foreach my $row (@data){ + @working = split(/\s+/,$row); + next if !defined $working[0]; + if ($working[0] =~ /^softraid/){ + if ($working[3] && main::is_numeric($working[3])){ + $size = $working[3]/1024;# it's in bytes + $raw_logical[0] += $size; + } + $status = lc($working[2]) if $working[2]; + $state = lc(join(' ', @working[6..$#working])) if $working[6]; + $level = lc($working[5]) if $working[5]; + } + elsif ($working[0] =~ /^[\d]{1,2}$/){ + my ($c_id,$c_device,$c_size,$c_status); + if ($working[2] && main::is_numeric($working[2])){ + $c_size = $working[2]/1024;# it's in bytes + $raw_logical[1] += $c_size; + } + $c_status = lc($working[1]) if $working[1]; + if ($working[3] && $working[3] =~ /^([\d:\.]+)$/){ + $c_device = $1; + } + if ($working[5] && $working[5] =~ /<([^>]+)>/){ + $c_id = $1; + } + # when offline, there will be no $c_id, but we want to show device + if (!$c_id && $c_device){ + $c_id = $c_device; + } + push(@components,[$c_id,$c_size,$c_status,$c_device]) if $c_id; + } + } + } + push(@softraid, { + 'components' => \@components, + 'id' => $id, + 'level' => $level, + 'size' => $size, + 'state' => $state, + 'status' => $status, + 'type' => 'softraid', + }); + } + } + print Data::Dumper::Dumper \@softraid if $dbg[37]; + main::log_data('dump','@softraid',\@softraid) if $b_log; + eval $end if $b_log; + return @softraid; +} + +sub zfs_data { + eval $start if $b_log; + my ($zpool) = @_; + my (@components,@zfs); + my ($allocated,$free,$size,$size_holder,$status,$zfs_used,$zfs_avail, + $zfs_size,@working); + my $b_v = 1; + my ($i,$j,$k) = (0,0,0); + if ($fake{'raid-zfs'}){ + # my $file; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-list-1-mirror-main-solestar.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-list-2-mirror-main-solestar.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-list-v-tank-1.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-list-v-gojev-1.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-list-v-w-spares-1.txt"; + #@working = main::reader($file);$zpool = ''; + } + else { + @working = main::grabber("$zpool list -v 2>/dev/null"); + } + # bsd sed does not support inserting a true \n so use this trick + # some zfs does not have -v + if (!@working){ + @working = main::grabber("$zpool list 2>/dev/null"); + $b_v = 0; + } + my $zfs_path = main::check_program('zfs'); + # print Data::Dumper::Dumper \@working; + main::log_data('dump','@working',\@working) if $b_log; + if (!@working){ + main::log_data('data','no zpool list data') if $b_log; + eval $end if $b_log; + return (); + } + my ($status_i) = (0); + # NAME SIZE ALLOC FREE EXPANDSZ FRAG CAP DEDUP HEALTH ALTROOT + my $test = shift @working; # get rid of first header line + if ($test){ + foreach (split(/\s+/, $test)){ + last if $_ eq 'HEALTH'; + $status_i++; + } + } + foreach (@working){ + my @row = split(/\s+/, $_); + if (/^[\S]+/){ + @components = (); + $i = 0; + $size = ($row[1] && $row[1] ne '-') ? main::translate_size($row[1]): ''; + $allocated = ($row[2] && $row[2] ne '-')? main::translate_size($row[2]): ''; + $free = ($row[3] && $row[3] ne '-')? main::translate_size($row[3]): ''; + ($zfs_used,$zfs_avail) = zfs_fs_sizes($zfs_path,$row[0]) if $zfs_path; + if (defined $zfs_used && defined $zfs_avail){ + $zfs_size = $zfs_used + $zfs_avail; + $raw_logical[0] += $zfs_size; + } + else { + # must be BEFORE '$size_holder =' because only used if hits a new device + # AND unassigned via raid/mirror arrays. Corner case for > 1 device systems. + $raw_logical[0] += $size_holder if $size_holder; + $size_holder = $size; + } + $status = (defined $row[$status_i] && $row[$status_i] ne '') ? $row[$status_i]: 'no-status'; + $j = scalar @zfs; + push(@zfs, { + 'id' => $row[0], + 'arrays' => ([],), + 'raw-allocated' => $allocated, + 'raw-free' => $free, + 'raw-size' => $size, + 'zfs-free' => $zfs_avail, + 'zfs-size' => $zfs_size, + 'status' => $status, + 'type' => 'zfs', + }); + } + # print Data::Dumper::Dumper \@zfs; + # raid level is the second item in the output, unless it is not, sometimes it is absent + elsif ($row[1] =~ /raid|mirror/){ + $row[1] =~ s/^raid1/mirror/; + #$row[1] =~ s/^raid/raid-/; # need to match in zpool status + $k = scalar @{$zfs[$j]->{'arrays'}}; + $zfs[$j]->{'arrays'}[$k]{'level'} = $row[1]; + $i = 0; + $size = ($row[2] && $row[2] ne '-') ? main::translate_size($row[2]) : ''; + if (!defined $zfs_used || !defined $zfs_avail){ + $size_holder = 0; + $raw_logical[0] += $size if $size; + } + $zfs[$j]->{'arrays'}[$k]{'raw-allocated'} = ($row[3] && $row[3] ne '-') ? main::translate_size($row[3]) : ''; + $zfs[$j]->{'arrays'}[$k]{'raw-free'} = ($row[4] && $row[4] ne '-') ? main::translate_size($row[4]) : ''; + $zfs[$j]->{'arrays'}[$k]{'raw-size'} = $size; + } + # https://blogs.oracle.com/eschrock/entry/zfs_hot_spares + elsif ($row[1] =~ /spares?/){ + next; + } + # A member of a raid array: + # ada2 - - - - - - + # A single device not in an array: + # ada0s2 25.9G 14.6G 11.3G - 0% 56% + # gptid/3838f796-5c46-11e6-a931-d05099ac4dc2 - - - - - - + # Using /dev/disk/by-id: + # ata-VBOX_HARDDISK_VB5b6350cd-06618d58 + # Using /dev/disk/by-partuuid: + # ec399377-c03c-e844-a876-8c8b044124b8 - - - - - - ONLINE + # Spare in use: + # /home/fred/zvol/hdd-2-3 - - - - - - - - INUSE + elsif ($row[1] =~ /^(sd[a-z]+|[a-z0-9]+[0-9]+|([\S]+)\/.*|(ata|mmc|nvme|pci|scsi|wwn)-\S+|[a-f0-9]{4,}(-[a-f0-9]{4,}){3,})$/ && + ($row[2] eq '-' || $row[2] =~ /^[0-9\.]+[MGTPE]$/)){ + #print "r1:$row[1]",' :: ', Cwd::abs_path('/dev/disk/by-id/'.$row[1]), "\n"; + $row[1] =~ /^(sd[a-z]+|[a-z0-9]+[0-9]+|([\S]+)\/.*|(ata|mmc|nvme|pci|scsi|wwn)-\S+|[a-f0-9]{4,}(-[a-f0-9]{4,}){3,})\s.*?(DEGRADED|FAULTED|INUSE|OFFLINE)?$/; + #my $working = ''; + my $working = ($1) ? $1 : ''; # note: the negative case can never happen + my $state = ($4) ? $4 : ''; + my ($maj_min,$real,$part_size); + if ($bsd_type && $working =~ /[\S]+\//){ + $working = GlabelData::get($working); + } + elsif (!$bsd_type && $row[1] =~ /^(ata|mmc|nvme|scsi|wwn)-/ && + -e "/dev/disk/by-id/$row[1]" && ($real = Cwd::abs_path('/dev/disk/by-id/'.$row[1]))){ + $real =~ s|/dev/||; + $working = $real; + } + elsif (!$bsd_type && $row[1] =~ /^(pci)-/ && + -e "/dev/disk/by-path/$row[1]" && ($real = Cwd::abs_path('/dev/disk/by-path/'.$row[1]))){ + $real =~ s|/dev/||; + $working = $real; + } + elsif (!$bsd_type && $row[1] =~ /^[a-f0-9]{4,}(-[a-f0-9]{4,}){3,}$/ && + -e "/dev/disk/by-partuuid/$row[1]" && ($real = Cwd::abs_path('/dev/disk/by-partuuid/'.$row[1]))){ + $real =~ s|/dev/||; + $working = $real; + } + # kind of a hack, things like cache may not show size/free + # data since they have no array row, but they might show it in + # component row: + # ada0s2 25.9G 19.6G 6.25G - 0% 75% + # ec399377-c03c-e844-a876-8c8b044124b8 1.88G 397M 1.49G - - 0% 20.7% - ONLINE + # keys were size/allocated/free but those keys don't exist, assume failed to add raw- + if (!$zfs[$j]->{'raw-size'} && $row[2] && $row[2] ne '-'){ + $size = ($row[2]) ? main::translate_size($row[2]): ''; + $size_holder = 0; + $zfs[$j]->{'arrays'}[$k]{'raw-size'} = $size; + $raw_logical[0] += $size if $size; + } + if (!$zfs[$j]->{'raw-allocated'} && $row[3] && $row[3] ne '-'){ + $allocated = ($row[3]) ? main::translate_size($row[3]) : ''; + $zfs[$j]->{'arrays'}[$k]{'raw-allocated'} = $allocated; + } + if (!$zfs[$j]->{'raw-free'} && $row[4] && $row[4] ne '-'){ + $free = ($row[4]) ? main::translate_size($row[4]) : ''; + $zfs[$j]->{'arrays'}[$k]{'raw-free'} = $free; + } + if ((!$maj_min || !$part_size) && $working && @proc_partitions){ + my $part = PartitionData::get($working); + if (@$part){ + $maj_min = $part->[0] . ':' . $part->[1]; + $part_size = $part->[2]; + } + } + if ((!$maj_min || !$part_size) && $working && @lsblk){ + my $data= LsblkData::get($working); + $maj_min = $data->{'maj-min'}; + $part_size = $data->{'size'}; + } + if (!$part_size && $bsd_type && $working){ + my $temp = DiskDataBSD::get($working); + $part_size = $temp->{'size'} if $temp->{'size'}; + } + $raw_logical[1] += $part_size if $part_size; + $zfs[$j]->{'arrays'}[$k]{'components'}[$i] = [$working,$part_size,$maj_min,$state]; + $i++; + } + } + $raw_logical[0] += $size_holder if $size_holder; + # print Data::Dumper::Dumper \@zfs; + # clear out undefined arrrays values + $j = 0; + foreach my $row (@zfs){ + my @arrays = (ref $row->{'arrays'} eq 'ARRAY') ? @{$row->{'arrays'}} : (); + @arrays = grep {defined $_} @arrays; + $zfs[$j]->{'arrays'} = \@arrays; + $j++; + } + @zfs = zfs_status($zpool,\@zfs); + print Data::Dumper::Dumper \@zfs if $dbg[37]; + eval $end if $b_log; + return @zfs; +} + +sub zfs_fs_sizes { + my ($path,$id) = @_; + eval $start if $b_log; + my @data; + my @result = main::grabber("$path list -pH $id 2>/dev/null",'','strip'); + main::log_data('dump','zfs list @result',\@result) if $b_log; + print Data::Dumper::Dumper \@result if $dbg[37]; + # some zfs devices do not have zfs data, lake spare storage devices + if (@result){ + my @working = split(/\s+/,$result[0]); + $data[0] = $working[1]/1024 if $working[1]; + $data[1] = $working[2]/1024 if $working[2]; + } + elsif ($b_log || $dbg[37]) { + @result = main::grabber("$path list -pH $id 2>&1",'','strip'); + main::log_data('dump','zfs list w/error @result',\@result) if $b_log; + print '@result w/error: ', Data::Dumper::Dumper \@result if $dbg[37]; + } + eval $end if $b_log; + return @data; +} + +sub zfs_status { + eval $start if $b_log; + my ($zpool,$zfs) = @_; + my ($cmd,$level,$status,@pool_status,@temp); + my ($i,$j,$k,$l) = (0,0,0,0); + foreach my $row (@$zfs){ + $i = 0; + $k = 0; + if ($fake{'raid-zfs'}){ + my $file; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-status-1-mirror-main-solestar.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-status-2-mirror-main-solestar.txt"; + # $file = "$fake_data_dir/raid-logical/zfs/zpool-status-tank-1.txt"; + #@pool_status = main::reader($file,'strip'); + } + else { + $cmd = "$zpool status $row->{'id'} 2>/dev/null"; + @pool_status = main::grabber($cmd,"\n",'strip'); + } + main::log_data('cmd',$cmd) if $b_log; + # @arrays = (ref $row->{'arrays'} eq 'ARRAY') ? @{$row->{'arrays'}} : (); + # print "$row->{'id'} rs:$row->{'status'}\n"; + $status = ($row->{'status'} && $row->{'status'} eq 'no-status') ? check_zfs_status($row->{'id'},\@pool_status): $row->{'status'}; + $zfs->[$j]{'status'} = $status if $status; + #@arrays = grep {defined $_} @arrays; + # print "$row->{id} $#arrays\n"; + # print Data::Dumper::Dumper \@arrays; + foreach my $array (@{$row->{'arrays'}}){ + # print 'ref: ', ref $array, "\n"; + #next if ref $array ne 'HASH'; + my @components = (ref $array->{'components'} eq 'ARRAY') ? @{$array->{'components'}} : (); + $l = 0; + # zpool status: mirror-0 ONLINE 2 0 0 + $level = ($array->{'level'}) ? "$array->{'level'}-$i": $array->{'level'}; + $status = ($level) ? check_zfs_status($level,\@pool_status): ''; + $zfs->[$j]{'arrays'}[$k]{'status'} = $status; + # print "$level i:$i j:$j k:$k $status\n"; + foreach my $component (@components){ + my @temp = split('~', $component); + $status = ($temp[0]) ? check_zfs_status($temp[0],\@pool_status): ''; + $zfs->[$j]{'arrays'}[$k]{'components'}[$l] .= $status if $status; + $l++; + } + $k++; + # haven't seen a raid5/6 type array yet, zfs uses z1,z2,and z3 + $i++ if $array->{'level'}; # && $array->{'level'} eq 'mirror'; + } + $j++; + } + eval $end if $b_log; + return @$zfs; +} + +sub check_zfs_status { + eval $start if $b_log; + my ($item,$pool_status) = @_; + my ($status) = (''); + foreach (@$pool_status){ + my @temp = split(/\s+/, $_); + if ($temp[0] eq $item){ + last if !$temp[1]; + $status = $temp[1]; + last; + } + } + eval $end if $b_log; + return $status; +} +} + +## RamItem +{ +package RamItem; +my ($vendors,$vendor_ids); +my $ram_total = 0; +sub get { + my ($key1,$ram,$val1); + my $rows = []; + my $num = 0; + if ($bsd_type && !$force{'dmidecode'} && ($dboot{'ram'} || $fake{'dboot'})){ + $ram = dboot_data(); + if (@$ram){ + ram_output($rows,$ram,'dboot'); + } + else { + $key1 = 'message'; + $val1 = main::message('ram-data-dmidecode'); + push(@$rows, { + main::key($num++,1,1,'RAM Report') => '', + main::key($num++,0,2,$key1) => $val1, + }); + } + } + elsif ($fake{'dmidecode'} || $alerts{'dmidecode'}->{'action'} eq 'use'){ + $ram = dmidecode_data(); + if (@$ram){ + ram_output($rows,$ram,'dmidecode'); + } + else { + $key1 = 'message'; + $val1 = main::message('ram-data'); + push(@$rows, { + main::key($num++,1,1,'RAM Report') => '', + main::key($num++,0,2,$key1) => $val1, + }); + } + } + else { + $key1 = $alerts{'dmidecode'}->{'action'}; + $val1 = $alerts{'dmidecode'}->{'message'}; + push(@$rows, { + main::key($num++,1,1,'RAM Report') => '', + main::key($num++,0,2,$key1) => $val1, + }); + } + # we want the real installed RAM total if detected so add this after. + if (!$loaded{'memory'}){ + $num = 0; + my $system_ram = {}; + MemoryData::row('ram',$system_ram,\$num,1); + unshift(@$rows,$system_ram); + } + ($vendors,$vendor_ids) = (); + eval $end if $b_log; + return $rows; +} + +sub ram_total { + return $ram_total; +} + +sub ram_output { + eval $start if $b_log; + my ($rows,$ram,$source) = @_; + return if !@$ram; + my $num = 0; + my $j = 0; + my ($b_non_system); + my ($arrays,$modules,$slots,$type_holder) = (0,0,0,''); + if ($source eq 'dboot'){ + push(@$rows, { + main::key($num++,0,1,'Message') => main::message('ram-data-complete'), + }); + } + foreach my $item (@$ram){ + $j = scalar @$rows; + if (!$show{'ram-short'}){ + $b_non_system = ($item->{'use'} && lc($item->{'use'}) ne 'system memory') ? 1:0; + $num = 1; + push(@$rows, { + main::key($num++,1,1,'Array') => '', + main::key($num++,1,2,'capacity') => process_size($item->{'capacity'}), + }); + if ($item->{'cap-qualifier'}){ + $rows->[$j]{main::key($num++,0,3,'note')} = $item->{'cap-qualifier'}; + } + # show if > 1 array otherwise shows in System RAM line. + if (scalar @$ram > 1){ + $rows->[$j]{main::key($num++,0,2,'installed')} = process_size($item->{'used-capacity'}); + } + $rows->[$j]{main::key($num++,0,2,'use')} = $item->{'use'} if $b_non_system; + $rows->[$j]{main::key($num++,1,2,'slots')} = $item->{'slots'}; + if ($item->{'slots-qualifier'}){ + $rows->[$j]{main::key($num++,0,3,'note')} = $item->{'slots-qualifier'}; + } + $rows->[$j]{main::key($num++,0,2,'modules')} = $item->{'slots-active'}; + $item->{'eec'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'EC')} = $item->{'eec'}; + if ($extra > 0 && (!$b_non_system || + (main::is_numeric($item->{'max-module-size'}) && + $item->{'max-module-size'} > 10))){ + $rows->[$j]{main::key($num++,1,2,'max-module-size')} = process_size($item->{'max-module-size'}); + if ($item->{'mod-qualifier'}){ + $rows->[$j]{main::key($num++,0,3,'note')} = $item->{'mod-qualifier'}; + } + } + if ($extra > 1 && $item->{'voltage'}){ + $rows->[$j]{main::key($num++,0,2,'voltage')} = $item->{'voltage'}; + } + } + else { + $slots += $item->{'slots'} if $item->{'slots'}; + $arrays++; + } + foreach my $entry ($item->{'modules'}){ + next if ref $entry ne 'ARRAY'; + # print Data::Dumper::Dumper $entry; + foreach my $mod (@$entry){ + $num = 1; + $j = scalar @$rows; + # Multi array setups will start index at next from previous array + next if ref $mod ne 'HASH'; + if ($show{'ram-short'}){ + $modules++ if ($mod->{'size'} =~ /^\d/); + $type_holder = $mod->{'device-type'} if $mod->{'device-type'}; + next; + } + next if ($show{'ram-modules'} && $mod->{'size'} =~ /\D/); + $mod->{'locator'} ||= 'N/A'; + push(@$rows, { + main::key($num++,1,2,'Device') => $mod->{'locator'}, + }); + # This will contain the no module string + if ($mod->{'size'} =~ /\D/){ + $rows->[$j]{main::key($num++,0,3,'type')} = lc($mod->{'size'}); + next; + } + if ($extra > 1 && $mod->{'type'}){ + $rows->[$j]{main::key($num++,0,3,'info')} = $mod->{'type'}; + } + $mod->{'device-type'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,3,'type')} = $mod->{'device-type'}; + if ($extra > 2 && $mod->{'device-type'} ne 'N/A'){ + $mod->{'device-type-detail'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,4,'detail')} = $mod->{'device-type-detail'}; + } + $rows->[$j]{main::key($num++,0,3,'size')} = process_size($mod->{'size'}); + if ($mod->{'speed'} && $mod->{'configured-clock-speed'} && + $mod->{'speed'} ne $mod->{'configured-clock-speed'}){ + $rows->[$j]{main::key($num++,1,3,'speed')} = ''; + $rows->[$j]{main::key($num++,0,4,'spec')} = $mod->{'speed'}; + if ($mod->{'speed-note'}){ + $rows->[$j]{main::key($num++,0,4,'note')} = $mod->{'speed-note'}; + } + $rows->[$j]{main::key($num++,0,4,'actual')} = $mod->{'configured-clock-speed'}; + if ($mod->{'configured-note'}){ + $rows->[$j]{main::key($num++,0,5,'note')} = $mod->{'configured-note'}; + } + } + else { + if (!$mod->{'speed'} && $mod->{'configured-clock-speed'}){ + if ($mod->{'configured-clock-speed'}){ + $mod->{'speed'} = $mod->{'configured-clock-speed'}; + if ($mod->{'configured-note'}){ + $mod->{'speed-note'} = $mod->{'configured-note'}; + } + } + } + # Rare instances, dmi type 6, no speed, dboot also no speed + $mod->{'speed'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,3,'speed')} = $mod->{'speed'}; + if ($mod->{'speed-note'}){ + $rows->[$j]{main::key($num++,0,4,'note')} = $mod->{'speed-note'}; + } + } + # Handle cases where -xx or -xxx and no voltage data (common) or voltages + # are all the same. + if ($extra > 1){ + if (($mod->{'voltage-config'} || $mod->{'voltage-max'} || + $mod->{'voltage-min'}) && ($b_admin || ( + ($mod->{'voltage-config'} && $mod->{'voltage-max'} && + $mod->{'voltage-config'} ne $mod->{'voltage-max'}) || + ($mod->{'voltage-config'} && $mod->{'voltage-min'} && + $mod->{'voltage-config'} ne $mod->{'voltage-min'}) || + ($mod->{'voltage-min'} && $mod->{'voltage-max'} && + $mod->{'voltage-max'} ne $mod->{'voltage-min'}) + ))){ + $rows->[$j]{main::key($num++,1,3,'volts')} = ''; + if ($mod->{'voltage-config'}){ + $rows->[$j]{main::key($num++,0,4,'curr')} = $mod->{'voltage-config'}; + } + if ($mod->{'voltage-min'}){ + $rows->[$j]{main::key($num++,0,4,'min')} = $mod->{'voltage-min'}; + } + if ($mod->{'voltage-max'}){ + $rows->[$j]{main::key($num++,0,4,'max')} = $mod->{'voltage-max'}; + } + } + else { + $mod->{'voltage-config'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'volts')} = $mod->{'voltage-config'}; + } + } + if ($source ne 'dboot' && $extra > 2){ + if (!$mod->{'data-width'} && !$mod->{'total-width'}){ + $rows->[$j]{main::key($num++,0,3,'width')} = 'N/A'; + } + else { + $rows->[$j]{main::key($num++,1,3,'width (bits)')} = ''; + $mod->{'data-width'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,4,'data')} = $mod->{'data-width'}; + $mod->{'total-width'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,4,'total')} = $mod->{'total-width'}; + } + } + if ($source ne 'dboot' && $extra > 1){ + $mod->{'manufacturer'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'manufacturer')} = $mod->{'manufacturer'}; + $mod->{'part-number'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,3,'part-no')} = $mod->{'part-number'}; + } + if ($source ne 'dboot' && $extra > 2){ + $mod->{'serial'} = main::filter($mod->{'serial'}); + $rows->[$j]{main::key($num++,0,3,'serial')} = $mod->{'serial'}; + } + } + } + } + if ($show{'ram-short'}){ + $num = 1; + $type_holder ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Report') => '', + main::key($num++,0,2,'arrays') => $arrays, + main::key($num++,0,2,'slots') => $slots, + main::key($num++,0,2,'modules') => $modules, + main::key($num++,0,2,'type') => $type_holder, + }); + } + eval $end if $b_log; +} + +sub dmidecode_data { + eval $start if $b_log; + my ($b_5,$handle,@temp); + my ($derived_module_size,$max_cap_5,$max_cap_16,$max_module_size, + $slots_active) = (0,0,0,0,0); + my ($i,$j,$k) = (0,0,0); + my $ram = []; + my $check = main::message('note-check'); + # print Data::Dumper::Dumper \@dmi; + foreach my $entry (@dmi){ + ## Note: do NOT reset these values, that causes failures + # ($derived_module_size,$max_cap_5,$max_cap_16,$max_module_size) = (0,0,0,0); + if ($entry->[0] == 5){ + $slots_active = 0; + foreach my $item (@$entry){ + @temp = split(/:\s*/, $item, 2); + next if !$temp[1]; + if ($temp[0] eq 'Maximum Memory Module Size'){ + $max_module_size = calculate_size($temp[1],$max_module_size); + $ram->[$k]{'max-module-size'} = $max_module_size; + } + elsif ($temp[0] eq 'Maximum Total Memory Size'){ + $max_cap_5 = calculate_size($temp[1],$max_cap_5); + $ram->[$k]{'max-capacity-5'} = $max_cap_5; + } + elsif ($temp[0] eq 'Memory Module Voltage'){ + $temp[1] =~ s/\s*V.*$//; # seen: 5.0 V 3.3 V + $ram->[$k]{'voltage'} = $temp[1]; + } + elsif ($temp[0] eq 'Associated Memory Slots'){ + $ram->[$k]{'slots-5'} = $temp[1]; + } + elsif ($temp[0] eq 'Error Detecting Method'){ + $temp[1] ||= 'None'; + $ram->[$k]{'eec'} = $temp[1]; + } + } + $ram->[$k]{'modules'} = []; + # print Data::Dumper::Dumper \@ram; + $b_5 = 1; + } + elsif ($entry->[0] == 6){ + my ($size,$speed,$type) = (0,0,0); + my ($bank_locator,$device_type,$locator,$main_locator) = ('','','',''); + foreach my $item (@$entry){ + @temp = split(/:\s*/, $item, 2); + next if !$temp[1]; + if ($temp[0] eq 'Installed Size'){ + # Get module size + $size = calculate_size($temp[1],0); + # Using this causes issues, really only works for 16 + # if ($size =~ /^[0-9][0-9]+$/){ + # $ram->[$k]{'device-count-found'}++; + # $ram->[$k]{'used-capacity'} += $size; + # } + # Get data after module size + $temp[1] =~ s/ Connection\)?//; + $temp[1] =~ s/^[0-9]+\s*[KkMGTP]B\s*\(?//; + $type = lc($temp[1]); + $slots_active++; + } + elsif ($temp[0] eq 'Current Speed'){ + $speed = main::clean_dmi($temp[1]); + } + elsif ($temp[0] eq 'Locator' || $temp[0] eq 'Socket Designation'){ + $temp[1] =~ s/D?RAM slot #?/Slot/i; # can be with or without # + $locator = $temp[1]; + } + elsif ($temp[0] eq 'Bank Locator'){ + $bank_locator = $temp[1]; + } + elsif ($temp[0] eq 'Type'){ + $device_type = main::clean_dmi($temp[1]); + } + } + # Because of the wide range of bank/slot type data, we will just use + # the one that seems most likely to be right. Some have: + # 'Bank: SO DIMM 0 slot: J6A' so we dump the useless data and use the + # one most likely to be visibly correct + if ($bank_locator =~ /DIMM/){ + $main_locator = $bank_locator; + } + else { + $main_locator = $locator; + } + $ram->[$k]{'modules'}[$j] = { + 'device-type' => $device_type, + 'locator' => $main_locator, + 'size' => $size, + 'speed' => $speed, + 'type' => $type, + }; + # print Data::Dumper::Dumper \@ram; + $j++; + } + elsif ($entry->[0] == 16){ + $handle = $entry->[1]; + $ram->[$handle] = $ram->[$k] if $ram->[$k]; + $ram->[$k] = undef; + $slots_active = 0; + # ($derived_module_size,$max_cap_16) = (0,0); + foreach my $item (@$entry){ + @temp = split(/:\s*/, $item, 2); + next if !$temp[1]; + if ($temp[0] eq 'Maximum Capacity'){ + $max_cap_16 = calculate_size($temp[1],$max_cap_16); + $ram->[$handle]{'max-capacity-16'} = $max_cap_16; + } + # Note: these 3 have cleaned data in DmiData, so replace stuff manually + elsif ($temp[0] eq 'Location'){ + $temp[1] =~ s/\sOr\sMotherboard//; + $temp[1] ||= 'System Board'; + $ram->[$handle]{'location'} = $temp[1]; + } + elsif ($temp[0] eq 'Use'){ + $temp[1] ||= 'System Memory'; + $ram->[$handle]{'use'} = $temp[1]; + } + elsif ($temp[0] eq 'Error Correction Type'){ + $temp[1] ||= 'None'; + $ram->[$handle]{'eec'} = $temp[1]; + } + elsif ($temp[0] eq 'Number Of Devices'){ + $ram->[$handle]{'slots-16'} = $temp[1]; + } + # print "0: $temp[0]\n"; + } + $ram->[$handle]{'derived-module-size'} = 0; + $ram->[$handle]{'device-count-found'} = 0; + $ram->[$handle]{'used-capacity'} = 0; + # print "s16: $ram->[$handle]{'slots-16'}\n"; + } + elsif ($entry->[0] == 17){ + my ($bank_locator,$configured_speed,$configured_note, + $data_width) = ('','','',''); + my ($device_type,$device_type_detail,$form_factor,$locator, + $main_locator) = ('','','','',''); + my ($manufacturer,$vendor_id,$part_number,$serial,$speed,$speed_note, + $total_width) = ('','','','','','',''); + my ($voltage_config,$voltage_max,$voltage_min); + my ($device_size,$i_data,$i_total,$working_size) = (0,0,0,0); + foreach my $item (@$entry){ + @temp = split(/:\s*/, $item, 2); + next if !$temp[1]; + if ($temp[0] eq 'Array Handle'){ + $handle = hex($temp[1]); + } + # These two can have 'none' or 'unknown' value + elsif ($temp[0] eq 'Data Width'){ + $data_width = main::clean_dmi($temp[1]); + $data_width =~ s/[\s_-]?bits// if $data_width; + } + elsif ($temp[0] eq 'Total Width'){ + $total_width = main::clean_dmi($temp[1]); + $total_width =~ s/[\s_-]?bits// if $total_width; + } + # Do not try to guess from installed modules, only use this to correct + # type 5 data + elsif ($temp[0] eq 'Size'){ + # we want any non real size data to be preserved + if ($temp[1] =~ /^[0-9]+\s*[KkMTPG]i?B/){ + $derived_module_size = calculate_size($temp[1],$derived_module_size); + $working_size = calculate_size($temp[1],0); + $device_size = $working_size; + $slots_active++; + } + else { + $device_size = $temp[1]; + } + } + elsif ($temp[0] eq 'Locator'){ + $temp[1] =~ s/D?RAM slot #?/Slot/i; + $locator = $temp[1]; + } + elsif ($temp[0] eq 'Bank Locator'){ + $bank_locator = $temp[1]; + } + elsif ($temp[0] eq 'Form Factor'){ + $form_factor = $temp[1]; + } + # these two can have 'none' or 'unknown' value + elsif ($temp[0] eq 'Type'){ + $device_type = main::clean_dmi($temp[1]); + } + elsif ($temp[0] eq 'Type Detail'){ + $device_type_detail = main::clean_dmi($temp[1]); + } + elsif ($temp[0] eq 'Speed'){ + my $result = process_speed($temp[1],$device_type,$check); + ($speed,$speed_note) = @$result; + } + # This is the actual speed the system booted at, speed is hardcoded + # clock speed means MHz, memory speed MT/S + elsif ($temp[0] eq 'Configured Clock Speed' || + $temp[0] eq 'Configured Memory Speed'){ + my $result = process_speed($temp[1],$device_type,$check); + ($configured_speed,$configured_note) = @$result; + } + elsif ($temp[0] eq 'Manufacturer'){ + $temp[1] = main::clean_dmi($temp[1]); + $manufacturer = $temp[1]; + } + elsif ($temp[0] eq 'Part Number'){ + $part_number = main::clean_unset($temp[1],'^[0]+$|.*Module.*|PartNum.*'); + } + elsif ($temp[0] eq 'Serial Number'){ + $serial = main::clean_unset($temp[1],'^[0]+$|SerNum.*'); + } + elsif ($temp[0] eq 'Configured Voltage'){ + if ($temp[1] =~ /^([\d\.]+)/){ + $voltage_config = $1; + } + } + elsif ($temp[0] eq 'Maximum Voltage'){ + if ($temp[1] =~ /^([\d\.]+)/){ + $voltage_max = $1; + } + } + elsif ($temp[0] eq 'Minimum Voltage'){ + if ($temp[1] =~ /^([\d\.]+)/){ + $voltage_min = $1; + } + } + } + # Because of the wide range of bank/slot type data, we will just use the + # one that seems most likely to be right. Some have: + # 'Bank: SO DIMM 0 slot: J6A' so we dump the useless data and use the one + # most likely to be visibly correct. + if ($bank_locator =~ /DIMM/){ + $main_locator = $bank_locator; + } + else { + $main_locator = $locator; + } + if ($working_size =~ /^[0-9][0-9]+$/){ + $ram->[$handle]{'device-count-found'}++; + # build up actual capacity found for override tests + $ram->[$handle]{'used-capacity'} += $working_size; + } + # Sometimes the data is just wrong, they reverse total/data. data I + # believe is used for the actual memory bus width, total is some synthetic + # thing, sometimes missing. Note that we do not want a regular string + # comparison, because 128 bit memory buses are in our future, and + # 128 bits < 64 bits with string compare. + $data_width =~ /(^[0-9]+).*/; + $i_data = $1; + $total_width =~ /(^[0-9]+).*/; + $i_total = $1; + if ($i_data && $i_total && $i_data > $i_total){ + my $temp_width = $data_width; + $data_width = $total_width; + $total_width = $temp_width; + } + if ($manufacturer && $manufacturer =~ /^([a-f0-9]{4})$/i){ + $vendor_id = lc($1) if $1; + } + if ((!$manufacturer || $vendor_id) && $part_number){ + my $result = ram_vendor($part_number); + $manufacturer = $result->[0] if $result->[0]; + $part_number = $result->[1] if $result->[1]; + } + if ($vendor_id && !$manufacturer){ + set_ram_vendor_ids() if !$vendor_ids; + if ($vendor_ids->{$vendor_id}){ + $manufacturer = $vendor_ids->{$vendor_id}; + } + } + $ram->[$handle]{'derived-module-size'} = $derived_module_size; + $ram->[$handle]{'slots-active'} = $slots_active; + $ram->[$handle]{'modules'}[$i]{'configured-clock-speed'} = $configured_speed; + $ram->[$handle]{'modules'}[$i]{'configured-note'} = $configured_note if $configured_note; + $ram->[$handle]{'modules'}[$i]{'data-width'} = $data_width; + $ram->[$handle]{'modules'}[$i]{'size'} = $device_size; + $ram->[$handle]{'modules'}[$i]{'device-type'} = $device_type; + $ram->[$handle]{'modules'}[$i]{'device-type-detail'} = lc($device_type_detail); + $ram->[$handle]{'modules'}[$i]{'form-factor'} = $form_factor; + $ram->[$handle]{'modules'}[$i]{'locator'} = $main_locator; + $ram->[$handle]{'modules'}[$i]{'manufacturer'} = $manufacturer; + $ram->[$handle]{'modules'}[$i]{'vendor-id'} = $vendor_id; + $ram->[$handle]{'modules'}[$i]{'part-number'} = $part_number; + $ram->[$handle]{'modules'}[$i]{'serial'} = $serial; + $ram->[$handle]{'modules'}[$i]{'speed'} = $speed; + $ram->[$handle]{'modules'}[$i]{'speed-note'} = $speed_note if $speed_note; + $ram->[$handle]{'modules'}[$i]{'total-width'} = $total_width; + $ram->[$handle]{'modules'}[$i]{'voltage-config'} = $voltage_config; + $ram->[$handle]{'modules'}[$i]{'voltage-max'} = $voltage_max; + $ram->[$handle]{'modules'}[$i]{'voltage-min'} = $voltage_min; + $i++ + } + elsif ($entry->[0] < 17){ + next; + } + elsif ($entry->[0] > 17){ + last; + } + } + print 'dmidecode pre process_data: ', Data::Dumper::Dumper $ram if $dbg[36]; + main::log_data('dump','@$ram',$ram) if $b_log; + process_data($ram) if @$ram; + main::log_data('dump','@$ram',$ram) if $b_log; + print 'dmidecode post process_data: ', Data::Dumper::Dumper $ram if $dbg[36]; + eval $end if $b_log; + return $ram; +} + +sub dboot_data { + eval $start if $b_log; + my $ram = []; + my $est = main::message('note-est'); + my ($arr,$derived_module_size,$slots_active,$subtract) = (0,0,0,0); + my ($holder); + foreach (@{$dboot{'ram'}}){ + my ($addr,$detail,$device_detail,$ecc,$iic,$locator,$size,$speed,$type); + # Note: seen a netbsd with multiline spdmem0/1 etc but not consistent, don't use + if (/^(spdmem([\d]+)):at iic([\d]+)(\saddr 0x([0-9a-f]+))?/){ + $iic = $3; + $locator = $1; + $holder = $iic if !defined $holder; # prime for first use + # Note: seen iic2 as only device + if ($iic != $holder){ + if ($ram->[$arr] && $ram->[$arr]{'slots-16'}){ + $subtract += $ram->[$arr]{'slots-16'}; + } + $holder = $iic; + # Then since we are on a new iic device, assume new ram array. + # This needs more data to confirm this guess. + $arr++; + $slots_active = 0; + } + if ($5){ + $addr = hex($5); + } + if (/(non?[\s-]parity)/i){ + $device_detail = $1; + $ecc = 'None'; + } + elsif (/EEC/i){ + $device_detail = 'EEC'; + $ecc = 'EEC'; + } + # Possible: PC2700CL2.5 PC3-10600 + if (/\b(PC([2-9]?-|)\d{4,})[^\d]/){ + $speed = $1; + $speed =~ s/PC/PC-/ if $speed =~ /^PC\d{4}/; + my $temp = speed_mapper($speed); + if ($temp ne $speed){ + $detail = $speed; + $speed = $temp; + } + } + # We want to avoid netbsd trying to complete @ram without real data. + if (/:(\d+[MGT])B?\s(DDR[0-9]*)\b/){ + $size = main::translate_size($1); # mbfix: /1024 + $type = $2; + $slots_active++; + if ($addr){ + $ram->[$arr]{'slots-16'} = $addr - 80 + 1 - $subtract; + $locator = 'Slot-' . $ram->[$arr]{'slots-16'}; + } + $derived_module_size = $size if $size > $derived_module_size; + $ram->[$arr]{'device-count-found'}++; + # Build up actual capacity found for override tests + $ram->[$arr]{'max-capacity-16'} += $size; + $ram->[$arr]{'max-cap-qualifier'} = $est; + $ram->[$arr]{'slots-16'}++ if !$addr; + $ram->[$arr]{'slots-active'} = $slots_active; + $ram->[$arr]{'slots-qualifier'} = $est; + $ram->[$arr]{'eec'} = $ecc; + $ram->[$arr]{'derived-module-size'} = $derived_module_size; + $ram->[$arr]{'used-capacity'} += $size; + push(@{$ram->[$arr]{'modules'}},{ + 'device-type' => $type, + 'device-type-detail' => $detail, + 'locator' => $locator, + 'size' => $size, + 'speed' => $speed, + }); + } + } + } + for (my $i = 0; $i++ ;scalar @$ram){ + next if ref $ram->[$i] ne 'HASH'; + # 1 slot is possible, but 3 is very unlikely due to dual channel ddr + if ($ram->[$i]{'slots'} && $ram->[$i]{'slots'} > 2 && $ram->[$i]{'slots'} % 2 == 1){ + $ram->[$i]{'slots'}++; + } + } + print 'dboot pre process_data: ', Data::Dumper::Dumper $ram if $dbg[36]; + main::log_data('dump','@$ram',$ram) if $b_log; + process_data($ram) if @$ram; + main::log_data('dump','@$ram',$ram) if $b_log; + print 'dboot post process_data: ', Data::Dumper::Dumper $ram if $dbg[36]; + eval $end if $b_log; + return $ram; +} + +sub process_data { + eval $start if $b_log; + my $ram = $_[0]; + my @result; + my $b_debug = 0; + my $check = main::message('note-check'); + my $est = main::message('note-est'); + foreach my $item (@$ram){ + # Because we use the actual array handle as the index, there will be many + # undefined keys. + next if ! defined $item; + my ($max_cap,$max_mod_size) = (0,0); + my ($alt_cap,$est_cap,$est_mod,$est_slots,$unit) = (0,'','','',''); + $max_cap = $item->{'max-capacity-16'}; + $max_cap ||= 0; + # Make sure they are integers not string if empty. + $item->{'slots-5'} ||= 0; + $item->{'slots-16'} ||= 0; + $item->{'slots-active'} ||= 0; + $item->{'device-count-found'} ||= 0; + $item->{'max-capacity-5'} ||= 0; + $item->{'max-module-size'} ||= 0; + $item->{'used-capacity'} ||= 0; + # $item->{'max-module-size'} = 0;# debugger + # 1: If max cap 1 is null, and max cap 2 not null, use 2 + if ($b_debug){ + print "1: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} "; + print ":mc: $max_cap :uc: $item->{'used-capacity'}\n"; + print "1a: s5: $item->{'slots-5'} s16: $item->{'slots-16'}\n"; + } + if (!$max_cap && $item->{'max-capacity-5'}){ + $max_cap = $item->{'max-capacity-5'}; + } + if ($b_debug){ + print "2: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} "; + print ":mc: $max_cap :uc: $item->{'used-capacity'}\n"; + } + # 2: Now check to see if actually found module sizes are > than listed + # max module, replace if > + if ($item->{'max-module-size'} && $item->{'derived-module-size'} && + $item->{'derived-module-size'} > $item->{'max-module-size'}){ + $item->{'max-module-size'} = $item->{'derived-module-size'}; + $est_mod = $est; + } + if ($b_debug){ + print "3: dcf: $item->{'device-count-found'} :dms: $item->{'derived-module-size'} "; + print ":mc: $max_cap :uc: $item->{'used-capacity'}\n"; + } + # Note: some cases memory capacity == max module size, so one stick will + # fill it but I think only with cases of 2 slots does this happen, so + # if > 2, use the count of slots. + if ($max_cap && ($item->{'device-count-found'} || $item->{'slots-16'})){ + # First check that actual memory found is not greater than listed max cap, + # or checking to see module count * max mod size is not > used capacity + if ($item->{'used-capacity'} && $item->{'max-capacity-16'}){ + if ($item->{'used-capacity'} > $max_cap){ + if ($item->{'max-module-size'} && + $item->{'used-capacity'} < ($item->{'slots-16'} * $item->{'max-module-size'})){ + $max_cap = $item->{'slots-16'} * $item->{'max-module-size'}; + $est_cap = $est; + print "A\n" if $b_debug; + } + elsif ($item->{'derived-module-size'} && + $item->{'used-capacity'} < ($item->{'slots-16'} * $item->{'derived-module-size'})){ + $max_cap = $item->{'slots-16'} * $item->{'derived-module-size'}; + $est_cap = $est; + print "B\n" if $b_debug; + } + else { + $max_cap = $item->{'used-capacity'}; + $est_cap = $est; + print "C\n" if $b_debug; + } + } + } + # Note that second case will never really activate except on virtual + # machines and maybe mobile devices. + if (!$est_cap){ + # Do not do this for only single modules found, max mod size can be + # equal to the array size. + if ($item->{'slots-16'} > 1 && $item->{'device-count-found'} > 1 && + $max_cap < ($item->{'derived-module-size'} * $item->{'slots-16'})){ + $max_cap = $item->{'derived-module-size'} * $item->{'slots-16'}; + $est_cap = $est; + print "D\n" if $b_debug; + } + elsif ($item->{'device-count-found'} > 0 && + $max_cap < ($item->{'derived-module-size'} * $item->{'device-count-found'})){ + $max_cap = $item->{'derived-module-size'} * $item->{'device-count-found'}; + $est_cap = $est; + print "E\n" if $b_debug; + } + # Handle cases where we have type 5 data: mms x device count equals + # type 5 max caphowever do not use it if cap / devices equals the + # derived module size. + elsif ($item->{'max-module-size'} > 0 && + ($item->{'max-module-size'} * $item->{'slots-16'}) == $item->{'max-capacity-5'} && + $item->{'max-capacity-5'} != $item->{'max-capacity-16'} && + $item->{'derived-module-size'} != ($item->{'max-capacity-16'}/$item->{'slots-16'})){ + $max_cap = $item->{'max-capacity-5'}; + $est_cap = $est; + print "F\n" if $b_debug; + } + + } + if ($b_debug){ + print "4: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} "; + print ":mc: $max_cap :uc: $item->{'used-capacity'}\n"; + } + # Some cases of type 5 have too big module max size, just dump the data + # then since we cannot know if it is valid or not, and a guess can be + # wrong easily. + if ($item->{'max-module-size'} && $max_cap && $item->{'max-module-size'} > $max_cap){ + $item->{'max-module-size'} = 0; + } + if ($b_debug){ + print "5: dms: $item->{'derived-module-size'} :s16: $item->{'slots-16'} :mc: $max_cap\n"; + } + # Now prep for rebuilding the ram array data. + if (!$item->{'max-module-size'}){ + # ie: 2x4gB + if (!$est_cap && $item->{'derived-module-size'} > 0 && + $max_cap > ($item->{'derived-module-size'} * $item->{'slots-16'} * 4)){ + $est_cap = $check; + print "G\n" if $b_debug; + } + if ($max_cap && ($item->{'slots-16'} || $item->{'slots-5'})){ + my $slots = 0; + if ($item->{'slots-16'} && $item->{'slots-16'} >= $item->{'slots-5'}){ + $slots = $item->{'slots-16'}; + } + elsif ($item->{'slots-5'} && $item->{'slots-5'} > $item->{'slots-16'}){ + $slots = $item->{'slots-5'}; + } + # print "slots: $slots\n" if $b_debug; + if ($item->{'derived-module-size'} * $slots > $max_cap){ + $item->{'max-module-size'} = $item->{'derived-module-size'}; + print "H\n" if $b_debug; + } + else { + $item->{'max-module-size'} = sprintf("%.f",$max_cap/$slots); + print "J\n" if $b_debug; + } + $est_mod = $est; + } + } + # Case where listed max cap is too big for actual slots x max cap, eg: + # listed max cap, 8gb, max mod 2gb, slots 2 + else { + if (!$est_cap && $item->{'max-module-size'} > 0){ + if ($max_cap > ($item->{'max-module-size'} * $item->{'slots-16'})){ + $est_cap = $check; + print "K\n" if $b_debug; + } + } + } + } + # No slots found due to legacy dmi probably. Note, too many logic errors + # happen if we just set a general slots above, so safest to do it here + $item->{'slots-16'} = $item->{'slots-5'} if $item->{'slots-5'} && !$item->{'slots-16'}; + if (!$item->{'slots-16'} && $item->{'modules'} && ref $item->{'modules'} eq 'ARRAY'){ + $est_slots = $check; + $item->{'slots-16'} = scalar @{$item->{'modules'}}; + print "L\n" if $b_debug; + } + # Only bsds using dmesg data + elsif ($item->{'slots-qualifier'}){ + $est_slots = $item->{'slots-qualifier'}; + $est_cap = $est; + } + $ram_total += $item->{'used-capacity'}; + push(@result, { + 'capacity' => $max_cap, + 'cap-qualifier' => $est_cap, + 'eec' => $item->{'eec'}, + 'location' => $item->{'location'}, + 'max-module-size' => $item->{'max-module-size'}, + 'mod-qualifier' => $est_mod, + 'modules' => $item->{'modules'}, + 'slots' => $item->{'slots-16'}, + 'slots-active' => $item->{'slots-active'}, + 'slots-qualifier' => $est_slots, + 'use' => $item->{'use'}, + 'used-capacity' => $item->{'used-capacity'}, + 'voltage-config' => $item->{'voltage-config'}, + 'voltage-max' => $item->{'voltage-max'}, + 'voltage-min' => $item->{'voltage-min'}, + }); + } + @$ram = @result; + eval $end if $b_log; +} + +sub process_speed { + my ($speed,$device_type,$check) = @_; + my $speed_note; + $speed = main::clean_dmi($speed) if $speed; + if ($device_type && $device_type =~ /ddr/i && $speed && + $speed =~ /^([0-9]+)\s*MHz/){ + $speed = ($1 * 2) . " MT/s ($speed)"; + } + # Seen cases of 1 MT/s, 61690 MT/s, not sure why, bug. Crucial is shipping + # 5100 MT/s now, and 6666 has been hit, so speeds can hit 10k. + if ($speed && $speed =~ /^([0-9]+)\s*M/){ + $speed_note = $check if $1 < 50 || $1 > 20000 ; + } + return [$speed,$speed_note]; +} + +# args: 0: size in KiB +sub process_size { + my ($size) = @_; + my ($b_trim,$unit) = (0,''); + # print "size0: $size\n"; + return 'N/A' if !$size; + # we're going to preserve the bad data for output + return $size if !main::is_numeric($size); + # print "size: $size\n"; + # We only want max 2 decimal places, and only when it's a unit > 1 GiB. + $b_trim = 1 if $size > 1024**2; + ($size,$unit) = main::get_size($size); + $size = sprintf("%.2f",$size) if $b_trim; + $size =~ s/\.[0]+$//; + $size = "$size $unit"; + return $size; +} + +# arg: 0: size string; 1: working size. If calculated result > $size, uses new +# value. If $data not valid, returns 0. +sub calculate_size { + my ($data, $size) = @_; + # Technically k is KiB, K is KB but can't trust that. + if ($data =~ /^([0-9]+\s*[kKGMTP])i?B/){ + my $working = $1; + # This converts it to KiB + my $working_size = main::translate_size($working); + # print "ws-a: $working_size s-1: $size\n"; + if (main::is_numeric($working_size) && $working_size > $size){ + $size = $working_size; + } + # print "ws-b: $working_size s-2: $size\n"; + } + else { + $size = 0; + } + # print "d-2: $data s-3: $size\n"; + return $size; +} + +# BSD: Map string to speed, in MT/s +sub speed_mapper { + my ($type) = @_; + my %speeds = ( + # DDR1 + 'PC-1600' => 200, + 'PC-2100' => 266, + 'PC-2400' => 300, + 'PC-2700' => 333, + 'PC-3200' => 400, + # DDR2 + 'PC2-3200' => 400, + 'PC2-4200' => 533, + 'PC2-5300' => 667, + 'PC2-6400' => 800, + 'PC2-8000' => 1000, + 'PC2-8500' => 1066, + # DDR3 + 'PC3-6400' => 800, + 'PC3-8500' => 1066, + 'PC3-10600' => 1333, + 'PC3-12800' => 1600, + 'PC3-14900 ' => 1866, + 'PC3-17000' => 2133, + # DDR4 + 'PC4-12800' => 1600, + 'PC4-14900' => 1866, + 'PC4-17000' => 2133, + 'PC4-19200' => 2400, + 'PC4-21333' => 2666, + 'PC4-23466' => 2933, + 'PC4-24000' => 3000, + 'PC4-25600' => 3200, + 'PC4-28800' => 3600, + 'PC4-32000' => 4000, + 'PC4-35200' => 4400, + # DDR5 + 'PC5-32000' => 4000, + 'PC5-35200' => 4400, + 'PC5-38400' => 4800, + 'PC5-41600' => 5200, + 'PC5-44800' => 5600, + 'PC5-48000' => 6000, + 'PC5-49600' => 6200, + 'PC5-51200' => 6400, + 'PC5-54400' => 6800, + 'PC5-57600' => 7200, + 'PC5-60800' => 7600, + 'PC5-64000' => 8000, + # DDR6, coming... + ); + return ($speeds{$type}) ? $speeds{$type} . ' MT/s' : $type; +} + + +## START RAM VENDOR ## +sub set_ram_vendors { + $vendors = [ + # A-Data xpg: AX4U; AX\d{4} for axiom + ['^(A[DX]\dU|AVD|A[\s-]?Data)','A[\s-]?Data','A-Data',''], + ['^(A[\s-]?Tech)','A[\s-]?Tech','A-Tech',''], # Don't know part nu + ['^(AX[\d]{4}|Axiom)','Axiom','Axiom',''], + ['^(BD\d|Black[s-]?Diamond)','Black[s-]?Diamond','Black Diamond',''], + ['^(-BN$|Brute[s-]?Networks)','Brute[s-]?Networks','Brute Networks',''], + ['^(CM|Corsair)','Corsair','Corsair',''], + ['^(CT\d|BL|Crucial)','Crucial','Crucial',''], + ['^(CY|Cypress)','Cypress','Cypress',''], + ['^(SNP|Dell)','Dell','Dell',''], + ['^(PE[\d]{4}|Edge)','Edge','Edge',''], + ['^(Elpida|EB)','^Elpida','Elpida',''], + ['^(GVT|Galvantech)','Galvantech','Galvantech',''], + # If we get more G starters, make rules tighter + ['^(G[A-Z]|Geil)','Geil','Geil',''], + # Note: FA- but make loose FA + ['^(F4|G[\s\.-]?Skill)','G[\s\.-]?Skill','G.Skill',''], + ['^(GJN)','GJN','GJN',''], + ['^(HP)','','HP',''], # no IDs found + ['^(HX|HyperX)','HyperX','HyperX',''], + # Qimonda spun out of Infineon, same ids + # ['^(HYS]|Qimonda)','Qimonda','Qimonda',''], + ['^(HY|Infineon)','Infineon','Infineon',''],#HY[A-Z]\d + ['^(KSM|KVR|Kingston)','Kingston','Kingston',''], + ['^(LuminouTek)','LuminouTek','LuminouTek',''], + ['^(MT|Micron)','Micron','Micron',''], + # Seen: 992069 991434 997110S + ['^(M[BLERS][A-Z][1-7]|99[0-9]{3}|Mushkin)','Mushkin','Mushkin',''], + ['^(OCZ)','^OCZ\b','OCZ',''], + ['^([MN]D\d|OLOy)','OLOy','OLOy',''], + ['^(M[ERS]\d|Nemix)','Nemix','Nemix',''], + # Before patriot just in case + ['^(MN\d|PNY)','PNY\s','PNY',''], + ['^(P[A-Z]|Patriot)','Patriot','Patriot',''], + ['^RAMOS','^RAMOS','RAmos',''], + ['^(K[1-6][ABLT]|K\d|M[\d]{3}[A-Z]|Samsung)','Samsung','Samsung',''], + ['^(SP|Silicon[\s-]?Power)','Silicon[\s-]?Power','Silicon Power',''], + ['^(STK|Simtek)','Simtek','Simtek',''], + ['^(Simmtronics|Gamex)','^Simmtronics','Simmtronics',''], + ['^(HM[ACT]|SK[\s-]?Hynix)','SK[\s-]?Hynix','SK-Hynix',''], + # TED TTZD TLRD TDZAD TF4D4 TPD4 TXKD4 seen: HMT but could by skh + #['^(T(ED|D[PZ]|F\d|LZ|P[DR]T[CZ]|XK)|Team[\s-]?Group)','Team[\s-]?Group','TeamGroup',''], + ['^(T[^\dR]|Team[\s-]?Group)','Team[\s-]?Group','TeamGroup',''], + ['^(TR\d|JM\d|Transcend)','Transcend','Transcend',''], + ['^(VK\d|Vaseky)','Vaseky','Vaseky',''], + ['^(Yangtze|Zhitai|YMTC)','(Yangtze(\s*Memory)?|YMTC)','YMTC',''], + ]; +} + +# Note: many of these are pci ids, not confirmed valid for ram +sub set_ram_vendor_ids { + $vendor_ids = { + '01f4' => 'Transcend',# confirmed + '02fe' => 'Elpida',# confirmed + '0314' => 'Mushkin',# confirmed + '0420' => 'Chips and Technologies', + '1014' => 'IBM', + '1099' => 'Samsung', + '10c3' => 'Samsung', + '11e2' => 'Samsung', + '1249' => 'Samsung', + '144d' => 'Samsung', + '15d1' => 'Infineon', + '167d' => 'Samsung', + '196e' => 'PNY', + '1b1c' => 'Corsair', + '1b85' => 'OCZ', + '1c5c' => 'SK-Hynix', + '1cc1' => 'A-Data', + '1e49' => 'YMTC',# Yangtze Memory confirmed + '0215' => 'Corsair',# confirmed + '2646' => 'Kingston', + '2c00' => 'Micron',# confirmed + '5105' => 'Qimonda',# confirmed + '802c' => 'Micron',# confirmed + '80ad' => 'SK-Hynix',# confirmed + '80ce' => 'Samsung',# confirmed + '8551' => 'Qimonda',# confirmed + '8564' => 'Transcend', + 'ad00' => 'SK-Hynix',# confirmed + 'c0a9' => 'Crucial', + 'ce00' => 'Samsung',# confirmed + # '' => '', + } +} +## END RAM VENDOR ## + +sub ram_vendor { + eval $end if $b_log; + my ($id) = $_[0]; + set_ram_vendors() if !$vendors; + my ($vendor); + foreach my $row (@$vendors){ + if ($id =~ /$row->[0]/i){ + $vendor = $row->[2]; + # Usually we want to assign N/A at output phase, maybe do this logic there? + if ($row->[1]){ + if ($id !~ m/$row->[1]$/i){ + $id =~ s/$row->[1]//i; + } + else { + $id = 'N/A'; + } + } + $id =~ s/^[\/\[\s_-]+|[\/\s_-]+$//g; + $id =~ s/\s\s/ /g; + last; + } + } + eval $end if $b_log; + return [$vendor,$id]; +} +} + +## RepoItem +{ +package RepoItem; +# easier to keep these package global, but undef after done +my (@dbg_files,$debugger_dir,%repo_keys); +my $num = 0; + +sub get { + eval $start if $b_log; + ($debugger_dir) = @_; + my $rows = []; + if ($extra > 0 && !$loaded{'package-data'}){ + my $packages = PackageData::get('main',\$num); + for (keys %$packages){ + $rows->[0]{$_} = $packages->{$_}; + } + } + my $start = scalar @$rows; # to test if we found more rows after + $num = 0; + if ($bsd_type){ + get_repos_bsd($rows); + } + else { + get_repos_linux($rows); + } + if ($debugger_dir){ + @$rows = @dbg_files; + undef @dbg_files; + undef $debugger_dir; + undef %repo_keys; + } + else { + if ($start == scalar @$rows){ + my $pm_missing; + if ($bsd_type){ + $pm_missing = main::message('repo-data-bsd',$uname[0]); + } + else { + $pm_missing = main::message('repo-data'); + } + push(@$rows,{main::key($num++,0,1,'Alert') => $pm_missing}); + } + } + eval $end if $b_log; + return $rows; +} + +sub get_repos_linux { + eval $start if $b_log; + my $rows = $_[0]; + my (@content,$data,@data2,@data3,@files,$repo,@repos); + my ($key,$path); + my $apk = '/etc/apk/repositories'; + my $apt = '/etc/apt/sources.list'; + my $apt_termux = '/data/data/com.termux/files/usr' . $apt; + $apt = $apt_termux if -e $apt_termux; # for android termux + my $cards = '/etc/cards.conf'; + my $dnf_conf = '/etc/dnf/dnf.conf'; + my $dnf_repo_dir = '/etc/dnf.repos.d/'; + my $eopkg_dir = '/var/lib/eopkg/'; + my $netpkg = '/etc/netpkg.conf'; + my $netpkg_dir = '/etc/netpkg.d'; + my $nix = '/etc/nix/nix.conf'; + my $pacman = '/etc/pacman.conf'; + my $pacman_g2 = '/etc/pacman-g2.conf'; + my $pisi_dir = '/etc/pisi/'; + my $portage_dir = '/etc/portage/repos.conf/'; + my $portage_gentoo_dir = '/etc/portage-gentoo/repos.conf/'; + my $sbopkg = '/etc/sbopkg/sbopkg.conf'; + my $sboui_backend = '/etc/sboui/sboui-backend.conf'; + my $scratchpkg = '/etc/scratchpkg.repo'; + my $slackpkg = '/etc/slackpkg/mirrors'; + my $slackpkg_plus = '/etc/slackpkg/slackpkgplus.conf'; + my $slapt_get = '/etc/slapt-get/'; + my $slpkg = '/etc/slpkg/repositories.toml'; + my $tce_app = '/usr/bin/tce'; + my $tce_file = '/opt/tcemirror'; + my $tce_file2 = '/opt/localmirrors'; + my $yum_conf = '/etc/yum.conf'; + my $yum_repo_dir = '/etc/yum.repos.d/'; + my $xbps_dir_1 = '/etc/xbps.d/'; + my $xbps_dir_2 = '/usr/share/xbps.d/'; + my $zypp_repo_dir = '/etc/zypp/repos.d/'; + my $b_test = 0; + ## apt: Debian, *buntus + derived (deb files);AltLinux, PCLinuxOS (rpm files) + # Sometimes some yum/rpm repos may create apt repos here as well + if (-f $apt || -d "$apt.d"){ + my ($apt_arch,$apt_comp,$apt_suites,$apt_types,@apt_urls,@apt_working, + $b_apt_enabled,$file,$string); + my $counter = 0; + @files = main::globber("$apt.d/*.list"); + push(@files, $apt); + # prefilter list for logging + @files = grep {-f $_} @files; # may not have $apt file. + main::log_data('data',"apt repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + foreach (sort @files){ + # altlinux/pclinuxos use rpms in apt files, -r to be on safe side + if (-r $_){ + $data = repo_builder($_,'apt','^\s*(deb|rpm)'); + push(@$rows,@$data); + } + } + # @files = main::globber("$fake_data_dir/repo/apt/*.sources"); + @files = main::globber("$apt.d/*.sources"); + # prefilter list for logging, sometimes globber returns non-prsent files. + @files = grep {-f $_} @files; + # @files = ("$fake_data_dir/repo/apt/deb822-u193-3.sources", + # "$fake_data_dir/repo/apt/deb822-u193-3.sourcesdeb822-u193-4-signed-by.sources"); + main::log_data('data',"apt deb822 repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + foreach $file (@files){ + # critical: whitespace is the separator, no logical ordering of + # field names exists within each entry. + @data2 = main::reader($file); + # print Data::Dumper::Dumper \@data2; + if (@data2){ + @data2 = map {s/^\s*$/~/;$_} @data2; + push(@data2, '~'); + } + push(@dbg_files, $file) if $debugger_dir; + # print "$file\n"; + @apt_urls = (); + @apt_working = (); + $b_apt_enabled = 1; + foreach my $row (@data2){ + # NOTE: the syntax of deb822 must be considered a bug, it's sloppy beyond belief. + # deb822 supports line folding which starts with space + # BUT: you can start a URIs: block of urls with a space, sigh. + next if $row =~ /^\s+/ && $row !~ /^\s+[^#]+:\//; + # strip out line space starters now that it's safe + $row =~ s/^\s+//; + # print "$row\n"; + if ($row eq '~'){ + if (@apt_working && $b_apt_enabled){ + # print "1: url builder\n"; + foreach $repo (@apt_working){ + $string = $apt_types; + $string .= ' [arch=' . $apt_arch . ']' if $apt_arch; + $string .= ' ' . $repo; + $string .= ' ' . $apt_suites if $apt_suites ; + $string .= ' ' . $apt_comp if $apt_comp; + # print "s1:$string\n"; + push(@data3, $string); + } + # print join("\n",@data3),"\n"; + push(@apt_urls,@data3); + } + @data3 = (); + @apt_working = (); + $apt_arch = ''; + $apt_comp = ''; + $apt_suites = ''; + $apt_types = ''; + $b_apt_enabled = 1; + } + elsif ($row =~ /^Types:\s*(.*)/i){ + # print "1:$1\n"; + $apt_types = $1; + } + elsif ($row =~ /^Enabled:\s*(.*)/i){ + $b_apt_enabled = ($1 =~ /\b(disable|false|off|no|without)\b/i) ? 0: 1; + } + elsif ($row =~ /^[^#]+:\//){ + my $url = $row; + $url =~ s/^URIs:\s*//i; + push(@apt_working, $url) if $url; + } + elsif ($row =~ /^Suites:\s*(.*)/i){ + $apt_suites = $1; + } + elsif ($row =~ /^Components:\s*(.*)/i){ + $apt_comp = $1; + } + elsif ($row =~ /^Architectures:\s*(.*)/i){ + $apt_arch = $1; + } + } + if (@apt_urls){ + $key = repo_data('active','apt'); + clean_url(\@apt_urls); + } + else { + $key = repo_data('missing','apt'); + } + push(@$rows, + {main::key($num++,1,1,$key) => $file}, + [@apt_urls], + ); + } + @files = (); + } + ## pacman, pacman-g2: Arch + derived, Frugalware + if (-f $pacman || -f $pacman_g2){ + $repo = 'pacman'; + if (-f $pacman_g2){ + $pacman = $pacman_g2; + $repo = 'pacman-g2'; + } + @files = main::reader($pacman,'strip'); + if (@files){ + @repos = grep {/^\s*Server/i} @files; + @files = grep {/^\s*Include/i} @files; + } + if (@files){ + @files = map { + my @working = split(/\s+=\s+/, $_); + $working[1]; + } @files; + } + @files = sort @files; + main::uniq(\@files); + unshift(@files, $pacman) if @repos; + foreach (@files){ + if (-f $_){ + $data = repo_builder($_,$repo,'^\s*Server','\s*=\s*',1); + push(@$rows,@$data); + } + else { + # set it so the debugger knows the file wasn't there + push(@dbg_files, $_) if $debugger_dir; + push(@$rows, + {main::key($num++,1,1,'File listed in') => $pacman}, + [("$_ does not seem to exist.")], + ); + } + } + if (!@$rows){ + push(@$rows, + {main::key($num++,0,1,repo_data('missing','files')) => $pacman }, + ); + } + } + ## netpkg: Zenwalk, Slackware + if (-f $netpkg){ + my @data2 = ($netpkg); + if (-d $netpkg_dir){ + @data3 = main::globber("$netpkg_dir/*"); + @data3 = grep {!/\/local$/} @data3 if @data3; # package directory + push(@data2,@data3) if @data3; + } + foreach my $file (@data2){ + $data = repo_builder($file,'netpkg','^URL\s*=','\s*=\s*',1); + push(@$rows,@$data); + } + } + ## sbopkg, sboui, slackpkg, slackpkg+, slapt_get, slpkg: Slackware + derived + # $slpkg = "$ENV{'HOME'}/bin/scripts/inxi/data/repo/slackware/slpkg-2.toml"; + # $sbopkg = "$ENV{HOME}/bin/scripts/inxi/data/repo/slackware/sbopkg-2.conf"; + # $sboui_backend = "$ENV{HOME}/bin/scripts/inxi/data/repo/slackware/sboui-backend-1.conf"; + if (-f $slackpkg || -f $slackpkg_plus || -d $slapt_get || -f $slpkg || + -f $sbopkg || -f $sboui_backend){ + if (-f $sbopkg){ + my $sbo_root = '/root/.sbopkg.conf'; + # $sbo_root = "$ENV{HOME}/bin/scripts/inxi/data/repo/slackware/sbopkg-root-1.conf"; + @files = ($sbopkg); + # /root not readable as user, unless it is, so just check if readable + push(@files,$sbo_root) if -r $sbo_root; + my ($branch,$name); + # SRC_REPO repo URL not used, not what we think + foreach my $file (@files){ + foreach my $row (main::reader($file,'strip')){ + if ($row =~ /^REPO_NAME=(\S\{REPO_NAME:-)?(.*?)\}?$/){ + $name = $2; + } + elsif ($row =~ /^REPO_BRANCH=(\S\{REPO_BRANCH:-)?(.*?)\}?$/){ + $branch = $2; + } + } + } + # First found overridden by next, so we don't care where the value came + # from. We do care if 1 file and not root however, since might be wrong. + if ($branch && $name){ + if ($b_root || scalar @files == 2){ + $key = repo_data('active','sbopkg'); + } + else { + $key = repo_data('active-permissions','sbopkg'); + } + @content = ("$name ~ $branch"); + } + else { + $key = repo_data('missing','sbopkg'); + } + my @data = ( + {main::key($num++,1,1,$key) => join(', ',@files)}, + [@content], + ); + push(@$rows,@data); + (@content,@files) = (); + } + if (-f $sboui_backend){ + my ($branch,$repo); + # Note: sboui also has a sboui.conf file, with the package_manager string + # but that is too hard to handle clearly in output so leaving aside. + foreach my $row (main::reader($sboui_backend,'strip')){ + if ($row =~ /^REPO\s*=\s*["']?(\S+?)["']?\s*$/){ + $repo = $1; + } + elsif ($row =~ /^BRANCH\s*=\s*["']?(\S+?)["']?\s*$/){ + $branch = $1; + } + } + if ($repo){ + $key = repo_data('active','sboui'); + $branch = 'current' if !$branch || $repo =~ /ponce/i; + @content = ("SBo $branch ~ $repo"); # we want SBo name to show + } + else { + $key = repo_data('missing','sboui'); + } + my @data = ( + {main::key($num++,1,1,$key) => $sboui_backend}, + [@content], + ); + push(@$rows,@data); + @content = (); + } + if (-f $slackpkg){ + $data = repo_builder($slackpkg,'slackpkg','^[[:space:]]*[^#]+'); + push(@$rows,@$data); + } + if (-d $slapt_get){ + @data2 = main::globber("${slapt_get}*"); + @data2 = grep {!/pubring/} @data2 if @data2; + foreach my $file (@data2){ + $data = repo_builder($file,'slaptget','^\s*SOURCE','\s*=\s*',1); + push(@$rows,@$data); + } + } + if (-f $slackpkg_plus){ + push(@dbg_files, $slackpkg_plus) if $debugger_dir; + my (@repoplus_list,$active_repos); + foreach my $row (main::reader($slackpkg_plus,'strip')){ + @data2 = split(/\s*=\s*/, $row); + @data2 = map { $_ =~ s/^\s+|\s+$//g ; $_ } @data2; + last if $data2[0] =~ /^SLACKPKGPLUS/i && $data2[1] eq 'off'; + # REPOPLUS=(slackpkgplus restricted alienbob ktown multilib slacky) + if ($data2[0] =~ /^REPOPLUS/i){ + @repoplus_list = split(/\s+/, $data2[1]); + @repoplus_list = map {s/\(|\)//g; $_} @repoplus_list; + $active_repos = join('|',@repoplus_list); + + } + # MIRRORPLUS['multilib']=http://taper.alienbase.nl/mirrors/people/alien/multilib/14.1/ + if ($active_repos && $data2[0] =~ /^MIRRORPLUS/i){ + $data2[0] =~ s/MIRRORPLUS\[\'|\'\]//ig; + if ($data2[0] =~ /$active_repos/){ + push(@content,"$data2[0] ~ $data2[1]"); + } + } + } + if (!@content){ + $key = repo_data('missing','slackpkg+'); + } + else { + clean_url(\@content); + $key = repo_data('active','slackpkg+'); + } + my @data = ( + {main::key($num++,1,1,$key) => $slackpkg_plus}, + [@content], + ); + push(@$rows,@data); + @content = (); + } + if (-f $slpkg){ + my ($active,$name,$repo); + my $holder = ''; + @data2 = main::reader($slpkg); + # We can't rely on the presence of empty lines as block separator. + push(@data2,'-eof-') if @data2; + # print Data::Dumper::Dumper \@data2; + # old: "https://download.salixos.org/x86_64/slackware-15.0/" + # new: ["https://slac...nl/people/alien/sbrepos/", "15.0/", "x86_64/"] + foreach (@data2){ + next if /^\s*([#\[]|$)/; + $_ = lc($_); + if (/^\s*(\S+?)_(repo(|_name|_mirror))\s*=\s*[\['"]{0,2}(.*?)[\]'"]{0,2}\s*$/ || + $_ eq '-eof-'){ + my ($key,$value) = ($2,$4); + if (($1 && $holder ne $1) || $_ eq '-eof-'){ + $holder = $1; + if ($name && $repo){ + if (!$active || $active =~ /^(true|1|yes)$/i){ + push(@content,"$name ~ $repo"); + } + ($active,$name,$repo) = (); + } + } + if ($key){ + if ($key eq 'repo'){ + $active = $value;} + elsif ($key eq 'repo_name'){ + $name = $value;} + elsif ($key eq 'repo_mirror'){ + # map new form to a real url + $value =~ s/['"],\s*['"]//g; + $repo = $value;} + } + } + } + if (!@content){ + $key = repo_data('missing','slpkg'); + } + else { + # Special case, sbo and ponce true, dump sbo, they conflict. + # slpkg does this internally so no other way to handle. + if (grep {/^ponce ~/} @content){ + @content = grep {!/sbo ~/} @content; + } + clean_url(\@content); + $key = repo_data('active','slpkg'); + } + push(@$rows, + {main::key($num++,1,1,$key) => $slpkg}, + [@content], + ); + (@content,@data2,@data3) = (); + } + } + ## dnf, yum, zypp: Redhat, Suse + derived (rpm based) + if (-f $dnf_conf ||-d $dnf_repo_dir|| -d $yum_repo_dir || -f $yum_conf || + -d $zypp_repo_dir){ + @files = (); + push(@files, $dnf_conf) if -f $dnf_conf; + push(@files, main::globber("$dnf_repo_dir*.repo")) if -d $dnf_repo_dir; + push(@files, $yum_conf) if -f $yum_conf; + push(@files, main::globber("$yum_repo_dir*.repo")) if -d $yum_repo_dir; + if (-d $zypp_repo_dir){ + push(@files, main::globber("$zypp_repo_dir*.repo")); + main::log_data('data',"zypp repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + } + # push(@files, "$fake_data_dir/repo/yum/rpmfusion-nonfree-1.repo"); + if (@files){ + foreach (sort @files){ + @data2 = main::reader($_); + push(@dbg_files, $_) if $debugger_dir; + if (/yum/){ + $repo = 'yum'; + } + elsif (/dnf/){ + $repo = 'dnf'; + } + elsif(/zypp/){ + $repo = 'zypp'; + } + my ($enabled,$url,$title) = (undef,'',''); + foreach my $line (@data2){ + # this is a hack, assuming that each item has these fields listed, we collect the 3 + # items one by one, then when the url/enabled fields are set, we print it out and + # reset the data. Not elegant but it works. Note that if enabled was not present + # we assume it is enabled then, and print the line, reset the variables. This will + # miss the last item, so it is printed if found in END + if ($line =~ /^\[(.+)\]/){ + my $temp = $1; + if ($url && $title && defined $enabled){ + if ($enabled > 0){ + push(@content, "$title ~ $url"); + } + ($enabled,$url,$title) = (undef,'',''); + } + $title = $temp; + } + # Note: it looks like enabled comes before url + elsif ($line =~ /^(metalink|mirrorlist|baseurl)\s*=\s*(.*)/i){ + $url = $2; + } + # note: enabled = 1. enabled = 0 means disabled + elsif ($line =~ /^enabled\s*=\s*(0|1|No|Yes|True|False)/i){ + $enabled = $1; + $enabled =~ s/(No|False)/0/i; + $enabled =~ s/(Yes|True)/1/i; + } + # print out the line if all 3 values are found, otherwise if a new + # repoTitle is hit above, it will print out the line there instead + if ($url && $title && defined $enabled){ + if ($enabled > 0){ + push(@content, "$title ~ $url"); + } + ($enabled,$url,$title) = (0,'',''); + } + } + # print the last one if there is data for it + if ($url && $title && $enabled){ + push(@content, "$title ~ $url"); + } + if (!@content){ + $key = repo_data('missing',$repo); + } + else { + clean_url(\@content); + $key = repo_data('active',$repo); + } + push(@$rows, + {main::key($num++,1,1,$key) => $_}, + [@content], + ); + @content = (); + } + } + # print Data::Dumper::Dumper \@$rows; + } + # emerge, portage: Gentoo + derived + if ((-d $portage_dir || -d $portage_gentoo_dir) && main::check_program('emerge')){ + @files = (main::globber("$portage_dir*.conf"),main::globber("$portage_gentoo_dir*.conf")); + $repo = 'portage'; + if (@files){ + foreach (sort @files){ + @data2 = main::reader($_); + push(@dbg_files, $_) if $debugger_dir; + my ($enabled,$url,$title) = (undef,'',''); + foreach my $line (@data2){ + # this is a hack, assuming that each item has these fields listed, we collect the 3 + # items one by one, then when the url/enabled fields are set, we print it out and + # reset the data. Not elegant but it works. Note that if enabled was not present + # we assume it is enabled then, and print the line, reset the variables. This will + # miss the last item, so it is printed if found in END + if ($line =~ /^\[(.+)\]/){ + my $temp = $1; + if ($url && $title && defined $enabled){ + if ($enabled > 0){ + push(@content, "$title ~ $url"); + } + ($enabled,$url,$title) = (undef,'',''); + } + $title = $temp; + } + elsif ($line =~ /^(sync-uri)\s*=\s*(.*)/i){ + $url = $2; + } + # note: enabled = 1. enabled = 0 means disabled + elsif ($line =~ /^auto-sync\s*=\s*(0|1|No|Yes|True|False)/i){ + $enabled = $1; + $enabled =~ s/(No|False)/0/i; + $enabled =~ s/(Yes|True)/1/i; + } + # print out the line if all 3 values are found, otherwise if a new + # repoTitle is hit above, it will print out the line there instead + if ($url && $title && defined $enabled){ + if ($enabled > 0){ + push(@content, "$title ~ $url"); + } + ($enabled,$url,$title) = (undef,'',''); + } + } + # print the last one if there is data for it + if ($url && $title && $enabled){ + push(@content, "$title ~ $url"); + } + if (! @content){ + $key = repo_data('missing','portage'); + } + else { + clean_url(\@content); + $key = repo_data('active','portage'); + } + push(@$rows, + {main::key($num++,1,1,$key) => $_}, + [@content], + ); + @content = (); + } + } + } + ## apk: Alpine, Chimera + if (-f $apk || -d "$apk.d"){ + @files = main::globber("$apk.d/*.list"); + push(@files, $apk); + # prefilter list for logging + @files = grep {-f $_} @files; # may not have $apk file. + main::log_data('data',"apk repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + foreach (sort @files){ + # -r to be on safe side + if (-r $_){ + $data = repo_builder($_,'apk','^\s*[^#]+'); + push(@$rows,@$data); + } + } + } + ## scratchpkg: Venom + if (-f $scratchpkg){ + $data = repo_builder($scratchpkg,'scratchpkg','^[[:space:]]*[^#]+'); + push(@$rows,@$data); + } + # cards: Nutyx + if (-f $cards){ + @data3 = main::reader($cards,'clean'); + push(@dbg_files, $cards) if $debugger_dir; + foreach (@data3){ + if ($_ =~ /^dir\s+\/[^\|]+\/([^\/\|]+)\s*(\|\s*((http|ftp).*))?/){ + my $type = ($3) ? $3: 'local'; + push(@content, "$1 ~ $type"); + } + } + if (! @content){ + $key = repo_data('missing','cards'); + } + else { + clean_url(\@content); + $key = repo_data('active','cards'); + } + push(@$rows, + {main::key($num++,1,1,$key) => $cards}, + [@content], + ); + @content = (); + } + ## tce: TinyCore + if (-e $tce_app || -f $tce_file || -f $tce_file2){ + if (-f $tce_file){ + $data = repo_builder($tce_file,'tce','^\s*[^#]+'); + push(@$rows,@$data); + } + if (-f $tce_file2){ + $data = repo_builder($tce_file2,'tce','^\s*[^#]+'); + push(@$rows,@$data); + } + } + ## xbps: Void + if (-d $xbps_dir_1 || -d $xbps_dir_2){ + @files = main::globber("$xbps_dir_1*.conf"); + push(@files,main::globber("$xbps_dir_2*.conf")) if -d $xbps_dir_2; + main::log_data('data',"xbps repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + foreach (sort @files){ + if (-r $_){ + $data = repo_builder($_,'xbps','^\s*repository\s*=','\s*=\s*',1); + push(@$rows,@$data); + } + } + } + ## urpmq: Mandriva, Mageia + if ($path = main::check_program('urpmq')){ + @data2 = main::grabber("$path --list-media active --list-url","\n",'strip'); + main::writer("$debugger_dir/system-repo-data-urpmq.txt",\@data2) if $debugger_dir; + # Now we need to create the structure: repo info: repo path. We do that by + # looping through the lines of the output and then putting it back into the + # : format print repos expects to see. Note this structure in the + # data, so store first line and make start of line then when it's an http + # line, add it, and create the full line collection. + # Contrib ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/contrib/release + # Contrib Updates ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/contrib/updates + # Non-free ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/non-free/release + # Non-free Updates ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/non-free/updates + # Nonfree Updates (Local19) /mnt/data/mirrors/mageia/distrib/cauldron/x86_64/media/nonfree/updates + foreach (@data2){ + # Need to dump leading/trailing spaces and clear out color codes for irc output + $_ =~ s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g; + $_ =~ s/\e\[([0-9];)?[0-9]+m//g; + # urpmq output is the same each line, repo name space repo url, can be: + # rsync://, ftp://, file://, http:// OR repo is locally mounted on FS in some cases + if (/(.+)\s([\S]+:\/\/.+)/){ + # pack the repo url + push(@content, $1); + clean_url(\@content); + # get the repo + $repo = $2; + push(@$rows, + {main::key($num++,1,1,'urpm repo') => $repo}, + [@content], + ); + @content = (); + } + } + } + # pisi: Pardus, Solus + if ((-d $pisi_dir && ($path = main::check_program('pisi'))) || + (-d $eopkg_dir && ($path = main::check_program('eopkg')))){ + #$path = 'eopkg'; + my $which = ($path =~ /pisi$/) ? 'pisi': 'eopkg'; + my $cmd = ($which eq 'pisi') ? "$path list-repo": "$path lr"; + # my $file = "$ENV{HOME}/bin/scripts/inxi/data/repo/solus/eopkg-2.txt"; + # @data2 = main::reader($file,'strip'); + @data2 = main::grabber("$cmd 2>/dev/null","\n",'strip'); + main::writer("$debugger_dir/system-repo-data-$which.txt",\@data2) if $debugger_dir; + # Now we need to create the structure: repo info: repo path + # We do that by looping through the lines of the output and then putting it + # back into the : format print repos expects to see. Note this + # structure in the data, so store first line and make start of line then + # when it's an http line, add it, and create the full line collection. + # Pardus-2009.1 [Aktiv] + # http://packages.pardus.org.tr/pardus-2009.1/pisi-index.xml.bz2 + # Contrib [Aktiv] + # http://packages.pardus.org.tr/contrib-2009/pisi-index.xml.bz2 + # Solus [inactive] + # https://packages.solus-project.com/shannon/eopkg-index.xml.xz + foreach (@data2){ + next if /^\s*$/; + # need to dump leading/trailing spaces and clear out color codes for irc output + $_ =~ s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g; + $_ =~ s/\e\[([0-9];)?[0-9]+m//g; + if (/^\/|:\/\//){ + push(@content, $_) if $repo; + } + # Local [inactive] Unstable [active] + elsif (/^(.*)\s\[([\S]+)\]/){ + $repo = $1; + $repo = ($2 =~ /^activ/i) ? $repo : ''; + } + if ($repo && @content){ + clean_url(\@content); + $key = repo_data('active',$which); + push(@$rows, + {main::key($num++,1,1,$key) => $repo}, + [@content], + ); + $repo = ''; + @content = (); + } + } + # last one if present + if ($repo && @content){ + clean_url(\@content); + $key = repo_data('active',$which); + push(@$rows, + {main::key($num++,1,1,$key) => $repo}, + [@content], + ); + } + } + ## nix: General pm for Linux/Unix + if (-f $nix && ($path = main::check_program('nix-channel'))){ + @content = main::grabber("$path --list 2>/dev/null","\n",'strip'); + main::writer("$debugger_dir/system-repo-data-nix.txt",\@content) if $debugger_dir; + if (!@content){ + $key = repo_data('missing','nix'); + } + else { + clean_url(\@content); + $key = repo_data('active','nix'); + } + my $user = ($ENV{'USER'}) ? $ENV{'USER'}: 'N/A'; + push(@$rows, + {main::key($num++,1,1,$key) => $user}, + [@content], + ); + @content = (); + + } + # print Dumper $rows; + eval $end if $b_log; +} + +sub get_repos_bsd { + eval $start if $b_log; + my $rows = $_[0]; + my (@content,$data,@data2,@data3,@files); + my ($key); + my $bsd_pkg = '/usr/local/etc/pkg/repos/'; + my $freebsd = '/etc/freebsd-update.conf'; + my $freebsd_pkg = '/etc/pkg/FreeBSD.conf'; + my $ghostbsd_pkg = '/etc/pkg/GhostBSD.conf'; + my $hardenedbsd_pkg = '/etc/pkg/HardenedBSD.conf'; + my $mports = '/usr/mports/Makefile'; + my $netbsd = '/usr/pkg/etc/pkgin/repositories.conf'; + my $openbsd = '/etc/pkg.conf'; + my $openbsd2 = '/etc/installurl'; + my $portsnap = '/etc/portsnap.conf'; + if (-f $portsnap || -f $freebsd || -d $bsd_pkg || + -f $ghostbsd_pkg || -f $hardenedbsd_pkg){ + if (-f $portsnap){ + $data = repo_builder($portsnap,'portsnap','^\s*SERVERNAME','\s*=\s*',1); + push(@$rows,@$data); + } + if (-f $freebsd){ + $data = repo_builder($freebsd,'freebsd','^\s*ServerName','\s+',1); + push(@$rows,@$data); + } + if (-d $bsd_pkg || -f $freebsd_pkg || -f $ghostbsd_pkg || -f $hardenedbsd_pkg){ + @files = main::globber('/usr/local/etc/pkg/repos/*.conf'); + push(@files, $freebsd_pkg) if -f $freebsd_pkg; + push(@files, $ghostbsd_pkg) if -f $ghostbsd_pkg; + push(@files, $hardenedbsd_pkg) if -f $hardenedbsd_pkg; + if (@files){ + my ($url); + foreach (@files){ + push(@dbg_files, $_) if $debugger_dir; + # these will be result sets separated by an empty line + # first dump all lines that start with # + @content = main::reader($_,'strip'); + # then do some clean up on the lines + @content = map { $_ =~ s/{|}|,|\*//g; $_;} @content if @content; + # get all rows not starting with a # and starting with a non space character + my $url = ''; + foreach my $line (@content){ + if ($line !~ /^\s*$/){ + my @data2 = split(/\s*:\s*/, $line); + @data2 = map { $_ =~ s/^\s+|\s+$//g; $_;} @data2; + if ($data2[0] eq 'url'){ + $url = "$data2[1]:$data2[2]"; + $url =~ s/"|,//g; + } + # print "url:$url\n" if $url; + if ($data2[0] eq 'enabled'){ + if ($url && $data2[1] =~ /^(1|true|yes)$/i){ + push(@data3, "$url"); + } + $url = ''; + } + } + } + if (!@data3){ + $key = repo_data('missing','bsd-package'); + } + else { + clean_url(\@data3); + $key = repo_data('active','bsd-package'); + } + push(@$rows, + {main::key($num++,1,1,$key) => $_}, + [@data3], + ); + @data3 = (); + } + } + } + } + if (-f $openbsd || -f $openbsd2){ + if (-f $openbsd){ + $data = repo_builder($openbsd,'openbsd','^installpath','\s*=\s*',1); + push(@$rows,@$data); + } + if (-f $openbsd2){ + $data = repo_builder($openbsd2,'openbsd','^(http|ftp)','',1); + push(@$rows,@$data); + } + } + if (-f $netbsd){ + # not an empty row, and not a row starting with # + $data = repo_builder($netbsd,'netbsd','^\s*[^#]+$'); + push(@$rows,@$data); + } + # I don't think this is right, have to find out, for midnightbsd + # if (-f $mports){ + # @data = main::reader($mports,'strip'); + # main::writer("$debugger_dir/system-repo-data-mports.txt",\@data) if $debugger_dir; + # for (@data){ + # if (!/^MASTER_SITE_INDEX/){ + # next; + # } + # else { + # push(@data3,(split(/=\s*/,$_))[1]); + # } + # last if /^INDEX/; + # } + # if (!@data3){ + # $key = repo_data('missing','mports'); + # } + # else { + # clean_url(\@data3); + # $key = repo_data('active','mports'); + # } + # push(@$rows, + # {main::key($num++,1,1,$key) => $mports}, + # [@data3], + # ); + # @data3 = (); + # } + # BSDs do not default always to having repo files, so show correct error + # mesage in that case + if (!@$rows){ + if ($bsd_type eq 'freebsd'){ + $key = repo_data('missing','freebsd-files'); + } + elsif ($bsd_type eq 'openbsd'){ + $key = repo_data('missing','openbsd-files'); + } + elsif ($bsd_type eq 'netbsd'){ + $key = repo_data('missing','netbsd-files'); + } + else { + $key = repo_data('missing','bsd-files'); + } + push(@$rows, + {main::key($num++,0,1,'Message') => $key}, + [()], + ); + } + eval $start if $b_log; +} + +sub set_repo_keys { + eval $start if $b_log; + %repo_keys = ( + 'apk-active' => 'APK repo', + 'apk-missing' => 'No active APK repos in', + 'apt-active' => 'Active apt repos in', + 'apt-missing' => 'No active apt repos in', + 'bsd-files-missing' => 'No pkg server files found', + 'bsd-package-active' => 'Enabled pkg servers in', + 'bsd-package-missing' => 'No enabled BSD pkg servers in', + 'cards-active' => 'Active CARDS collections in', + 'cards-missing' => 'No active CARDS collections in', + 'dnf-active' => 'Active dnf repos in', + 'dnf-missing' => 'No active dnf repos in', + 'eopkg-active' => 'Active eopkg repo', + 'eopkg-missing' => 'No active eopkg repos found', + 'files-missing' => 'No repo files found in', + 'freebsd-active' => 'FreeBSD update server', + 'freebsd-files-missing' => 'No FreeBSD update server files found', + 'freebsd-missing' => 'No FreeBSD update servers in', + 'freebsd-pkg-active' => 'FreeBSD default pkg server', + 'freebsd-pkg-missing' => 'No FreeBSD default pkg server in', + 'mports-active' => 'mports servers', + 'mports-missing' => 'No mports servers found', + 'netbsd-active' => 'NetBSD pkg servers', + 'netbsd-files-missing' => 'No NetBSD pkg server files found', + 'netbsd-missing' => 'No NetBSD pkg servers in', + 'netpkg-active' => 'Active netpkg repos in', + 'netpkg-missing' => 'No active netpkg repos in', + 'nix-active' => 'Active nix channels for user', + 'nix-missing' => 'No nix channels found for user', + 'openbsd-active' => 'OpenBSD pkg mirror', + 'openbsd-files-missing' => 'No OpenBSD pkg mirror files found', + 'openbsd-missing' => 'No OpenBSD pkg mirrors in', + 'pacman-active' => 'Active pacman repo servers in', + 'pacman-missing' => 'No active pacman repos in', + 'pacman-g2-active' => 'Active pacman-g2 repo servers in', + 'pacman-g2-missing' => 'No active pacman-g2 repos in', + 'pisi-active' => 'Active pisi repo', + 'pisi-missing' => 'No active pisi repos found', + 'portage-active' => 'Enabled portage sources in', + 'portage-missing' => 'No enabled portage sources in', + 'portsnap-active' => 'Ports server', + 'portsnap-missing' => 'No ports servers in', + 'sbopkg-active' => 'Active sbopkg repo', + 'sbopkg-active-permissions' => 'Active sbopkg repo (confirm with root)', + 'sbopkg-missing' => 'No sbopkg repo', + 'sboui-active' => 'Active sboui repo', + 'sboui-missing' => 'No sboui repo', + 'scratchpkg-active' => 'scratchpkg repos in', + 'scratchpkg-missing' => 'No active scratchpkg repos in', + 'slackpkg-active' => 'slackpkg mirror in', + 'slackpkg-missing' => 'No slackpkg mirror set in', + 'slackpkg+-active' => 'slackpkg+ repos in', + 'slackpkg+-missing' => 'No active slackpkg+ repos in', + 'slaptget-active' => 'slapt-get repos in', + 'slaptget-missing' => 'No active slapt-get repos in', + 'slpkg-active' => 'Active slpkg repos in', + 'slpkg-missing' => 'No active slpkg repos in', + 'tce-active' => 'tce mirrors in', + 'tce-missing' => 'No tce mirrors in', + 'xbps-active' => 'Active xbps repos in', + 'xbps-missing' => 'No active xbps repos in', + 'yum-active' => 'Active yum repos in', + 'yum-missing' => 'No active yum repos in', + 'zypp-active' => 'Active zypp repos in', + 'zypp-missing' => 'No active zypp repos in', + ); + eval $end if $b_log; +} + +sub repo_data { + eval $start if $b_log; + my ($status,$type) = @_; + set_repo_keys() if !%repo_keys; + eval $end if $b_log; + return $repo_keys{$type . '-' . $status}; +} + +sub repo_builder { + eval $start if $b_log; + my ($file,$type,$search,$split,$count) = @_; + my (@content,$key); + push(@dbg_files, $file) if $debugger_dir; + if (-r $file){ + @content = main::reader($file); + @content = grep {/$search/i && !/^\s*$/} @content if @content; + clean_data(\@content) if @content; + } + if ($split && @content){ + @content = map { + my @inner = split(/$split/, $_); + $inner[$count]; + } @content; + } + if (!@content){ + $key = repo_data('missing',$type); + } + else { + $key = repo_data('active',$type); + clean_url(\@content); + } + eval $end if $b_log; + return [ + {main::key($num++,1,1,$key) => $file}, + [@content], + ]; +} + +sub clean_data { + # basics: trim white space, get rid of double spaces; trim comments at + # ends of repo values + @{$_[0]} = map { + $_ =~ s/\s\s+/ /g; + $_ =~ s/^\s+|\s+$//g; + $_ =~ s/\[\s+/[/g; # [ signed-by + $_ =~ s/\s+\]/]/g; + $_ =~ s/^(.*\/.*) #.*/$1/; + $_;} @{$_[0]}; +} + +# Clean if irc +sub clean_url { + @{$_[0]} = map {$_ =~ s/:\//: \//; $_} @{$_[0]} if $b_irc; + # trim comments at ends of repo values + @{$_[0]} = map {$_ =~ s/^(.*\/.*) #.*/$1/; $_} @{$_[0]}; +} + +sub file_path { + my ($filename,$dir) = @_; + my ($working); + $working = $filename; + $working =~ s/^\///; + $working =~ s/\//-/g; + $working = "$dir/file-repo-$working.txt"; + return $working; +} +} + +## SensorItem +{ +package SensorItem; +my $gpu_data = []; +my $sensors_raw = {}; +my $max_fan = 15000; + +sub get { + eval $start if $b_log; + my ($b_data,$b_ipmi,$b_no_lm,$b_no_sys); + my ($message_type,$program,$val1,$sensors); + my ($key1,$num,$rows) = ('Message',0,[]); + my $source = 'sensors'; # will trip some type output if ipmi + another type + # we're allowing 1 or 2 ipmi tools, first the gnu one, then the + # almost certain to be present in BSDs + if ($fake{'ipmi'} || (main::globber('/dev/ipmi**') && + (($program = main::check_program('ipmi-sensors')) || + ($program = main::check_program('ipmitool'))))){ + if ($fake{'ipmi'} || $b_root){ + $sensors = ipmi_data($program); + $b_data = sensors_output($rows,'ipmi',$sensors); + if (!$b_data){ + $val1 = main::message('sensor-data-ipmi'); + push(@$rows,{ + main::key($num++,1,1,'Src') => 'ipmi', + main::key($num++,0,1,$key1) => $val1, + }); + } + } + else { + $key1 = 'Permissions'; + $val1 = main::message('sensor-data-ipmi-root'); + push(@$rows,{ + main::key($num++,1,1,'Src') => 'ipmi', + main::key($num++,0,2,$key1) => $val1, + }); + } + $b_ipmi = 1; + } + $b_data = 0; + if ($bsd_type){ + if ($sysctl{'sensor'}){ + $sensors = sysctl_data(); + $source = 'sysctl' if $b_ipmi; + $b_data = sensors_output($rows,$source,$sensors); + if (!$b_data){ + $source = 'sysctl'; + $val1 = main::message('sensor-data-bsd',$uname[0]); + } + } + else { + if ($bsd_type =~ /^(free|open)bsd/){ + $source = 'sysctl'; + $val1 = main::message('sensor-data-bsd-ok'); + } + else { + $source = 'N/A'; + $val1 = main::message('sensor-data-bsd-unsupported'); + } + } + } + else { + if (!$force{'sensors-sys'} && + ($fake{'sensors'} || $alerts{'sensors'}->{'action'} eq 'use')){ + load_lm_sensors(); + $sensors = linux_sensors_data(); + $source = 'lm-sensors' if $b_ipmi; # trips per sensor type output + $b_data = sensors_output($rows,$source,$sensors); + # print "here 1\n"; + $b_no_lm = 1 if !$b_data; + } + # given recency of full /sys data, we want to prefer lm-sensors for a long time + # and use /sys as a fallback. This will handle servers, which often do not + # have lm-sensors installed, but do have /sys hwmon data. + if (!$b_data && -d '/sys/class/hwmon'){ + load_sys_data(); + $sensors = linux_sensors_data(); + $source = '/sys'; # trips per sensor type output + $b_data = sensors_output($rows,$source,$sensors); + # print "here 2\n"; + $b_no_sys = 1 if !$b_data; + } + if (!$b_data){ + if ($b_no_lm || $b_no_sys){ + if ($b_no_lm && $b_no_sys){ + $source = 'lm-sensors+/sys'; + $val1 = main::message('sensor-data-sys-lm'); + } + elsif ($b_no_lm){ + $source = 'lm-sensors'; + $val1 = main::message('sensor-data-lm-sensors'); + } + else { + $val1 = main::message('sensor-data-sys'); + } + } + elsif (!$fake{'sensors'} && $alerts{'sensors'}->{'action'} ne 'use'){ + # print "here 3\n"; + $source = 'lm-sensors'; + $key1 = $alerts{'sensors'}->{'action'}; + $key1 = ucfirst($key1); + $val1 = $alerts{'sensors'}->{'message'}; + } + else { + $source = 'N/A'; + $val1 = main::message('sensors-data-linux'); + } + } + } + if (!$b_data){ + push(@$rows,{ + main::key($num++,1,1,'Src') => $source, + main::key($num++,0,2,$key1) => $val1, + }); + } + eval $end if $b_log; + return $rows; +} + +sub sensors_output { + eval $start if $b_log; + my ($rows,$source,$sensors) = @_; + my ($b_result,@fan_default,@fan_main); + my $fan_number = 0; + my $num = 0; + my $j = scalar @$rows; + if (!$loaded{'gpu-data'} && + ($source eq 'sensors' || $source eq 'lm-sensors' || $source eq '/sys')){ + gpu_sensor_data(); + } + # gpu sensors data might be present even if standard sensors data wasn't + return if !%$sensors && !@$gpu_data; + $b_result = 1; ## need to trip data found conditions + my $temp_unit = (defined $sensors->{'temp-unit'}) ? " $sensors->{'temp-unit'}": ''; + my $cpu_temp = (defined $sensors->{'cpu-temp'}) ? $sensors->{'cpu-temp'} . $temp_unit: 'N/A'; + my $mobo_temp = (defined $sensors->{'mobo-temp'}) ? $sensors->{'mobo-temp'} . $temp_unit: 'N/A'; + my $cpu1_key = ($sensors->{'cpu2-temp'}) ? 'cpu-1': 'cpu'; + my ($l1,$l2,$l3) = (1,2,3); + if ($source ne 'sensors'){ + $rows->[$j]{main::key($num++,1,1,'Src')} = $source; + ($l1,$l2,$l3) = (2,3,4); + } + $rows->[$j]{main::key($num++,1,$l1,'System Temperatures')} = ''; + $rows->[$j]{main::key($num++,0,$l2,$cpu1_key)} = $cpu_temp; + if ($sensors->{'cpu2-temp'}){ + $rows->[$j]{main::key($num++,0,$l2,'cpu-2')} = $sensors->{'cpu2-temp'} . $temp_unit; + } + if ($sensors->{'cpu3-temp'}){ + $rows->[$j]{main::key($num++,0,$l2,'cpu-3')} = $sensors->{'cpu3-temp'} . $temp_unit; + } + if ($sensors->{'cpu4-temp'}){ + $rows->[$j]{main::key($num++,0,$l2,'cpu-4')} = $sensors->{'cpu4-temp'} . $temp_unit; + } + if (defined $sensors->{'pch-temp'}){ + my $pch_temp = $sensors->{'pch-temp'} . $temp_unit; + $rows->[$j]{main::key($num++,0,$l2,'pch')} = $pch_temp; + } + $rows->[$j]{main::key($num++,0,$l2,'mobo')} = $mobo_temp; + if (defined $sensors->{'sodimm-temp'}){ + my $sodimm_temp = $sensors->{'sodimm-temp'} . $temp_unit; + $rows->[$j]{main::key($num++,0,$l2,'sodimm')} = $sodimm_temp; + } + if (defined $sensors->{'psu-temp'}){ + my $psu_temp = $sensors->{'psu-temp'} . $temp_unit; + $rows->[$j]{main::key($num++,0,$l2,'psu')} = $psu_temp; + } + if (defined $sensors->{'ambient-temp'}){ + my $ambient_temp = $sensors->{'ambient-temp'} . $temp_unit; + $rows->[$j]{main::key($num++,0,$l2,'ambient')} = $ambient_temp; + } + if (scalar @$gpu_data == 1 && defined $gpu_data->[0]{'temp'}){ + my $gpu_temp = $gpu_data->[0]{'temp'}; + my $gpu_type = $gpu_data->[0]{'type'}; + my $gpu_unit = (defined $gpu_data->[0]{'temp-unit'} && $gpu_temp) ? " $gpu_data->[0]{'temp-unit'}" : ' C'; + $rows->[$j]{main::key($num++,1,$l2,'gpu')} = $gpu_type; + $rows->[$j]{main::key($num++,0,$l3,'temp')} = $gpu_temp . $gpu_unit; + if ($extra > 1 && $gpu_data->[0]{'temp-mem'}){ + $rows->[$j]{main::key($num++,0,$l3,'mem')} = $gpu_data->[0]{'temp-mem'} . $gpu_unit; + } + } + $j = scalar @$rows; + @fan_main = @{$sensors->{'fan-main'}} if $sensors->{'fan-main'}; + @fan_default = @{$sensors->{'fan-default'}} if $sensors->{'fan-default'}; + my $fan_def = (!@fan_main && !@fan_default) ? 'N/A' : ''; + $rows->[$j]{main::key($num++,1,$l1,'Fan Speeds (rpm)')} = $fan_def; + my $b_cpu = 0; + for (my $i = 0; $i < scalar @fan_main; $i++){ + next if $i == 0;# starts at 1, not 0 + if (defined $fan_main[$i]){ + if ($i == 1 || ($i == 2 && !$b_cpu)){ + $rows->[$j]{main::key($num++,0,$l2,'cpu')} = $fan_main[$i]; + $b_cpu = 1; + } + elsif ($i == 2 && $b_cpu){ + $rows->[$j]{main::key($num++,0,$l2,'mobo')} = $fan_main[$i]; + } + elsif ($i == 3){ + $rows->[$j]{main::key($num++,0,$l2,'psu')} = $fan_main[$i]; + } + elsif ($i == 4){ + $rows->[$j]{main::key($num++,0,$l2,'sodimm')} = $fan_main[$i]; + } + elsif ($i > 4){ + $fan_number = $i - 4; + $rows->[$j]{main::key($num++,0,$l2,"case-$fan_number")} = $fan_main[$i]; + } + } + } + for (my $i = 0; $i < scalar @fan_default; $i++){ + next if $i == 0;# starts at 1, not 0 + if (defined $fan_default[$i]){ + $rows->[$j]{main::key($num++,0,$l2,"fan-$i")} = $fan_default[$i]; + } + } + $rows->[$j]{main::key($num++,0,$l2,'psu')} = $sensors->{'fan-psu'} if defined $sensors->{'fan-psu'}; + $rows->[$j]{main::key($num++,0,$l2,'psu-1')} = $sensors->{'fan-psu1'} if defined $sensors->{'fan-psu1'}; + $rows->[$j]{main::key($num++,0,$l2,'psu-2')} = $sensors->{'fan-psu2'} if defined $sensors->{'fan-psu2'}; + # note: so far, only nvidia-settings returns speed, and that's in percent + if (scalar @$gpu_data == 1 && defined $gpu_data->[0]{'fan-speed'}){ + my $gpu_fan = $gpu_data->[0]{'fan-speed'} . $gpu_data->[0]{'speed-unit'}; + my $gpu_type = $gpu_data->[0]{'type'}; + $rows->[$j]{main::key($num++,1,$l2,'gpu')} = $gpu_type; + $rows->[$j]{main::key($num++,0,$l3,'fan')} = $gpu_fan; + } + if (scalar @$gpu_data > 1){ + $j = scalar @$rows; + $rows->[$j]{main::key($num++,1,$l1,'GPU')} = ''; + my $gpu_unit = (defined $gpu_data->[0]{'temp-unit'}) ? " $gpu_data->[0]{'temp-unit'}" : ' C'; + foreach my $info (@$gpu_data){ + # speed unit is either '' or % + my $gpu_fan = (defined $info->{'fan-speed'}) ? $info->{'fan-speed'} . $info->{'speed-unit'}: undef; + my $gpu_type = $info->{'type'}; + my $gpu_temp = (defined $info->{'temp'}) ? $info->{'temp'} . $gpu_unit: 'N/A'; + $rows->[$j]{main::key($num++,1,$l2,'device')} = $gpu_type; + if (defined $info->{'screen'}){ + $rows->[$j]{main::key($num++,0,$l3,'screen')} = $info->{'screen'}; + } + $rows->[$j]{main::key($num++,0,$l3,'temp')} = $gpu_temp; + if ($extra > 1 && $info->{'temp-mem'}){ + $rows->[$j]{main::key($num++,0,$l3,'mem')} = $info->{'temp-mem'} . $gpu_unit; + } + if (defined $gpu_fan){ + $rows->[$j]{main::key($num++,0,$l3,'fan')} = $gpu_fan; + } + if ($extra > 2 && $info->{'watts'}){ + $rows->[$j]{main::key($num++,0,$l3,'watts')} = $info->{'watts'}; + } + if ($extra > 2 && $info->{'volts-gpu'}){ + $rows->[$j]{main::key($num++,0,$l3,$info->{'volts-gpu'}[1])} = $info->{'volts-gpu'}[0]; + } + } + } + if ($extra > 0 && ($source eq 'ipmi' || + ($sensors->{'volts-12'} || $sensors->{'volts-5'} || $sensors->{'volts-3.3'} || + $sensors->{'volts-vbat'}))){ + $j = scalar @$rows; + $sensors->{'volts-12'} ||= 'N/A'; + $sensors->{'volts-5'} ||= 'N/A'; + $sensors->{'volts-3.3'} ||= 'N/A'; + $sensors->{'volts-vbat'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,$l1,'Power')} = ''; + $rows->[$j]{main::key($num++,0,$l2,'12v')} = $sensors->{'volts-12'}; + $rows->[$j]{main::key($num++,0,$l2,'5v')} = $sensors->{'volts-5'}; + $rows->[$j]{main::key($num++,0,$l2,'3.3v')} = $sensors->{'volts-3.3'}; + $rows->[$j]{main::key($num++,0,$l2,'vbat')} = $sensors->{'volts-vbat'}; + if ($extra > 1 && $source eq 'ipmi'){ + $sensors->{'volts-dimm-p1'} ||= 'N/A'; + $sensors->{'volts-dimm-p2'} ||= 'N/A'; + if ($sensors->{'volts-dimm-p1'}){ + $rows->[$j]{main::key($num++,0,$l2,'dimm-p1')} = $sensors->{'volts-dimm-p1'}; + } + if ($sensors->{'volts-dimm-p2'}){ + $rows->[$j]{main::key($num++,0,$l2,'dimm-p2')} = $sensors->{'volts-dimm-p2'}; + } + if ($sensors->{'volts-soc-p1'}){ + $rows->[$j]{main::key($num++,0,$l2,'soc-p1')} = $sensors->{'volts-soc-p1'}; + } + if ($sensors->{'volts-soc-p2'}){ + $rows->[$j]{main::key($num++,0,$l2,'soc-p2')} = $sensors->{'volts-soc-p2'}; + } + } + if (scalar @$gpu_data == 1 && $extra > 2 && + ($gpu_data->[0]{'watts'} || $gpu_data->[0]{'volts-gpu'})){ + $rows->[$j]{main::key($num++,1,$l2,'gpu')} = $gpu_data->[0]{'type'}; + if ($gpu_data->[0]{'watts'}){ + $rows->[$j]{main::key($num++,0,$l3,'watts')} = $gpu_data->[0]{'watts'}; + } + if ($gpu_data->[0]{'volts-gpu'}){ + $rows->[$j]{main::key($num++,0,$l3,$gpu_data->[0]{'volts-gpu'}[1])} = $gpu_data->[0]{'volts-gpu'}[0]; + } + } + } + eval $end if $b_log; + return $b_result; +} + +sub ipmi_data { + eval $start if $b_log; + my ($program) = @_; + my ($b_cpu_0,$cmd,$file,@data,$fan_working,@row,$speed,$sys_fan_nu,$temp_working, + $working_unit); + my ($b_ipmitool,$i_key,$i_value,$i_unit); + my $sensors = {}; + if ($fake{'ipmi'}){ + ## ipmitool ## + # $file = "$fake_data_dir/sensors/ipmitool/ipmitool-sensors-archerseven-1.txt";$program='ipmitool'; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensors-epyc-1.txt";$program='ipmitool'; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensors-RK016013.txt";$program='ipmitool'; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensors-freebsd-offsite-backup.txt"; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensor-shom-1.txt";$program='ipmitool'; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensor-shom-2.txt";$program='ipmitool'; + # $file = "$fake_data_dir/sensorsipmitool/ipmitool-sensor-tyan-1.txt";$program='ipmitool'; + # ($b_ipmitool,$i_key,$i_value,$i_unit) = (1,0,1,2); # ipmitool sensors + ## ipmi-sensors ## + # $file = "$fake_data_dir/sensorsipmitool/ipmi-sensors-epyc-1.txt";$program='ipmi-sensors'; + # $file = "$fake_data_dir/sensorsipmitool/ipmi-sensors-lathander.txt";$program='ipmi-sensors'; + # $file = "$fake_data_dir/sensorsipmitool/ipmi-sensors-zwerg.txt";$program='ipmi-sensors'; + # $file = "$fake_data_dir/sensorsipmitool/ipmi-sensors-arm-server-1.txt";$program='ipmi-sensors'; + # ($b_ipmitool,$i_key,$i_value,$i_unit) = (0,1,3,4); # ipmi-sensors + # @data = main::reader($file); + } + else { + if ($program =~ /ipmi-sensors$/){ + $cmd = $program; + ($b_ipmitool,$i_key,$i_value,$i_unit) = (0,1,3,4); + } + else { # ipmitool + $cmd = "$program sensor"; # note: 'sensor' NOT 'sensors' !! + ($b_ipmitool,$i_key,$i_value,$i_unit) = (1,0,1,2); + } + @data = main::grabber("$cmd 2>/dev/null"); + } + # print join("\n", @data), "\n"; + # shouldn't need to log, but saw a case with debugger ipmi data, but none here apparently + main::log_data('dump','ipmi @data',\@data) if $b_log; + return $sensors if !@data; + foreach (@data){ + next if /^\s*$/; + # print "$_\n"; + @row = split(/\s*\|\s*/, $_); + # print "$row[$i_value]\n"; + next if !main::is_numeric($row[$i_value]); + # print "$row[$i_key] - $row[$i_value]\n"; + if (!$sensors->{'mobo-temp'} && $row[$i_key] =~ /^(MB[\s_-]?TEMP[0-9]|System[\s_-]?Temp|System[\s_-]?Board([\s_-]?Temp)?)$/i){ + $sensors->{'mobo-temp'} = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($row[$i_key] =~ /^(System[\s_-]?)?(Ambient)([\s_-]?Temp)?$/i){ + $sensors->{'ambient-temp'} = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # Platform Control Hub (PCH), it is the X370 chip on the Crosshair VI Hero. + # VRM: voltage regulator module + # NOTE: CPU0_TEMP CPU1_TEMP is possible, unfortunately; CPU Temp Interf + elsif (!$sensors->{'cpu-temp'} && $row[$i_key] =~ /^CPU[\s_-]?([01])?([\s_](below[\s_]Tmax|Temp))?$/i){ + $b_cpu_0 = 1 if defined $1 && $1 == 0; + $sensors->{'cpu-temp'} = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($row[$i_key] =~ /^CPU[\s_-]?([1-4])([\s_](below[\s_]Tmax|Temp))?$/i){ + $temp_working = $1; + $temp_working++ if $b_cpu_0; + $sensors->{"cpu${temp_working}-temp"} = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # for temp1/2 only use temp1/2 if they are null or greater than the last ones + elsif ($row[$i_key] =~ /^(MB[\s_-]?TEMP1|Temp[\s_]1)$/i){ + $temp_working = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + if (!$sensors->{'temp1'} || (defined $temp_working && $temp_working > 0)){ + $sensors->{'temp1'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($row[$i_key] =~ /^(MB[_]?TEMP2|Temp[\s_]2)$/i){ + $temp_working = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + if (!$sensors->{'temp2'} || (defined $temp_working && $temp_working > 0)){ + $sensors->{'temp2'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # temp3 is only used as an absolute override for systems with all 3 present + elsif ($row[$i_key] =~ /^(MB[_]?TEMP3|Temp[\s_]3)$/i){ + $temp_working = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + if (!$sensors->{'temp3'} || (defined $temp_working && $temp_working > 0)){ + $sensors->{'temp3'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif (!$sensors->{'sodimm-temp'} && ($row[$i_key] =~ /^(DIMM[-_]([A-Z][0-9]+[-_])?[A-Z]?[0-9]+[A-Z]?)$/i || + $row[$i_key] =~ /^DIMM\s?[0-9]+ (Area|Temp).*/)){ + $sensors->{'sodimm-temp'} = int($row[$i_value]); + $working_unit = $row[$i_unit]; + $working_unit =~ s/degrees\s// if $b_ipmitool; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # note: can be cpu fan:, cpu fan speed:, etc. + elsif ($row[$i_key] =~ /^(CPU|Processor)[\s_]Fan/i || + $row[$i_key] =~ /^SYS\.[0-9][\s_]?\(CPU\s?0\)$/i){ + $speed = int($row[$i_value]); + $sensors->{'fan-main'}->[1] = $speed if $speed < $max_fan; + } + # note that the counters are dynamically set for fan numbers here + # otherwise you could overwrite eg aux fan2 with case fan2 in theory + # note: cpu/mobo/ps are 1/2/3 + # SYS.3(Front 2) + # $row[$i_key] =~ /^(SYS[\.])([0-9])\s?\((Front|Rear).+\)$/i + elsif ($row[$i_key] =~ /^(SYS[\s_])?FAN[\s_]?([0-9A-F]+)/i){ + $sys_fan_nu = hex($2); + $fan_working = int($row[$i_value]); + next if $fan_working > $max_fan; + $sensors->{'fan-default'} = () if !$sensors->{'fan-default'}; + if ($sys_fan_nu =~ /^([0-9]+)$/){ + # add to array if array index does not exist OR if number is > existing number + if (defined $sensors->{'fan-default'}->[$sys_fan_nu]){ + if ($fan_working >= $sensors->{'fan-default'}->[$sys_fan_nu]){ + $sensors->{'fan-default'}->[$sys_fan_nu] = $fan_working; + } + } + else { + $sensors->{'fan-default'}->[$sys_fan_nu] = $fan_working; + } + } + } + elsif ($row[$i_key] =~ /^(FAN PSU|PSU FAN)$/i){ + $speed = int($row[$i_value]); + $sensors->{'fan-psu'} = $speed if $speed < $max_fan; + } + elsif ($row[$i_key] =~ /^(FAN PSU1|PSU1 FAN)$/i){ + $speed = int($row[$i_value]); + $sensors->{'fan-psu-1'} = $speed if $speed < $max_fan; + } + elsif ($row[$i_key] =~ /^(FAN PSU2|PSU2 FAN)$/i){ + $speed = int($row[$i_value]); + $sensors->{'fan-psu-2'} = $speed if $speed < $max_fan; + } + if ($extra > 0){ + if ($row[$i_key] =~ /^((.+\s|P[_]?)?\+?12V|PSU[12]_VOUT)$/i){ + $sensors->{'volts-12'} = $row[$i_value]; + } + elsif ($row[$i_key] =~ /^(.+\s5V|P5V|5VCC|5V( PG)?|5V_SB)$/i){ + $sensors->{'volts-5'} = $row[$i_value]; + } + elsif ($row[$i_key] =~ /^(.+\s3\.3V|P3V3|3\.3VCC|3\.3V( PG)?|3V3_SB)$/i){ + $sensors->{'volts-3.3'} = $row[$i_value]; + } + elsif ($row[$i_key] =~ /^((P_)?VBAT|CMOS Battery|BATT 3.0V)$/i){ + $sensors->{'volts-vbat'} = $row[$i_value]; + } + # NOTE: VDimmP1ABC VDimmP1DEF + elsif (!$sensors->{'volts-dimm-p1'} && $row[$i_key] =~ /^(P1_VMEM|VDimmP1|MEM RSR A PG|DIMM_VR1_VOLT)/i){ + $sensors->{'volts-dimm-p1'} = $row[$i_value]; + } + elsif (!$sensors->{'volts-dimm-p2'} && $row[$i_key] =~ /^(P2_VMEM|VDimmP2|MEM RSR B PG|DIMM_VR2_VOLT)/i){ + $sensors->{'volts-dimm-p2'} = $row[$i_value]; + } + elsif (!$sensors->{'volts-soc-p1'} && $row[$i_key] =~ /^(P1_SOC_RUN$)/i){ + $sensors->{'volts-soc-p1'} = $row[$i_value]; + } + elsif (!$sensors->{'volts-soc-p2'} && $row[$i_key] =~ /^(P2_SOC_RUN$)/i){ + $sensors->{'volts-soc-p2'} = $row[$i_value]; + } + } + } + print Data::Dumper::Dumper $sensors if $dbg[31]; + process_data($sensors) if %$sensors; + main::log_data('dump','ipmi: %$sensors',$sensors) if $b_log; + eval $end if $b_log; + print Data::Dumper::Dumper $sensors if $dbg[31]; + return $sensors; +} + +sub linux_sensors_data { + eval $start if $b_log; + my $sensors = {}; + my ($sys_fan_nu) = (0); + my ($adapter,$fan_working,$temp_working,$working_unit) = ('','','','',''); + foreach $adapter (keys %{$sensors_raw->{'main'}}){ + next if !$adapter || ref $sensors_raw->{'main'}{$adapter} ne 'ARRAY'; + # not sure why hwmon is excluded, forgot to add info in comments + if ((@sensors_use && !(grep {/$adapter/} @sensors_use)) || + (@sensors_exclude && (grep {/$adapter/} @sensors_exclude))){ + next; + } + foreach (@{$sensors_raw->{'main'}{$adapter}}){ + my @working = split(':', $_); + next if !$working[0]; + # print "$working[0]:$working[1]\n"; + # There are some guesses here, but with more sensors samples it will get closer. + # note: using arrays starting at 1 for all fan arrays to make it easier overall + # we have to be sure we are working with the actual real string before assigning + # data to real variables and arrays. Extracting C/F degree unit as well to use + # when constructing temp items for array. + # note that because of charset issues, no "°" degree sign used, but it is required + # in testing regex to avoid error. It might be because I got that data from a forum post, + # note directly via debugger. + if ($_ =~ /^T?(AMBIENT|M\/B|MB|Motherboard|SIO|SYS).*:([0-9\.]+)[\s°]*(C|F)/i){ + # avoid SYSTIN: 118 C + if (main::is_numeric($2) && $2 < 90){ + $sensors->{'mobo-temp'} = $2; + $working_unit = $3; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + } + # issue 58 msi/asus show wrong for CPUTIN so overwrite it if PECI 0 is present + # http://www.spinics.net/lists/lm-sensors/msg37308.html + # NOTE: had: ^CPU.*\+([0-9]+): but that misses: CPUTIN and anything not with + in starter + # However, "CPUTIN is not a reliable measurement because it measures difference to Tjmax, + # which is the maximum CPU temperature reported as critical temperature by coretemp" + # NOTE: I've seen an inexplicable case where: CPU:52.0°C fails to match with [\s°] but + # does match with: [\s°]*. I can't account for this, but that's why the * is there + # Tdie is a new k10temp-pci syntax for real cpu die temp. Tctl is cpu control value, + # NOT the real cpu die temp: UNLESS tctl and tdie are equal, sigh.. + elsif ($_ =~ /^(Chip 0.*?|T?CPU.*|Tdie.*):([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $2; + $working_unit = $3; + if (!$sensors->{'cpu-temp'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'cpu-temp'})){ + $sensors->{'cpu-temp'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($_ =~ /^(Tctl.*):([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $2; + $working_unit = $3; + if (!$sensors->{'tctl-temp'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'tctl-temp'})){ + $sensors->{'tctl-temp'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($_ =~ /^PECI\sAgent\s0.*:([0-9\.]+)[\s°]*(C|F)/i){ + $sensors->{'cpu-peci-temp'} = $1; + $working_unit = $2; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($_ =~ /^T?(P\/S|Power).*:([0-9\.]+)[\s°]*(C|F)/i){ + $sensors->{'psu-temp'} = $2; + $working_unit = $3; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($_ =~ /^T?(dimm|mem|sodimm).*?:([0-9\.]+)[\s°]*(C|F)/i){ + $sensors->{'sodimm-temp'} = $1; + $working_unit = $2; + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # for temp1/2 only use temp1/2 if they are null or greater than the last ones + elsif ($_ =~ /^temp1:([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $1; + $working_unit = $2; + if (!$sensors->{'temp1'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'temp1'})){ + $sensors->{'temp1'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + elsif ($_ =~ /^temp2:([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $1; + $working_unit = $2; + if (!$sensors->{'temp2'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'temp2'})){ + $sensors->{'temp2'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # temp3 is only used as an absolute override for systems with all 3 present + elsif ($_ =~ /^temp3:([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $1; + $working_unit = $2; + if (!$sensors->{'temp3'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'temp3'})){ + $sensors->{'temp3'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # final fallback if all else fails, funtoo user showed sensors putting + # temp on wrapped second line, not handled + elsif ($_ =~ /^T?(core0|core 0|Physical id 0)(.*):([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $3; + $working_unit = $4; + if (!$sensors->{'core-0-temp'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'core-0-temp'})){ + $sensors->{'core-0-temp'} = $temp_working; + } + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit) if $working_unit; + } + # note: can be cpu fan:, cpu fan speed:, etc. + elsif (!defined $sensors->{'fan-main'}->[1] && $_ =~ /^F?(CPU|Processor).*:([0-9]+)[\s]RPM/i){ + $sensors->{'fan-main'}->[1] = $2 if $2 < $max_fan; + } + elsif (!defined $sensors->{'fan-main'}->[2] && $_ =~ /^F?(M\/B|MB|SYS|Motherboard).*:([0-9]+)[\s]RPM/i){ + $sensors->{'fan-main'}->[2] = $2 if $2 < $max_fan; + } + elsif (!defined $sensors->{'fan-main'}->[3] && $_ =~ /F?(Power|P\/S|POWER).*:([0-9]+)[\s]RPM/i){ + $sensors->{'fan-main'}->[3] = $2 if $2 < $max_fan; + } + elsif (!defined $sensors->{'fan-main'}->[4] && $_ =~ /F?(dimm|mem|sodimm).*:([0-9]+)[\s]RPM/i){ + $sensors->{'fan-main'}->[4] = $2 if $2 < $max_fan; + } + # note that the counters are dynamically set for fan numbers here + # otherwise you could overwrite eg aux fan2 with case fan2 in theory + # note: cpu/mobo/ps/sodimm are 1/2/3/4 + elsif ($_ =~ /^F?(AUX|CASE|CHASSIS|FRONT|REAR).*:([0-9]+)[\s]RPM/i){ + next if $2 > $max_fan; + $temp_working = $2; + for (my $i = 5; $i < 30; $i++){ + next if defined $sensors->{'fan-main'}->[$i]; + if (!defined $sensors->{'fan-main'}->[$i]){ + $sensors->{'fan-main'}->[$i] = $temp_working; + last; + } + } + } + # in rare cases syntax is like: fan1: xxx RPM + elsif ($_ =~ /^FAN(1)?:([0-9]+)[\s]RPM/i){ + $sensors->{'fan-default'}->[1] = $2 if $2 < $max_fan; + } + elsif ($_ =~ /^FAN([2-9]|1[0-9]).*:([0-9]+)[\s]RPM/i){ + next if $2 > $max_fan; + $fan_working = $2; + $sys_fan_nu = $1; + if ($sys_fan_nu =~ /^([0-9]+)$/){ + # add to array if array index does not exist OR if number is > existing number + if (defined $sensors->{'fan-default'}->[$sys_fan_nu]){ + if ($fan_working >= $sensors->{'fan-default'}->[$sys_fan_nu]){ + $sensors->{'fan-default'}->[$sys_fan_nu] = $fan_working; + } + } + else { + $sensors->{'fan-default'}->[$sys_fan_nu] = $fan_working; + } + } + } + if ($extra > 0){ + if ($_ =~ /^[+]?(12 Volt|12V|V\+?12).*:([0-9\.]+)\sV/i){ + $sensors->{'volts-12'} = $2; + } + # note: 5VSB is a field name + elsif ($_ =~ /^[+]?(5 Volt|5V|V\+?5):([0-9\.]+)\sV/i){ + $sensors->{'volts-5'} = $2; + } + elsif ($_ =~ /^[+]?(3\.3 Volt|3\.3V|V\+?3\.3).*:([0-9\.]+)\sV/i){ + $sensors->{'volts-3.3'} = $2; + } + elsif ($_ =~ /^(Vbat).*:([0-9\.]+)\sV/i){ + $sensors->{'volts-vbat'} = $2; + } + elsif ($_ =~ /^v(dimm|mem|sodimm).*:([0-9\.]+)\sV/i){ + $sensors->{'volts-mem'} = $2; + } + } + } + } + foreach $adapter (keys %{$sensors_raw->{'pch'}}){ + next if !$adapter || ref $sensors_raw->{'pch'}{$adapter} ne 'ARRAY'; + if ((@sensors_use && !(grep {/$adapter/} @sensors_use)) || + (@sensors_exclude && (grep {/$adapter/} @sensors_exclude))){ + next; + } + $temp_working = ''; + foreach (@{$sensors_raw->{'pch'}{$adapter}}){ + if ($_ =~ /^[^:]+:([0-9\.]+)[\s°]*(C|F)/i){ + $temp_working = $1; + $working_unit = $2; + if (!$sensors->{'pch-temp'} || + (defined $temp_working && $temp_working > 0 && $temp_working > $sensors->{'pch-temp'})){ + $sensors->{'pch-temp'} = $temp_working; + } + if (!$sensors->{'temp-unit'} && $working_unit){ + $sensors->{'temp-unit'} = set_temp_unit($sensors->{'temp-unit'},$working_unit); + } + } + } + } + print Data::Dumper::Dumper $sensors if $dbg[31]; + process_data($sensors) if %$sensors; + main::log_data('dump','lm-sensors: %sensors',$sensors) if $b_log; + print Data::Dumper::Dumper $sensors if $dbg[31]; + eval $end if $b_log; + return $sensors; +} + +sub load_lm_sensors { + eval $start if $b_log; + my (@sensors_data,@values); + my ($adapter,$holder,$type) = ('','',''); + if ($fake{'sensors'}){ + # my $file; + # $file = "$fake_data_dir/sensors/lm-sensors/amdgpu-w-fan-speed-stretch-k10.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/peci-tin-geggo.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-w-other-biker.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-asus-chassis-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-devnull-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-jammin1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-mx-incorrect-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-maximus-arch-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/kernel-58-sensors-ant-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-zenpower-nvme-2.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-pch-intel-1.txt"; + # $file = "$fake_data_dir/sensors/slm-sensors/ensors-ppc-sr71.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-coretemp-acpitz-1.txt"; + # $file = "$fake_data_dir/sensors/lm-sensors/sensors-applesmc-1.txt"; + # @sensors_data = main::reader($file); + } + else { + # only way to get sensor array data? Unless using sensors -j, but can't assume json + @sensors_data = main::grabber($alerts{'sensors'}->{'path'} . ' 2>/dev/null'); + } + # print join("\n", @sensors_data), "\n"; + if (@sensors_data){ + @sensors_data = map {$_ =~ s/\s*:\s*\+?/:/;$_} @sensors_data; + push(@sensors_data, 'END'); + } + # print Data::Dumper::Dumper \@sensors_data; + foreach (@sensors_data){ + # print 'st:', $_, "\n"; + next if /^\s*$/; + $_ = main::trimmer($_); + if (@values && $adapter && (/^Adapter/ || $_ eq 'END')){ + # note: drivetemp: known, but many others could exist + if ($adapter =~ /^(drive|nvme)/){ + $type = 'disk'; + } + elsif ($adapter =~ /^(BAT)/){ + $type = 'bat'; + } + # intel on die io controller, like southbridge/northbridge used to be + elsif ($adapter =~ /^(pch[_-])/){ + $type = 'pch'; + } + elsif ($adapter =~ /^(.*hwmon)-/){ + $type = 'hwmon'; + } + # ath/iwl: wifi; enp/eno/eth/i350bb: lan nic + elsif ($adapter =~ /^(ath|i350bb|iwl|en[op][0-9]|eth)[\S]+-/){ + $type = 'network'; + } + # put last just in case some other sensor type above had intel in name + elsif ($adapter =~ /^(amdgpu|intel|nouveau|radeon)-/){ + $type = 'gpu'; + } + elsif ($adapter =~ /^(acpitz)-/ && $adapter !~ /^(acpitz-virtual)-/ ){ + $type = 'acpitz'; + } + else { + $type = 'main'; + } + $sensors_raw->{$type}{$adapter} = [@values]; + @values = (); + $adapter = ''; + } + if (/^Adapter/){ + $adapter = $holder; + } + elsif (/\S:\S/){ + push(@values, $_); + } + else { + $holder = $_; + } + } + print 'lm sensors: ' , Data::Dumper::Dumper $sensors_raw if $dbg[18]; + main::log_data('dump','lm-sensors data: %$sensors_raw',$sensors_raw) if $b_log; + eval $end if $b_log; +} + +sub load_sys_data { + eval $start if $b_log; + my ($device,$mon,$name,$label,$unit,$value,@values,%hwmons); + my ($j,$holder,$sensor,$type) = (0,'','',''); + my $glob = '/sys/class/hwmon/hwmon*/'; + $glob .= '{name,device,{curr,fan,in,power,temp}*_{input,label}}'; + my @hwmon = main::globber($glob); + # print Data::Dumper::Dumper \@sensors_data; + @hwmon = sort @hwmon; + push(@hwmon,'END'); + foreach my $item (@hwmon){ + next if ! -e $item; + $item =~ m|/sys/class/hwmon/(hwmon\d+)/|; + $mon = $1; + $mon =~ s/hwmon(\d)$/hwmon0$1/ if $mon =~ /hwmon\d$/; + # if it's a new hwmon, dump all previous data to avoid carry-over + if (!defined $hwmons{$mon}){ + $sensor = ''; + $holder = ''; + $j = 0; + } + if ($item =~ m/([^\/]+)_input$/){ + $sensor = $1; + $value = main::reader($item,'strip',0);; + } + # add the label to the just created _input item, if valid + elsif ($item =~ m/([^\/]+)_label$/){ + print "3: mon: $mon id: $sensor holder: $holder file: $item\n" if $dbg[51]; + # if this doesn't match, something unexpected happened, like no _input for + # _label item. Seen that, real. + next if !$holder || $1 ne $holder; + if (defined $hwmons{$mon}->{'sensors'}[$j]{'id'}){ + $sensor = $1; + $hwmons{$mon}->{'sensors'}[$j]{'label'} = main::reader($item,'strip',0); + } + } + if ($sensor && ($sensor ne $holder || $item eq 'END')){ + print "2: mon: $mon id: $sensor holder: $holder file: $item\n" if $dbg[51]; + # add the item, we'll add label after if it's located since it will be next + # in loop due to sort order. + if ($value){ + push(@{$hwmons{$mon}->{'sensors'}},{ + 'id' => $sensor, + 'value' => $value, + }); + $j = $#{$hwmons{$mon}->{'sensors'}}; + } + $holder = $sensor; + ($sensor,$value) = ('',undef,undef); + } + print "1: mon: $mon id: $sensor holder: $holder file: $item\n" if $dbg[51]; + # print "$item\n"; + if ($item =~ /name$/){ + $name = main::reader($item,'strip',0); + if ($name =~ /^(drive|nvme)/){ + $type = 'disk'; + } + elsif ($name =~ /^(BAT)/i){ + $type = 'bat'; + } + # intel on die io controller, like southbridge/northbridge used to be + elsif ($name =~ /^(pch)/){ + $type = 'pch'; + } + elsif ($name =~ /^(.*hwmon)/){ + $type = 'hwmon'; + } + # ath/iwl: wifi; enp/eno/eth/i350bb: lan nic + elsif ($name =~ /^(ath|i350|iwl|en[op][0-9]|eth)[\S]/){ + $type = 'network'; + } + # put last just in case some other sensor type above had intel in name + elsif ($name =~ /^(amdgpu|intel|nouveau|radeon)/){ + $type = 'gpu'; + } + # not confirmed in /sys that name will be acpitz-virtual, verify + elsif ($name =~ /^(acpitz)/ && $name !~ /^(acpitz-virtual)/ ){ + $type = 'acpitz'; + } + else { + $type = 'main'; + } + $hwmons{$mon}->{'name'} = $name; + $hwmons{$mon}->{'type'} = $type; + } + elsif ($item =~ /device$/){ + $device = readlink($item); + print "device: $device\n" if $dbg[51]; + $device =~ s|^.*/||; + $hwmons{$mon}->{'device'} = $device; + } + } + print '/sys/class/hwmon raw: ', Data::Dumper::Dumper \%hwmons if $dbg[18]; + main::log_data('dump','/sys data raw: %hwmons',\%hwmons) if $b_log; + # $sensors_raw->{$type}{$adapter} = [@values]; + foreach my $hwmon (sort keys %hwmons){ + my $adapter = $hwmons{$hwmon}->{'name'}; + $hwmons{$hwmon}->{'device'} =~ s/^0000://; + $adapter .= '-' . $hwmons{$hwmon}->{'device'}; + ($unit,$value,@values) = (); + foreach my $item (@{$hwmons{$hwmon}->{'sensors'}}){ + next if !defined $item->{'id'}; + my $name = ($item->{'label'}) ? $item->{'label'}: $item->{'id'}; + if ($item->{'id'} =~ /^temp/){ + $unit = 'C'; + $value = sprintf('%0.1f',$item->{'value'}/1000); + } + elsif ($item->{'id'} =~ /^fan/){ + $unit = 'rpm'; + $value = $item->{'value'}; + } + # note: many sensors require further math on value, so these will be wrong + # in many cases since this is not running the math on the results like + # lm-sensors will do if sensors are detected and loaded and configured. + elsif ($item->{'id'} =~ /^in\d/){ + if ($item->{'value'} >= 1000){ + $unit = 'V'; + $value = sprintf('%0.2f',$item->{'value'}/1000) + 0; + if ($hwmons{$hwmon}->{'type'} eq 'main' && $name =~ /^in\d/){ + if ($value >= 10 && $value <= 14){ + $name = '12V'; + } + elsif ($value >= 4 && $value <= 6){ + $name = '5V'; + } + # vbat can be 3, 3.3, but so can 3.3V board + } + } + else { + $unit = 'mV'; + $value = $item->{'value'}; + } + } + elsif ($item->{'id'} =~ /^power/){ + $unit = 'W'; + $value = sprintf('%0.1f',$item->{'value'}/1000); + } + if (defined $value && defined $unit){ + my $string = $name . ':' . $value . " $unit"; + push(@values,$string); + } + } + # if ($hwmons{$hwmon}->{'type'} eq 'acpitz' && $hwmons{$hwmon}->{'device'}){ + # my $tz ='/sys/class/thermal/' . $hwmons{$hwmon}->{'device'} . '/type'; + # if (-e $tz){ + # my $tz_type = main::reader($tz,'strip',0),"\n"; + # } + # } + if (@values){ + $sensors_raw->{$hwmons{$hwmon}->{'type'}}{$adapter} = [@values]; + } + } + print '/sys/class/hwmon processed: ' , Data::Dumper::Dumper $sensors_raw if $dbg[18]; + main::log_data('dump','/sys data: %$sensors_raw',$sensors_raw) if $b_log; + eval $end if $b_log; +} + +# bsds sysctl may have hw.sensors data +sub sysctl_data { + eval $start if $b_log; + my (@data); + my $sensors = {}; + # assume always starts at 0, can't do dynamic because freebsd shows tz1 first + my $add = 1; + print Data::Dumper::Dumper $sysctl{'sensor'} if $dbg[18];; + foreach (@{$sysctl{'sensor'}}){ + my ($sensor,$type,$number,$value); + if (/^hw\.sensors\.([a-z]+)([0-9]+)\.(cpu|temp|fan|volt)([0-9])/){ + $sensor = $1; + $type = $3; + $number = $4; + # hw.sensors.cpu0.temp0:47.00 degC + # hw.sensors.acpitz0.temp0:43.00 degC + $type = 'cpu' if $sensor eq 'cpu'; + } + elsif (/^hw\.sensors\.(acpi)\.(thermal)\.(tz)([0-9]+)\.(temperature)/){ + $sensor = $1 . $3; # eg acpitz + $type = ($5 eq 'temperature') ? 'temp': $5; + $number = $4; + } + elsif (/^dev\.(cpu)\.([0-9]+)\.(temperature)/){ + $sensor = $1; + $type = $3; + $number = $2; + $type = 'cpu' if $sensor eq 'cpu'; + } + if ($sensor && $type){ + if ($sensor && ((@sensors_use && !(grep {/$sensor/} @sensors_use)) || + (@sensors_exclude && (grep {/$sensor/} @sensors_exclude)))){ + next; + } + my $working = (split(':\s*', $_))[1]; + if (defined $working && $working =~ /^([0-9\.]+)\s?((deg)?([CF]))?\b/){ + $value = $1 ; + $sensors->{'temp-unit'} = $4 if $4 && !$sensors->{'temp-unit'}; + } + else { + next; + } + $number += $add; + if ($type eq 'cpu' && !defined $sensors->{'cpu-temp'}){ + $sensors->{'cpu-temp'} = $value; + } + elsif ($type eq 'temp' && !defined $sensors->{'temp' . $number}){ + $sensors->{'temp' . $number} = $value; + } + elsif ($type eq 'fan' && !defined $sensors->{'fan-main'}->[$number]){ + $sensors->{'fan-main'}->[$number] = $value if $value < $max_fan; + } + elsif ($type eq 'volt'){ + if ($working =~ /\+3\.3V/i){ + $sensors->{'volts-3.3'} = $value; + } + elsif ($working =~ /\+5V/i){ + $sensors->{'volts-5'} = $value; + } + elsif ($working =~ /\+12V/i){ + $sensors->{'volts-12'} = $value; + } + elsif ($working =~ /VBAT/i){ + $sensors->{'volts-vbat'} = $value; + } + } + } + } + process_data($sensors) if %$sensors; + main::log_data('dump','%$sensors',$sensors) if $b_log; + print Data::Dumper::Dumper $sensors if $dbg[31];; + eval $end if $b_log; + return $sensors; +} + +sub set_temp_unit { + my ($sensors,$working) = @_; + my $return_unit = ''; + if (!$sensors && $working){ + $return_unit = $working; + } + elsif ($sensors){ + $return_unit = $sensors; + } + return $return_unit; +} + +sub process_data { + eval $start if $b_log; + my ($sensors) = @_; + my ($cpu_temp,$cpu2_temp,$cpu3_temp,$cpu4_temp,$mobo_temp,$pch_temp,$psu_temp); + my ($fan_type,$i,$j,$index_count_fan_default,$index_count_fan_main) = (0,0,0,0,0); + my $temp_diff = 20; # for C, handled for F after that is determined + my (@fan_main,@fan_default); + # kernel/sensors only show Tctl if Tctl == Tdie temp, sigh... + if (!$sensors->{'cpu-temp'} && $sensors->{'tctl-temp'}){ + $sensors->{'cpu-temp'} = $sensors->{'tctl-temp'}; + undef $sensors->{'tctl-temp'}; + } + # first we need to handle the case where we have to determine which temp/fan to use for cpu and mobo: + # note, for rare cases of weird cool cpus, user can override in their prefs and force the assignment + # this is wrong for systems with > 2 tempX readings, but the logic is too complex with 3 variables + # so have to accept that it will be wrong in some cases, particularly for motherboard temp readings. + if ($sensors->{'temp1'} && $sensors->{'temp2'}){ + if ($sensors_cpu_nu){ + $fan_type = $sensors_cpu_nu; + } + else { + # first some fringe cases with cooler cpu than mobo: assume which is cpu temp based on fan speed + # but only if other fan speed is 0. + if ($sensors->{'temp1'} >= $sensors->{'temp2'} && + defined $fan_default[1] && defined $fan_default[2] && $fan_default[1] == 0 && $fan_default[2] > 0){ + $fan_type = 2; + } + elsif ($sensors->{'temp2'} >= $sensors->{'temp1'} && + defined $fan_default[1] && defined $fan_default[2] && $fan_default[2] == 0 && $fan_default[1] > 0){ + $fan_type = 1; + } + # then handle the standard case if these fringe cases are false + elsif ($sensors->{'temp1'} >= $sensors->{'temp2'}){ + $fan_type = 1; + } + else { + $fan_type = 2; + } + } + } + # need a case for no temps at all reported, like with old intels + elsif (!$sensors->{'temp2'} && !$sensors->{'cpu-temp'}){ + if (!$sensors->{'temp1'} && !$sensors->{'mobo-temp'}){ + $fan_type = 1; + } + elsif ($sensors->{'temp1'} && !$sensors->{'mobo-temp'}){ + $fan_type = 1; + } + elsif ($sensors->{'temp1'} && $sensors->{'mobo-temp'}){ + $fan_type = 1; + } + } + # convert the diff number for F, it needs to be bigger that is + if ($sensors->{'temp-unit'} && $sensors->{'temp-unit'} eq "F"){ + $temp_diff = $temp_diff * 1.8 + } + if ($sensors->{'cpu-temp'}){ + # specific hack to handle broken CPUTIN temps with PECI + if ($sensors->{'cpu-peci-temp'} && ($sensors->{'cpu-temp'} - $sensors->{'cpu-peci-temp'}) > $temp_diff){ + $cpu_temp = $sensors->{'cpu-peci-temp'}; + } + # then get the real cpu temp, best guess is hottest is real, though only within narrowed diff range + else { + $cpu_temp = $sensors->{'cpu-temp'}; + } + } + else { + if ($fan_type){ + # there are some weird scenarios + if ($fan_type == 1){ + if ($sensors->{'temp1'} && $sensors->{'temp2'} && $sensors->{'temp2'} > $sensors->{'temp1'}){ + $cpu_temp = $sensors->{'temp2'}; + } + else { + $cpu_temp = $sensors->{'temp1'}; + } + } + else { + if ($sensors->{'temp1'} && $sensors->{'temp2'} && $sensors->{'temp1'} > $sensors->{'temp2'}){ + $cpu_temp = $sensors->{'temp1'}; + } + else { + $cpu_temp = $sensors->{'temp2'}; + } + } + } + else { + $cpu_temp = $sensors->{'temp1'}; # can be null, that is ok + } + if ($cpu_temp){ + # using $sensors->{'temp3'} is just not reliable enough, more errors caused than fixed imo + # if ($sensors->{'temp3'} && $sensors->{'temp3'} > $cpu_temp){ + # $cpu_temp = $sensors->{'temp3'}; + # } + # there are some absurdly wrong $sensors->{'temp1'}: acpitz-virtual-0 $sensors->{'temp1'}: +13.8°C + if ($sensors->{'core-0-temp'} && ($sensors->{'core-0-temp'} - $cpu_temp) > $temp_diff){ + $cpu_temp = $sensors->{'core-0-temp'}; + } + } + } + # if all else fails, use core0/peci temp if present and cpu is null + if (!$cpu_temp){ + if ($sensors->{'core-0-temp'}){ + $cpu_temp = $sensors->{'core-0-temp'}; + } + # note that peci temp is known to be colder than the actual system + # sometimes so it is the last fallback we want to use even though in theory + # it is more accurate, but fact suggests theory wrong. + elsif ($sensors->{'cpu-peci-temp'}){ + $cpu_temp = $sensors->{'cpu-peci-temp'}; + } + } + # then the real mobo temp + if ($sensors->{'mobo-temp'}){ + $mobo_temp = $sensors->{'mobo-temp'}; + } + elsif ($fan_type){ + if ($fan_type == 1){ + if ($sensors->{'temp1'} && $sensors->{'temp2'} && $sensors->{'temp2'} > $sensors->{'temp1'}){ + $mobo_temp = $sensors->{'temp1'}; + } + else { + $mobo_temp = $sensors->{'temp2'}; + } + } + else { + if ($sensors->{'temp1'} && $sensors->{'temp2'} && $sensors->{'temp1'} > $sensors->{'temp2'}){ + $mobo_temp = $sensors->{'temp2'}; + } + else { + $mobo_temp = $sensors->{'temp1'}; + } + } + ## NOTE: not safe to assume $sensors->{'temp3'} is the mobo temp, sad to say + # if ($sensors->{'temp1'} && $sensors->{'temp2'} && $sensors->{'temp3'} && $sensors->{'temp3'} < $mobo_temp){ + # $mobo_temp = $sensors->{'temp3'}; + # } + } + # in case with cpu-temp AND temp1 and not temp 2, or temp 2 only, fan type: 0 + else { + if ($sensors->{'cpu-temp'} && $sensors->{'temp1'} && + $sensors->{'cpu-temp'} > $sensors->{'temp1'}){ + $mobo_temp = $sensors->{'temp1'}; + } + elsif ($sensors->{'temp2'}){ + $mobo_temp = $sensors->{'temp2'}; + } + } + @fan_main = @{$sensors->{'fan-main'}} if $sensors->{'fan-main'}; + $index_count_fan_main = (@fan_main) ? scalar @fan_main : 0; + @fan_default = @{$sensors->{'fan-default'}} if $sensors->{'fan-default'}; + $index_count_fan_default = (@fan_default) ? scalar @fan_default : 0; + # then set the cpu fan speed + if (!$fan_main[1]){ + # note, you cannot test for $fan_default[1] or [2] != "" + # because that creates an array item in gawk just by the test itself + if ($fan_type == 1 && defined $fan_default[1]){ + $fan_main[1] = $fan_default[1]; + $fan_default[1] = undef; + } + elsif ($fan_type == 2 && defined $fan_default[2]){ + $fan_main[1] = $fan_default[2]; + $fan_default[2] = undef; + } + } + # clear out any duplicates. Primary fan real trumps fan working always if same speed + for ($i = 1; $i <= $index_count_fan_main; $i++){ + if (defined $fan_main[$i] && $fan_main[$i]){ + for ($j = 1; $j <= $index_count_fan_default; $j++){ + if (defined $fan_default[$j] && $fan_main[$i] == $fan_default[$j]){ + $fan_default[$j] = undef; + } + } + } + } + # now see if you can find the fast little mobo fan, > 5000 rpm and put it as mobo + # note that gawk is returning true for some test cases when $fan_default[j] < 5000 + # which has to be a gawk bug, unless there is something really weird with arrays + # note: 500 > $fan_default[j] < 1000 is the exact trigger, and if you manually + # assign that value below, the > 5000 test works again, and a print of the value + # shows the proper value, so the corruption might be internal in awk. + # Note: gensub is the culprit I think, assigning type string for range 501-1000 but + # type integer for all others, this triggers true for > + for ($j = 1; $j <= $index_count_fan_default; $j++){ + if (defined $fan_default[$j] && $fan_default[$j] > 5000 && !$fan_main[2]){ + $fan_main[2] = $fan_default[$j]; + $fan_default[$j] = undef; + # then add one if required for output + if ($index_count_fan_main < 2){ + $index_count_fan_main = 2; + } + } + } + # if they are ALL null, print error message. psFan is not used in output currently + if (!$cpu_temp && !$mobo_temp && !$fan_main[1] && !$fan_main[2] && !$fan_main[1] && !@fan_default){ + %$sensors = (); + } + else { + my ($ambient_temp,$psu_fan,$psu1_fan,$psu2_fan,$psu_temp,$sodimm_temp, + $v_12,$v_5,$v_3_3,$v_dimm_p1,$v_dimm_p2,$v_soc_p1,$v_soc_p2,$v_vbat); + $psu_temp = $sensors->{'psu-temp'} if $sensors->{'psu-temp'}; + # sodimm fan is fan_main[4] + $sodimm_temp = $sensors->{'sodimm-temp'} if $sensors->{'sodimm-temp'}; + $cpu2_temp = $sensors->{'cpu2-temp'} if $sensors->{'cpu2-temp'}; + $cpu3_temp = $sensors->{'cpu3-temp'} if $sensors->{'cpu3-temp'}; + $cpu4_temp = $sensors->{'cpu4-temp'} if $sensors->{'cpu4-temp'}; + $ambient_temp = $sensors->{'ambient-temp'} if $sensors->{'ambient-temp'}; + $pch_temp = $sensors->{'pch-temp'} if $sensors->{'pch-temp'}; + $psu_fan = $sensors->{'fan-psu'} if $sensors->{'fan-psu'}; + $psu1_fan = $sensors->{'fan-psu-1'} if $sensors->{'fan-psu-1'}; + $psu2_fan = $sensors->{'fan-psu-2'} if $sensors->{'fan-psu-2'}; + # so far only for ipmi, sensors data is junk for volts + if ($extra > 0 && ($sensors->{'volts-12'} || $sensors->{'volts-5'} || + $sensors->{'volts-3.3'} || $sensors->{'volts-vbat'})){ + $v_12 = $sensors->{'volts-12'} if $sensors->{'volts-12'}; + $v_5 = $sensors->{'volts-5'} if $sensors->{'volts-5'}; + $v_3_3 = $sensors->{'volts-3.3'} if $sensors->{'volts-3.3'}; + $v_vbat = $sensors->{'volts-vbat'} if $sensors->{'volts-vbat'}; + $v_dimm_p1 = $sensors->{'volts-dimm-p1'} if $sensors->{'volts-dimm-p1'}; + $v_dimm_p2 = $sensors->{'volts-dimm-p2'} if $sensors->{'volts-dimm-p2'}; + $v_soc_p1 = $sensors->{'volts-soc-p1'} if $sensors->{'volts-soc-p1'}; + $v_soc_p2 = $sensors->{'volts-soc-p2'} if $sensors->{'volts-soc-p2'}; + } + %$sensors = ( + 'ambient-temp' => $ambient_temp, + 'cpu-temp' => $cpu_temp, + 'cpu2-temp' => $cpu2_temp, + 'cpu3-temp' => $cpu3_temp, + 'cpu4-temp' => $cpu4_temp, + 'mobo-temp' => $mobo_temp, + 'pch-temp' => $pch_temp, + 'psu-temp' => $psu_temp, + 'temp-unit' => $sensors->{'temp-unit'}, + 'fan-main' => \@fan_main, + 'fan-default' => \@fan_default, + 'fan-psu' => $psu_fan, + 'fan-psu1' => $psu1_fan, + 'fan-psu2' => $psu2_fan, + ); + if ($psu_temp){ + $sensors->{'psu-temp'} = $psu_temp; + } + if ($sodimm_temp){ + $sensors->{'sodimm-temp'} = $sodimm_temp; + } + if ($extra > 0 && ($v_12 || $v_5 || $v_3_3 || $v_vbat)){ + $sensors->{'volts-12'} = $v_12; + $sensors->{'volts-5'} = $v_5; + $sensors->{'volts-3.3'} = $v_3_3; + $sensors->{'volts-vbat'} = $v_vbat; + $sensors->{'volts-dimm-p1'} = $v_dimm_p1; + $sensors->{'volts-dimm-p2'} = $v_dimm_p2; + $sensors->{'volts-soc-p1'} = $v_soc_p1; + $sensors->{'volts-soc-p2'} = $v_soc_p2; + } + } + eval $end if $b_log; +} + +sub gpu_sensor_data { + eval $start if $b_log; + my ($cmd,@data,@data2,$path,@screens,$temp); + my $j = 0; + $loaded{'gpu-data'} = 1; + if ($path = main::check_program('nvidia-settings')){ + # first get the number of screens. This only work if you are in X + if ($b_display){ + @data = main::grabber("$path -q screens 2>/dev/null"); + foreach (@data){ + if (/(:[0-9]\.[0-9])/){ + push(@screens, $1); + } + } + } + # do a guess, this will work for most users, it's better than nothing for out of X + else { + $screens[0] = ':0.0'; + } + # now we'll get the gpu temp for each screen discovered. The print out function + # will handle removing screen data for single gpu systems. -t shows only data we want + # GPUCurrentClockFreqs: 520,600 + # GPUCurrentFanSpeed: 50 0-100, not rpm, percent I think + # VideoRam: 1048576 + # CUDACores: 16 + # PCIECurrentLinkWidth: 16 + # PCIECurrentLinkSpeed: 5000 + # RefreshRate: 60.02 Hz [oer screen] + # ViewPortOut=1280x1024+0+0}, DPY-1: nvidia-auto-select @1280x1024 +1280+0 {ViewPortIn=1280x1024, + # ViewPortOut=1280x1024+0+0} + # ThermalSensorReading: 50 + # PCIID: 4318,2661 - the pci stuff doesn't appear to work + # PCIBus: 2 + # PCIDevice: 0 + # Irq: 30 + foreach my $screen (@screens){ + my $screen2 = $screen; + $screen2 =~ s/\.[0-9]$//; + $cmd = '-q GPUCoreTemp -q VideoRam -q GPUCurrentClockFreqs -q PCIECurrentLinkWidth '; + $cmd .= '-q Irq -q PCIBus -q PCIDevice -q GPUCurrentFanSpeed'; + $cmd = "$path -c $screen2 $cmd 2>/dev/null"; + @data = main::grabber($cmd); + main::log_data('cmd',$cmd) if $b_log; + push(@data,@data2); + $j = scalar @$gpu_data; + foreach my $item (@data){ + if ($item =~ /^\s*Attribute\s\'([^']+)\'\s.*:\s*([\S]+)\.$/){ + my $attribute = $1; + my $value = $2; + $gpu_data->[$j]{'type'} = 'nvidia'; + $gpu_data->[$j]{'speed-unit'} = '%'; + $gpu_data->[$j]{'screen'} = $screen; + if (!$gpu_data->[$j]{'temp'} && $attribute eq 'GPUCoreTemp'){ + $gpu_data->[$j]{'temp'} = $value; + } + elsif (!$gpu_data->[$j]{'ram'} && $attribute eq 'VideoRam'){ + $gpu_data->[$j]{'ram'} = $value; + } + elsif (!$gpu_data->[$j]{'clock'} && $attribute eq 'GPUCurrentClockFreqs'){ + $gpu_data->[$j]{'clock'} = $value; + } + elsif (!$gpu_data->[$j]{'bus'} && $attribute eq 'PCIBus'){ + $gpu_data->[$j]{'bus'} = $value; + } + elsif (!$gpu_data->[$j]{'bus-id'} && $attribute eq 'PCIDevice'){ + $gpu_data->[$j]{'bus-id'} = $value; + } + elsif (!$gpu_data->[$j]{'fan-speed'} && $attribute eq 'GPUCurrentFanSpeed'){ + $gpu_data->[$j]{'fan-speed'} = $value; + } + } + } + } + } + if ($path = main::check_program('aticonfig')){ + # aticonfig --adapter=0 --od-gettemperature + @data = main::grabber("$path --adapter=all --od-gettemperature 2>/dev/null"); + foreach (@data){ + if (/Sensor [^0-9]*([0-9\.]+) /){ + $j = scalar @$gpu_data; + my $value = $1; + $gpu_data->[$j]{'type'} = 'amd'; + $gpu_data->[$j]{'temp'} = $value; + } + } + } + if ($sensors_raw->{'gpu'}){ + # my ($b_found,$holder) = (0,''); + foreach my $adapter (keys %{$sensors_raw->{'gpu'}}){ + $j = scalar @$gpu_data; + $gpu_data->[$j]{'type'} = $adapter; + $gpu_data->[$j]{'type'} =~ s/^(amdgpu|intel|nouveau|radeon)-.*/$1/; + # print "ad: $adapter\n"; + foreach (@{$sensors_raw->{'gpu'}{$adapter}}){ + # print "val: $_\n"; + if (/^[^:]*mem[^:]*:([0-9\.]+).*\b(C|F)\b/i){ + $gpu_data->[$j]{'temp-mem'} = $1; + $gpu_data->[$j]{'unit'} = $2; + # print "temp: $_\n"; + } + elsif (/^[^:]+:([0-9\.]+).*\b(C|F)\b/i){ + $gpu_data->[$j]{'temp'} = $1; + $gpu_data->[$j]{'unit'} = $2; + # print "temp: $_\n"; + } + # speeds can be in percents or rpms, so need the 'fan' in regex + elsif (/^.*?fan.*?:([0-9\.]+).*(RPM)?/i){ + $gpu_data->[$j]{'fan-speed'} = $1; + # NOTE: we test for nvidia %, everything else stays with nothing + $gpu_data->[$j]{'speed-unit'} = ''; + } + elsif (/^[^:]+:([0-9\.]+)\s+W\s/i){ + $gpu_data->[$j]{'watts'} = $1; + } + elsif (/^[^:]+:([0-9\.]+)\s+(m?V)\s/i){ + $gpu_data->[$j]{'volts-gpu'} = [$1,$2]; + } + } + } + } + main::log_data('dump','sensors output: video: @$gpu_data',$gpu_data) if $b_log; + print 'gpu_data: ', Data::Dumper::Dumper $gpu_data if $dbg[18]; + eval $end if $b_log; +} +} + +## SlotItem +{ +package SlotItem; +my ($sys_slots); + +sub get { + eval $start if $b_log; + my ($data,$key1,$val1); + my $rows = []; + my $num = 0; + if ($fake{'dmidecode'} || ($alerts{'dmidecode'}->{'action'} eq 'use' && + (!%risc || $use{'slot-tool'}))){ + if ($b_admin && -e '/sys/devices/pci0000:00'){ + slot_data_sys(); + } + $data = slot_data_dmi(); + slot_output($rows,$data) if @$data; + if (!@$rows){ + my $key = 'Message'; + push(@$rows, { + main::key($num++,0,1,$key) => main::message('pci-slot-data','') + }); + } + } + elsif (%risc && !$use{'slot-tool'}){ + $key1 = 'Message'; + $val1 = main::message('risc-pci',$risc{'id'}); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + elsif ($alerts{'dmidecode'}->{'action'} ne 'use'){ + $key1 = $alerts{'dmidecode'}->{'action'}; + $val1 = $alerts{'dmidecode'}->{'message'}; + $key1 = ucfirst($key1); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + eval $end if $b_log; + return $rows; +} + +sub slot_output { + eval $start if $b_log; + my ($rows,$data) = @_; + my $num = 1; + foreach my $slot_data (@$data){ + next if !$slot_data || ref $slot_data ne 'HASH'; + $num = 1; + my $j = scalar @$rows; + $slot_data->{'id'} = 'N/A' if !defined $slot_data->{'id'}; # can be 0 + $slot_data->{'pci'} ||= 'N/A'; + push(@$rows, { + main::key($num++,1,1,'Slot') => $slot_data->{'id'}, + main::key($num++,0,2,'type') => $slot_data->{'pci'}, + },); + # PCIe only + if ($extra > 1 && $slot_data->{'gen'}){ + $rows->[$j]{main::key($num++,0,2,'gen')} = $slot_data->{'gen'}; + } + if ($slot_data->{'lanes-phys'} && $slot_data->{'lanes-active'} && + $slot_data->{'lanes-phys'} ne $slot_data->{'lanes-active'}){ + $rows->[$j]{main::key($num++,1,2,'lanes')} = ''; + $rows->[$j]{main::key($num++,0,3,'phys')} = $slot_data->{'lanes-phys'}; + $rows->[$j]{main::key($num++,0,3,'active')} = $slot_data->{'lanes-active'}; + } + elsif ($slot_data->{'lanes-phys'}){ + $rows->[$j]{main::key($num++,0,2,'lanes')} = $slot_data->{'lanes-phys'}; + } + # Non PCIe only + if ($extra > 1 && $slot_data->{'bits'}){ + $rows->[$j]{main::key($num++,0,2,'bits')} = $slot_data->{'bits'}; + } + # PCI-X and PCI only + if ($extra > 1 && $slot_data->{'mhz'}){ + $rows->[$j]{main::key($num++,0,2,'MHz')} = $slot_data->{'mhz'}; + } + $rows->[$j]{main::key($num++,0,2,'status')} = $slot_data->{'usage'}; + if ($slot_data->{'extra'}){ + $rows->[$j]{main::key($num++,0,2,'info')} = join(', ', @{$slot_data->{'extra'}}); + } + if ($extra > 1){ + $slot_data->{'length'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'length')} = $slot_data->{'length'}; + if ($slot_data->{'cpu'}){ + $rows->[$j]{main::key($num++,0,2,'cpu')} = $slot_data->{'cpu'}; + } + if ($slot_data->{'volts'}){ + $rows->[$j]{main::key($num++,0,2,'volts')} = $slot_data->{'volts'}; + } + } + if ($extra > 0){ + $slot_data->{'bus_address'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,2,'bus-ID')} = $slot_data->{'bus_address'}; + if ($b_admin && $slot_data->{'children'}){ + children_output($rows,$j,\$num,$slot_data->{'children'},3); + } + } + } + eval $end if $b_log; +} +sub children_output { + my ($rows,$j,$num,$children,$ind) = @_; + my $cnt = 0; + $rows->[$j]{main::key($$num++,1,$ind,'children')} = ''; + $ind++; + foreach my $id (sort keys %{$children}){ + $cnt++; + $rows->[$j]{main::key($$num++,1,$ind,$cnt)} = $id; + if ($children->{$id}{'class-id'} && $children->{$id}{'class-id-sub'}){ + my $class = $children->{$id}{'class-id'} . $children->{$id}{'class-id-sub'}; + $rows->[$j]{main::key($$num++,0,($ind + 1),'class-ID')} = $class; + if ($children->{$id}{'class'}){ + $rows->[$j]{main::key($$num++,0,($ind + 1),'type')} = $children->{$id}{'class'}; + } + } + if ($children->{$id}{'children'}){ + children_output($rows,$j,$num,$children->{$id}{'children'},$ind + 1); + } + } +} + +sub slot_data_dmi { + eval $start if $b_log; + my $i = 0; + my $slots = []; + foreach my $slot_data (@dmi){ + next if $slot_data->[0] != 9; + my (%data,@extra); + # skip first two row, we don't need that data + foreach my $item (@$slot_data[2 .. $#$slot_data]){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split(/:\s+/, $item, 2); + if ($value[0] eq 'Type'){ + $data{'type'} = $value[1]; + } + if ($value[0] eq 'Designation'){ + $data{'designation'} = $value[1]; + } + if ($value[0] eq 'Current Usage'){ + $data{'usage'} = lc($value[1]); + } + if ($value[0] eq 'ID'){ + $data{'id'} = $value[1]; + } + if ($value[0] eq 'Length'){ + $data{'length'} = lc($value[1]); + } + if ($value[0] eq 'Bus Address'){ + $value[1] =~ s/^0000://; + $data{'bus_address'} = $value[1]; + if ($b_admin && $sys_slots){ + $data{'children'} = slot_children($data{'bus_address'},$sys_slots); + } + } + } + elsif ($item =~ /^~([\d.]+)[\s-]?V is provided/){ + $data{'volts'} = $1; + } + } + if ($data{'type'} eq 'Other' && $data{'designation'}){ + $data{'type'} = $data{'designation'}; + undef $data{'designation'}; + } + foreach my $string (($data{'type'},$data{'designation'})){ + next if !$string; + print "st: $string\n" if $dbg[48]; + $string =~ s/(PCI[\s_-]?Express|Pci[_-]?e)/PCIe /ig; + $string =~ s/PCI[\s_-]?X/PCIX /ig; + $string =~ s/Mini[\s_-]?PCI/MiniPCI /ig; + $string =~ s/Media[\s_-]?Card/MediaCard/ig; + $string =~ s/Express[\s_-]?Card/ExpressCard/ig; + $string =~ s/Card[\s_-]?Bus/CardBus/ig; + $string =~ s/PCMCIA/PCMCIA /ig; + if (!$data{'pci'} && $string =~ /(AGP|ISA|MiniPCI|PCIe|PCIX|PCMCIA|PCI)/){ + $data{'pci'} = $1; + # print "pci: $data{'pci'}\n"; + } + if ($string =~ /(MiniPCI|PCMCIA)/){ + $data{'pci'} = $1; + # print "pci: $data{'pci'}\n"; + } + # legacy format: PCIE#3-x8 + if (!$data{'lanes-phys'} && $string =~ /(^x|#\d+-x)(\d+)/){ + $data{'lanes-phys'} = $2; + } + if (!$data{'lanes-active'} && $string =~ /^x\d+ .*? x(\d+)/){ + $data{'lanes-active'} = $1; + } + # legacy format, seens with PCI-X/PCIe mobos: PCIX#2-100MHz, PCIE#3-x8 + if (!defined $data{'id'} && $string =~ /(#|PCI)(\d+)\b/){ + $data{'id'} = $2; + } + if (!defined $data{'id'} && $string =~ /SLOT[\s-]?(\d+)\b/i){ + $data{'id'} = $1; + } + if ($string =~ s/\bJ-?(\S+)\b//){ + push(@extra,'J' . $1) if ! grep {$_ eq 'J' . $1} @extra; + } + if ($string =~ s/\bM\.?2\b//){ + push(@extra,'M.2') if ! grep {$_ eq 'M.2'} @extra; + } + if ($string =~ /(ExpressCard|MediaCard|CardBus)/){ + push(@extra,$1) if ! grep {$_ eq $1} @extra; + } + if (!$data{'cpu'} && $string =~ s/CPU-?(\d+)\b//){ + $data{'cpu'} = $1; + } + if (!$data{'gen'} && $data{'pci'} && $data{'pci'} eq 'PCIe' && + $string =~ /PCIe[\s_-]*([\d.]+)/){ + $data{'gen'} = $1 + 0; + } + if (!$data{'mhz'} && $data{'pci'} && $string =~ /(\d+)[\s_-]?MHz/){ + $data{'mhz'} = $1; + } + if (!$data{'bits'} && $data{'pci'} && $string =~ /\b(\d+)[\s_-]?bit/){ + $data{'bits'} = $1; + } + $i++; + } + if (!$data{'pci'} && $data{'type'} && + $data{'type'} =~ /(ExpressCard|MediaCard|CardBus)/){ + $data{'pci'} = $1; + @extra = grep {$_ ne $data{'pci'}} @extra; + } + $data{'extra'} = [@extra] if @extra; + push(@$slots,{%data}) if %data; + } + print '@$slots: ', Data::Dumper::Dumper $slots if $dbg[48]; + main::log_data('dump','@$slots final',$slots) if $b_log; + eval $end if $b_log; + return $slots; +} + +sub slot_data_sys { + eval $start if $b_log; + my $path = '/sys/devices/pci0000:*/00*'; + my @data = main::globber($path); + my ($full,$id); + foreach $full (@data){ + $id = $full; + $id =~ s/^.*\/\S+:([0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]+)$/$1/; + $sys_slots->{$id} = slot_data_recursive($full); + } + print 'sys_slots: ', Data::Dumper::Dumper $sys_slots if $dbg[49]; + main::log_data('dump','$sys_slots',$sys_slots) if $b_log; + eval $end if $b_log; +} + +sub slot_data_recursive { + eval $start if $b_log; + my $path = shift @_; + my $info = {}; + my $id = $path; + $id =~ s/^.*\/\S+:(\S{2}:\S{2}\.\S+)$/$1/; + my ($content,$id2,@files); + # @files = main::globber("$full/{class,current_link_speed,current_link_width,max_link_speed,max_link_width,00*}"); + if (-e "$path/class" && ($content = main::reader("$path/class",'strip',0))){ + if ($content =~ /^0x(\S{2})(\S{2})/){ + $info->{'class-id'} = $1; + $info->{'class-id-sub'} = $2; + $info->{'class'} = DeviceData::pci_class($1); + if ($info->{'class-id'} eq '06'){ + my @files = main::globber("$path/00*:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f].[0-9a-f]"); + foreach my $item (@files){ + $id = $item; + $id =~ s/^.*\/[0-9a-f]+:([0-9a-f]{2}:[0-9a-f]{2}\.[0-9a-f]+)$/$1/; + $info->{'children'}{$id} = slot_data_recursive($item); + } + } + } + } + if (-e "$path/current_link_speed" && + ($content = main::reader("$path/current_link_speed",'strip',0))){ + $content =~ s/\sPCIe//i; + $info->{'current-link-speed'} = main::clean_dmi($content); + } + if (-e "$path/current_link_width" && + ($content = main::reader("$path/current_link_width",'strip',0))){ + $info->{'current-link-width'} = $content; + } + eval $end if $b_log; + return $info; +} + +sub slot_children { + eval $start if $b_log; + my ($bus_id,$slots) = @_; + my $children = slot_children_recursive($bus_id,$slots); + # $children->{'0a:00.0'}{'children'} = {'3423' => { + # 'class' => 'test','class-id' => '05','class-id-sub' => '10'}}; + print $bus_id, ' children: ', Data::Dumper::Dumper $children if $dbg[49]; + main::log_data('dump','$children',$children) if $b_log; + eval $end if $b_log; + return $children; +} + +sub slot_children_recursive { + my ($bus_id,$slots) = @_; + my $children; + foreach my $key (keys %{$slots}){ + if ($slots->{$bus_id}){ + $children = $slots->{$bus_id}{'children'} if $slots->{$bus_id}{'children'}; + last; + } + elsif ($slots->{$key}{'children'}){ + slot_children_recursive($bus_id,$slots->{$key}{'children'}); + } + } + return $children; +} +} + +## SwapItem +{ +package SwapItem; + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + create_output($rows); + if (!@$rows){ + @$rows = ({main::key($num++,0,1,'Alert') => main::message('swap-data')}); + } + eval $end if $b_log; + return $rows; +} + +sub create_output { + eval $start if $b_log; + my $rows = $_[0]; + my $num = 0; + my $j = 0; + my (@rows,$dev,$percent,$raw_size,$size,$used); + PartitionData::set() if !$bsd_type && !$loaded{'partition-data'}; + DiskDataBSD::set() if $bsd_type && !$loaded{'disk-data-bsd'}; + main::set_mapper() if !$loaded{'mapper'}; + PartitionItem::swap_data() if !$loaded{'set-swap'}; + foreach my $row (@swaps){ + $num = 1; + $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A'; + $used = main::get_size($row->{'used'},'string','N/A'); # used can be 0 + $percent = (defined $row->{'percent-used'}) ? ' (' . $row->{'percent-used'} . '%)' : ''; + $dev = ($row->{'swap-type'} eq 'file') ? 'file' : 'dev'; + $row->{'swap-type'} = ($row->{'swap-type'}) ? $row->{'swap-type'} : 'N/A'; + if ($b_admin && !$bsd_type && $j == 0){ + $j = scalar @rows; + if (defined $row->{'swappiness'} || defined $row->{'cache-pressure'}){ + $rows->[$j]{main::key($num++,1,1,'Kernel')} = ''; + if (defined $row->{'swappiness'}){ + $rows->[$j]{main::key($num++,0,2,'swappiness')} = $row->{'swappiness'}; + } + if (defined $row->{'cache-pressure'}){ + $rows->[$j]{main::key($num++,0,2,'cache-pressure')} = $row->{'cache-pressure'}; + } + $row->{'zswap-enabled'} ||= 'N/A'; + $rows->[$j]{main::key($num++,1,2,'zswap')} = $row->{'zswap-enabled'}; + if ($row->{'zswap-enabled'} eq 'yes'){ + if (defined $row->{'zswap-compressor'}){ + $rows->[$j]{main::key($num++,0,1,'compressor')} = $row->{'zswap-compressor'}; + } + if (defined $row->{'zswap-max-pool-percent'}){ + $rows->[$j]{main::key($num++,0,1,'max-pool')} = $row->{'zswap-max-pool-percent'} . '%'; + } + } + } + else { + $rows->[$j]{main::key($num++,0,1,'Message')} = main::message('swap-admin'); + } + } + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'ID') => $row->{'id'}, + main::key($num++,0,2,'type') => $row->{'swap-type'}, + }); + # not used for swap as far as I know + if ($b_admin && $row->{'raw-size'}){ + # It's an error! permissions or missing tool + $raw_size = main::get_size($row->{'raw-size'},'string'); + $rows->[$j]{main::key($num++,0,2,'raw-size')} = $raw_size; + } + # not used for swap as far as I know + if ($b_admin && $row->{'raw-available'} && $size ne 'N/A'){ + $size .= ' (' . $row->{'raw-available'} . '%)'; + } + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + $rows->[$j]{main::key($num++,0,2,'used')} = $used . $percent; + # not used for swap as far as I know + if ($b_admin && $row->{'block-size'}){ + $rows->[$j]{main::key($num++,0,2,'block-size')} = $row->{'block-size'} . ' B';; + #$rows->[$j]{main::key($num++,0,2,'physical')} = $row->{'block-size'} . ' B'; + #$rows->[$j]{main::key($num++,0,2,'logical')} = $row->{'block-logical'} . ' B'; + } + if ($extra > 1 && defined $row->{'priority'}){ + $rows->[$j]{main::key($num++,0,2,'priority')} = $row->{'priority'}; + } + if ($b_admin && $row->{'swap-type'} eq 'zram'){ + if ($row->{'zram-comp'}){ + $rows->[$j]{main::key($num++,1,2,'comp')} = $row->{'zram-comp'}; + if ($row->{'zram-comp-avail'}){ + $rows->[$j]{main::key($num++,0,3,'avail')} = $row->{'zram-comp-avail'}; + } + } + if ($row->{'zram-max-comp-streams'}){ + $rows->[$j]{main::key($num++,0,3,'max-streams')} = $row->{'zram-max-comp-streams'}; + } + } + $row->{'mount'} =~ s|/home/[^/]+/(.*)|/home/$filter_string/$1| if $row->{'mount'} && $use{'filter'}; + $rows->[$j]{main::key($num++,1,2,$dev)} = ($row->{'mount'}) ? $row->{'mount'} : 'N/A'; + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,3,'maj-min')} = $row->{'maj-min'}; + } + if ($extra > 0 && $row->{'dev-mapped'}){ + $rows->[$j]{main::key($num++,0,3,'mapped')} = $row->{'dev-mapped'}; + } + if ($show{'label'} && ($row->{'label'} || $row->{'swap-type'} eq 'partition')){ + if ($use{'filter-label'}){ + $row->{'label'} = main::filter_partition('part', $row->{'label'}, ''); + } + $row->{'label'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'label')} = $row->{'label'}; + } + if ($show{'uuid'} && ($row->{'uuid'} || $row->{'swap-type'} eq 'partition')){ + if ($use{'filter-uuid'}){ + $row->{'uuid'} = main::filter_partition('part', $row->{'uuid'}, ''); + } + $row->{'uuid'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'uuid')} = $row->{'uuid'}; + } + } + eval $end if $b_log; +} +} + +## UnmountedItem +{ +package UnmountedItem; + +sub get { + eval $start if $b_log; + my ($data,$key1,$val1); + my $rows = []; + my $num = 0; + if ($bsd_type){ + DiskDataBSD::set() if !$loaded{'disk-data-bsd'}; + if (%disks_bsd && ($alerts{'disklabel'}->{'action'} eq 'use' || + $alerts{'gpart'}->{'action'} eq 'use')){ + $data = bsd_data(); + if (!@$data){ + $key1 = 'Message'; + $val1 = main::message('unmounted-data'); + } + else { + create_output($rows,$data); + } + } + else { + if ($alerts{'disklabel'}->{'action'} eq 'permissions'){ + $key1 = 'Message'; + $val1 = $alerts{'disklabel'}->{'message'}; + } + else { + $key1 = 'Message'; + $val1 = main::message('unmounted-data-bsd',$uname[0]); + } + } + } + else { + if ($system_files{'proc-partitions'}){ + $data = proc_data(); + if (!@$data){ + $key1 = 'Message'; + $val1 = main::message('unmounted-data'); + } + else { + create_output($rows,$data); + } + } + else { + $key1 = 'Message'; + $val1 = main::message('unmounted-file'); + } + } + if (!@$rows && $key1){ + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + eval $end if $b_log; + return $rows; +} + +sub create_output { + eval $start if $b_log; + my ($rows,$unmounted) = @_; + my ($fs); + my ($j,$num) = (0,0); + @$unmounted = sort { $a->{'dev-base'} cmp $b->{'dev-base'} } @$unmounted; + my $fs_skip = PartitionItem::get_filters('fs-skip'); + foreach my $row (@$unmounted){ + $num = 1; + my $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A'; + if ($row->{'fs'}){ + $fs = lc($row->{'fs'}); + } + else { + if ($bsd_type){ + $fs = 'N/A'; + } + elsif (main::check_program('file')){ + $fs = ($b_root) ? 'N/A' : main::message('root-required'); + } + else { + $fs = main::message('tool-missing-basic','file'); + } + } + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'ID') => "/dev/$row->{'dev-base'}", + }); + if ($b_admin && $row->{'maj-min'}){ + $rows->[$j]{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'}; + } + if ($extra > 0 && $row->{'dev-mapped'}){ + $rows->[$j]{main::key($num++,0,2,'mapped')} = $row->{'dev-mapped'}; + } + $row->{'label'} ||= 'N/A'; + $row->{'uuid'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'size')} = $size; + $rows->[$j]{main::key($num++,0,2,'fs')} = $fs; + # don't show for fs known to not have label/uuid + if (($show{'label'} || $show{'uuid'}) && $fs !~ /^$fs_skip$/){ + if ($show{'label'}){ + if ($use{'filter-label'}){ + $row->{'label'} = main::filter_partition('part', $row->{'label'}, ''); + } + $row->{'label'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'label')} = $row->{'label'}; + } + if ($show{'uuid'}){ + if ($use{'filter-uuid'}){ + $row->{'uuid'} = main::filter_partition('part', $row->{'uuid'}, ''); + } + $row->{'uuid'} ||= 'N/A'; + $rows->[$j]{main::key($num++,0,2,'uuid')} = $row->{'uuid'}; + } + } + } + eval $end if $b_log; +} + +sub proc_data { + eval $start if $b_log; + my ($dev_mapped,$fs,$label,$maj_min,$size,$uuid,$part); + my $unmounted = []; + # last filters to make sure these are dumped + my @filters = ('scd[0-9]+','sr[0-9]+','cdrom[0-9]*','cdrw[0-9]*', + 'dvd[0-9]*','dvdrw[0-9]*','fd[0-9]','ram[0-9]*'); + my $num = 0; + # set labels, uuid, gpart + PartitionItem::set_partitions() if !$loaded{'set-partitions'}; + RaidItem::raid_data() if !$loaded{'raid'}; + my $mounted = get_mounted(); + # print join("\n",(@filters,@$mounted)),"\n"; + foreach my $row (@proc_partitions){ + ($dev_mapped,$fs,$label,$maj_min,$uuid,$size) = ('','','','','',''); + # note that size 1 means it is a logical extended partition container + # lvm might have dm-1 type syntax + # need to exclude loop type file systems, squashfs for example + # NOTE: nvme needs special treatment because the main device is: nvme0n1 + # note: $working[2] != 1 is wrong, it's not related + # note: for zfs using /dev/sda no partitions, previous rule would have removed + # the unmounted report because sdb was found in sdb1, but match of eg sdb1 and sdb12 + # makes this a problem, so using zfs_member test instead to filter out zfs members. + # For zfs using entire disk, ie, sda, in that case, all partitions sda1 sda9 (8BiB) + # belong to zfs, and aren't unmmounted, so if sda and partition sda9, + # remove from list. this only works for sdxx drives, but is better than no fix + # This logic may also end up working for btrfs partitions, and maybe hammer? + # In arm/android seen /dev/block/mmcblk0p12 + # print "mount: $row->[-1]\n"; + if ($row->[-1] !~ /^(nvme[0-9]+n|mmcblk|mtdblk|mtdblock)[0-9]+$/ && + $row->[-1] =~ /[a-z][0-9]+$|dm-[0-9]+$/ && + $row->[-1] !~ /\bloop/ && + !(grep {$row->[-1] =~ /$_$/} (@filters,@$mounted)) && + !(grep {$_ =~ /(block\/)?$row->[-1]$/} @$mounted) && + !(grep {$_ =~ /^sd[a-z]+$/ && $row->[-1] =~ /^$_[0-9]+/} @$mounted)){ + $dev_mapped = $dmmapper{$row->[-1]} if $dmmapper{$row->[-1]}; + if (@lsblk){ + my $id = ($dev_mapped) ? $dev_mapped: $row->[-1]; + $part = LsblkData::get($id); + if (%$part){ + $fs = $part->{'fs'}; + $label = $part->{'label'}; + $maj_min = $part->{'maj-min'}; + $uuid = $part->{'uuid'}; + $size = $part->{'size'} if $part->{'size'} && !$row->[2]; + } + } + $size ||= $row->[2]; + $fs = unmounted_filesystem($row->[-1]) if !$fs; + # seen: (zfs|lvm2|linux_raid)_member; crypto_luks + # note: lvm, raid members are never mounted. luks member is never mounted. + next if $fs && $fs =~ /(bcache|crypto|luks|_member)$/i; + # these components of lvm raid will show as partitions byt are reserved private lvm member + # See man lvm for all current reserved private volume names + next if $dev_mapped && $dev_mapped =~ /_([ctv]data|corig|[mr]image|mlog|[crt]meta|pmspare|pvmove|vorigin)(_[0-9]+)?$/; + if (!$bsd_type){ + $label = PartitionItem::get_label("/dev/$row->[-1]") if !$label; + $uuid = PartitionItem::get_uuid("/dev/$row->[-1]") if !$uuid; + } + else { + my @temp = GpartData::get($row->[-1]); + $label = $temp[1] if $temp[1]; + $uuid = $temp[2] if $temp[2]; + } + $maj_min = "$row->[0]:$row->[1]" if !$maj_min; + push(@$unmounted, { + 'dev-base' => $row->[-1], + 'dev-mapped' => $dev_mapped, + 'fs' => $fs, + 'label' => $label, + 'maj-min' => $maj_min, + 'size' => $size, + 'uuid' => $uuid, + }); + } + } + print Data::Dumper::Dumper $unmounted if $dbg[35]; + main::log_data('dump','@$unmounted',$unmounted) if $b_log; + eval $end if $b_log; + return $unmounted; +} + +sub bsd_data { + eval $start if $b_log; + my ($fs,$label,$size,$uuid,%part); + my $unmounted = []; + PartitionItem::set_partitions() if !$loaded{'set-partitions'}; + RaidItem::raid_data() if !$loaded{'raid'}; + my $mounted = get_mounted(); + foreach my $id (sort keys %disks_bsd){ + next if !$disks_bsd{$id}->{'partitions'}; + foreach my $part (sort keys %{$disks_bsd{$id}->{'partitions'}}){ + if (!(grep {$_ =~ /$part$/} @$mounted)){ + $fs = $disks_bsd{$id}->{'partitions'}{$part}{'fs'}; + next if $fs && $fs =~ /(raid|_member)$/i; + $label = $disks_bsd{$id}->{'partitions'}{$part}{'label'}; + $size = $disks_bsd{$id}->{'partitions'}{$part}{'size'}; + $uuid = $disks_bsd{$id}->{'partitions'}{$part}{'uuid'}; + # $fs = unmounted_filesystem($part) if !$fs; + push(@$unmounted, { + 'dev-base' => $part, + 'dev-mapped' => '', + 'fs' => $fs, + 'label' => $label, + 'maj-min' => '', + 'size' => $size, + 'uuid' => $uuid, + }); + } + } + } + print Data::Dumper::Dumper $unmounted if $dbg[35]; + main::log_data('dump','@$unmounted',$unmounted) if $b_log; + eval $end if $b_log; + return $unmounted; +} + +sub get_mounted { + eval $start if $b_log; + my (@arrays); + my $mounted = []; + foreach my $row (@partitions){ + push(@$mounted, $row->{'dev-base'}) if $row->{'dev-base'}; + } + # print Data::Dumper::Dumper \@zfs_raid; + foreach my $row ((@btrfs_raid,@lvm_raid,@md_raid,@soft_raid,@zfs_raid)){ + # we want to not show md0 etc in unmounted report + push(@$mounted, $row->{'id'}) if $row->{'id'}; + # print Data::Dumper::Dumper $row; + # row->arrays->components: zfs; row->components: lvm,mdraid,softraid + if ($row->{'arrays'} && ref $row->{'arrays'} eq 'ARRAY'){ + push(@arrays,@{$row->{'arrays'}}); + } + elsif ($row->{'components'} && ref $row->{'components'} eq 'ARRAY'){ + push(@arrays,$row); + } + @arrays = grep {defined $_} @arrays; + # print Data::Dumper::Dumper \@arrays; + foreach my $item (@arrays){ + # print Data::Dumper::Dumper $item; + my @components = (ref $item->{'components'} eq 'ARRAY') ? @{$item->{'components'}} : (); + foreach my $component (@components){ + # md has ~, not zfs,lvm,softraid + my $temp = (split('~', $component->[0]))[0]; + push(@$mounted, $temp); + } + } + } + eval $end if $b_log; + return $mounted; +} + +# bsds do not seem to return any useful data so only for linux +sub unmounted_filesystem { + eval $start if $b_log; + my ($item) = @_; + my ($data,%part); + my ($file,$fs,$path) = ('','',''); + if ($path = main::check_program('file')){ + $file = $path; + } + # order matters in this test! + my @filesystems = ('ext2','ext3','ext4','ext5','ext','ntfs', + 'fat32','fat16','FAT\s\(.*\)','vfat','fatx','tfat','exfat','swap','btrfs', + 'ffs','hammer','hfs\+','hfs\splus','hfs\sextended\sversion\s[1-9]','hfsj', + 'hfs','apfs','jfs','nss','reiserfs','reiser4','ufs2','ufs','xfs','zfs'); + if ($file){ + # this will fail if regular user and no sudo present, but that's fine, it will just return null + # note the hack that simply slices out the first line if > 1 items found in string + # also, if grub/lilo is on partition boot sector, no file system data is available + $data = (main::grabber("$sudoas$file -s /dev/$item 2>/dev/null"))[0]; + if ($data){ + foreach (@filesystems){ + if ($data =~ /($_)[\s,]/i){ + $fs = $1; + $fs = main::trimmer($fs); + last; + } + } + } + } + main::log_data('data',"fs: $fs") if $b_log; + eval $end if $b_log; + return $fs; +} +} + +## UsbItem +{ +package UsbItem; + +sub get { + eval $start if $b_log; + my ($key1,$val1); + my $rows = []; + my $num = 0; + if (!$usb{'main'} && $alerts{'lsusb'}->{'action'} ne 'use' && + $alerts{'usbdevs'}->{'action'} ne 'use' && + $alerts{'usbconfig'}->{'action'} ne 'use'){ + if ($os eq 'linux'){ + $key1 = $alerts{'lsusb'}->{'action'}; + $val1 = $alerts{'lsusb'}->{'message'}; + } + else { + # note: usbdevs only has 'missing', usbconfig has missing/permissions + # both have platform, but irrelevant since testing for linux here + if ($alerts{'usbdevs'}->{'action'} eq 'missing' && + $alerts{'usbconfig'}->{'action'} eq 'missing'){ + $key1 = $alerts{'usbdevs'}->{'action'}; + $val1 = main::message('tools-missing-bsd','usbdevs/usbconfig'); + } + elsif ($alerts{'usbconfig'}->{'action'} eq 'permissions'){ + $key1 = $alerts{'usbconfig'}->{'action'}; + $val1 = $alerts{'usbconfig'}->{'message'}; + } + # elsif ($alerts{'lsusb'}->{'action'} eq 'missing'){ + # $key1 = $alerts{'lsusb'}->{'action'}; + # $val1 = $alerts{'lsusb'}->{'message'}; + # } + } + $key1 = ucfirst($key1); + @$rows = ({main::key($num++,0,1,$key1) => $val1}); + } + else { + usb_output($rows); + if (!@$rows){ + my $key = 'Message'; + @$rows = ({ + main::key($num++,0,1,$key) => main::message('usb-data','') + }); + } + } + eval $end if $b_log; + return $rows; +} + +sub usb_output { + eval $start if $b_log; + return if !$usb{'main'}; + my $rows = $_[0]; + my ($b_hub,$bus_id,$chip_id,$driver,$ind_rc,$ind_sc,$path_id,$ports,$product, + $rev,$serial,$speed_si,$type); + my $num = 0; + my $j = 0; + # note: the data has been presorted in UsbData: + # bus alpah id, so we don't need to worry about the order + foreach my $id (@{$usb{'main'}}){ + $j = scalar @$rows; + ($b_hub,$ind_rc,$ind_sc,$num) = (0,4,3,1); + ($driver,$path_id,$ports,$product,$rev,$serial,$speed_si, + $type) = ('','','','','','','','',''); + $rev = $id->[8] if $id->[8]; + $product = main::clean($id->[13]) if $id->[13]; + $serial = main::filter($id->[16]) if $id->[16]; + $product ||= 'N/A'; + $rev ||= 'N/A'; + $path_id = $id->[2] if $id->[2]; + $bus_id = "$path_id:$id->[1]"; + # it's a hub + if ($id->[4] eq '09'){ + $ports = $id->[10] if $id->[10]; + $ports ||= 'N/A'; + # print "pt0:$protocol\n"; + push(@$rows, { + main::key($num++,1,1,'Hub') => $bus_id, + main::key($num++,0,2,'info') => $product, + main::key($num++,0,2,'ports') => $ports, + },); + $b_hub = 1; + $ind_rc =3; + $ind_sc =2; + } + # it's a device + else { + $type = $id->[14] if $id->[14]; + $driver = $id->[15] if $id->[15]; + $type ||= 'N/A'; + $driver ||= 'N/A'; + # print "pt3:$class:$product\n"; + $rows->[$j]{main::key($num++,1,2,'Device')} = $bus_id; + $rows->[$j]{main::key($num++,0,3,'info')} = $product; + $rows->[$j]{main::key($num++,0,3,'type')} = $type; + if ($extra > 0){ + $rows->[$j]{main::key($num++,0,3,'driver')} = $driver; + } + if ($extra > 2 && $id->[9]){ + $rows->[$j]{main::key($num++,0,3,'interfaces')} = $id->[9]; + } + } + # for either hub or device + $rows->[$j]{main::key($num++,1,$ind_sc,'rev')} = $rev; + if ($extra > 0){ + $speed_si = ($id->[17]) ? $id->[17] : 'N/A'; + $speed_si .= " ($id->[25])" if ($b_admin && $id->[25]); + $rows->[$j]{main::key($num++,0,$ind_rc,'speed')} = $speed_si; + if ($extra > 1){ + if ($id->[24]){ + if ($id->[23] == $id->[24]){ + $rows->[$j]{main::key($num++,0,$ind_rc,'lanes')} = $id->[24]; + } + else { + $rows->[$j]{main::key($num++,1,$ind_rc,'lanes')} = ''; + $rows->[$j]{main::key($num++,0,($ind_rc+1),'rx')} = $id->[23]; + $rows->[$j]{main::key($num++,0,($ind_rc+1),'tx')} = $id->[24]; + } + } + } + # 22 is only available if 23 and 24 are present as well + if ($b_admin && $id->[22]){ + $rows->[$j]{main::key($num++,0,$ind_rc,'mode')} = $id->[22]; + } + if ($extra > 2 && $id->[19] && $id->[19] ne '0mA'){ + $rows->[$j]{main::key($num++,0,$ind_sc,'power')} = $id->[19]; + } + $chip_id = $id->[7]; + $chip_id ||= 'N/A'; + $rows->[$j]{main::key($num++,0,$ind_sc,'chip-ID')} = $chip_id; + if ($extra > 2 && defined $id->[5] && $id->[5] ne ''){ + my $id = sprintf("%02s",$id->[4]) . sprintf("%02s", $id->[5]); + $rows->[$j]{main::key($num++,0,$ind_sc,'class-ID')} = $id; + } + if (!$b_hub && $extra > 2){ + if ($serial){ + $rows->[$j]{main::key($num++,0,$ind_sc,'serial')} = main::filter($serial); + } + } + } + } + # print Data::Dumper::Dumper \@rows; + eval $end if $b_log; +} +} + +## WeatherItem +# add metric / imperial (us) switch +{ +package WeatherItem; + +sub get { + eval $start if $b_log; + my $rows = []; + my $num = 0; + my $location = []; + location_data($location); + # print Data::Dumper::Dumper $location;exit; + if (!$location->[0]){ + @$rows = ({ + main::key($num++,0,1,'Message') => main::message('weather-null','current location') + }); + } + else { + my $weather = get_weather($location); + if ($weather->{'error'}){ + @$rows = ({ + main::key($num++,0,1,'Message') => main::message('weather-error',$weather->{'error'}) + }); + } + elsif (!$weather->{'weather'}){ + @$rows = ({ + main::key($num++,0,1,'Message') => main::message('weather-null','weather data') + }); + } + else { + weather_output($rows,$location,$weather); + } + } + if (!@$rows){ + @$rows = ({ + main::key($num++,0,1,'Message') => main::message('weather-null','weather data') + }); + } + eval $end if $b_log; + return $rows; +} + +sub weather_output { + eval $start if $b_log; + my ($rows,$location,$weather) = @_; + my ($j,$num) = (0,0); + my ($value); + my ($conditions) = ('NA'); + $conditions = "$weather->{'weather'}"; + my $temp = process_unit( + $weather->{'temp'}, + $weather->{'temp-c'},'C', + $weather->{'temp-f'},'F'); + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'Report') => '', + main::key($num++,0,2,'temperature') => $temp, + main::key($num++,0,2,'conditions') => $conditions, + },); + if ($extra > 0){ + my $pressure = process_unit( + $weather->{'pressure'}, + $weather->{'pressure-mb'},'mb', + $weather->{'pressure-in'},'in'); + my $wind = process_wind( + $weather->{'wind'}, + $weather->{'wind-direction'}, + $weather->{'wind-mph'}, + $weather->{'wind-ms'}, + $weather->{'wind-gust-mph'}, + $weather->{'wind-gust-ms'}); + $rows->[$j]{main::key($num++,0,2,'wind')} = $wind; + if ($extra > 1){ + if (defined $weather->{'cloud-cover'}){ + $rows->[$j]{main::key($num++,0,2,'cloud cover')} = $weather->{'cloud-cover'} . '%'; + } + if ($weather->{'precip-1h-mm'} && defined $weather->{'precip-1h-in'}){ + $value = process_unit('',$weather->{'precip-1h-mm'},'mm', + $weather->{'precip-1h-in'},'in'); + $rows->[$j]{main::key($num++,0,2,'precipitation')} = $value; + } + if ($weather->{'rain-1h-mm'} && defined $weather->{'rain-1h-in'}){ + $value = process_unit('',$weather->{'rain-1h-mm'},'mm', + $weather->{'rain-1h-in'},'in'); + $rows->[$j]{main::key($num++,0,2,'rain')} = $value; + } + if ($weather->{'snow-1h-mm'} && defined $weather->{'snow-1h-in'}){ + $value = process_unit('',$weather->{'snow-1h-mm'},'mm', + $weather->{'snow-1h-in'},'in'); + $rows->[$j]{main::key($num++,0,2,'snow')} = $value; + } + } + $rows->[$j]{main::key($num++,0,2,'humidity')} = $weather->{'humidity'} . '%'; + if ($extra > 1){ + if ($weather->{'dewpoint'} || (defined $weather->{'dewpoint-c'} && + defined $weather->{'dewpoint-f'})){ + $value = process_unit( + $weather->{'dewpoint'}, + $weather->{'dewpoint-c'}, + 'C', + $weather->{'dewpoint-f'}, + 'F'); + $rows->[$j]{main::key($num++,0,2,'dew point')} = $value; + } + } + $rows->[$j]{main::key($num++,0,2,'pressure')} = $pressure; + } + if ($extra > 1){ + if ($weather->{'heat-index'} || (defined $weather->{'heat-index-c'} && + defined $weather->{'heat-index-f'})){ + $value = process_unit( + $weather->{'heat-index'}, + $weather->{'heat-index-c'},'C', + $weather->{'heat-index-f'},'F'); + $rows->[$j]{main::key($num++,0,2,'heat index')} = $value; + } + if ($weather->{'windchill'} || (defined $weather->{'windchill-c'} && + defined $weather->{'windchill-f'})){ + $value = process_unit( + $weather->{'windchill'}, + $weather->{'windchill-c'},'C', + $weather->{'windchill-f'},'F'); + $rows->[$j]{main::key($num++,0,2,'wind chill')} = $value; + } + if ($extra > 2){ + if ($weather->{'forecast'}){ + $j = scalar @$rows; + push(@$rows, { + main::key($num++,1,1,'Forecast') => $weather->{'forecast'}, + },); + } + } + } + $j = scalar @$rows; + if ($extra > 2 && !$use{'filter'}){ + complete_location( + $location, + $weather->{'city'}, + $weather->{'state'}, + $weather->{'country'}); + } + push(@$rows, { + main::key($num++,1,1,'Locale') => $location->[1], + },); + if ($extra > 2 && !$use{'filter'} && ($weather->{'elevation-m'} || + $weather->{'elevation-ft'})){ + $rows->[$j]{main::key($num++,0,2,'altitude')} = process_elevation( + $weather->{'elevation-m'}, + $weather->{'elevation-ft'}); + } + $rows->[$j]{main::key($num++,0,2,'current time')} = $weather->{'date-time'},; + if ($extra > 2){ + $weather->{'observation-time-local'} = 'N/A' if !$weather->{'observation-time-local'}; + $rows->[$j]{main::key($num++,0,2,'observation time')} = $weather->{'observation-time-local'}; + if ($weather->{'sunrise'}){ + $rows->[$j]{main::key($num++,0,2,'sunrise')} = $weather->{'sunrise'}; + } + if ($weather->{'sunset'}){ + $rows->[$j]{main::key($num++,0,2,'sunset')} = $weather->{'sunset'}; + } + if ($weather->{'moonphase'}){ + $value = $weather->{'moonphase'} . '%'; + $value .= ($weather->{'moonphase-graphic'}) ? ' ' . $weather->{'moonphase-graphic'} :''; + $rows->[$j]{main::key($num++,0,2,'moonphase')} = $value; + } + } + if ($weather->{'api-source'}){ + $rows->[$j]{main::key($num++,0,1,'Source')} = $weather->{'api-source'}; + } + eval $end if $b_log; +} + +sub process_elevation { + eval $start if $b_log; + my ($meters,$feet) = @_; + my ($result,$i_unit,$m_unit) = ('','ft','m'); + $feet = sprintf("%.0f", 3.28 * $meters) if defined $meters && !$feet; + $meters = sprintf("%.1f", $feet/3.28) if defined $feet && !$meters; + $meters = sprintf("%.0f", $meters) if $meters; + if (defined $meters && $weather_unit eq 'mi'){ + $result = "$meters $m_unit ($feet $i_unit)"; + } + elsif (defined $meters && $weather_unit eq 'im'){ + $result = "$feet $i_unit ($meters $m_unit)"; + } + elsif (defined $meters && $weather_unit eq 'm'){ + $result = "$meters $m_unit"; + } + elsif (defined $feet && $weather_unit eq 'i'){ + $result = "$feet $i_unit"; + } + else { + $result = 'N/A'; + } + eval $end if $b_log; + return $result; +} + +sub process_unit { + eval $start if $b_log; + my ($primary,$metric,$m_unit,$imperial,$i_unit) = @_; + my $result = ''; + if (defined $metric && defined $imperial && $weather_unit eq 'mi'){ + $result = "$metric $m_unit ($imperial $i_unit)"; + } + elsif (defined $metric && defined $imperial && $weather_unit eq 'im'){ + $result = "$imperial $i_unit ($metric $m_unit)"; + } + elsif (defined $metric && $weather_unit eq 'm'){ + $result = "$metric $m_unit"; + } + elsif (defined $imperial && $weather_unit eq 'i'){ + $result = "$imperial $i_unit"; + } + elsif ($primary){ + $result = $primary; + } + else { + $result = 'N/A'; + } + eval $end if $b_log; + return $result; +} + +sub process_wind { + eval $start if $b_log; + my ($primary,$direction,$mph,$ms,$gust_mph,$gust_ms) = @_; + my ($result,$gust_kmh,$kmh,$i_unit,$m_unit,$km_unit) = ('','','','mph','m/s','km/h'); + # get rid of possible gust values if they are the same as wind values + $gust_mph = undef if $gust_mph && $mph && $mph eq $gust_mph; + $gust_ms = undef if $gust_ms && $ms && $ms eq $gust_ms; + # calculate and round, order matters so that rounding only happens after math done + $ms = 0.44704 * $mph if defined $mph && !defined $ms; + $mph = $ms * 2.23694 if defined $ms && !defined $mph; + $kmh = sprintf("%.0f", 18*$ms/5) if defined $ms; + $ms = sprintf("%.1f", $ms) if defined $ms; # very low mph speeds yield 0, which is wrong + $mph = sprintf("%.0f", $mph) if defined $mph; + $gust_ms = 0.44704 * $gust_mph if $gust_mph && !$gust_ms; + $gust_kmh = 18 * $gust_ms / 5 if $gust_ms; + $gust_mph = $gust_ms * 2.23694 if $gust_ms && !$gust_mph; + $gust_mph = sprintf("%.0f", $gust_mph) if $gust_mph; + $gust_kmh = sprintf("%.0f", $gust_kmh) if $gust_kmh; + $gust_ms = sprintf("%.0f", $gust_ms) if $gust_ms; + if (!defined $mph && $primary){ + $result = $primary; + } + elsif (defined $mph && defined $direction){ + if ($weather_unit eq 'mi'){ + $result = "from $direction at $ms $m_unit ($kmh $km_unit, $mph $i_unit)"; + } + elsif ($weather_unit eq 'im'){ + $result = "from $direction at $mph $i_unit ($ms $m_unit, $kmh $km_unit)"; + } + elsif ($weather_unit eq 'm'){ + $result = "from $direction at $ms $m_unit ($kmh $km_unit)"; + } + elsif ($weather_unit eq 'i'){ + $result = "from $direction at $mph $i_unit"; + } + if ($gust_mph){ + if ($weather_unit eq 'mi'){ + $result .= ". Gusting to $ms $m_unit ($kmh $km_unit, $mph $i_unit)"; + } + elsif ($weather_unit eq 'im'){ + $result .= ". Gusting to $mph $i_unit ($ms $m_unit, $kmh $km_unit)"; + } + elsif ($weather_unit eq 'm'){ + $result .= ". Gusting to $ms $m_unit ($kmh $km_unit)"; + } + elsif ($weather_unit eq 'i'){ + $result .= ". Gusting to $mph $i_unit"; + } + } + } + elsif ($primary){ + $result = $primary; + } + else { + $result = 'N/A'; + } + eval $end if $b_log; + return $result; +} + +sub get_weather { + eval $start if $b_log; + my ($location) = @_; + my $now = POSIX::strftime "%Y%m%d%H%M", localtime; + my ($date_time,$freshness,$tz,$weather_data); + my $weather = {}; + my $loc_name = lc($location->[0]); + $loc_name =~ s/-\/|\s|,/-/g; + $loc_name =~ s/--/-/g; + my $file_cached = "$user_data_dir/weather-$loc_name-$weather_source.txt"; + if (-r $file_cached){ + @$weather_data = main::reader($file_cached); + $freshness = (split(/\^\^/, $weather_data->[0]))[1]; + # print "$now:$freshness\n"; + } + if (!$freshness || $freshness < ($now - 60)){ + $weather_data = download_weather($now,$file_cached,$location); + } + # print join("\n", @weather_data), "\n"; + # NOTE: because temps can be 0, we can't do if value tests + foreach (@$weather_data){ + my @working = split(/\s*\^\^\s*/, $_); + next if ! defined $working[1] || $working[1] eq ''; + if ($working[0] eq 'api_source'){ + $weather->{'api-source'} = $working[1]; + } + elsif ($working[0] eq 'city'){ + $weather->{'city'} = $working[1]; + } + elsif ($working[0] eq 'cloud_cover'){ + $weather->{'cloud-cover'} = $working[1]; + } + elsif ($working[0] eq 'country'){ + $weather->{'country'} = $working[1]; + } + elsif ($working[0] eq 'dewpoint_string'){ + $weather->{'dewpoint'} = $working[1]; + $working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/; + $weather->{'dewpoint-c'} = $2;; + $weather->{'dewpoint-f'} = $1;; + } + elsif ($working[0] eq 'dewpoint_c'){ + $weather->{'dewpoint-c'} = $working[1]; + } + elsif ($working[0] eq 'dewpoint_f'){ + $weather->{'dewpoint-f'} = $working[1]; + } + # WU: there are two elevations, we want the first one + elsif (!$weather->{'elevation-m'} && $working[0] eq 'elevation'){ + # note: bug in source data uses ft for meters, not 100% of time, but usually + $weather->{'elevation-m'} = $working[1]; + $weather->{'elevation-m'} =~ s/\s*(ft|m).*$//; + } + elsif ($working[0] eq 'error'){ + $weather->{'error'} = $working[1]; + } + elsif ($working[0] eq 'forecast'){ + $weather->{'forecast'} = $working[1]; + } + elsif ($working[0] eq 'heat_index_string'){ + $weather->{'heat-index'} = $working[1]; + $working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/; + $weather->{'heat-index-c'} = $2;; + $weather->{'heat-index-f'} = $1; + } + elsif ($working[0] eq 'heat_index_c'){ + $weather->{'heat-index-c'} = $working[1]; + } + elsif ($working[0] eq 'heat_index_f'){ + $weather->{'heat-index-f'} = $working[1]; + } + elsif ($working[0] eq 'relative_humidity'){ + $working[1] =~ s/%$//; + $weather->{'humidity'} = $working[1]; + } + elsif ($working[0] eq 'local_time'){ + $weather->{'local-time'} = $working[1]; + } + elsif ($working[0] eq 'local_epoch'){ + $weather->{'local-epoch'} = $working[1]; + } + elsif ($working[0] eq 'moonphase'){ + $weather->{'moonphase'} = $working[1]; + } + elsif ($working[0] eq 'moonphase_graphic'){ + $weather->{'moonphase-graphic'} = $working[1]; + } + elsif ($working[0] eq 'observation_time_rfc822'){ + $weather->{'observation-time-rfc822'} = $working[1]; + } + elsif ($working[0] eq 'observation_epoch'){ + $weather->{'observation-epoch'} = $working[1]; + } + elsif ($working[0] eq 'observation_time'){ + $weather->{'observation-time-local'} = $working[1]; + $weather->{'observation-time-local'} =~ s/Last Updated on //; + } + elsif ($working[0] eq 'precip_mm'){ + $weather->{'precip-1h-mm'} = $working[1]; + } + elsif ($working[0] eq 'precip_in'){ + $weather->{'precip-1h-in'} = $working[1]; + } + elsif ($working[0] eq 'pressure_string'){ + $weather->{'pressure'} = $working[1]; + } + elsif ($working[0] eq 'pressure_mb'){ + $weather->{'pressure-mb'} = $working[1]; + } + elsif ($working[0] eq 'pressure_in'){ + $weather->{'pressure-in'} = $working[1]; + } + elsif ($working[0] eq 'rain_1h_mm'){ + $weather->{'rain-1h-mm'} = $working[1]; + } + elsif ($working[0] eq 'rain_1h_in'){ + $weather->{'rain-1h-in'} = $working[1]; + } + elsif ($working[0] eq 'snow_1h_mm'){ + $weather->{'snow-1h-mm'} = $working[1]; + } + elsif ($working[0] eq 'snow_1h_in'){ + $weather->{'snow-1h-in'} = $working[1]; + } + elsif ($working[0] eq 'state_name'){ + $weather->{'state'} = $working[1]; + } + elsif ($working[0] eq 'sunrise'){ + if ($working[1]){ + if ($working[1] !~ /^[0-9]+$/){ + $weather->{'sunrise'} = $working[1]; + } + # trying to figure out remote time from UTC is too hard + elsif (!$show{'weather-location'}){ + $weather->{'sunrise'} = POSIX::strftime "%T", localtime($working[1]); + } + } + } + elsif ($working[0] eq 'sunset'){ + if ($working[1]){ + if ($working[1] !~ /^[0-9]+$/){ + $weather->{'sunset'} = $working[1]; + } + # trying to figure out remote time from UTC is too hard + elsif (!$show{'weather-location'}){ + $weather->{'sunset'} = POSIX::strftime "%T", localtime($working[1]); + } + } + } + elsif ($working[0] eq 'temperature_string'){ + $weather->{'temp'} = $working[1]; + $working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/; + $weather->{'temp-c'} = $2;; + $weather->{'temp-f'} = $1; + # $weather->{'temp'} =~ s/\sF/\xB0 F/; # B0 + # $weather->{'temp'} =~ s/\sF/\x{2109}/; + # $weather->{'temp'} =~ s/\sC/\x{2103}/; + } + elsif ($working[0] eq 'temp_f'){ + $weather->{'temp-f'} = $working[1]; + } + elsif ($working[0] eq 'temp_c'){ + $weather->{'temp-c'} = $working[1]; + } + elsif ($working[0] eq 'timezone'){ + $weather->{'timezone'} = $working[1]; + } + elsif ($working[0] eq 'visibility'){ + $weather->{'visibility'} = $working[1]; + } + elsif ($working[0] eq 'visibility_km'){ + $weather->{'visibility-km'} = $working[1]; + } + elsif ($working[0] eq 'visibility_mi'){ + $weather->{'visibility-mi'} = $working[1]; + } + elsif ($working[0] eq 'weather'){ + $weather->{'weather'} = $working[1]; + } + elsif ($working[0] eq 'wind_degrees'){ + $weather->{'wind-degrees'} = $working[1]; + } + elsif ($working[0] eq 'wind_dir'){ + $weather->{'wind-direction'} = $working[1]; + } + elsif ($working[0] eq 'wind_mph'){ + $weather->{'wind-mph'} = $working[1]; + } + elsif ($working[0] eq 'wind_gust_mph'){ + $weather->{'wind-gust-mph'} = $working[1]; + } + elsif ($working[0] eq 'wind_gust_ms'){ + $weather->{'wind-gust-ms'} = $working[1]; + } + elsif ($working[0] eq 'wind_ms'){ + $weather->{'wind-ms'} = $working[1]; + } + elsif ($working[0] eq 'wind_string'){ + $weather->{'wind'} = $working[1]; + } + elsif ($working[0] eq 'windchill_string'){ + $weather->{'windchill'} = $working[1]; + $working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/; + $weather->{'windchill-c'} = $2; + $weather->{'windchill-f'} = $1; + } + elsif ($working[0] eq 'windchill_c'){ + $weather->{'windchill-c'} = $working[1]; + } + elsif ($working[0] eq 'windchill_f'){ + $weather->{'windchill_f'} = $working[1]; + } + } + if ($show{'weather-location'}){ + if ($weather->{'observation-time-local'} && + $weather->{'observation-time-local'} =~ /^(.*)\s([a-z_]+\/[a-z_]+)$/i){ + $tz = $2; + } + if (!$tz && $weather->{'timezone'}){ + $tz = $weather->{'timezone'}; + $weather->{'observation-time-local'} .= ' (' . $weather->{'timezone'} . ')' if $weather->{'observation-time-local'}; + } + # very clever trick, just make the system think it's in the + # remote timezone for this local block only + local $ENV{'TZ'} = $tz if $tz; + $date_time = POSIX::strftime "%c", localtime(); + $date_time = test_locale_date($date_time,'',''); + $weather->{'date-time'} = $date_time; + # only wu has rfc822 value, and we want the original observation time then + if ($weather->{'observation-epoch'} && $tz){ + $date_time = POSIX::strftime "%Y-%m-%d %T ($tz %z)", localtime($weather->{'observation-epoch'}); + $date_time = test_locale_date($date_time,$show{'weather-location'},$weather->{'observation-epoch'}); + $weather->{'observation-time-local'} = $date_time; + } + } + else { + $date_time = POSIX::strftime "%c", localtime(); + $date_time = test_locale_date($date_time,'',''); + $tz = ($location->[2]) ? " ($location->[2])" : ''; + $weather->{'date-time'} = $date_time . $tz; + } + # we get the wrong time using epoch for remote -W location + if (!$show{'weather-location'} && $weather->{'observation-epoch'}){ + $date_time = POSIX::strftime "%c", localtime($weather->{'observation-epoch'}); + $date_time = test_locale_date($date_time,$show{'weather-location'},$weather->{'observation-epoch'}); + $weather->{'observation-time-local'} = $date_time; + } + eval $end if $b_log; + return $weather; +} + +sub download_weather { + eval $start if $b_log; + my ($now,$file_cached,$location) = @_; + my ($temp,$ua,$url); + my $weather = []; + $url = "https://smxi.org/opt/xr2.php?loc=$location->[0]&src=$weather_source"; + $ua = 'weather'; + if ($fake{'weather'}){ + # my $file2 = "$fake_data_dir/weather/weather-1.xml"; + # my $file2 = "$fake_data_dir/weather/feed-oslo-1.xml"; + # local $/; + # my $file = "$fake_data_dir/weather/weather-1.xml"; + # open(my $fh, '<', $file) or die "can't open $file: $!"; + # $temp = <$fh>; + } + else { + $temp = main::download_file('stdout',$url,'',$ua); + } + @$weather = split('\n', $temp) if $temp; + unshift(@$weather, "timestamp^^$now"); + main::writer($file_cached,$weather); + # print "$file_cached: download/cleaned\n"; + eval $end if $b_log; + return $weather; +} + +# Rsolve wide character issue, if detected, switch to iso +# date format, we won't try to be too clever here. +sub test_locale_date { + my ($date_time,$location,$epoch) = @_; + # $date_time .= 'дек'; + # print "1: $date_time\n"; + if ($date_time =~ m/[^\x00-\x7f]/){ + if (!$location && $epoch){ + $date_time = POSIX::strftime "%Y-%m-%d %H:%M:%S", localtime($epoch); + } + else { + $date_time = POSIX::strftime "%Y-%m-%d %H:%M:%S", localtime(); + } + } + $date_time =~ s/\s+$//; + # print "2: $date_time\n"; + return $date_time; +} + +## Location Data ## +sub location_data { + eval $start if $b_log; + my $location = $_[0]; + if ($show{'weather-location'}){ + my $location_string; + $location_string = $show{'weather-location'}; + $location_string =~ s/\+/ /g; + if ($location_string =~ /,/){ + my @temp = split(',', $location_string); + my $sep = ''; + my $string = ''; + foreach (@temp){ + $_ = ucfirst($_); + $string .= $sep . $_; + $sep = ', '; + } + $location_string = $string; + } + $location_string = main::filter($location_string); + @$location = ($show{'weather-location'},$location_string,''); + } + else { + get_location($location); + } + eval $end if $b_log; +} + +sub get_location { + eval $start if $b_log; + my $location = $_[0]; + my ($city,$country,$freshness,%loc,$loc_arg,$loc_string,@loc_data,$state); + my $now = POSIX::strftime "%Y%m%d%H%M", localtime; + my $file_cached = "$user_data_dir/location-main.txt"; + if (-r $file_cached){ + @loc_data = main::reader($file_cached); + $freshness = (split(/\^\^/, $loc_data[0]))[1]; + } + if (!$freshness || $freshness < $now - 90){ + my $temp; + my $url = "http://geoip.ubuntu.com/lookup"; + # { + # local $/; + # my $file = "$fake_data_dir/weather/location-1.xml"; + # open(my $fh, '<', $file) or die "can't open $file: $!"; + # $temp = <$fh>; + # } + $temp = main::download_file('stdout',$url); + @loc_data = split('\n', $temp); + @loc_data = map { + s/<\?.*//; + s/<\/[^>]+>/\n/g; + s/>/^^/g; + s/[1] && $location->[1] =~ /[0-9+-]/ && $city){ + $location->[1] = $country . ', ' . $location->[1] if $country && $location->[1] !~ m|$country|i; + $location->[1] = $state . ', ' . $location->[1] if $state && $location->[1] !~ m|$state|i; + $location->[1] = $city . ', ' . $location->[1] if $city && $location->[1] !~ m|$city|i; + } + eval $end if $b_log; +} +} + +#### ------------------------------------------------------------------- +#### ITEM UTILITIES +#### ------------------------------------------------------------------- + +# android only, for distro / OS id and machine data +sub set_build_prop { + eval $start if $b_log; + my $path = '/system/build.prop'; + $loaded{'build-prop'} = 1; + return if ! -r $path; + my @data = reader($path,'strip'); + foreach (@data){ + my @working = split('=', $_); + next if $working[0] !~ /^ro\.(build|product)/; + if ($working[0] eq 'ro.build.date.utc'){ + $build_prop{'build-date'} = strftime "%F", gmtime($working[1]); + } + # ldgacy, replaced by ro.product.device + elsif ($working[0] eq 'ro.build.product'){ + $build_prop{'build-product'} = $working[1]; + } + # this can be brand, company, android, it varies, but we don't want android value + elsif ($working[0] eq 'ro.build.user'){ + $build_prop{'build-user'} = $working[1] if $working[1] !~ /android/i; + } + elsif ($working[0] eq 'ro.build.version.release'){ + $build_prop{'build-version'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.board'){ + $build_prop{'product-board'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.brand'){ + $build_prop{'product-brand'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.device'){ + $build_prop{'product-device'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.manufacturer'){ + $build_prop{'product-manufacturer'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.model'){ + $build_prop{'product-model'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.name'){ + $build_prop{'product-name'} = $working[1]; + } + elsif ($working[0] eq 'ro.product.screensize'){ + $build_prop{'product-screensize'} = $working[1]; + } + } + log_data('dump','%build_prop',\%build_prop) if $b_log; + print Dumper \%build_prop if $dbg[20]; + eval $end if $b_log; +} + +## CompilerVersion +{ +package CompilerVersion; + +sub get { + eval $start if $b_log; + my $compiler = []; # we want an array ref to return if not set + if (my $file = $system_files{'proc-version'}){ + version_proc($compiler,$file); + } + elsif ($bsd_type){ + version_bsd($compiler); + } + eval $end if $b_log; + return $compiler; +} + +# args: 0: compiler by ref +sub version_bsd { + eval $start if $b_log; + my $compiler = $_[0]; + if ($alerts{'sysctl'}->{'action'} && $alerts{'sysctl'}->{'action'} eq 'use'){ + if ($sysctl{'kernel'}){ + my @working; + foreach (@{$sysctl{'kernel'}}){ + # Not every line will have a : separator though the processor should make + # most have it. This appears to be 10.x late feature add, I don't see it + # on earlier BSDs + if (/^kern.compiler_version/){ + @working = split(/:\s*/, $_); + $working[1] =~ /.*(gcc|clang)\sversion\s([\S]+)\s.*/; + @$compiler = ($1,$2); + last; + } + } + } + # OpenBSD doesn't show compiler data in sysctl or dboot but it's going to + # be Clang until way into the future, and it will be the installed version. + if (ref $compiler ne 'ARRAY' || !@$compiler){ + if (my $path = main::check_program('clang')){ + $compiler->[0] = 'clang'; + $compiler->[1] =main::program_version($path,'clang',3,'--version'); + } + } + } + main::log_data('dump','@$compiler',$compiler) if $b_log; + eval $end if $b_log; +} + +# args: 0: compiler by ref; 1: proc file name +sub version_proc { + eval $start if $b_log; + my ($compiler,$file) = @_; + if (my $result = main::reader($file,'',0)){ + my $version; + if ($fake{'compiler'}){ + # $result = $result =~ /\*(gcc|clang)\*eval\*/; + # $result='Linux version 5.4.0-rc1 (sourav@archlinux-pc) (clang version 9.0.0 (tags/RELEASE_900/final)) #1 SMP PREEMPT Sun Oct 6 18:02:41 IST 2019'; + # $result='Linux version 5.8.3-fw1 (fst@x86_64.frugalware.org) ( OpenMandriva 11.0.0-0.20200819.1 clang version 11.0.0 (/builddir/build/BUILD/llvm-project-release-11.x/clang 2a0076812cf106fcc34376d9d967dc5f2847693a), LLD 11.0.0)'; + # $result='Linux version 5.8.0-18-generic (buildd@lgw01-amd64-057) (gcc (Ubuntu 10.2.0-5ubuntu2) 10.2.0, GNU ld (GNU Binutils for Ubuntu) 2.35) #19-Ubuntu SMP Wed Aug 26 15:26:32 UTC 2020'; + # $result='Linux version 5.8.9-fw1 (fst@x86_64.frugalware.org) (gcc (Frugalware Linux) 9.2.1 20200215, GNU ld (GNU Binutils) 2.35) #1 SMP PREEMPT Tue Sep 15 16:38:57 CEST 2020'; + # $result='Linux version 5.8.0-2-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.0-9) 10.2.0, GNU ld (GNU Binutils for Debian) 2.35) #1 SMP Debian 5.8.10-1 (2020-09-19)'; + # $result='Linux version 5.9.0-5-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-1) 10.2.1 20201207, GNU ld (GNU Binutils for Debian) 2.35.1) #1 SMP Debian 5.9.15-1 (2020-12-17)'; + # $result='Linux version 2.6.1 (GNU 0.9 GNU-Mach 1.8+git20201007-486/Hurd-0.9 i686-AT386)'; + # $result='NetBSD version 9.1 (netbsd@localhost) (gcc version 7.5.0) NetBSD 9.1 (GENERIC) #0: Sun Oct 18 19:24:30 UTC 2020'; + # $result='Linux version 6.0.8-0-generic (chimera@chimera) (clang version 15.0.4, LLD 15.0.4) #1 SMP PREEMPT_DYNAMIC Fri Nov 11 13:45:29 UTC 2022'; + } + if ($result =~ /(gcc|clang).*version\s([^,\s\)]+)/){ + $version = $2; + $version ||= 'N/A'; + @$compiler = ($1,$version); + } + elsif ($result =~ /\((gcc|clang)[^\(]*\([^\)]+\)\s+([0-9\.]+)(\s[^.]*)?,\s*/){ + $version = $2; + $version ||= 'N/A'; + @$compiler = ($1,$version); + } + } + main::log_data('dump','@$compiler',$compiler) if $b_log; + eval $end if $b_log; +} +} + +sub set_dboot_data { + eval $start if $b_log; + $loaded{'dboot'} = 1; + my ($file,@db_data,@dm_data,@temp); + my ($counter) = (0); + if (!$fake{'dboot'}){ + $file = $system_files{'dmesg-boot'}; + } + else { + # $file = "$fake_data_dir/bsd/dmesg-boot/bsd-disks-diabolus.txt"; + # $file = "$fake_data_dir/bsd/dmesg-boot/freebsd-disks-solestar.txt"; + # $file = "$fake_data_dir/bsd/dmesg-boot/freebsd-enceladus-1.txt"; + ## matches: toshiba: openbsd-5.6-sysctl-2.txt + # $file = "$fake_data_dir/bsd/dmesg-boot/openbsd-5.6-dmesg.boot-1.txt"; + ## matches: compaq: openbsd-5.6-sysctl-1.txt" + # $file = "$fake_data_dir/bsd/dmesg-boot/openbsd-dmesg.boot-1.txt"; + # $file = "$fake_data_dir/bsd/dmesg-boot/openbsd-6.8-battery-sensors-1.txt"; + } + if ($file){ + return if ! -r $file; + @db_data = reader($file); + # sometimes > 1 sessions stored, dump old ones + for (@db_data){ + if (/^(Dragonfly|OpenBSD|NetBSD|FreeBSD is a registered trademark|Copyright.*Midnight)/){ + $counter++; + undef @temp if $counter > 1; + } + push(@temp,$_); + } + @db_data = @temp; + undef @temp; + my @dm_data = grabber('dmesg 2>/dev/null'); + # clear out for netbsd, only 1 space following or lines won't match + @dm_data = map {$_ =~ s/^\[[^\]]+\]\s//;$_} @dm_data; + $counter = 0; + # dump previous sessions, and also everything roughly before dmesg.boot + # ends, it does't need to be perfect, we just only want the actual post + # boot data + for (@dm_data){ + if (/^(Dragonfly|OpenBSD|NetBSD|FreeBSD is a registered trademark|Copyright.*Midnight)/ || + /^(smbus[0-9]:|Security policy loaded|root on)/){ + $counter++; + undef @temp if $counter > 1; + } + push(@temp,$_); + } + @dm_data = @temp; + undef @temp; + push(@db_data,'~~~~~',@dm_data); + # uniq(\@db_data); # get rid of duplicate lines + # some dmesg repeats, so we need to dump the second and > iterations + # replace all indented items with ~ so we can id them easily while + # processing note that if user, may get error of read permissions + # for some weird reason, real mem and avail mem are use a '=' separator, + # who knows why, the others are ':' + foreach (@db_data){ + $_ =~ s/\s*=\s*|:\s*/:/; + $_ =~ s/\"//g; + $_ =~ s/^\s+/~/; + $_ =~ s/\s\s/ /g; + $_ =~ s/^(\S+)\sat\s/$1:at /; # ada0 at ahcich0 + push(@{$dboot{'main'}}, $_); + if ($use{'bsd-battery'} && /^acpi(bat|cmb)/){ + push(@{$sysctl{'battery'}}, $_); + } + # ~Debug Features 0:<2 CTX BKPTs,4 Watchpoints,6 Breakpoints,PMUv3,Debugv8> + elsif ($use{'bsd-cpu'} && + (!/^~(Debug|Memory)/ && /(^cpu[0-9]+:|Features|^~*Origin:\s*)/)){ + push(@{$dboot{'cpu'}}, $_); + } + # FreeBSD: 'da*' is a USB device 'ada*' is a SATA device 'mmcsd*' is an SD card + # OpenBSD: 'sd' is usb device, 'wd' normal drive. OpenBSD uses sd for nvme drives + # but also has the nvme data: + # nvme1 at pci6 dev 0 function 0 vendor "Phison", unknown product 0x5012 rev 0x01: msix, NVMe 1.3 + # nvme1: OWC Aura P12 1.0TB, firmware ECFM22.6, serial 2003100010208 + # scsibus2 at nvme1: 2 targets, initiator 0 + # sd1 at scsibus2 targ 1 lun 0: + # sd1: 915715MB, 4096 bytes/sector, 234423126 sectors + elsif ($use{'bsd-disk'} && + /^(ad|ada|da|mmcblk|mmcsd|nvme([0-9]+n)?|sd|wd)[0-9]+(:|\sat\s|.*?\sdetached$)/){ + $_ =~ s/^\(//; + push (@{$dboot{'disk'}},$_); + } + if ($use{'bsd-machine'} && /^bios[0-9]:(at|vendor)/){ + push(@{$sysctl{'machine'}}, $_); + } + elsif ($use{'bsd-machine'} && !$dboot{'machine-vm'} && + /(\bhvm\b|innotek|\bkvm\b|microsoft.*virtual machine|openbsd[\s-]vmm|qemu|qumranet|vbox|virtio|virtualbox|vmware)/i){ + push(@{$dboot{'machine-vm'}}, $_); + } + elsif ($use{'bsd-optical'} && /^(cd)[0-9]+(\([^)]+\))?(:|\sat\s)/){ + push(@{$dboot{'optical'}},$_); + } + elsif ($use{'bsd-pci'} && /^(pci[0-9]+:at|\S+:at pci)/){ + push(@{$dboot{'pci'}},$_); + } + elsif ($use{'bsd-ram'} && /(^spdmem)/){ + push(@{$dboot{'ram'}}, $_); + } + } + log_data('dump','$dboot{main}',$dboot{'main'}) if $b_log; + print Dumper $dboot{'main'} if $dbg[11]; + + if ($dboot{'main'} && $b_log){ + log_data('dump','$dboot{cpu}',$dboot{'cpu'}); + log_data('dump','$dboot{disk}',$dboot{'disk'}); + log_data('dump','$dboot{machine-vm}',$dboot{'machine-vm'}); + log_data('dump','$dboot{optical}',$dboot{'optical'}); + log_data('dump','$dboot{ram}',$dboot{'ram'}); + log_data('dump','$dboot{usb}',$dboot{'usb'}); + log_data('dump','$sysctl{battery}',$sysctl{'battery'}); + log_data('dump','$sysctl{machine}',$sysctl{'machine'}); + } + if ($dboot{'main'} && $dbg[11]){ + print("cpu:\n", Dumper $dboot{'cpu'}); + print("disk:\n", Dumper $dboot{'disk'}); + print("machine vm:\n", Dumper $dboot{'machine-vm'}); + print("optical:\n", Dumper $dboot{'optical'}); + print("ram:\n", Dumper $dboot{'ram'}); + print("usb:\n", Dumper $dboot{'usb'}); + print("sys battery:\n", Dumper $sysctl{'battery'}); + print("sys machine:\n", Dumper $sysctl{'machine'}); + } + # this should help get rid of dmesg usb mounts not present + # note if you take out one, put in another, it will always show the first + # one, I think. Not great. Not using this means all drives attached + # current session are shown, using it, possibly wrong drive shown, which is bad + # not using this for now: && (my @disks = grep {/^hw\.disknames/} @{$dboot{'disk'}} + if ($dboot{'disk'}){ + # hw.disknames:sd0:,sd1:3242432,sd2: + #$disks[0] =~ s/(^hw\.disknames:|:[^,]*)//g; + #@disks = split(',',$disks[0]) if $disks[0]; + my ($id,$value,%dboot_disks,@disks_live,@temp); + # first, since openbsd has this, let's use it + foreach (@{$dboot{'disk'}}){ + if (!@disks_live && /^hw\.disknames/){ + $_ =~ s/(^hw\.disknames:|:[^,]*)//g; + @disks_live = split(/[,\s]/,$_) if $_; + } + else { + push(@temp,$_); + } + } + @{$dboot{'disk'}} = @temp if @temp; + foreach my $row (@temp){ + $row =~ /^([^:\s]+)[:\s]+(.+)/; + $id = $1; + $value = $2; + push(@{$dboot_disks{$id}},$value); + # get rid of detached or non present drives + if ((@disks_live && !(grep {$id =~ /^$_/} @disks_live)) || + $value =~ /\b(destroyed|detached)$/){ + delete $dboot_disks{$id}; + } + } + $dboot{'disk'} = \%dboot_disks; + log_data('dump','post: $dboot{disk}',$dboot{'disk'}) if $b_log; + print("post: disk:\n",Dumper $dboot{'disk'}) if $dbg[11]; + } + if ($use{'bsd-pci'} && $dboot{'pci'}){ + my $bus_id = 0; + foreach (@{$dboot{'pci'}}){ + if (/^pci[0-9]+:at.*?bus\s([0-9]+)/){ + $bus_id = $1; + next; + } + elsif (/:at pci[0-9]+\sdev/){ + $_ =~ s/^(\S+):at.*?dev\s([0-9]+)\sfunction\s([0-9]+)\s/$bus_id:$2:$3:$1:/; + push(@temp,$_); + } + } + $dboot{'pci'} = [@temp]; + log_data('dump','$dboot{pci}',$dboot{'pci'}) if $b_log; + print("pci:\n",Dumper $dboot{'pci'}) if $dbg[11]; + } + } + eval $end if $b_log; +} + +## DesktopEnvironment +# returns array: +# 0: desktop name +# 1: version +# 2: toolkit +# 3: toolkit version +# 4: info extra desktop data +# 5: wm +# 6: wm version +{ +package DesktopEnvironment; +my ($b_gtk,$b_qt,$b_xprop,$desktop_session,$gdmsession,$kde_session_version, +$xdg_desktop,@data,@xprop); +my $desktop = []; + +sub get { + eval $start if $b_log; + set_desktop_values(); + main::set_ps_gui() if !$loaded{'ps-gui'}; + get_kde_trinity_data(); + if (!@$desktop){ + get_env_de_data(); + } + if (!@$desktop){ + get_env_xprop_gnome_based_data(); + } + if (!@$desktop){ + get_env_xfce_data(); + } + if (!@$desktop){ + get_env_xprop_misc_data(); + } + if (!@$desktop){ + get_ps_de_data(); + } + if ($extra > 2 && @$desktop){ + set_info_data(); + } + if ($b_display && !$force{'display'} && $extra > 1){ + get_wm(); + } + set_gtk_data() if $b_gtk && $extra > 1; + set_qt_data() if $b_qt && $extra > 1; + main::log_data('dump','@$desktop', $desktop) if $b_log; + # ($b_xprop,$kde_session_version,$xdg_desktop,@data,@xprop) = (); + eval $end if $b_log; + return $desktop; +} + +sub set_desktop_values { + # NOTE $XDG_CURRENT_DESKTOP envvar is not reliable, but it shows certain desktops better. + # most desktops are not using it as of 2014-01-13 (KDE, UNITY, LXDE. Not Gnome) + $desktop_session = ($ENV{'DESKTOP_SESSION'}) ? prep_desktop_value($ENV{'DESKTOP_SESSION'}) : ''; + $xdg_desktop = ($ENV{'XDG_CURRENT_DESKTOP'}) ? prep_desktop_value($ENV{'XDG_CURRENT_DESKTOP'}) : ''; + $kde_session_version = ($ENV{'KDE_SESSION_VERSION'}) ? $ENV{'KDE_SESSION_VERSION'} : ''; + # for fallback to fallback protections re false gnome id + $gdmsession = ($ENV{'GDMSESSION'}) ? prep_desktop_value($ENV{'GDMSESSION'}) : ''; +} + +# Note: an ubuntu regresssion replaces or adds 'ubuntu' string to +# real value. Since ubuntu is the only distro I know that does this, +# will add more distro type filters as/if we come across them +sub prep_desktop_value { + $_[0] = lc(main::trimmer($_[0])); + $_[0] =~ s/\b(arch|debian|fedora|manjaro|mint|opensuse|ubuntu):?\s*//i; + return $_[0]; +} + +sub get_kde_trinity_data { + eval $start if $b_log; + my ($kded,$kded_name,$program,@version_data,@version_data2); + my $kde_full_session = ($ENV{'KDE_FULL_SESSION'}) ? $ENV{'KDE_FULL_SESSION'} : ''; + # we can't rely on 3 using kded3, it could be kded + if ($kde_full_session && ($program = main::check_program('kded' . $kde_full_session))){ + $kded = $program; + $kded_name = 'kded' . $kde_full_session; + } + elsif ($program = main::check_program('kded')){ + $kded = $program; + $kded_name = 'kded'; + } + # note: if TDM is used to start kde, can pass ps tde test + if ($desktop_session eq 'trinity' || $xdg_desktop eq 'trinity' || + (!$desktop_session && !$xdg_desktop && (grep {/^tde/} @ps_gui))){ + $desktop->[0] = 'Trinity'; + if ($program = main::check_program('kdesktop')){ + @version_data = main::grabber("$program --version 2>/dev/null"); + $desktop->[1] = main::awk(\@version_data,'^TDE:',2,'\s+') if @version_data; + } + if ($extra > 1 && @version_data){ + $desktop->[2] = 'Qt'; + $desktop->[3] = main::awk(\@version_data,'^Qt:',2,'\s+') if @version_data; + } + } + # works on 4, assume 5 will id the same, why not, no need to update in future + # KDE_SESSION_VERSION is the integer version of the desktop + # NOTE: as of plasma 5, the tool: about-distro MAY be available, that will show + # actual desktop data, so once that's in debian/ubuntu, if it gets in, add that test + elsif ($desktop_session eq 'kde-plasma' || $xdg_desktop eq 'kde' || + $kde_session_version){ + if ($kde_session_version && $kde_session_version <= 4){ + @data = ($kded_name) ? main::program_values($kded_name) : (); + if (@data){ + $desktop->[0] = $data[3]; + $desktop->[1] = main::program_version($kded,$data[0],$data[1],$data[2],$data[5],$data[6]); + # kded exists, so we can now get the qt data string as well + if ($desktop->[1] && $kded){ + @version_data = main::grabber("$kded --version 2>/dev/null"); + } + } + $desktop->[0] = 'KDE' if !$desktop->[0]; + } + else { + # NOTE: this command string is almost certain to change, and break, with next + # major plasma desktop, ie, 6. + # qdbus org.kde.plasmashell /MainApplication org.qtproject.Qt.QCoreApplication.applicationVersion + # Qt: 5.4.2 + # KDE Frameworks: 5.11.0 + # kf5-config: 1.0 + # for QT, and Frameworks if we use it + if (!@version_data && ($program = main::check_program("kf$kde_session_version-config"))){ + @version_data = main::grabber("$program --version 2>/dev/null"); + } + if (!@version_data && ($program = main::check_program("kf-config"))){ + @version_data = main::grabber("$program --version 2>/dev/null"); + } + # hope we don't use this fallback, not the same version as kde always + if (!@version_data && $kded){ + @version_data = main::grabber("$kded --version 2>/dev/null"); + } + if ($program = main::check_program("plasmashell")){ + @version_data2 = main::grabber("$program --version 2>/dev/null"); + $desktop->[1] = main::awk(\@version_data2,'^plasmashell',-1,'\s+'); + } + $desktop->[0] = 'KDE Plasma'; + } + if (!$desktop->[1]){ + $desktop->[1] = ($kde_session_version) ? $kde_session_version : main::message('unknown-desktop-version'); + } + # print Data::Dumper::Dumper \@version_data; + if ($extra > 1){ + if (@version_data){ + $desktop->[3] = main::awk(\@version_data,'^Qt:', 2,'\s+'); + } + # qmake can have variants, qt4-qmake, qt5-qmake, also qt5-default but not tested + if (!$desktop->[3] && main::check_program("qmake")){ + # note: this program has issues, it may appear to be in /usr/bin, but it + # often fails to execute, so the below will have null output, but use as a + # fall back test anyway. + ($desktop->[2],$desktop->[3]) = main::program_data('qmake'); + } + $desktop->[2] ||= 'Qt'; + } + } + # KDE_FULL_SESSION property is only available since KDE 3.5.5. + elsif ($kde_full_session eq 'true'){ + @version_data = ($kded) ? main::grabber("$kded --version 2>/dev/null") : (); + $desktop->[0] = 'KDE'; + $desktop->[1] = main::awk(\@version_data,'^KDE:',2,'\s+') if @version_data; + if (!$desktop->[1]){ + $desktop->[1] = '3.5'; + } + if ($extra > 1 && @version_data){ + $desktop->[2] = 'Qt'; + $desktop->[3] = main::awk(\@version_data,'^Qt:',2,'\s+') if @version_data; + } + } + eval $end if $b_log; +} + +sub get_env_de_data { + eval $start if $b_log; + my ($program,@version_data); + if (!$desktop->[0]){ + # 0: 1/0; 1: env var search; 2: data; 3: gtk tk; 4: qt tk; 5: ps_gui search + my @desktops =( + [1,'unity','unity',0,0], + [0,'budgie','budgie-desktop',0,0], + # debian package: lxde-core. + # NOTE: some distros fail to set XDG data for root + [1,'lxde','lxpanel',0,0,',^lxsession$'], + [1,'razor','razor-session',0,1,'^razor-session$'], + # BAD: lxqt-about opens dialogue, sigh. + # Checked, lxqt-panel does show same version as lxqt-about + [1,'lxqt','lxqt-panel',0,1,'^lxqt-session$'], + [0,'^(razor|lxqt)$','lxqt-variant',0,1,'^(razor-session|lxqt-session)$'], + # note, X-Cinnamon value strikes me as highly likely to change, so just + # search for the last part + [0,'cinnamon','cinnamon',1,0], + # these so far have no cli version data + [1,'deepin','deepin',0,1], # version comes from file read + [1,'leftwm','leftwm',0,0], + [1,'pantheon','pantheon',0,0], + [1,'penrose','penrose',0,0],# unknown, just guessing + [1,'lumina','lumina-desktop',0,1], + [0,'manokwari','manokwari',1,0], + [1,'ukui','ukui-session',0,1], + ); + foreach my $item (@desktops){ + # Check if in xdg_desktop OR desktop_session OR if in $item->[6] and in ps_gui + if ((($item->[0] && ($xdg_desktop eq $item->[1] || $desktop_session eq $item->[1])) || + (!$item->[0] && ($xdg_desktop =~ /$item->[1]/ || $desktop_session =~ /$item->[1]/))) || + ($item->[5] && @ps_gui && (grep {/$item->[5]/} @ps_gui))){ + ($desktop->[0],$desktop->[1]) = main::program_data($item->[2]); + $b_gtk = $item->[3]; + $b_qt = $item->[4]; + last; + } + } + } + eval $end if $b_log; +} + +sub get_env_xprop_gnome_based_data { + eval $start if $b_log; + my ($program,$value,@version_data); + # NOTE: Always add to set_prop the search term if you add an item!! + set_xprop(); + # add more as discovered + return if $xdg_desktop eq 'xfce' || $gdmsession eq 'xfce'; + # note that cinnamon split from gnome, and and can now be id'ed via xprop, + # but it will still trigger the next gnome true case, so this needs to go + # before gnome test eventually this needs to be better organized so all the + # xprop tests are in the same section, but this is good enough for now. + # NOTE: was checking for 'muffin' but that's not part of cinnamon + if ($xdg_desktop eq 'cinnamon' || $gdmsession eq 'cinnamon' || ($b_xprop && + (main::check_program('muffin') || main::check_program('cinnamon-session')) && + main::awk(\@xprop,'_muffin'))){ + ($desktop->[0],$desktop->[1]) = main::program_data('cinnamon','cinnamon',0); + $b_gtk = 1; + $desktop->[0] ||= 'Cinnamon'; + } + elsif ($xdg_desktop eq 'mate' || $gdmsession eq 'mate' || + ($b_xprop && main::awk(\@xprop,'_marco'))){ + # NOTE: mate-about and mate-sesssion vary which has the higher number, neither + # consistently corresponds to the actual MATE version, so check both. + my %versions = ('mate-about' => '','mate-session' => ''); + foreach my $key (keys %versions){ + if ($program = main::check_program($key)){ + @data = main::program_data($key,$program,0); + $desktop->[0] = $data[0]; + $versions{$key} = $data[1]; + } + } + # no consistent rule about which version is higher, so just compare them and take highest + $desktop->[1] = main::compare_versions($versions{'mate-about'},$versions{'mate-session'}); + # $b_gtk = 1; + $desktop->[0] ||= 'MATE'; + } + # See sub for logic and comments + elsif (check_gnome()){ + if (main::check_program('gnome-about')){ + ($desktop->[0],$desktop->[1]) = main::program_data('gnome-about'); + } + elsif (main::check_program('gnome-shell')){ + ($desktop->[0],$desktop->[1]) = main::program_data('gnome','gnome-shell'); + } + $b_gtk = 1; + $desktop->[0] ||= 'GNOME'; + } + eval $end if $b_log; +} + +# Note, GNOME_DESKTOP_SESSION_ID is deprecated so we'll see how that works out +# https://bugzilla.gnome.org/show_bug.cgi?id=542880. +# NOTE: manjaro is leaving XDG data null, which forces the manual check for gnome, sigh... +# some gnome programs can trigger a false xprop gnome ID +# _GNOME_BACKGROUND_REPRESENTATIVE_COLORS(STRING) = "rgb(23,31,35)" +sub check_gnome { + eval $start if $b_log; + my ($b_gnome,$detection) = (0,''); + if ($xdg_desktop && $xdg_desktop =~ /gnome/){ + $detection = 'xdg_current_desktop'; + $b_gnome = 1; + } + # should work as long as string contains gnome, eg: peppermint:gnome + # filtered explicitly in set_desktop_values + elsif ($xdg_desktop && $xdg_desktop !~ /gnome/){ + $detection = 'xdg_current_desktop'; + } + # possible values: lightdm-xsession, only positive match tests will work + elsif ($gdmsession && $gdmsession eq 'gnome'){ + $detection = 'gdmsession'; + $b_gnome = 1; + } + # risky: Debian: $DESKTOP_SESSION = lightdm-xsession; Manjaro/Arch = xfce + # note that mate/cinnamon would already have been caught so no need to add + # explicit tests for them + elsif ($desktop_session && $desktop_session eq 'gnome'){ + $detection = 'desktop_session'; + $b_gnome = 1; + } + # possible value: this-is-deprecated, but I believe only gnome based desktops + # set this variable, so it doesn't matter what it contains + elsif ($ENV{'GNOME_DESKTOP_SESSION_ID'}){ + $detection = 'gnome_destkop_session_id'; + $b_gnome = 1; + } + # maybe use ^_gnome_session instead? try it for a while + elsif ($b_xprop && main::check_program('gnome-shell') && + main::awk(\@xprop,'^_gnome_session')){ + $detection = 'xprop-root'; + $b_gnome = 1; + } + main::log_data('data','$detection:$b_gnome>>' . $detection . ":$b_gnome") if $b_log; + eval $end if $b_log; + return $b_gnome; +} + +# Not strictly dependent on xprop data, which is not necessarily always present +sub get_env_xfce_data { + eval $start if $b_log; + my (@version_data); + # print join("\n", @xprop), "\n"; + # String: "This is xfdesktop version 4.2.12" + # alternate: xfce4-about --version > xfce4-about 4.10.0 (Xfce 4.10) + # note: some distros/wm (e.g. bunsen) set $xdg_desktop to xfce to solve some + # other issues so but are OpenBox. Not inxi issue. + # $xdg_desktop can be /usr/bin/startxfce4 + # print "xdg_d: $xdg_desktop gdms: $gdmsession\n"; + if ($xdg_desktop eq 'xfce' || $gdmsession eq 'xfce' || + ($b_xprop && main::check_program('xfdesktop')) && + main::awk(\@xprop,'^(xfdesktop|xfce)')){ + @data = main::program_values('xfdesktop'); + $desktop->[0] = $data[3]; + # xfdesktop --version out of x fails to get display, so no data + @version_data = main::grabber('xfdesktop --version 2>/dev/null'); + # out of x, this error goes to stderr, so it's an empty result + $desktop->[1] = main::awk(\@version_data,$data[0],$data[1],'\s+'); + #$desktop->[1] = main::program_version('xfdesktop',$data[0],$data[1],$data[2],$data[5],$data[6]); + if (!$desktop->[1]){ + my $version = '4'; # just assume it's 4, we tried + if (main::check_program('xfce4-panel')){ + $version = '4'; + } + # talk to xfce to see what id they will be using for xfce 5 + elsif (main::check_program('xfce5-panel')){ + $version = '5'; + } + # they might get rid of number, we'll see + elsif (main::check_program('xfce-panel')){ + $version = ''; + } + @data = main::program_values("xfce${version}-panel"); + # print Data::Dumper::Dumper \@data; + # this returns an error message to stdout in x, which breaks the version + # xfce4-panel --version out of x fails to get display, so no data + $desktop->[1] = main::program_version("xfce${version}-panel",$data[0],$data[1],$data[2],$data[5],$data[6]); + # out of x this kicks out an error: xfce4-panel: Cannot open display + $desktop->[1] = '' if $desktop->[1] !~ /[0-9]\./; + } + $desktop->[0] ||= 'Xfce'; + $desktop->[1] ||= ''; # xfce isn't going to be 4 forever + if ($extra > 1){ + @data = main::program_values('xfdesktop-toolkit'); + #$desktop->[3] = main::program_version('xfdesktop',$data[0],$data[1],$data[2],$data[5],$data[6]); + $desktop->[3] = main::awk(\@version_data,$data[0],$data[1],'\s+'); + $desktop->[2] = $data[3]; + } + } + eval $end if $b_log; +} + +# These require data from xprop, at least partially +sub get_env_xprop_misc_data { + eval $start if $b_log; + # print join("\n", @xprop), "\n"; + if ($xdg_desktop eq 'moksha' || $gdmsession eq 'moksha' || ($b_xprop && + (main::check_program('enlightenment') || main::check_program('moksha')) && + main::awk(\@xprop,'moksha'))){ + # no -v or --version but version is in xprop -root + # ENLIGHTENMENT_VERSION(STRING) = "Moksha 0.2.0.15989" + $desktop->[0] = 'Moksha'; + if ($b_xprop){ + $desktop->[1] = main::awk(\@xprop,'(enlightenment|moksha)_version',2,'\s+=\s+'); + $desktop->[1] =~ s/"?(Moksha|Enlightenment)\s([^"]+)"?/$2/i if $desktop->[1]; + } + } + elsif ($xdg_desktop eq 'enlightenment' || $gdmsession eq 'enlightenment' || + ($b_xprop && main::check_program('enlightenment') && + main::awk(\@xprop,'enlightenment'))){ + # no -v or --version but version is in xprop -root + # ENLIGHTENMENT_VERSION(STRING) = "Enlightenment 0.16.999.49898" + $desktop->[0] = 'Enlightenment'; + if ($b_xprop){ + $desktop->[1] = main::awk(\@xprop,'(enlightenment|moksha)_version',2,'\s+=\s+'); + $desktop->[1] =~ s/"?(Moksha|Enlightenment)\s([^"]+)"?/$2/i if $desktop->[1]; + } + } + # the sequence here matters, some desktops like icewm, razor, let you set different + # wm, so we want to get the main controlling desktop first, then fall back to the wm + # detections. get_ps_de_data() and get_wm() will handle alternate wm detections. + # I believe all these will be X only wm, so xprop tests fine here. + if ($b_xprop && !$desktop->[0]){ + # 0 check program; 1 xprop search; 2: data; 3 - optional: ps_gui search + my @desktops =( + ['icewm','icewm','icewm'], + # debian package: i3-wm + ['i3','i3','i3'], + ['mwm','^_motif','mwm'], + # debian package name: wmaker + ['WindowMaker','^_?windowmaker','wmaker'], + ['wm2','^_wm2','wm2'], + ['herbstluftwm','herbstluftwm','herbstluftwm'], + ['fluxbox','blackbox_pid','fluxbox','^fluxbox$'], + ['blackbox','blackbox_pid','blackbox'], + ['openbox','openbox_pid','openbox'], + ['amiwm','amiwm','amiwm'], + ); + foreach my $item (@desktops){ + if (main::check_program($item->[0]) && main::awk(\@xprop,$item->[1]) && + (!$item->[4] || (@ps_gui && (grep {/$item->[4]/} @ps_gui)))){ + ($desktop->[0],$desktop->[1]) = main::program_data($item->[2]); + last; + } + } + } + # need to check starts line because it's so short + eval $end if $b_log; +} + +sub get_ps_de_data { + eval $start if $b_log; + my ($program,@version_data); + main::set_ps_gui() if !$loaded{'ps-gui'}; + if (@ps_gui){ + # the sequence here matters, some desktops like icewm, razor, let you set different + # wm, so we want to get the main controlling desktop first + # icewm and any other that permits alternate wm to be used need to go first + # in this list. + # unverfied: 2bwm catwm mcwm penrose snapwm uwm wmfs wmfs2 wingo wmii2 + # xfdesktoo is fallback in case not in xprop + my @wms = qw(icewm 2bwm 9wm aewm aewm\+\+ afterstep amiwm antiwm awesome + blackbox bspwm calmwm catwm cde ctwm dwm echinus evilwm fluxbox fvwm + hackedbox herbstluftwm instantwm i3 ion3 jbwm jwm larswm leftwm lwm + matchbox-window-manager mcwm mini musca mvwm mwm nawm notion nscde + openbox pekwm penrose qvwm ratpoison + sawfish scrotwm snapwm spectrwm tinywm tvtwm twm uwm + windowlab wmfs wmfs2 wingo wmii2 wmii wmx xmonad yeahwm); + my $matches = join('|',@wms) . $wl_compositors; + # note: use my $psg to avoid bizarre return from program_data to ps_gui write + foreach my $psg (@ps_gui){ + # no need to use check program with short list of ps_gui + if ($psg =~ /^($matches)$/){ + my $item = $1; + ($desktop->[0],$desktop->[1]) = main::program_data($item); + if ($extra > 1 && $item eq 'xfdesktop'){ + ($desktop->[2],$desktop->[3]) = main::program_data('xfdesktop-toolkit',$item,1); + } + last; + } + } + if (!$desktop->[0]){ + # order matters, these have alternate search patterns from default name + # 1 check program; 2 ps_gui search; 3 data; 4: trigger alternate values/version + @wms =( + ['WindowMaker','WindowMaker','wmaker',''], + ['clfswm','.*(sh|c?lisp)?.*clfswm','clfswm',''], + ['cwm','(openbsd-)?cwm','cwm',''], + ['flwm','flwm','flwm',''], + ['flwm','flwm_topside','flwm',''], + ['fvwm-crystal','fvwm.*-crystal','fvwm-crystal','fvwm'], + ['fvwm1','fvwm1','fvwm1',''], + ['fvwm2','fvwm2','fvwm2',''], + ['fvwm3','fvwm3','fvwm3',''], + ['fvwm95','fvwm95','fvwm95',''], + ['qtile','.*(python.*)?qtile','qtile',''], + ['stumpwm','(sh|c?lisp)?.*stumpwm','stumpwm',''], + ); + foreach my $item (@wms){ + # no need to use check program with short list of ps_gui + if (grep {/^$item->[1]$/} @ps_gui){ + ($desktop->[0],$desktop->[1]) = main::program_data($item->[2],$item->[3]); + if ($extra > 1 && $item->[0] eq 'xfdesktop'){ + ($desktop->[2],$desktop->[3]) = main::program_data('xfdesktop-toolkit',$item->[0],1); + } + last; + } + } + } + } + eval $end if $b_log; +} + +# NOTE: used to use a super slow method here, but gtk-launch returns +# the gtk version I believe +sub set_gtk_data { + eval $start if $b_log; + if (main::check_program('gtk-launch')){ + ($desktop->[2],$desktop->[3]) = main::program_data('gtk-launch'); + } + eval $end if $b_log; +} + +sub set_qt_data { + eval $start if $b_log; + my ($program,@data,@version_data); + my $kde_version = $kde_session_version; + $program = ''; + if (!$kde_version){ + if ($program = main::check_program("kded6")){$kde_version = 6;} + elsif ($program = main::check_program("kded5")){$kde_version = 5;} + elsif ($program = main::check_program("kded4")){$kde_version = 4;} + elsif ($program = main::check_program("kded")){$kde_version = '';} + } + # alternate: qt4-default, qt4-qmake or qt5-default, qt5-qmake + # often this exists, is executable, but actually is nothing, shows error + if (!$desktop->[3] && main::check_program('qmake')){ + ($desktop->[2],$desktop->[3]) = main::program_data('qmake'); + } + if (!$desktop->[3] && main::check_program('qtdiag')){ + ($desktop->[2],$desktop->[3]) = main::program_data('qtdiag'); + } + if (!$desktop->[3] && ($program = main::check_program("kf$kde_version-config"))){ + @version_data = main::grabber("$program --version 2>/dev/null"); + $desktop->[2] = 'Qt'; + $desktop->[3] = main::awk(\@version_data,'^Qt:',2) if @version_data; + } + # note: qt 5 does not show qt version in kded5, sigh + if (!$desktop->[3] && ($program = main::check_program("kded$kde_version"))){ + @version_data = main::grabber("$program --version 2>/dev/null"); + $desktop->[2] = 'Qt'; + $desktop->[3] = main::awk(\@version_data,'^Qt:',2) if @version_data; + } + eval $end if $b_log; +} + +sub get_wm { + eval $start if $b_log; + if (!$force{'wmctrl'}){ + get_wm_main(); + } + # note, some wm, like cinnamon muffin, do not appear in ps aux, but do in wmctrl + if ((!$desktop->[5] || $force{'wmctrl'}) && (my $program = main::check_program('wmctrl'))){ + get_wm_wmctrl($program); + } + eval $end if $b_log; +} + +sub get_wm_main { + eval $start if $b_log; + my ($wms,$working); + # xprop is set only if not kde/gnome/cinnamon/mate/budgie/lx.. + if ($b_xprop){ + #KWIN_RUNNING + $wms = 'amiwm|blackbox|bspwm|compiz|kwin_wayland|kwin_x11|kwinft|kwin|marco|'; + $wms .= 'motif|muffin|openbox|herbstluftwm|twin|ukwm|wm2|windowmaker|i3'; + foreach (@xprop){ + if (/($wms)/){ + $working = $1; + $working = 'wmaker' if $working eq 'windowmaker'; + last; + } + } + } + if (!$desktop->[5]){ + main::set_ps_gui() if !$loaded{'ps-gui'}; + # order matters, see above logic + # due to lisp/python starters, clfswm/stumpwm/qtile will not detect here + my @wms = qw(2bwm 9wm aewm aewm\+\+ afterstep amiwm antiwm awesome blackbox + calmwm catwm clfswm compiz ctwm (openbsd-)?cwm fluxbox bspwm budgie-wm + deepin-wm dwm echinus evilwm flwm fvwm-crystal fvwm1 fvwm2 fvwm3 fvwm95 + fvwm gala gnome-shell hackedbox i3 instantwm ion3 jbwm jwm twin kwin_wayland + kwin_x11 kwinft kwin larswm leftwm lwm matchbox-window-manager marco mcwm mini + muffin musca deepin-mutter mutter deepin-metacity metacity mvwm mwm + nawm notion openbox qtile qvwm penrose ratpoison sawfish scrotwm snapwm + spectrwm stumpwm tinywm tvtwm twm ukwm windowlab WindowMaker wingo wmfs2? + wmii2? wmx xfwm[45]? xmonad yeahwm); + my $wms = join('|',@wms) . $wl_compositors; + foreach my $psg (@ps_gui){ + if ($psg =~ /^($wms)$/){ + $working = $1; + last; + } + } + } + get_wm_version('manual',$working) if $working; + $desktop->[5] = $working if !$desktop->[5] && $working; + eval $end if $b_log; +} + +sub get_wm_wmctrl { + eval $start if $b_log; + my ($program) = @_; + my $cmd = "$program -m 2>/dev/null"; + my @data = main::grabber($cmd,'','strip'); + main::log_data('dump','@data',\@data) if $b_log; + $desktop->[5] = main::awk(\@data,'^Name',2,'\s*:\s*'); + $desktop->[5] = '' if $desktop->[5] && $desktop->[5] eq 'N/A'; + if ($desktop->[5]){ + # variants: gnome shell; + # IceWM 1.3.8 (Linux 3.2.0-4-amd64/i686) ; Metacity (Marco) ; Xfwm4 + $desktop->[5] =~ s/\d+\.\d\S+|[\[\(].*\d+\.\d.*[\)\]]//g; + $desktop->[5] = main::trimmer($desktop->[5]); + # change Metacity (Marco) to marco + if ($desktop->[5] =~ /marco/i){$desktop->[5] = 'marco'} + elsif ($desktop->[5] =~ /muffin/i){$desktop->[5] = 'muffin'} + elsif (lc($desktop->[5]) eq 'gnome shell'){$desktop->[5] = 'gnome-shell'} + elsif ($desktop_session eq 'trinity' && lc($desktop->[5]) eq 'kwin'){$desktop->[5] = 'Twin'} + get_wm_version('wmctrl',$desktop->[5]); + } + eval $end if $b_log; +} + +sub get_wm_version { + eval $start if $b_log; + my ($type,$wm) = @_; + # we don't want the gnome-shell version, and the others have no --version + # we also don't want to run --version again on stuff we already have tested + return if !$wm || $wm =~ /^(budgie-wm|gnome-shell)$/ || ($desktop->[0] && lc($desktop->[0]) eq lc($wm)); + my $temp = (split(/\s+/, $wm))[0]; + if ($temp){ + $temp = (split(/\s+/, $temp))[0]; + $temp = lc($temp); + $temp = 'wmaker' if $temp eq 'windowmaker'; + my @data = main::program_data($temp,$temp,3); + return if !$data[0]; + # print Data::Dumper::Dumper \@data; + $desktop->[5] = $data[0] if $type eq 'manual'; + $desktop->[6] = $data[1] if $data[1]; + } + eval $end if $b_log; +} + +sub set_info_data { + eval $start if $b_log; + main::set_ps_gui() if !$loaded{'ps-gui'}; + my (@data,@info,$item); + my $pattern = 'alltray|awn|bar|bmpanel|bmpanel2|budgie-panel|cairo-dock|'; + $pattern .= 'dde-dock|dmenu|dockbarx|docker|docky|dzen|dzen2|'; + $pattern .= 'fancybar|fbpanel|fspanel|glx-dock|gnome-panel|hpanel|'; + $pattern .= 'i3bar|i3status|i3-status-rs|icewmtray|'; + $pattern .= 'kdocker|kicker|'; + $pattern .= 'latte|latte-dock|lemonbar|ltpanel|luastatus|lxpanel|lxqt-panel|'; + $pattern .= 'matchbox-panel|mate-panel|nwg-bar|nwg-dock|nwg-panel|ourico|'; + $pattern .= 'perlpanel|plank|plasma-desktop|plasma-netbook|polybar|pypanel|'; + $pattern .= 'razor-panel|razorqt-panel|rootbar|sfwbar|stalonetray|swaybar|'; + $pattern .= 'taskbar|tint2|trayer|'; + $pattern .= 'ukui-panel|vala-panel|wapanel|waybar|wbar|wharf|wingpanel|witray|'; + $pattern .= 'xfce4-panel|xfce5-panel|xmobar|yabar|yambar'; + if (@data = grep {/^($pattern)$/} @ps_gui){ + # only one entry per type, can be multiple + foreach $item (@data){ + if (! grep {$item =~ /$_/} @info){ + $item = main::trimmer($item); + $item =~ s/.*\///; + push(@info, (split(/\s+/, $item))[0]); + } + } + } + if (@info){ + main::uniq(\@info); + $desktop->[4] = join(', ', @info); + } + eval $end if $b_log; +} + +sub set_xprop { + eval $start if $b_log; + if (my $program = main::check_program('xprop')){ + @xprop = main::grabber("xprop -root $display_opt 2>/dev/null"); + if (@xprop){ + # add wm / de as required, but only add what is really tested for above + # XFDESKTOP_IMAGE_FILE; XFCE_DESKTOP + my $pattern = '^amiwm|blackbox_pid|bspwm|compiz|enlightenment|^_gnome|'; + $pattern .= 'herbstluftwm|^kwin_|^i3_|icewm|_marco|moksha|^_motif|_muffin|'; + $pattern .= 'openbox_pid|^_ukwm|^_?windowmaker|^_wm2|^(xfdesktop|xfce)'; + # let's only do these searches once + @xprop = grep {/^\S/ && /($pattern)/i} @xprop; + $_ = lc for @xprop; + $b_xprop = 1 if scalar @xprop > 0; + } + } + # print "@xprop\n"; + eval $end if $b_log; +} +} + +## DeviceData +# creates arrays: $devices{'audio'}; $devices{'graphics'}; $devices{'hwraid'}; +# $devices{'network'}; $devices{'timer'} and local @devices for logging/debugging +# 0: type +# 1: type_id +# 2: bus_id +# 3: sub_id +# 4: device +# 5: vendor_id +# 6: chip_id +# 7: rev +# 8: port +# 9: driver +# 10: modules +# 11: driver_nu [bsd, like: em0 - driver em; nu 0. Used to match IF in -n +# 12: subsystem/vendor +# 13: subsystem vendor_id:chip id +# 14: soc handle +# 15: serial number +{ +package DeviceData; +my (@bluetooth,@devices,@files,@full_names,@pcis,@temp,@temp2,@temp3,%lspci_n); +my ($b_bt_check,$b_lspci_n); +my ($busid,$busid_nu,$chip_id,$content,$device,$driver,$driver_nu,$file, +$handle,$modules,$port,$rev,$serial,$temp,$type,$type_id,$vendor,$vendor_id); + +sub set { + eval $start if $b_log; + ${$_[0]} = 1; # set check by reference + if ($use{'pci'}){ + if (!$bsd_type){ + if ($alerts{'lspci'}->{'action'} eq 'use'){ + lspci_data(); + } + # ! -d '/proc/bus/pci' + # this is sketchy, a sbc won't have pci, but a non sbc arm may have it, so + # build up both and see what happens + if (%risc){ + soc_data(); + } + } + else { + # if (1 == 1){ + if ($alerts{'pciconf'}->{'action'} eq 'use'){ + pciconf_data(); + } + elsif ($alerts{'pcidump'}->{'action'} eq 'use'){ + pcidump_data(); + } + elsif ($alerts{'pcictl'}->{'action'} eq 'use'){ + pcictl_data(); + } + } + if ($dbg[9]){ + print Data::Dumper::Dumper $devices{'audio'}; + print Data::Dumper::Dumper $devices{'bluetooth'}; + print Data::Dumper::Dumper $devices{'graphics'}; + print Data::Dumper::Dumper $devices{'network'}; + print Data::Dumper::Dumper $devices{'hwraid'}; + print Data::Dumper::Dumper $devices{'timer'}; + print "vm: $device_vm\n"; + } + if ($b_log){ + main::log_data('dump','$devices{audio}',$devices{'audio'}); + main::log_data('dump','$devices{bluetooth}',$devices{'bluetooth'}); + main::log_data('dump','$devices{graphics}',$devices{'graphics'}); + main::log_data('dump','$devices{hwraid}',$devices{'hwraid'}); + main::log_data('dump','$devices{network}',$devices{'network'}); + main::log_data('dump','$devices{timer}',$devices{'timer'}); + } + } + undef @devices; + eval $end if $b_log; +} + +sub lspci_data { + eval $start if $b_log; + my ($busid_full,$subsystem,$subsystem_id); + my $data = pci_grabber('lspci'); + # print Data::Dumper::Dumper $data; + foreach (@$data){ + # print "$_\n"; + if ($device){ + if ($_ eq '~'){ + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu,$subsystem,$subsystem_id); + assign_data('pci',\@temp); + $device = ''; + # print "$busid $device_id r:$rev p: $port\n$type\n$device\n"; + } + elsif ($_ =~ /^Subsystem.*\[([a-f0-9]{4}:[a-f0-9]{4})\]/){ + $subsystem_id = $1; + $subsystem = (split(/^Subsystem:\s*/, $_))[1]; + $subsystem =~ s/(\s?\[[^\]]+\])+$//g; + $subsystem = main::clean($subsystem); + $subsystem = main::clean_pci($subsystem,'pci'); + $subsystem = main::clean_pci_subsystem($subsystem); + # print "ss:$subsystem\n"; + } + elsif ($_ =~ /^I\/O\sports/){ + $port = (split(/\s+/, $_))[3]; + # print "p:$port\n"; + } + elsif ($_ =~ /^Kernel\sdriver\sin\suse/){ + $driver = (split(/:\s*/, $_))[1]; + } + elsif ($_ =~ /^Kernel\smodules/i){ + $modules = (split(/:\s*/, $_))[1]; + } + } + # note: arm servers can have more complicated patterns + # 0002:01:02.0 Ethernet controller [0200]: Cavium, Inc. THUNDERX Network Interface Controller virtual function [177d:a034] (rev 08) + # seen cases of lspci trimming too long lines like this: + # 01:00.0 Display controller [0380]: Advanced Micro Devices, Inc. [AMD/ATI] Topaz XT [Radeon R7 M260/M265 / M340/M360 / M440/M445 / 530/535 / 620/625 Mobile] [10... (rev c3) (prog-if 00 [Normal decode]) + # \s(.*)\s\[([0-9a-f]{4}):([0-9a-f]{4})\](\s\(rev\s([^\)]+)\))? + elsif ($_ =~ /^((([0-9a-f]{2,4}:)?[0-9a-f]{2}:[0-9a-f]{2})[.:]([0-9a-f]+))\s+/){ + $busid_full = $1; + $busid = $2; + $busid_nu = hex($4); + ($chip_id,$rev,$type,$type_id,$vendor_id) = ('','','','',''); + $_ =~ s/^\Q$busid_full\E\s+//; + # old systems didn't use [...] but type will get caught in lspci_n check + if ($_ =~ /^(([^\[]+?)\s+\[([a-f0-9]{4})\]:\s+)/){ + $type = $2; + $type_id = $3; + $_ =~ s/^\Q$1\E//; + $type = lc($type); + $type = main::clean_pci($type,'pci'); + $type =~ s/\s+$//; + } + # trim off end prog-if and rev items + if ($_ =~ /(\s+\(prog[^\)]+\))/){ + $_ =~ s/\Q$1\E//; + } + if ($_ =~ /(\s+\(rev\s+[^\)]+\))/){ + $rev = $2; + $_ =~ s/\Q$1\E//; + } + # get rid of anything in parentheses at end in case other variants show + # up, which they probably will. + if ($_ =~ /((\s+\([^\)]+\))+)$/){ + $_ =~ s/\Q$1\E//; + } + if ($_ =~ /(\s+\[([0-9a-f]{4}):([0-9a-f]{4})\])$/){ + $vendor_id = $2; + $chip_id = $3; + $_ =~ s/\Q$1\E//; + } + # lspci -nnv string trunctation bug + elsif ($_ =~ /(\s+\[[^\]]*\.\.\.)$/){ + $_ =~ s/\Q$1\E//; + } + $device = $_; + # cases of corrupted string set to '' + $device = main::clean($device); + # corrupted lspci truncation bug; and ancient lspci, 2.4 kernels + if (!$vendor_id){ + my $temp = lspci_n_data($busid_full); + if (@$temp){ + $type_id = $temp->[0] if !$type_id; + $vendor_id = $temp->[1]; + $chip_id = $temp->[2]; + $rev = $temp->[3] if !$rev && $temp->[3]; + } + } + $use{'hardware-raid'} = 1 if $type_id eq '0104'; + ($driver,$driver_nu,$modules,$port,$subsystem,$subsystem_id) = ('','','','','',''); + } + } + print Data::Dumper::Dumper \@devices if $dbg[4]; + main::log_data('dump','lspci @devices',\@devices) if $b_log; + eval $end if $b_log; +} + +# args: 0: busID +# returns if valid busID: (classID,vendorID,productID,revNu) +# almost never used, only in case of lspci -nnv line truncation bug +sub lspci_n_data { + eval $start if $b_log; + my ($bus_id) = @_; + if (!$b_lspci_n){ + $b_lspci_n = 1; + my (@data); + if ($fake{'lspci'}){ + # my $file = "$fake_data_dir/pci/lspci/steve-mint-topaz-lspci-n.txt"; + # my $file = "$fake_data_dir/pci/lspci/ben81-hwraid-lspci-n.txt"; + # @data = main::reader($file,'strip'); + } + else { + @data = main::grabber($alerts{'lspci'}->{'path'} . ' -n 2>/dev/null','','strip'); + } + foreach (@data){ + if (/^([a-f0-9:\.]+)\s+([a-f0-9]{4}):\s+([a-f0-9]{4}):([a-f0-9]{4})(\s+\(rev\s+([0-9a-z\.]+)\))?/){ + my $rev = (defined $6) ? $6 : ''; + $lspci_n{$1} = [$2,$3,$4,$rev]; + } + } + print Data::Dumper::Dumper \%lspci_n if $dbg[4]; + main::log_data('dump','%lspci_n',\%lspci_n) if $b_log; + } + my $return = ($lspci_n{$bus_id}) ? $lspci_n{$bus_id}: []; + print Data::Dumper::Dumper $return if $dbg[50]; + main::log_data('dump','@$return') if $b_log; + eval $end if $b_log; + return $return; +} + +# em0@pci0:6:0:0: class=0x020000 card=0x10d315d9 chip=0x10d38086 rev=0x00 hdr=0x00 +# vendor = 'Intel Corporation' +# device = 'Intel 82574L Gigabit Ethernet Controller (82574L)' +# class = network +# subclass = ethernet +sub pciconf_data { + eval $start if $b_log; + my $data = pci_grabber('pciconf'); + foreach (@$data){ + if ($driver){ + if ($_ eq '~'){ + $vendor = main::clean($vendor); + $device = main::clean($device); + # handle possible regex in device name, like [ConnectX-3] + # and which could make matches fail + my $device_temp = main::clean_regex($device); + if ($vendor && $device){ + if (main::clean_regex($vendor) !~ /\Q$device_temp\E/i){ + $device = "$vendor $device"; + } + } + elsif (!$device){ + $device = $vendor; + } + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu); + assign_data('pci',\@temp); + $driver = ''; + # print "$busid $device_id r:$rev p: $port\n$type\n$device\n"; + } + elsif ($_ =~ /^vendor/){ + $vendor = (split(/\s+=\s+/, $_))[1]; + # print "p:$port\n"; + } + elsif ($_ =~ /^device/){ + $device = (split(/\s+=\s+/, $_))[1]; + } + elsif ($_ =~ /^class/i){ + $type = (split(/\s+=\s+/, $_))[1]; + } + } + # pre freebsd 13, note chip is product+vendor + # atapci0@pci0:0:1:1: class=0x01018a card=0x00000000 chip=0x71118086 rev=0x01 hdr=0x00 + # freebsd 13 + # isab0@pci0:0:1:0: class=0x060100 rev=0x00 hdr=0x00 vendor=0x8086 device=0x7000 subvendor=0x0000 subdevice=0x0000 + if (/^([^@]+)\@pci([0-9]{1,3}:[0-9]{1,3}:[0-9]{1,3}):([0-9]{1,3}):/){ + $driver = $1; + $busid = $2; + $busid_nu = $3; + $driver = $1; + $driver =~ s/([0-9]+)$//; + $driver_nu = $1; + # we don't use the sub sub class part of the class id, just first 4 + if (/\bclass=0x([\S]{4})\S*\b/){ + $type_id = $1; + } + if (/\brev=0x([\S]+)\b/){ + $rev = $1; + } + if (/\bvendor=0x([\S]+)\b/){ + $vendor_id = $1; + } + if (/\bdevice=0x([\S]+)\b/){ + $chip_id = $1; + } + # yes, they did it backwards, product+vendor id + if (/\bchip=0x([a-f0-9]{4})([a-f0-9]{4})\b/){ + $chip_id = $1; + $vendor_id = $2; + } + ($device,$type,$vendor) = ('','',''); + } + } + print Data::Dumper::Dumper \@devices if $dbg[4]; + main::log_data('dump','pciconf @devices',\@devices) if $b_log; + eval $end if $b_log; +} + +sub pcidump_data { + eval $start if $b_log; + my $data = pci_grabber('pcidump'); + main::set_dboot_data() if !$loaded{'dboot'}; + foreach (@$data){ + if ($_ eq '~' && $busid && $device){ + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu,'','','',$serial); + assign_data('pci',\@temp); + ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu,$serial) = (); + next; + } + if ($_ =~ /^([0-9a-f:]+):([0-9]+):\s([^:]+)$/i){ + $busid = $1; + $busid_nu = $2; + ($driver,$driver_nu) = pcidump_driver("$busid:$busid_nu") if $dboot{'pci'}; + $device = main::clean($3); + } + elsif ($_ =~ /^0x[\S]{4}:\s+Vendor ID:\s+([0-9a-f]{4}),?\s+Product ID:\s+([0-9a-f]{4})/){ + $vendor_id = $1; + $chip_id = $2; + } + elsif ($_ =~ /^0x[\S]{4}:\s+Class:\s+([0-9a-f]{2})(\s[^,]+)?,?\s+Subclass:\s+([0-9a-f]{2})(\s+[^,]+)?,?(\s+Interface: ([0-9a-f]+),?\s+Revision: ([0-9a-f]+))?/){ + $type = pci_class($1); + $type_id = "$1$3"; + } + elsif (/^Serial Number:\s*(\S+)/){ + $serial = $1; + } + } + print Data::Dumper::Dumper \@devices if $dbg[4]; + main::log_data('dump','pcidump @devices',\@devices) if $b_log; + eval $end if $b_log; +} + +sub pcidump_driver { + eval $start if $b_log; + my $bus_id = $_[0]; + my ($driver,$nu); + for (@{$dboot{'pci'}}){ + if (/^$bus_id:([^0-9]+)([0-9]+):/){ + $driver = $1; + $nu = $2; + last; + } + } + eval $end if $b_log; + return ($driver,$nu); +} + +sub pcictl_data { + eval $start if $b_log; + my $data = pci_grabber('pcictl'); + my $data2 = pci_grabber('pcictl-n'); + foreach (@$data){ + if ($_ eq '~' && $busid && $device){ + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu); + assign_data('pci',\@temp); + ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$driver_nu) = (); + next; + } + # it's too fragile to get these in one matching so match, trim, next match + if (/\s+\[([^\]0-9]+)([0-9]+)\]$/){ + $driver = $1; + $driver_nu = $2; + $_ =~ s/\s+\[[^\]]+\]$//; + } + if (/\s+\(.*?(revision 0x([^\)]+))?\)/){ + $rev = $2 if $2; + $_ =~ s/\s+\([^\)]+?\)$//; + } + if ($_ =~ /^([0-9a-f:]+):([0-9]+):\s+([^.]+?)$/i){ + $busid = $1; + $busid_nu = $2; + $device = main::clean($3); + my $working = (grep {/^${busid}:${busid_nu}:\s/} @$data2)[0]; + if ($working && + $working =~ /^${busid}:${busid_nu}:\s+0x([0-9a-f]{4})([0-9a-f]{4})\s+\(0x([0-9a-f]{2})([0-9a-f]{2})[0-9a-f]+\)/){ + $vendor_id = $1; + $chip_id = $2; + $type = pci_class($3); + $type_id = "$3$4"; + } + } + } + print Data::Dumper::Dumper \@devices if $dbg[4]; + main::log_data('dump','pcidump @devices',\@devices) if $b_log; + eval $end if $b_log; +} + +sub pci_grabber { + eval $start if $b_log; + my ($program) = @_; + my ($args,$path,$pattern,$data); + my $working = []; + if ($program eq 'lspci'){ + # 2.2.8 lspci did not support -k, added in 2.2.9, but -v turned on -k + $args = ' -nnv'; + $path = $alerts{'lspci'}->{'path'}; + $pattern = q/^[0-9a-f]+:/; # i only added perl 5.14, don't use qr/ + } + elsif ($program eq 'pciconf'){ + $args = ' -lv'; + $path = $alerts{'pciconf'}->{'path'}; + $pattern = q/^([^@]+)\@pci/; # i only added perl 5.14, don't use qr/ + } + elsif ($program eq 'pcidump'){ + $args = ' -v'; + $path = $alerts{'pcidump'}->{'path'}; + $pattern = q/^[0-9a-f]+:/; # i only added perl 5.14, don't use qr/ + } + elsif ($program eq 'pcictl'){ + $args = ' pci0 list -N'; + $path = $alerts{'pcictl'}->{'path'}; + $pattern = q/^[0-9a-f:]+:/; # i only added perl 5.14, don't use qr/ + } + elsif ($program eq 'pcictl-n'){ + $args = ' pci0 list -n'; + $path = $alerts{'pcictl'}->{'path'}; + $pattern = q/^[0-9a-f:]+:/; # i only added perl 5.14, don't use + } + if ($fake{'lspci'} || $fake{'pciconf'} || $fake{'pcictl'} || $fake{'pcidump'}){ + # my $file = "$fake_data_dir/pci/pciconf/pci-freebsd-8.2-2"; + # my $file = "$fake_data_dir/pci/pcidump/pci-openbsd-6.1-vm.txt"; + # my $file = "$fake_data_dir/pci/pcictl/pci-netbsd-9.1-vm.txt"; + # my $file = "$fake_data_dir/pci/lspci/racermach-1-knnv.txt"; + # my $file = "$fake_data_dir/pci/lspci/rk016013-knnv.txt"; + # my $file = "$fake_data_dir/pci/lspci/kot--book-lspci-nnv.txt"; + # my $file = "$fake_data_dir/pci/lspci/steve-mint-topaz-lspci-nnkv.txt"; + # my $file = "$fake_data_dir/pci/lspci/ben81-hwraid-lspci-nnv.txt"; + # my $file = "$fake_data_dir/pci/lspci/gx78b-lspci-nnv.txt"; + # $data = main::reader($file,'strip','ref'); + } + else { + $data = main::grabber("$path $args 2>/dev/null",'','strip','ref'); + } + if (@$data){ + $use{'pci-tool'} = 1 if scalar @$data > 10; + foreach (@$data){ + # this is the group separator and assign trigger + if ($_ =~ /$pattern/i){ + push(@$working, '~'); + } + push(@$working, $_); + } + push(@$working, '~'); + } + print Data::Dumper::Dumper $working if $dbg[30]; + eval $end if $b_log; + return $working; +} + +sub soc_data { + eval $start if $b_log; + soc_devices_files(); + soc_devices(); + soc_devicetree(); + print Data::Dumper::Dumper \@devices if $dbg[4]; + main::log_data('dump','soc @devices',\@devices) if $b_log; + eval $end if $b_log; +} + +# 1: /sys/devices/platform/soc/1c30000.ethernet/uevent:["DRIVER=dwmac-sun8i", "OF_NAME=ethernet", +# "OF_FULLNAME=/soc/ethernet@1c30000", "OF_COMPATIBLE_0=allwinner,sun8i-h3-emac", +# "OF_COMPATIBLE_N=1", "OF_ALIAS_0=ethernet0", # "MODALIAS=of:NethernetTCallwinner,sun8i-h3-emac"] +# 2: /sys/devices/platform/soc:audio/uevent:["DRIVER=bcm2835_audio", "OF_NAME=audio", "OF_FULLNAME=/soc/audio", +# "OF_COMPATIBLE_0=brcm,bcm2835-audio", "OF_COMPATIBLE_N=1", "MODALIAS=of:NaudioTCbrcm,bcm2835-audio"] +# 3: /sys/devices/platform/soc:fb/uevent:["DRIVER=bcm2708_fb", "OF_NAME=fb", "OF_FULLNAME=/soc/fb", +# "OF_COMPATIBLE_0=brcm,bcm2708-fb", "OF_COMPATIBLE_N=1", "MODALIAS=of:NfbTCbrcm,bcm2708-fb"] +# 4: /sys/devices/platform/soc/1c40000.gpu/uevent:["OF_NAME=gpu", "OF_FULLNAME=/soc/gpu@1c40000", +# "OF_COMPATIBLE_0=allwinner,sun8i-h3-mali", "OF_COMPATIBLE_1=allwinner,sun7i-a20-mali", +# "OF_COMPATIBLE_2=arm,mali-400", "OF_COMPATIBLE_N=3", +# "MODALIAS=of:NgpuTCallwinner,sun8i-h3-maliCallwinner,sun7i-a20-maliCarm,mali-400"] +# 5: /sys/devices/platform/soc/soc:internal-regs/d0018180.gpio/uevent +# 6: /sys/devices/soc.0/1180000001800.mdio/8001180000001800:05/uevent +# ["DRIVER=AR8035", "OF_NAME=ethernet-phy" +# 7: /sys/devices/soc.0/1c30000.eth/uevent +# 8: /sys/devices/wlan.26/uevent [from pine64] +# 9: /sys/devices/platform/audio/uevent:["DRIVER=bcm2835_AUD0", "OF_NAME=audio" +# 10: /sys/devices/vio/71000002/uevent:["DRIVER=ibmveth", "OF_NAME=l-lan" +# 11: /sys/devices/platform/soc:/soc:i2c-hdmi:/i2c-2/2-0050/uevent:['OF_NAME=hdmiddc' +# 12: /sys/devices/platform/soc:/soc:i2c-hdmi:/uevent:['DRIVER=i2c-gpio', 'OF_NAME=i2c-hdmi' +# 13: /sys/devices/platform/scb/fd580000.ethernet/uevent +# 14: /sys/devices/platform/soc/fe300000.mmcnr/mmc_host/mmc1/mmc1:0001/mmc1:0001:1/uevent (wifi, pi 3,4) +# 15: Pi BT: /sys/devices/platform/soc/fe201000.serial/uevent +# 16: Pi BT: /sys/devices/platform/soc/fe201000.serial/tty/ttyAMA0/hci0 +sub soc_devices_files { + eval $start if $b_log; + if (-d '/sys/devices/platform/'){ + @files = main::globber('/sys/devices/platform/soc*/*/uevent'); + @temp2 = main::globber('/sys/devices/platform/soc*/*/*/uevent'); + push(@files,@temp2) if @temp2; + if (-e '/sys/devices/platform/scb'){ + @temp2 = main::globber('/sys/devices/platform/scb/*/uevent'); + push(@files,@temp2) if @temp2; + @temp2 = main::globber('/sys/devices/platform/scb/*/*/uevent'); + push(@files,@temp2) if @temp2; + } + @temp2 = main::globber('/sys/devices/platform/*/uevent'); + push(@files,@temp2) if @temp2; + } + if (main::globber('/sys/devices/soc*')){ + @temp2 = main::globber('/sys/devices/soc*/*/uevent'); + push(@files,@temp2) if @temp2; + @temp2 = main::globber('/sys/devices/soc*/*/*/uevent'); + push(@files,@temp2) if @temp2; + } + @temp2 = main::globber('/sys/devices/*/uevent'); # see case 8 + push(@files,@temp2) if @temp2; + @temp2 = main::globber('/sys/devices/*/*/uevent'); # see case 10 + push(@files,@temp2) if @temp2; + undef @temp2; + # not sure why, but even as root/sudo, /subsystem|driver/uevent are unreadable with -r test true + @files = grep {!/\/(subsystem|driver)\//} @files if @files; + main::uniq(\@files); + eval $end if $b_log; +} + +sub soc_devices { + eval $start if $b_log; + my (@working); + set_bluetooth() if !$b_bt_check; + foreach $file (@files){ + next if -z $file; + $chip_id = $file; + # variants: /soc/20100000.ethernet/ /soc/soc:audio/ /soc:/ /soc@0/ /soc:/12cb0000.i2c:/ + # mips: /sys/devices/soc.0/1180000001800.mdio/8001180000001800:07/ + # ppc: /sys/devices/vio/71000002/ + $chip_id =~ /\/sys\/devices\/(platform\/)?(soc[^\/]*\/)?([^\/]+\/)?([^\/]+\/)?([^\/\.:]+)([\.:])?([^\/:]+)?:?\/uevent$/; + $chip_id = $5; + $temp = $7; + @working = main::reader($file, 'strip') if -r $file; + ($device,$driver,$handle,$type,$vendor_id) = (); + foreach my $data (@working){ + @temp2 = split('=', $data); + if ($temp2[0] eq 'DRIVER'){ + $driver = $temp2[1]; + $driver =~ s/-/_/g if $driver; # kernel uses _, not - in module names + } + elsif ($temp2[0] eq 'OF_NAME'){ + $type = $temp2[1]; + } + # we'll use these paths to test in device tree pci completer + elsif ($temp2[0] eq 'OF_FULLNAME' && $temp2[1]){ + # we don't want the short names like /soc, /led and so on + push(@full_names, $temp2[1]) if (() = $temp2[1] =~ /\//g) > 1; + $handle = (split('@', $temp2[1]))[-1] if $temp2[1] =~ /@/; + } + elsif ($temp2[0] eq 'OF_COMPATIBLE_0'){ + @temp3 = split(',', $temp2[1]); + $device = $temp3[-1]; + $vendor_id = $temp3[0]; + } + } + # it's worthless, we can't use it + next if ! defined $type; + $type_id = $type; + if (@bluetooth && $type eq 'serial'){ + my $file_temp = $file; + $file_temp =~ s/uevent$//; + $type = 'bluetooth' if grep {/$file_temp/} @bluetooth; + } + $chip_id = '' if ! defined $chip_id; + $vendor_id = '' if ! defined $vendor_id; + $driver = '' if ! defined $driver; + $handle = '' if ! defined $handle; + $busid = (defined $temp && main::is_int($temp)) ? $temp: 0; + $type = soc_type($type,$vendor_id,$driver); + ($busid_nu,$modules,$port,$rev) = (0,'','',''); + @temp3 = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,$rev, + $port,$driver,$modules,'','','',$handle); + assign_data('soc',\@temp3); + main::log_data('dump','soc devices: @devices @temp3',\@temp3) if $b_log; + } + eval $end if $b_log; +} + +sub soc_devicetree { + eval $start if $b_log; + # now we want to fill in stuff that was not in /sys/devices/ + if (-d '/sys/firmware/devicetree/base/soc'){ + @files = main::globber('/sys/firmware/devicetree/base/soc/*/compatible'); + my $test = (@full_names) ? join('|', sort @full_names) : 'xxxxxx'; + set_bluetooth() if !$b_bt_check; + foreach $file (@files){ + if ($file !~ m%$test%){ + ($handle,$content,$device,$type,$type_id,$vendor_id) = ('','','','','',''); + $content = main::reader($file, 'strip',0) if -r $file; + $file =~ m%soc/([^@]+)@([^/]+)/compatible$%; + $type = $1; + next if !$type || !$content; + $handle = $2 if $2; + $type_id = $type; + if (@bluetooth && $type eq 'serial'){ + my $file_temp = $file; + $file_temp =~ s/uevent$//; + $type = 'bluetooth' if grep {/$file_temp/} @bluetooth; + } + if ($content){ + @temp3 = split(',', $content); + $vendor_id = $temp3[0]; + $device = $temp3[-1]; + # strip off those weird device tree special characters + $device =~ s/\x01|\x02|\x03|\x00//g; + } + $type = soc_type($type,$vendor_id,''); + @temp3 = ($type,$type_id,0,0,$device,$vendor_id,'soc','','','','','','','',$handle); + assign_data('soc',\@temp3); + main::log_data('dump','devicetree: @devices @temp3',\@temp3) if $b_log; + } + } + } + eval $end if $b_log; +} + +sub set_bluetooth { + # special case of pi bt on ttyAMA0 + $b_bt_check = 1; + @bluetooth = main::globber('/sys/class/bluetooth/*') if -e '/sys/class/bluetooth'; + @bluetooth = map {$_ = Cwd::abs_path($_);$_} @bluetooth if @bluetooth; + @bluetooth = grep {!/usb/} @bluetooth if @bluetooth; # we only want non usb bt + main::log_data('dump','soc bt: @bluetooth', \@bluetooth) if $b_log; +} + +sub assign_data { + my ($tool,$data) = @_; + if (check_graphics($data->[0],$data->[1])){ + push(@{$devices{'graphics'}},[@$data]); + $use{'soc-gfx'} = 1 if $tool eq 'soc'; + } + # for hdmi, we need gfx/audio both + if (check_audio($data->[0],$data->[1])){ + push(@{$devices{'audio'}},[@$data]); + $use{'soc-audio'} = 1 if $tool eq 'soc'; + } + if (check_bluetooth($data->[0],$data->[1])){ + push(@{$devices{'bluetooth'}},[@$data]); + $use{'soc-bluetooth'} = 1 if $tool eq 'soc'; + } + elsif (check_hwraid($data->[0],$data->[1])){ + push(@{$devices{'hwraid'}},[@$data]); + $use{'soc-hwraid'} = 1 if $tool eq 'soc'; + } + elsif (check_network($data->[0],$data->[1])){ + push(@{$devices{'network'}},[@$data]); + $use{'soc-network'} = 1 if $tool eq 'soc'; + } + elsif (check_timer($data->[0],$data->[1])){ + push(@{$devices{'timer'}},[@$data]); + $use{'soc-timer'} = 1 if $tool eq 'soc'; + } + # not used at this point, -M comes before ANG + # $device_vm = check_vm($data[4]) if ((!$risc{'ppc'} && !$risc{'mips'}) && !$device_vm); + push(@devices,[@$data]); +} + +# Note: for SOC these have been converted in soc_type() +sub check_audio { + if (($_[1] && length($_[1]) == 4 && $_[1] =~ /^04/) || + ($_[0] && $_[0] =~ /^(audio|hdmi|multimedia|sound)$/i)){ + return 1; + } + else {return 0} +} + +sub check_bluetooth { + if (($_[1] && length($_[1]) == 4 && $_[1] eq '0d11') || + ($_[0] && $_[0] =~ /^(bluetooth)$/i)){ + return 1; + } + else {return 0} +} + +sub check_graphics { + # note: multimedia class 04 is video if 0400. 'tv' is risky I think + if (($_[1] && length($_[1]) == 4 && ($_[1] =~ /^03/ || $_[1] eq '0400' || + $_[1] eq '0d80')) || + ($_[0] && $_[0] =~ /^(vga|display|hdmi|3d|video|tv|television)$/i)){ + return 1; + } + else {return 0} +} + +sub check_hwraid { + return 1 if ($_[1] && $_[1] eq '0104'); +} + +# NOTE: class 06 subclass 80 +# https://www-s.acm.illinois.edu/sigops/2007/roll_your_own/7.c.1.html +# 0d20: 802.11a 0d21: 802.11b 0d80: other wireless +sub check_network { + if (($_[1] && length($_[1]) == 4 && ($_[1] =~/^02/ || $_[1] =~ /^0d2/ || $_[1] eq '0680')) || + ($_[0] && $_[0] =~ /^(ethernet|network|wifi|wlan)$/i)){ + return 1; + } + else {return 0} +} + +sub check_timer { + return 1 if ($_[0] && $_[0] eq 'timer'); +} + +sub check_vm { + if ($_[0] && $_[0] =~ /(innotek|vbox|virtualbox|vmware|qemu)/i){ + return $1 + } + else {return ''} +} + +sub soc_type { + my ($type,$info,$driver) = @_; + # I2S or i2s. I2C is i2 controller |[iI]2[Ss]. note: odroid hdmi item is sound only + # snd_soc_dummy. simple-audio-amplifier driver: speaker_amp + if (($driver && $driver =~ /codec/) || ($info && $info =~ /codec/) || + ($type && $type =~ /codec/)){ + $type = 'codec'; + } + elsif (($driver && $driver =~ /dummy/i) || ($info && $info =~ /dummy/i)){ + $type = 'dummy'; + } + # rome_vreg reg_fixed_voltage regulator-fixed wlan_en_vreg + elsif (($driver && $driver =~ /\bv?reg(ulat|_)|voltage/i) || + ($info && $info =~ /_v?reg|\bv?reg(ulat|_)|voltage/i)){ + $type = 'regulator'; + } + elsif ($type =~ /^(daudio|.*hifi.*|.*sound[_-]card|.*dac[0-9]?)$/i || + ($info && $info !~ /amp/i && $info =~ /(sound|audio)/i) || + ($driver && $driver =~ /(audio|snd|sound)/i)){ + $type = 'audio'; + } + # no need for bluetooth since that's only found in pi, handled above + elsif ($type =~ /^((meson-?)?fb|disp|display(-[^\s]+)?|gpu|.*mali|vpu)$/i){ + $type = 'display'; + } + # includes ethernet-phy, meson-eth + elsif ($type =~ /^(([^\s]+-)?eth|ethernet(-[^\s]+)?|lan|l-lan)$/i){ + $type = 'ethernet'; + } + elsif ($type =~ /^(.*wlan.*|.*wifi.*|.*mmcnr.*)$/i){ + $type = 'wifi'; + } + # needs to catch variants like hdmi-tx but not hdmi-connector + elsif ($type =~ /^(.*hdmi(-?tx)?)$/i){ + $type = 'hdmi'; + } + elsif ($type =~ /^timer$/i){ + $type = 'timer'; + } + return $type; +} + +sub pci_class { + eval $start if $b_log; + my ($id) = @_; + $id = lc($id); + my %classes = ( + '00' => 'unclassified', + '01' => 'mass-storage', + '02' => 'network', + '03' => 'display', + '04' => 'audio', + '05' => 'memory', + '06' => 'bridge', + '07' => 'communication', + '08' => 'peripheral', + '09' => 'input', + '0a' => 'docking', + '0b' => 'processor', + '0c' => 'serialbus', + '0d' => 'wireless', + '0e' => 'intelligent', + '0f' => 'satellite', + '10' => 'encryption', + '11' => 'signal-processing', + '12' => 'processing-accelerators', + '13' => 'non-essential-instrumentation', + # 14 - fe reserved + '40' => 'coprocessor', + 'ff' => 'unassigned', + ); + my $type = (defined $classes{$id}) ? $classes{$id}: 'unhandled'; + eval $end if $b_log; + return $type; +} +} + +# if > 1, returns first found, not going to be too granular with this yet. +sub get_device_temp { + eval $start if $b_log; + my $bus_id = $_[0]; + my $glob = "/sys/devices/pci*/*/*:$bus_id/hwmon/hwmon*/temp*_input"; + my @files = main::globber($glob); + my $temp; + foreach my $file (@files){ + $temp = main::reader($file,'strip',0); + if ($temp){ + $temp = sprintf('%0.1f',$temp/1000); + last; + } + } + eval $end if $b_log; + return $temp; +} + +## DiskDataBSD +# handles disks and partition extra data for disks bsd, raid-zfs, +# partitions, swap, unmounted +# glabel: partID, logical/physical-block-size, uuid, label, size +# disklabel: partID, block-size, fs, size +{ +package DiskDataBSD; + +# Sets initial pure dboot data, and fills it in with +# disklabel/gpart partition and advanced data +sub set { + eval $start if $b_log; + $loaded{'disk-data-bsd'} = 1; + set_dboot_disks(); + if ($use{'bsd-partition'}){ + if ($alerts{'gpart'}->{'action'} eq 'use'){ + set_gpart_data(); + } + elsif ($alerts{'disklabel'}->{'action'} eq 'use'){ + set_disklabel_data(); + } + } + eval $end if $b_log; +} + +sub get { + eval $start if $b_log; + my $id = $_[0]; + return if !$id || !%disks_bsd; + $id =~ s|^/dev/||; + my $data = {}; + # this handles mainly zfs, which can be either disk or part + if ($disks_bsd{$id}){ + $data = $disks_bsd{$id}; + delete $data->{'partitions'} if $data->{'partitions'}; + } + else { + OUTER: foreach my $key (keys %disks_bsd){ + if ($disks_bsd{$key}->{'partitions'}){ + foreach my $part (keys %{$disks_bsd{$key}->{'partitions'}}){ + if ($part eq $id){ + $data = $disks_bsd{$key}->{'partitions'}{$part}; + last OUTER; + } + } + } + } + } + eval $end if $b_log; + return $data; +} + +sub set_dboot_disks { + eval $start if $b_log; + my ($working,@temp); + foreach my $id (sort keys %{$dboot{'disk'}}){ + next if !@{$dboot{'disk'}->{$id}}; + foreach (@{$dboot{'disk'}->{$id}}){ + my @row = split(/:\s*/, $_); + next if !$row[0]; + # no dots, note: ada2: 2861588MB BUT: ada2: 600.000MB/s + # print "$_ i: $i\n"; + # openbsd/netbsd matches will often work + if ($row[0] =~ /(^|,\s*)([0-9\.]+\s*[MGTPE])i?B?[,.\s]+([0-9]+)\ssectors$|^{'block-physical'} = POSIX::ceil(($working/$3)*1024) if $3; + $disks_bsd{$id}->{'size'} = $working; + } + # don't set both, if smartctl installed, we want to use its data so having + # only one of logical/physical will trip use of smartctl values + if ($row[0] =~ /[\s,]+([0-9]+)\sbytes?[\s\/]sect/){ + #$disks_bsd{$id}->{'block-logical'} = $1; + $disks_bsd{$id}->{'block-physical'} = $1; + } + if ($row[1]){ + if ($row[1] =~ /<([^>]+)>/){ + $disks_bsd{$id}->{'model'} = $1 if $1; + $disks_bsd{$id}->{'type'} = 'removable' if $_ =~ /removable/; + # + my $count = ($disks_bsd{$id}->{'model'} =~ tr/,//); + if ($count && $count > 1){ + @temp = split(/,\s*/, $disks_bsd{$id}->{'model'}); + $disks_bsd{$id}->{'model'} = $temp[1]; + } + } + if ($row[1] =~ /\bserial\.(\S*)/){ + $disks_bsd{$id}->{'serial'} = $1; + } + } + if (!$disks_bsd{$id}->{'serial'} && $row[0] =~ /^Serial\sNumber\s(.*)/){ + $disks_bsd{$id}->{'serial'} = $1; + } + # mmcsd0:32GB at mmc0 50.0MHz/4bit/65535-block + if (!$disks_bsd{$id}->{'serial'} && $row[0] =~ /(\s(SN|s\/n)\s(\S+))[>\s]/){ + $disks_bsd{$id}->{'serial'} = $3; + # strip out the SN/MFG so it won't show in model + $row[0] =~ s/$1//; + $row[0] =~ s/\sMFG\s[^>]+//; + } + # these were mainly FreeBSD/Dragonfly matches + if (!$disks_bsd{$id}->{'size'} && $row[0] =~ /^([0-9]+\s*[KMGTPE])i?B?[\s,]/){ + $working = main::translate_size($1); + $disks_bsd{$id}->{'size'} = $working; + } + if ($row[0] =~ /(device$|^([0-9\.]+\s*[KMGT]B\s+)?<)/){ + $row[0] =~ s/\bdevice$//g; + $row[0] =~ /<([^>]*)>(\s(.*))?/; + $disks_bsd{$id}->{'model'} = $1 if $1; + $disks_bsd{$id}->{'spec'} = $3 if $3; + } + if ($row[0] =~ /^([0-9\.]+[MG][B]?\/s)/){ + $disks_bsd{$id}->{'speed'} = $1; + $disks_bsd{$id}->{'speed'} =~ s/\.[0-9]+// if $disks_bsd{$id}->{'speed'}; + } + $disks_bsd{$id}->{'model'} = main::clean_disk($disks_bsd{$id}->{'model'}); + if (!$disks_bsd{$id}->{'serial'} && $show{'disk'} && $extra > 1 && + $alerts{'bioctl'}->{'action'} eq 'use'){ + $disks_bsd{$id}->{'serial'} = bioctl_data($id); + } + } + } + print Data::Dumper::Dumper \%disks_bsd if $dbg[34]; + main::log_data('dump','%disks_bsd',\%disks_bsd) if $b_log; + eval $end if $b_log; +} + +sub bioctl_data { + eval $start if $b_log; + my $id = $_[0]; + my $serial; + my $working = (main::grabber($alerts{'bioctl'}->{'path'} . " $id 2>&1",'','strip'))[0]; + if ($working){ + if ($working =~ /permission/i){ + $alerts{'bioctl'}->{'action'} = 'permissions'; + } + elsif ($working =~ /serial[\s-]?(number|n[ou]\.?)?\s+(\S+)$/i){ + $serial = $2; + } + } + eval $end if $b_log; + return $serial; +} + +sub set_disklabel_data { + eval $start if $b_log; + my ($cmd,@data,@working); + # see docs/inxi-data.txt for fs info + my %fs = ( + '4.2bsd' => 'ffs', + '4.4lfs' => 'lfs', + ); + foreach my $id (keys %disks_bsd){ + $cmd = "$alerts{'disklabel'}->{'path'} $id 2>&1"; + @data = main::grabber($cmd,'','strip'); + main::log_data('dump','disklabel @data', \@data) if $b_log; + if (scalar @data < 4 && (grep {/permission/i} @data)){ + $alerts{'disklabel'}->{'action'} = 'permissions'; + $alerts{'disklabel'}->{'message'} = main::message('root-feature'); + last; + } + else { + my ($b_part,$duid,$part_id,$bytes_sector) = (); + if ($extra > 2 && $show{'disk'} && $alerts{'fdisk'}->{'action'} eq 'use'){ + $disks_bsd{$id}->{'partition-table'} = fdisk_data($id); + } + foreach my $row (@data){ + if ($row =~ /^\d+\spartitions:/){ + $b_part = 1; + next; + } + if (!$b_part){ + @working = split(/:\s*/, $row); + if ($working[0] eq 'bytes/sector'){ + $disks_bsd{$id}->{'block-physical'} = $working[1]; + $bytes_sector = $working[1]; + } + elsif ($working[0] eq 'duid'){ + $working[1] =~ s/^0+$//; # dump duid if all 0s + $disks_bsd{$id}->{'duid'} = $working[1]; + } + elsif ($working[0] eq 'label'){ + $disks_bsd{$id}->{'dlabel'} = $working[1]; + } + } + # part: size [bytes*sector] offset fstype [fsize bsize cpg]# mount + # d: 8388608 18838976 4.2BSD 2048 16384 12960 # /tmp + else { + @working = split(/:?\s+#?\s*/, $row); + # netbsd: disklabel: super block size 0 AFTER partitions started! + # note: 'unused' fs type is NOT unused space, it's often the entire disk!! + if (($working[0] && $working[0] eq 'disklabel') || + ($working[3] && $working[3] =~ /ISO9660|unused/i) || + (!$working[1] || !main::is_numeric($working[1]))){ + next; + } + $part_id = $id . $working[0]; + $working[1] = $working[1]*$bytes_sector/1024 if $working[1]; + $disks_bsd{$id}->{'partitions'}{$part_id}{'size'} = $working[1]; + if ($working[3]){ # fs + $working[3] = lc($working[3]); + $working[3] = $fs{$working[3]} if $fs{$working[3]}; #translate + } + $disks_bsd{$id}->{'partitions'}{$part_id}{'fs'} = $working[3]; + # OpenBSD: mount point; NetBSD: (Cyl. 0 - 45852*) + if ($working[7] && $working[7] =~ m|^/|){ + $disks_bsd{$id}->{'partitions'}{$part_id}{'mount'} = $working[7]; + } + $disks_bsd{$id}->{'partitions'}{$part_id}{'uuid'} = ''; + $disks_bsd{$id}->{'partitions'}{$part_id}{'label'} = ''; + } + } + } + } + print Data::Dumper::Dumper \%disks_bsd if $dbg[34]; + main::log_data('dump', '%disks_bsd', \%disks_bsd) if $b_log; + eval $end if $b_log; +} + +sub fdisk_data { + eval $start if $b_log; + my $id = $_[0]; + my ($scheme); + my @data = main::grabber($alerts{'fdisk'}->{'path'} . " -v $id 2>&1",'','strip'); + foreach (@data){ + if (/permission/i){ + $alerts{'fdisk'}->{'action'} = 'permissions'; + last; + } + elsif (/^(GUID|MBR):/){ + $scheme = ($1 eq 'GUID') ? 'GPT' : $1; + last; + } + } + eval $start if $b_log; + return $scheme; +} + +# 2021-03: openbsd: n/a; dragonfly: no 'list'; freebsd: yes +sub set_gpart_data { + eval $start if $b_log; + my @data = main::grabber($alerts{'gpart'}->{'path'} . " list 2>/dev/null",'','strip'); + main::log_data('dump', 'gpart: @data', \@data) if $b_log; + my ($b_cd,$id,$part_id,$type); + for (@data){ + my @working = split(/\s*:\s*/, $_); + if ($working[0] eq 'Geom name'){ + $id = $working[1]; + # [1. Name|Geom name]: iso9660/FVBE + $b_cd = ($id =~ /iso9660/i) ? 1: 0; + next; + } + elsif ($working[0] eq 'scheme'){ + $disks_bsd{$id}->{'scheme'} = $working[1]; + next; + } + elsif ($working[0] eq 'Consumers'){ + $type = 'disk'; + next; + } + elsif ($working[0] eq 'Providers'){ + $type = 'part'; + next; + } + if (!$b_cd && $type && $type eq 'part'){ + if ($working[0] =~ /^[0-9]+\.\s*Name/){ + $part_id = $working[1]; + } + # eg: label:(null) - we want to show null + elsif ($working[0] eq 'label'){ + $working[1] =~ s/\(|\)//g; + $disks_bsd{$id}->{'partitions'}{$part_id}{'label'} = $working[1]; + } + elsif ($working[0] eq 'Mediasize'){ + $working[1] =~ s/\s+\(.*$//; # trim off the (2.4G) + # gpart shows in bytes, not KiB. For the time being... + $disks_bsd{$id}->{'partitions'}{$part_id}{'size'} = $working[1]/1024 if $working[1]; + } + elsif ($working[0] eq 'rawuuid'){ + $working[1] =~ s/\(|\)//g; + $disks_bsd{$id}->{'partitions'}{$part_id}{'uuid'} = $working[1]; + } + elsif ($working[0] eq 'Sectorsize'){ + $disks_bsd{$id}->{'partitions'}{$part_id}{'physical-block-size'} = $working[1]; + } + elsif ($working[0] eq 'Stripesize'){ + $disks_bsd{$id}->{'partitions'}{$part_id}{'logical-block-size'} = $working[1]; + } + elsif ($working[0] eq 'type'){ + $working[1] =~ s/\(|\)//g; + $disks_bsd{$id}->{'partitions'}{$part_id}{'fs'} = $working[1]; + } + } + # really strange results happen if no dboot disks were found and it's zfs! + elsif (!$b_cd && $type && $type eq 'disk' && $disks_bsd{$id}->{'size'}){ + # need to see raid, may be > 1 Consumers + if ($working[0] =~ /^[0-9]+\.\s*Name/){ + $id = $working[1]; + } + elsif ($working[0] eq 'Mediasize'){ + $working[1] =~ s/\s+\(.*$//; # trim off the (2.4G) + # gpart shows in bytes, not KiB. For the time being... + $disks_bsd{$id}->{'size'} = $working[1]/1024 if $working[1]; + } + elsif ($working[0] eq 'Sectorsize'){ + $disks_bsd{$id}->{'block-physical'} = $working[1]; + } + } + } + print Data::Dumper::Dumper \%disks_bsd if $dbg[34]; + main::log_data('dump', '%disks_bsd', \%disks_bsd) if $b_log; + eval $end if $b_log; +} +} + +sub get_display_manager { + eval $start if $b_log; + my (@data,@glob,$link,$path,@temp); + my $found = []; + # ldm - LTSP display manager. Note that sddm does not appear to have a .pid + # extension in Arch. Guessing on cdm, qingy. pcdm uses vt, PCDM-vt9.pid + # Not verified: qingy emptty; greetd.run verified, but alternate: + # greetd-684.sock if no .run seen. Add Ly in case they add run file/directory. + # greetd frontends: agreety dlm gtkgreet qtgreet tuigreet wlgreet + # mlogin may be mlogind, not verified + my @dms = qw(brzdm cdm clogin emptty entranced gdm gdm3 greetd kdm kdm3 kdmctl + ldm lightdm lxdm ly mdm mlogin nodm pcdm qingy sddm slim slimski tbsm tdm + udm wdm xdm xdmctl xenodm xlogin); + # these are the only one I know of so far that have version info. xlogin / + # clogin do, -V, brzdm -v, but syntax not verified. + my @dms_version = qw(gdm gdm3 lightdm ly slim); + my $pattern = ''; + if (-d '/run'){ + $pattern .= '/run'; + } + # in most linux, /var/run is a sym link to /run, so no need to check it twice + if (-d '/var/run' && ! -l '/var/run'){ + $pattern .= ',' if $pattern; + $pattern .= '/var/run'; + } + if (-d '/var/run/rc.d'){ + $pattern .= ',' if $pattern; + $pattern .= '/var/run/rc.d'; + } + if ($pattern){ + $pattern = '{' . $pattern . '/*}' if $pattern; + # for dm.pid type file or dm directory names, like greetd-684.sock + @glob = globber($pattern) if $pattern; + } + # print Data::Dumper::Dumper \@glob; + # used to test for .pid/lock type file or directory, now just see if the + # search name exists in run and call it good since test would always be true + # if directory existed previously anyway. + if (@glob){ + my $search = join('|',@dms); + @glob = grep {/\/($search)\b/} @glob; + # $search = join('|',@glob); + if (@glob){ + uniq(\@glob); + foreach my $item (@glob){ + my @id = grep {$item =~ /\/$_\b/} @dms; + push(@temp,@id) if @id; + } + # note: there were issues with duplicated dm's, using uniq will handle those + uniq(\@temp) if @temp; + } + } + @dms = @temp; + my (@dm_info); + # print Data::Dumper::Dumper \@dms; + # we know the files or directories exist so no need for further checks here + foreach my $dm (@dms){ + # do nothing, we just wanted the path + if ($extra > 2 && (grep {$dm eq $_} @dms_version) && + ($path = check_program($dm))){} + else {$path = $dm} + # print "$path $extra\n"; + @dm_info = (); + @data = program_data($dm,$path,3); + $dm_info[0] = $data[0]; + $dm_info[1] = $data[1]; + if (scalar @dms > 1 && (my $temp = ServiceData::get('status',$dm))){ + $dm_info[2] = message('stopped') if $temp && $temp =~ /stopped|disabled/; + } + push(@$found,[@dm_info]); + } + if (!@$found){ + # ly does not have a run/pid file + if (grep {$_ eq 'ly'} @ps_gui){ + @data = program_data('ly','ly',3); + $dm_info[0] = $data[0]; + $dm_info[1] = $data[1]; + $found->[0] = [@dm_info]; + } + elsif (grep {/startx$/} @ps_gui){ + $found->[0] = ['startx']; + } + elsif (grep {$_ eq 'xinit'} @ps_gui){ + $found->[0] = ['xinit']; + } + } + # might add this in, but the rate of new dm's makes it more likely it's an + # unknown dm, so we'll keep output to N/A + # print Data::Dumper::Dumper \@found; + log_data('dump','display manager: @$found',$found) if $b_log; + eval $end if $b_log; + return $found; +} + +## DistroData +{ +package DistroData; +my (@distro_files,@osr,@working); +my ($distro,$distro_file,$distro_id,$system_base) = ('','','',''); +my ($etc_issue,$lc_issue,$os_release) = ('','','/etc/os-release'); + +sub get { + eval $start if $b_log; + if ($bsd_type){ + get_bsd_os(); + } + else { + get_linux_distro(); + } + eval $end if $b_log; + return ($distro,$system_base); +} + +sub get_bsd_os { + eval $start if $b_log; + # used to parse /System/Library/CoreServices/SystemVersion.plist for Darwin + # but dumping that since it broke, just using standard BSD uname 0 2 name. + if (!$distro){ + my $bsd_type_osr = 'dragonfly'; + @osr = main::reader($os_release) if -r $os_release; + if (@osr && $bsd_type =~ /($bsd_type_osr)/ && (grep {/($bsd_type_osr)/i} @osr)){ + $distro = get_os_release(); + $distro_id = lc($1); + } + } + if (!$distro){ + my $bsd_type_version = 'truenas'; + my ($version_file,$version_info) = ('/etc/version',''); + $version_info = main::reader($version_file,'strip') if -r $version_file; + if ($version_info && $version_info =~ /($bsd_type_version)/i){ + $distro = $version_info; + $distro_id = lc($1); + } + } + if (!$distro){ + # seen a case without osx file, or was it permissions? + # this covers all the other bsds anyway, no problem. + $distro = "$uname[0] $uname[2]"; + $distro_id = lc($uname[0]); + } + if ($distro && + (-e '/etc/pkg/GhostBSD.conf' || -e '/usr/local/etc/pkg/repos/GhostBSD.conf') && + $distro =~ /freebsd/i){ + my $version = (main::grabber("pkg query '%v' os-generic-userland-base 2>/dev/null"))[0]; + # only swap if we get result from the query + if ($version){ + $system_base = $distro; + $distro = "GhostBSD $version"; + } + } + system_base_bsd() if $extra > 0; + eval $end if $b_log; +} + +sub get_linux_distro { + # NOTE: increasingly no distro release files are present, so this logic is + # deprecated, but still works often. + # order matters! + my @derived = qw(antix-version aptosid-version bodhibuilder.conf kanotix-version + knoppix-version pclinuxos-release mandrake-release manjaro-release mx-version + pardus-release porteus-version q4os_version sabayon-release + siduction-version sidux-version slax-version slint-version slitaz-release + solusos-release turbolinux-release zenwalk-version); + my $derived_s = join('|', @derived); + my @primary = qw(altlinux-release arch-release gentoo-release redhat-release + slackware-version SuSE-release); + my $primary_s = join('|', @primary); + my $exclude_s = 'debian_version|devuan_version|ubuntu_version'; + # note, pclinuxos has all these mandrake/mandriva files, careful! + my $lsb_good_s = 'mandrake-release|mandriva-release|mandrakelinux-release|'; + $lsb_good_s .= 'manjaro-release'; + my $os_release_good_s = 'altlinux-release|arch-release|mageia-release|'; + $os_release_good_s .= 'pclinuxos-release|rpi-issue|SuSE-release'; + # We need these empirically verified one by one as they appear, but always remember + # that stuff changes, legacy, deprecated, but these ideally are going to be right + my $osr_good = 'antergos|chakra|guix|mageia|manjaro|oracle|pclinuxos|porteux|'; + $osr_good .= 'raspberry pi os|slint|zorin'; + # Force use of pretty name because that's only location of derived distro name + my $osr_pretty = 'zinc'; + my ($b_issue,$b_lsb,$b_osr_pretty,$b_skip_issue,$b_skip_osr); + my ($issue,$lsb_release) = ('/etc/issue','/etc/lsb-release'); + # Note: OpenSuse Tumbleweed 2018-05 has made /etc/issue created by sym link to /run/issue + # and then made that resulting file 700 permissions, which is obviously a mistake + $etc_issue = main::reader($issue,'strip',0) if -r $issue; + # debian issue can end with weird escapes like \n \l + # antergos: Antergos Linux \r (\l) + $etc_issue = main::clean_characters($etc_issue) if $etc_issue; + # Note: always exceptions, so wild card after release/version: + # /etc/lsb-release-crunchbang + # Wait to handle since crunchbang file is one of the few in the world that + # uses this method + @distro_files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*'); + push(@distro_files, '/etc/bodhibuilder.conf') if -r '/etc/bodhibuilder.conf'; # legacy + @osr = main::reader($os_release) if -r $os_release; + if (-f '/etc/bodhi/info'){ + $lsb_release = '/etc/bodhi/info'; + $distro_file = $lsb_release; + $b_skip_issue = 1; + push(@distro_files, $lsb_release); + } + $b_issue = 1 if -f $issue; + $b_lsb = 1 if -f $lsb_release; + if (!$b_skip_issue && $etc_issue){ + $lc_issue = lc($etc_issue); + if ($lc_issue =~ /(antergos|grml|linux lite|openmediavault)/){ + $distro_id = $1; + $b_skip_issue = 1; + } + # This raspbian detection fails for raspberry pi os + elsif ($lc_issue =~ /(raspbian|peppermint)/){ + $distro_id = $1; + $distro_file = $os_release if @osr; + } + # Note: wrong fix, applies to both raspbian and raspberry pi os + # assumption here is that r pi os fixes this before stable release + elsif ($lc_issue =~ /^debian/ && -e '/etc/apt/sources.list.d/raspi.list' && + (grep {/[^#]+raspberrypi\.org/} main::reader('/etc/apt/sources.list.d/raspi.list'))){ + $distro_id = 'raspios' ; + } + } + # Note that antergos changed this around # 2018-05, and now lists + # antergos in os-release, sigh... We want these distros to use os-release + # if it contains their names. Last check below + if (@osr){ + if (grep {/($osr_good)/i} @osr){ + $distro_file = $os_release; + } + elsif (grep {/($osr_pretty)/i} @osr){ + $b_osr_pretty = 1; + } + } + if (grep {/armbian/} @distro_files){ + $distro_id = 'armbian' ; + } + main::log_data('dump','@distro_files',\@distro_files) if $b_log; + main::log_data('data',"distro_file-1: $distro_file") if $b_log; + if (!$distro_file){ + if (scalar @distro_files == 1){ + $distro_file = $distro_files[0]; + } + elsif (scalar @distro_files > 1){ + # Special case, to force manjaro/antergos which also have arch-release + # manjaro should use lsb, which has the full info, arch uses os release + # antergos should use /etc/issue. We've already checked os-release above + if ($distro_id eq 'antergos' || (grep {/antergos|chakra|manjaro/} @distro_files)){ + @distro_files = grep {!/arch-release/} @distro_files; + } + my $distro_files_s = join('|', @distro_files); + @working = (@derived,@primary); + foreach my $file (@working){ + if ("/etc/$file" =~ /($distro_files_s)$/){ + # These is for only those distro's with self named release/version files + # because Mint does not use such, it must be done as below + # Force use of os-release file in cases where there might be conflict + # between lsb-release rules and os-release priorities. + if (@osr && $file =~ /($os_release_good_s)$/){ + $distro_file = $os_release; + } + # Now lets see if the distro file is in the known-good working-lsb-list + # if so, use lsb-release, if not, then just use the found file + elsif ($b_lsb && $file =~ /$lsb_good_s/){ + $distro_file = $lsb_release; + } + else { + $distro_file = "/etc/$file"; + } + last; + } + } + } + } + main::log_data('data',"distro_file-2: $distro_file") if $b_log; + # first test for the legacy antiX distro id file + if (-r '/etc/antiX'){ + @working = main::reader('/etc/antiX'); + $distro = main::awk(\@working,'antix.*\.iso') if @working; + $distro = main::clean_characters($distro) if $distro; + } + # This handles case where only one release/version file was found, and it's lsb-release. + # This would never apply for ubuntu or debian, which will filter down to the following + # conditions. In general if there's a specific distro release file available, that's to + # be preferred, but this is a good backup. + elsif ($distro_file && $b_lsb && + ($distro_file =~ /\/etc\/($lsb_good_s)$/ || $distro_file eq $lsb_release)){ + print "df: $distro_file lf: $lsb_release\n"; + $distro = get_lsb_release($lsb_release); + } + elsif ($distro_file && $distro_file eq $os_release){ + $distro = get_os_release($b_osr_pretty); + $b_skip_osr = 1; + } + # If distro id file was found and it's not in the exluded primary distro file list, read it + elsif ($distro_file && -s $distro_file && $distro_file !~ /\/etc\/($exclude_s)$/){ + # New opensuse uses os-release, but older ones may have a similar syntax, so just use + # the first line + if ($distro_file eq '/etc/SuSE-release'){ + # Leaving off extra data since all new suse have it, in os-release, this file has + # line breaks, like os-release but in case we want it, it's: + # CODENAME = Mantis | VERSION = 12.2 + # For now, just take first occurrence, which should be the first line, which does + # not use a variable type format + @working = main::reader($distro_file); + $distro = main::awk(\@working,'suse'); + } + elsif ($distro_file eq '/etc/bodhibuilder.conf'){ + @working = main::reader($distro_file); + $distro = main::awk(\@working,'^LIVECDLABEL',2,'\s*=\s*'); + $distro =~ s/"//g if $distro; + } + else { + $distro = main::reader($distro_file,'',0); + # only contains version number. Why? who knows. + if ($distro_file eq '/etc/q4os_version' && $distro !~ /q4os/i){ + $distro = "Q4OS $distro" ; + } + } + $distro = main::clean_characters($distro) if $distro; + } + # Otherwise try the default debian/ubuntu/distro /etc/issue file + elsif ($b_issue){ + if (!$distro_id && $lc_issue && $lc_issue =~ /(mint|lmde)/){ + $distro_id = $1; + $b_skip_issue = 1; + } + # os-release/lsb gives more manageable and accurate output than issue, + # but mint should use issue for now. Antergos uses arch os-release, but issue shows them + if (!$b_skip_issue && @osr){ + $distro = get_os_release($b_osr_pretty); + $b_skip_osr = 1; + } + elsif (!$b_skip_issue && $b_lsb){ + $distro = get_lsb_release(); + } + elsif ($etc_issue){ + if (-d '/etc/guix' && $lc_issue =~ /^this is the gnu system\./){ + $distro = 'Guix'; + # They didn't use any standard paths or files for os data, sigh, use pm version + my $version = main::program_version('guix', '^guix', '4','--version',1); + $distro .= " $version" if $version; + $b_skip_issue = 1; + } + else { + $distro = $etc_issue; + # This handles an arch bug where /etc/arch-release is empty and /etc/issue + # is corrupted only older arch installs that have not been updated should + # have this fallback required, new ones use os-release + if ($distro =~ /arch linux/i){ + $distro = 'Arch Linux'; + } + } + } + } + # A final check. If a long value, before assigning the debugger output, if os-release + # exists then let's use that if it wasn't tried already. Maybe that will be better. + # not handling the corrupt data, maybe later if needed. 10 + distro: (8) + string + if ($distro && length($distro) > 60){ + if (!$b_skip_osr && @osr){ + $distro = get_os_release($b_osr_pretty); + $b_skip_osr = 1; + } + } + # Test for /etc/lsb-release as a backup in case of failure, in cases + # where > one version/release file were found but the above resulted + # in null distro value. + if (!$distro && $windows{'cygwin'}){ + $distro = $uname[0]; # like so: CYGWIN_NT-10.0-19043 + $b_skip_osr = 1; + } + if (!$distro){ + if (!$b_skip_osr && @osr){ + $distro = get_os_release($b_osr_pretty); + $b_skip_osr = 1; + } + elsif ($b_lsb){ + $distro = get_lsb_release(); + } + } + # Now some final null tries + if (!$distro){ + # If the file was null but present, which can happen in some cases, then use + # the file name itself to set the distro value. Why say unknown if we have + # a pretty good idea, after all? + if ($distro_file){ + $distro_file =~ s/\/etc\/|[-_]|release|version//g; + $distro = $distro_file; + } + } + system_base() if $extra > 0; + # Some last customized changes, double check if possible to verify still valid + if ($distro){ + if ($distro_id eq 'armbian'){ + $distro =~ s/Debian/Armbian/; + } + elsif ($distro_id eq 'raspios'){ + $system_base = $distro; + # No need to repeat the debian version info if base: + if ($extra == 0){$distro =~ s/Debian\s*GNU\/Linux/Raspberry Pi OS/;} + else {$distro = 'Raspberry Pi OS';} + } + elsif (-d '/etc/salixtools/' && $distro =~ /Slackware/i){ + $distro =~ s/Slackware/Salix/; + } + } + else { + # android fallback, sometimes requires root, sometimes doesn't + android_info() if $b_android; + } + ## Finally, if all else has failed, give up + $distro ||= 'unknown'; + eval $end if $b_log; +} + +sub android_info { + eval $start if $b_log; + main::set_build_prop() if !$loaded{'build-prop'};; + $distro = 'Android'; + $distro .= ' ' . $build_prop{'build-version'} if $build_prop{'build-version'}; + $distro .= ' ' . $build_prop{'build-date'} if $build_prop{'build-date'}; + if (!$show{'machine'}){ + if ($build_prop{'product-manufacturer'} && $build_prop{'product-model'}){ + $distro .= ' (' . $build_prop{'product-manufacturer'} . ' ' . $build_prop{'product-model'} . ')'; + } + elsif ($build_prop{'product-device'}){ + $distro .= ' (' . $build_prop{'product-device'} . ')'; + } + elsif ($build_prop{'product-name'}){ + $distro .= ' (' . $build_prop{'product-name'} . ')'; + } + } + eval $end if $b_log; +} + +sub system_base_bsd { + eval $start if $b_log; + # ghostbsd is handled in main bsd section + if (lc($uname[1]) eq 'nomadbsd' && $distro_id eq 'freebsd'){ + $system_base = $distro; + $distro = $uname[1]; + } + elsif (-f '/etc/pkg/HardenedBSD.conf' && $distro_id eq 'freebsd'){ + $system_base = $distro; + $distro = 'HardenedBSD'; + } + elsif ($distro_id =~ /^(truenas)$/){ + $system_base = "$uname[0] $uname[2]"; + } + eval $end if $b_log; +} + +sub system_base { + eval $start if $b_log; + # Need data on these Arch derived: CachyOS; can be ArchLab/Labs + my $base_distro_arch = 'anarchy|antergos|apricity'; + $base_distro_arch .= '|arch(bang|craft|ex|lab|man|strike)|arco|artix'; + $base_distro_arch .= '|blackarch|bluestar|bridge|cachyos|chakra|condres|ctlos'; + # note: arch linux derived distro page claims kaos as arch derived but it is NOT + $base_distro_arch .= '|endeavour|feliz|garuda|hyperbola|linhes|liri'; + $base_distro_arch .= '|mabox|magpie|manjaro|mysys2|namib|netrunner\s?rolling|ninja'; + $base_distro_arch .= '|obarun|parabola|porteus|puppyrus-?a'; + $base_distro_arch .= '|reborn|revenge|salient|snal|steamos'; + $base_distro_arch .= '|talkingarch|theshell|ubos|velt|xero'; + my $base_file_debian_version = 'sidux'; + # detect debian steamos before arch steamos + my $base_osr_debian_version = '\belive|lmde|neptune|nitrux|parrot|pureos|'; + $base_osr_debian_version .= 'rescatux|septor|sparky|steamos|tails'; + # osr has base ids + my $base_default = 'antix-version|bodhi|mx-version'; + # base only found in issue + my $base_issue = 'bunsen'; + # synthesize, no direct data available + my $base_manual = 'blankon|deepin|kali'; + # osr base, distro id in list of distro files + my $base_osr = 'aptosid|bodhi|grml|q4os|siduction|slax|zenwalk'; + # osr base, distro id in issue + my $base_osr_issue = 'grml|linux lite|openmediavault'; + # osr has distro name but has fedora centos redhat ID_LIKE and VERSION_ID same + my $base_osr_redhat = 'almalinux|centos|eurolinux|oracle|puias|rocky|'; + $base_osr_redhat .= 'scientific|springdale'; + # osr has distro name but has ubuntu (or debian) ID_LIKE/UBUNTU_CODENAME + my $base_osr_ubuntu = 'feren|mint|neon|nitrux|pop!?_os|tuxedo|zinc|zorin'; + my $base_upstream_lsb = '/etc/upstream-release/lsb-release'; + my $base_upstream_osr = '/etc/upstream-release/os-release'; + # These id as themselves, but system base is version file. Slackware mostly. + my %base_version = ( + 'porteux|salix|slint' => '/etc/slackware-version', + ); + # First: try, some distros have upstream-release, elementary, new mint + # and anyone else who uses this method for fallback ID + if (-r $base_upstream_osr){ + my @osr_working = main::reader($base_upstream_osr); + if (@osr_working){ + my (@osr_temp); + @osr_temp = @osr; + @osr = @osr_working; + $system_base = get_os_release(); + @osr = @osr_temp if !$system_base; + (@osr_temp,@osr_working) = (); + } + } + elsif (-r $base_upstream_lsb){ + $system_base = get_lsb_release($base_upstream_lsb); + } + # probably no need for these @osr greps, just grep $distro instead? + if (!$system_base && @osr){ + my ($base_type) = (''); + if ($etc_issue && (grep {/($base_issue)/i} @osr)){ + $system_base = $etc_issue; + } + # more tests added here for other ubuntu derived distros + elsif (@distro_files && (grep {/($base_default)/} @distro_files)){ + $base_type = 'default'; + } + # must go before base_osr_arch,ubuntu tests. For steamos, use fallback arch + elsif (grep {/($base_osr_debian_version)/i} @osr){ + $system_base = debian_id(); + } + elsif (grep {/($base_osr_redhat)/i} @osr){ + $base_type = 'rhel'; + } + elsif (grep {/($base_osr_ubuntu)/i} @osr){ + $base_type = 'ubuntu'; + } + elsif ((($distro_id && $distro_id =~ /($base_osr_issue)/) || + (@distro_files && (grep {/($base_osr)/} @distro_files))) && + !(grep {/($base_osr)/i} @osr)){ + $system_base = get_os_release(); + } + if (!$system_base && $base_type){ + $system_base = get_os_release('',$base_type); + } + } + if (!$system_base && @distro_files && + (grep {/($base_file_debian_version)/i} @distro_files)){ + $system_base = debian_id(); + } + if (!$system_base && $lc_issue && $lc_issue =~ /($base_manual)/){ + my $id = $1; + my %manual = ( + 'blankon' => 'Debian unstable', + 'deepin' => 'Debian unstable', + 'kali' => 'Debian testing', + ); + $system_base = $manual{$id}; + } + if (!$system_base && $distro && $distro =~ /^($base_distro_arch)/i){ + $system_base = 'Arch Linux'; + } + if (!$system_base && $distro){ + foreach my $key (keys %base_version){ + if (-r $base_version{$key} && $distro =~ /($key)/i){ + $system_base = main::reader($base_version{$key},'strip',0); + $system_base = main::clean_characters($system_base) if $system_base; + last; + } + } + } + if (!$system_base && $distro && -d '/etc/salixtools/' && $distro =~ /Slackware/i){ + $system_base = $distro; + } + eval $end if $b_log; +} + +# Note: corner case when parsing the bodhi distro file +# args: 0: file name +sub get_lsb_release { + eval $start if $b_log; + my ($lsb_file) = @_; + $lsb_file ||= '/etc/lsb-release'; + my ($distro_lsb,$id,$release,$codename,$description) = ('','','','',''); + my ($dist_id,$dist_release,$dist_code,$dist_desc) = ('DISTRIB_ID', + 'DISTRIB_RELEASE','DISTRIB_CODENAME','DISTRIB_DESCRIPTION'); + if ($lsb_file eq '/etc/bodhi/info'){ + $id = 'Bodhi Linux'; + # note: No ID field, hard code + ($dist_id,$dist_release,$dist_code,$dist_desc) = ('ID','RELEASE', + 'CODENAME','DESCRIPTION'); + } + my @content = main::reader($lsb_file); + main::log_data('dump','@content',\@content) if $b_log; + @content = map {s/,|\*|\\||\"|[:\47]|^\s+|\s+$|n\/a//ig; $_} @content if @content; + foreach (@content){ + next if /^\s*$/; + my @working = split(/\s*=\s*/, $_); + next if !$working[0]; + if ($working[0] eq $dist_id && $working[1]){ + if ($working[1] =~ /^Manjaro/i){ + $id = 'Manjaro Linux'; + } + # in the old days, arch used lsb_release + # elsif ($working[1] =~ /^Arch$/i){ + # $id = 'Arch Linux'; + # } + else { + $id = $working[1]; + } + } + elsif ($working[0] eq $dist_release && $working[1]){ + $release = $working[1]; + } + elsif ($working[0] eq $dist_code && $working[1]){ + $codename = $working[1]; + } + # sometimes some distros cannot do their lsb-release files correctly, + # so here is one last chance to get it right. + elsif ($working[0] eq $dist_desc && $working[1]){ + $description = $working[1]; + } + } + if (!$id && !$release && !$codename && $description){ + $distro_lsb = $description; + } + else { + # avoid duplicates + $distro_lsb = $id; + $distro_lsb .= " $release" if $release && $distro_lsb !~ /$release/; + # eg: release: 9 codename: mga9 + if ($codename && $distro_lsb !~ /$codename/i && + (!$release || $codename !~ /$release/)){ + $distro_lsb .= " $codename"; + } + $distro_lsb =~ s/^\s+|\s\s+|\s+$//g; # get rid of double and trailing spaces + } + eval $end if $b_log; + return $distro_lsb; +} + +sub get_os_release { + eval $start if $b_log; + my ($b_osr_pretty,$base_type) = @_; + my ($base_id,$base_name,$base_version,$distro_osr,$name,$name_lc,$name_pretty, + $version_codename,$version_name,$version_id) = ('','','','','','','','','',''); + my @content = @osr; + main::log_data('dump','@content',\@content) if $b_log; + @content = map {s/\\||\"|[:\47]|^\s+|\s+$|n\/a//ig; $_} @content if @content; + foreach (@content){ + next if /^\s*$/; + my @working = split(/\s*=\s*/, $_); + next if !$working[0]; + if ($working[0] eq 'PRETTY_NAME' && $working[1]){ + $name_pretty = $working[1]; + } + elsif ($working[0] eq 'NAME' && $working[1]){ + $name = $working[1]; + $name_lc = lc($name); + } + elsif ($working[0] eq 'VERSION_CODENAME' && $working[1]){ + $version_codename = $working[1]; + } + elsif ($working[0] eq 'VERSION' && $working[1]){ + $version_name = $working[1]; + $version_name =~ s/,//g; + } + elsif ($working[0] eq 'VERSION_ID' && $working[1]){ + $version_id = $working[1]; + } + # for mint/zorin, other ubuntu base system base + if ($base_type){ + if ($working[0] eq 'ID_LIKE' && $working[1]){ + if ($base_type eq 'ubuntu'){ + # feren,popos shows debian, feren ID ubuntu + $working[1] =~ s/^(debian|ubuntu\sdebian|debian\subuntu)/ubuntu/; + $base_name = $working[1]; + } + # oracle ID_LIKE="fedora". Why? who knows. + if ($base_type eq 'rhel' && $working[1] =~ /rhel|fedora/i){ + $base_name = 'RHEL'; + $base_version = $version_id if $version_id; + } + elsif ($base_type eq 'arch' && $working[1] =~ /$base_type/i){ + $base_name = 'Arch Linux'; + } + else { + $base_name = ucfirst($working[1]); + } + } + elsif ($base_type eq 'ubuntu' && $working[0] eq 'UBUNTU_CODENAME' && $working[1]){ + $base_version = ucfirst($working[1]); + } + elsif ($base_type eq 'debian' && $working[0] eq 'DEBIAN_CODENAME' && $working[1]){ + $base_version = $working[1]; + } + } + } + # NOTE: tumbleweed has pretty name but pretty name does not have version id + # arco shows only the release name, like kirk, in pretty name. Too many distros + # are doing pretty name wrong, and just putting in the NAME value there + if (!$base_type){ + if ((!$b_osr_pretty || !$name_pretty) && $name && $version_name){ + $distro_osr = $name; + $distro_osr = 'Arco Linux' if $name_lc =~ /^arco/; + if ($version_id && $version_name !~ /$version_id/){ + $distro_osr .= ' ' . $version_id; + } + $distro_osr .= " $version_name"; + } + elsif ($name_pretty && ($name_pretty !~ /tumbleweed/i && $name_lc ne 'arcolinux')){ + $distro_osr = $name_pretty; + } + elsif ($name){ + $distro_osr = $name; + if ($version_id){ + $distro_osr .= ' ' . $version_id; + } + } + if ($version_codename && $distro_osr !~ /$version_codename/i){ + $distro_osr .= " $version_codename"; + } + } + # note: mint has varying formats here, some have ubuntu as name, 17 and earlier + else { + # incoherent feren use of version, id, etc + if ($base_type eq 'ubuntu' && !$base_version && $version_codename && + $name =~ /feren/i){ + $base_version = ucfirst($version_codename); + $distro =~ s/ $version_codename//; + } + # mint 17 used ubuntu os-release, so won't have $base_version, steamos holo + if ($base_name && $base_type eq 'rhel'){ + $distro_osr = $base_name; + $distro_osr .= ' ' . $version_id if $version_id; + } + elsif ($base_name && $base_type eq 'arch'){ + $distro_osr = $base_name; + } + elsif ($base_name && $base_version){ + $base_id = ubuntu_id($base_version) if $base_type eq 'ubuntu' && $base_version; + $base_id = '' if $base_id && "$base_name$base_version" =~ /$base_id/; + $base_id .= ' ' if $base_id; + $distro_osr = "$base_name $base_id$base_version"; + } + elsif ($base_type eq 'default' && ($name_pretty || ($name && $version_name))){ + $distro_osr = ($name && $version_name) ? "$name $version_name" : $name_pretty; + } + # LMDE 2 has only limited data in os-release, no _LIKE values. 3 has like and debian_codename + elsif ($base_type eq 'ubuntu' && $name_lc =~ /^(debian|ubuntu)/ && + ($name_pretty || ($name && $version_name))){ + $distro_osr = ($name && $version_name) ? "$name $version_name": $name_pretty; + } + elsif ($base_type eq 'debian' && $base_version){ + $distro_osr = debian_id($base_version); + } + } + eval $end if $b_log; + return $distro_osr; +} + +# args: 0: optional: debian codename +sub debian_id { + eval $start if $b_log; + my ($codename) = @_; + my ($debian_version,$id); + if (-r '/etc/debian_version'){ + $debian_version = main::reader('/etc/debian_version','strip',0); + } + $id = 'Debian'; + return if !$debian_version && !$codename; + # note, 3.0, woody, 3.1, sarge, but after it's integer per version + my %debians = ( + '4' => 'etch', + '5' => 'lenny', + '6' => 'squeeze', + '7' => 'wheezy', + '8' => 'jessie', + '9' => 'stretch', + '10' => 'buster', + '11' => 'bullseye', + '12' => 'bookworm', + '13' => 'trixie', + '14' => 'forky', + ); + if (main::is_numeric($debian_version)){ + $id .= " $debian_version " . $debians{int($debian_version)}; + } + elsif ($codename){ + my %by_value = reverse %debians; + my $version = (main::is_numeric($debian_version)) ? "$debian_version $codename": $debian_version; + $id .= " $version"; + } + # like buster/sid + elsif ($debian_version){ + $id .= " $debian_version"; + } + eval $end if $b_log; + return $id; +} + +# Note, these are only for matching distro/mint derived names. +# Update list as new names become available. While first Mint was 2006-08, +# this method depends on /etc/os-release which was introduced 2012-02. +# Mint is using UBUNTU_CODENAME without ID data. +sub ubuntu_id { + eval $start if $b_log; + my ($codename) = @_; + $codename = lc($codename); + my ($id) = (''); + # xx.04, xx.10 + my %codenames = ( + # '??' => '25.04', + # '??' => '24.10', + 'noble' => '24.04 LTS', + 'mantic' => '23.10', + 'lunar' => '23.04', + 'kinetic' => '22.10', + 'jammy' => '22.04 LTS', + 'impish' => '21.10', + 'hirsute' => '21.04', + 'groovy' => '20.10', + 'focal' => '20.04 LTS', + 'eoan' => '19.10', + 'disco' => '19.04', + 'cosmic' => '18.10', + 'bionic' => '18.04 LTS', + 'artful' => '17.10', + 'zesty' => '17.04', + 'yakkety' => '16.10', + 'xenial' => '16.04 LTS', + 'wily' => '15.10', + 'vivid' => '15.04', + 'utopic' => '14.10', + 'trusty' => '14.04 LTS ', + 'saucy' => '13.10', + 'raring' => '13.04', + 'quantal' => '12.10', + 'precise' => '12.04 LTS ', + # 'natty' => '11.04','oneiric' => '11.10', + # 'lucid' => '10.04','maverick' => '10.10', + # 'jaunty' => '9.04','karmic' => '9.10', + # 'hardy' => '8.04','intrepid' => '8.10', + # 'feisty' => '7.04','gutsy' => '7.10', + # 'dapper' => '6.06','edgy' => '6.10', + # 'hoary' => '5.04','breezy' => '5.10', + # 'warty' => '4.10', # warty was the first ubuntu release + ); + $id = $codenames{$codename} if defined $codenames{$codename}; + eval $end if $b_log; + return $id; +} +} + +## DmidecodeData +{ +package DmidecodeData; + +# Note, all actual tests have already been run in check_tools so if we +# got here, we're good. +sub set { + eval $start if $b_log; + ${$_[0]} = 1; # set check boolean by reference + if ($fake{'dmidecode'} || $alerts{'dmidecode'}->{'action'} eq 'use'){ + generate_data(); + } + eval $end if $b_log; +} + +sub generate_data { + eval $start if $b_log; + my ($content,@data,@working,$type,$handle); + if ($fake{'dmidecode'}){ + my $file; + # $file = "$fake_data_dir/dmidecode/pci-freebsd-8.2-2"; + # $file = "$fake_data_dir/dmidecode/dmidecode-loki-1.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-t41-1.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-mint-20180106.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-vmware-ram-1.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-tyan-4408.txt"; + # $file = "$fake_data_dir/ram/dmidecode-speed-configured-1.txt"; + # $file = "$fake_data_dir/ram/dmidecode-speed-configured-2.txt"; + # $file = "$fake_data_dir/ram/00srv-dmidecode-mushkin-1.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-slots-pcix-pcie-1.txt"; + # $file = "$fake_data_dir/dmidecode/dmidecode-Microknopix-pci-vga-types-5-6-16-17.txt"; + # open(my $fh, '<', $file) or die "can't open $file: $!"; + # chomp(@data = <$fh>); + } + else { + $content = qx($alerts{'dmidecode'}->{'path'} 2>/dev/null); + @data = split('\n', $content); + } + # we don't need the opener lines of dmidecode output + # but we do want to preserve the indentation. Empty lines + # won't matter, they will be skipped, so no need to handle them. + # some dmidecodes do not use empty line separators + splice(@data, 0, 5) if @data; + my $j = 0; + my $b_skip = 1; + foreach (@data){ + if (!/^Hand/){ + next if $b_skip; + if (/^[^\s]/){ + $_ = lc($_); + $_ =~ s/\s(information)//; + push(@working, $_); + } + elsif (/^\t/){ + $_ =~ s/^\t\t/~/; + $_ =~ s/^\t|\s+$//g; + push(@working, $_); + } + } + elsif (/^Handle\s(0x[0-9A-Fa-f]+).*DMI\stype\s([0-9]+),.*/){ + $j = scalar @dmi; + $handle = hex($1); + $type = $2; + $use{'slot-tool'} = 1 if $type && $type == 9; + $b_skip = ($type > 126) ? 1 : 0; + next if $b_skip; + # we don't need 32, system boot, or 127, end of table + if (@working){ + if ($working[0] != 32 && $working[0] < 127){ + $dmi[$j] = ( + [@working], + ); + } + } + @working = ($type,$handle); + } + } + if (@working && $working[0] != 32 && $working[0] != 127){ + $j = scalar @dmi; + $dmi[$j] = \@working; + } + # last by not least, sort it by dmi type, now we don't have to worry + # about random dmi type ordering in the data, which happens. Also sort + # by handle, as secondary sort. + @dmi = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @dmi; + main::log_data('dump','@dmi',\@dmi) if $b_log; + print Data::Dumper::Dumper \@dmi if $dbg[2]; + eval $end if $b_log; +} +} + +# args: 0: driver; 1: modules, comma separated, return only modules +# which do not equal the driver string itself. Sometimes the module +# name is different from the driver name, even though it's the same thing. +sub get_driver_modules { + eval $start if $b_log; + my ($driver,$modules) = @_; + return if !$modules; + my @mods = split(/,\s+/, $modules); + if ($driver){ + @mods = grep {!/^$driver$/} @mods; + my $join = (length(join(',', @mods)) > 40) ? ', ' : ','; + $modules = join($join, @mods); + } + log_data('data','$modules',$modules) if $b_log; + eval $end if $b_log; + return $modules; +} + +# Return all detected gcc versions +sub get_gcc_data { + eval $start if $b_log; + my ($gcc,@data,@temp); + my $gccs = []; + # NOTE: We can't use program_version because we don't yet know where + # the version number is + if (my $program = check_program('gcc')){ + @data = grabber("$program --version 2>/dev/null"); + $gcc = awk(\@data,'^gcc'); + } + if ($gcc){ + # strip out: gcc (Debian 6.3.0-18) 6.3.0 20170516 + # gcc (GCC) 4.2.2 20070831 prerelease [FreeBSD] + $gcc =~ s/\([^\)]*\)//g; + $gcc = get_piece($gcc,2); + } + if ($extra > 1){ + # glob /usr/bin for gccs, strip out all non numeric values + @temp = globber('/usr/bin/gcc-*'); + # usually like gcc-11 but sometimes gcc-11.2.0 + foreach (@temp){ + if (/\/gcc-([0-9.]+)$/){ + push(@$gccs, $1) if !$gcc || $1 ne $gcc; + } + } + } + unshift(@$gccs, $gcc) if $gcc; + log_data('dump','@gccs',$gccs) if $b_log; + eval $end if $b_log; + return $gccs; +} + +## GlabelData - set/get +# Used only to get RAID ZFS gptid path standard name, like ada0p1 +{ +package GlabelData; + +# gptid/c5e940f1-5ce2-11e6-9eeb-d05099ac4dc2 N/A ada0p1 +sub get { + eval $start if $b_log; + my ($gptid) = @_; + set() if !$loaded{'glabel'}; + return if !@glabel || !$gptid; + my ($dev_id) = (''); + foreach (@glabel){ + my @temp = split(/\s+/, $_); + my $gptid_trimmed = $gptid; + # slice off s[0-9] from end in case they use slice syntax + $gptid_trimmed =~ s/s[0-9]+$//; + if (defined $temp[0] && ($temp[0] eq $gptid || $temp[0] eq $gptid_trimmed)){ + $dev_id = $temp[2]; + last; + } + } + $dev_id ||= $gptid; # no match? return full string + eval $end if $b_log; + return $dev_id; +} + +sub set { + eval $start if $b_log; + $loaded{'glabel'} = 1; + if (my $path = main::check_program('glabel')){ + @glabel = main::grabber("$path status 2>/dev/null"); + } + main::log_data('dump','@glabel:with Headers',\@glabel) if $b_log; + # get rid of first header line + shift @glabel; + eval $end if $b_log; +} +} + +sub get_hostname { + eval $start if $b_log; + my $hostname = ''; + if ($ENV{'HOSTNAME'}){ + $hostname = $ENV{'HOSTNAME'}; + } + elsif (!$bsd_type && -r "/proc/sys/kernel/hostname"){ + $hostname = reader('/proc/sys/kernel/hostname','',0); + } + # puppy removed this from core modules, sigh + # this is faster than subshell of hostname + elsif (check_perl_module('Sys::Hostname')){ + Sys::Hostname->import; + $hostname = Sys::Hostname::hostname(); + } + elsif (my $program = check_program('hostname')){ + $hostname = (grabber("$program 2>/dev/null"))[0]; + } + $hostname ||= 'N/A'; + eval $end if $b_log; + return $hostname; +} + +## InitData +{ +package InitData; +my ($init,$init_version,$program) = ('','',''); + +sub get { + eval $start if $b_log; + my $runlevel = get_runlevel(); + my $default = ($extra > 1) ? get_runlevel_default() : ''; + my ($rc,$rc_version) = ('',''); + my $comm = (-r '/proc/1/comm') ? main::reader('/proc/1/comm','',0) : ''; + # this test is pretty solid, if pid 1 is owned by systemd, it is systemd + # otherwise that is 'init', which covers the rest of the init systems. + # more data may be needed for other init systems. + # Some systemd cases no /proc/1/comm exists however :( + if (($comm && $comm =~ /systemd/) || -e '/run/systemd/units'){ + $init = 'systemd'; + if ($program = main::check_program('systemd')){ + $init_version = main::program_version($program,'^systemd','2','--version',1); + } + if (!$init_version && ($program = main::check_program('systemctl'))){ + $init_version = main::program_version($program,'^systemd','2','--version',1); + } + if ($runlevel && $runlevel =~ /^\d$/){ + my $target = ''; + if ($runlevel == 1){ + $target = 'rescue';} + elsif ($runlevel > 1 && $runlevel < 5){ + $target = 'multi-user';} + elsif ($runlevel == 5){ + $target = 'graphical';} + $runlevel = "$target ($runlevel)" if $target; + } + } + if (!$init && $comm){ + # not verified + if ($comm =~ /^31init/){ + $init = '31init'; + # no version, this is a 31 line C program + } + # epoch version == Epoch Init System 1.0.1 "Sage" + elsif ($comm =~ /epoch/){ + $init = 'Epoch'; + $init_version = main::program_version('epoch', '^Epoch', '4','version'); + } + # if they fix dinit to show /proc/1/comm == dinit + elsif ($comm =~ /^dinit/){ + dinit_data(); + } + elsif ($comm =~ /finit/){ + $init = 'finit'; + if ($program = main::check_program('finit')){ + $init_version = main::program_version($program,'^Finit','2','-v',1); + } + } + # not verified + elsif ($comm =~ /^hummingbird/){ + $init = 'Hummingbird'; + # no version data known. Complete if more info found. + } + # nosh can map service manager to systemctl, service, rcctl, at least. + elsif ($comm =~ /^nosh/){ + $init = 'nosh'; + } + # missing data: note, runit can install as a dependency without being the + # init system: http://smarden.org/runit/sv.8.html + # NOTE: the proc test won't work on bsds, so if runit is used on bsds we + # will need more data + elsif ($comm =~ /runit/){ + $init = 'runit'; + # no version data as of 2022-10-26 + } + elsif ($comm =~ /^s6/){ + $init = 's6'; + # no version data as of 2022-10-26 + } + elsif ($comm =~ /shepherd/){ + $init = 'Shepherd'; + $init_version = main::program_version('shepherd', '^shepherd', '4','--version',1); + } + # fallback for some inits that link to /sbin/init + elsif ($comm eq 'init'){ + # shows /sbin/dinit-init but may change + if (-e '/sbin/dinit' && readlink('/sbin/init') =~ /dinit/){ + dinit_data(); + } + elsif (-e '/sbin/openrc-init' && readlink('/sbin/init') =~ /openrc/){ + ($init,$init_version) = openrc_data(); + } + } + } + if (!$init){ + # output: /sbin/init --version: init (upstart 1.1) + # init (upstart 0.6.3) + # openwrt /sbin/init hangs on --version command, I think + if (!%risc && + ($init_version = main::program_version('init', 'upstart', '3','--version'))){ + $init = 'Upstart'; + } + elsif (main::check_program('launchctl')){ + $init = 'launchd'; + } + # could be nosh or runit as well for BSDs, not handled yet + elsif (-f '/etc/inittab'){ + $init = 'SysVinit'; + if (main::check_program('strings')){ + my @data = main::grabber('strings /sbin/init'); + $init_version = main::awk(\@data,'^version\s+[0-9]',2); + } + } + elsif (-f '/etc/ttys'){ + $init = 'init (BSD)'; + } + } + if ((grep { /openrc/ } main::globber('/run/*openrc*')) || (grep {/openrc/} @ps_cmd)){ + if (!$init || $init ne 'OpenRC'){ + ($rc,$rc_version) = openrc_data(); + } + if (-r '/run/openrc/softlevel'){ + $runlevel = main::reader('/run/openrc/softlevel','',0); + } + elsif (-r '/var/run/openrc/softlevel'){ + $runlevel = main::reader('/var/run/openrc/softlevel','',0); + } + elsif ($program = main::check_program('rc-status')){ + $runlevel = (main::grabber("$program -r 2>/dev/null"))[0]; + } + } + eval $end if $b_log; + return { + 'init-type' => $init, + 'init-version' => $init_version, + 'rc-type' => $rc, + 'rc-version' => $rc_version, + 'runlevel' => $runlevel, + 'default' => $default, + }; +} + +sub dinit_data { + eval $start if $b_log; + $init = 'dinit'; + # Dinit version 0.15.1. + if ($program = main::check_program('dinit')){ + $init_version = main::program_version($program,'^Dinit','3','--version',1); + $init_version =~ s/\.$//; + } + eval $end if $b_log; +} + +sub openrc_data { + eval $start if $b_log; + my $version; + # /sbin/openrc --version == openrc (OpenRC) 0.13 + if ($program = main::check_program('openrc')){ + $version = main::program_version($program, '^openrc', '3','--version'); + } + # /sbin/rc --version == rc (OpenRC) 0.11.8 (Gentoo Linux) + elsif ($program = main::check_program('rc')){ + $version = main::program_version($program, '^rc', '3','--version'); + } + eval $end if $b_log; + return ('OpenRC',$version); +} + +# Check? /var/run/nologin for bsds? +sub get_runlevel { + eval $start if $b_log; + my $runlevel = ''; + if ($program = main::check_program('runlevel')){ + # variants: N 5; 3 5; unknown + $runlevel = (main::grabber("$program 2>/dev/null"))[0]; + $runlevel = undef if $runlevel && lc($runlevel) eq 'unknown'; + $runlevel =~ s/^(\S\s)?(\d)$/$2/ if $runlevel; + # print_line($runlevel . ";;"); + } + eval $end if $b_log; + return $runlevel; +} + +# Note: it appears that at least as of 2014-01-13, /etc/inittab is going +# to be used for default runlevel in upstart/sysvinit. systemd default is +# not always set so check to see if it's linked. +sub get_runlevel_default { + eval $start if $b_log; + my @data; + my $default = ''; + if ($program = main::check_program('systemctl')){ + # note: systemd systems do not necessarily have this link created + my $systemd = '/etc/systemd/system/default.target'; + # faster to read than run + if (-e $systemd){ + $default = readlink($systemd); + $default =~ s/(.*\/|\.target$)//g if $default; + } + if (!$default){ + $default = (main::grabber("$program get-default 2>/dev/null"))[0]; + $default =~ s/\.target$// if $default; + } + } + if (!$default){ + # http://askubuntu.com/questions/86483/how-can-i-see-or-change-default-run-level + # note that technically default can be changed at boot but for inxi purposes + # that does not matter, we just want to know the system default + my $upstart = '/etc/init/rc-sysinit.conf'; + my $inittab = '/etc/inittab'; + if (-r $upstart){ + # env DEFAULT_RUNLEVEL=2 + @data = main::reader($upstart); + $default = main::awk(\@data,'^env\s+DEFAULT_RUNLEVEL',2,'='); + } + # handle weird cases where null but inittab exists + if (!$default && -r $inittab){ + @data = main::reader($inittab); + $default = main::awk(\@data,'^id.*initdefault',2,':'); + } + } + eval $end if $b_log; + return $default; +} +} + +## IpData +{ +package IpData; + +sub set { + eval $start if $b_log; + if ($force{'ip'} || + (!$force{'ifconfig'} && $alerts{'ip'}->{'action'} eq 'use')){ + set_ip_addr(); + } + elsif ($force{'ifconfig'} || $alerts{'ifconfig'}->{'action'} eq 'use'){ + set_ifconfig(); + } + eval $end if $b_log; +} + +sub set_ip_addr { + eval $start if $b_log; + my @data = main::grabber($alerts{'ip'}->{'path'} . " addr 2>/dev/null",'\n','strip'); + if ($fake{'ip-if'}){ + # my $file = "$fake_data_dir/if/scope-ipaddr-1.txt"; + # my $file = "$fake_data_dir/network/ip-addr-blue-advance.txt"; + # my $file = "$fake_data_dir/network/ppoe/ppoe-ip-address-1.txt"; + # my $file = "$fake_data_dir/network/ppoe/ppoe-ip-addr-2.txt"; + # my $file = "$fake_data_dir/network/ppoe/ppoe-ip-addr-3.txt"; + # @data = main::reader($file,'strip') or die $!; + } + my ($b_skip,$broadcast,$if,$if_id,$ip,@ips,$scope,$type,@temp,@temp2); + foreach (@data){ + if (/^[0-9]/){ + # print "$_\n"; + if (@ips){ + # print "$if\n"; + push(@ifs,($if,[@ips])); + @ips = (); + } + @temp = split(/:\s+/, $_); + $if = $temp[1]; + if ($if eq 'lo'){ + $b_skip = 1; + $if = ''; + next; + } + ($b_skip,@temp) = (); + } + elsif (!$b_skip && /^inet/){ + # print "$_\n"; + ($broadcast,$ip,$scope,$if_id,$type) = (); + @temp = split(/\s+/, $_); + $ip = $temp[1]; + $type = ($temp[0] eq 'inet') ? 4 : 6 ; + if ($temp[2] eq 'brd'){ + $broadcast = $temp[3]; + } + if (/scope\s([^\s]+)(\s(.+))?/){ + $scope = $1; + $if_id = $3; + } + push(@ips,[$type,$ip,$broadcast,$scope,$if_id]); + # print Data::Dumper::Dumper \@ips; + } + } + if (@ips){ + push(@ifs,($if,[@ips])); + } + main::log_data('dump','@ifs',\@ifs) if $b_log; + print 'ip addr: ', Data::Dumper::Dumper \@ifs if $dbg[3]; + eval $end if $b_log; +} + +sub set_ifconfig { + eval $start if $b_log; + # whitespace matters!! Don't use strip + my @data = main::grabber($alerts{'ifconfig'}->{'path'} . " 2>/dev/null",'\n',''); + if ($fake{'ip-if'}){ + # my $file = "$fake_data_dir/network/ppoe/ppoe-ifconfig-all-1.txt"; + # my $file = "$fake_data_dir/network/vps-ifconfig-1.txt"; + # @data = main::reader($file) or die $!; + } + my ($b_skip,$broadcast,$if,@ips_bsd,$ip,@ips,$scope,$if_id,$type,@temp,@temp2); + my ($state,$speed,$duplex,$mac); + foreach (@data){ + if (/^[\S]/i){ + # print "$_\n"; + if (@ips){ + # print "here\n"; + push(@ifs,($if,[@ips])); + @ips = (); + } + if ($mac){ + push(@ifs_bsd,($if,[$state,$speed,$duplex,$mac])); + ($state,$speed,$duplex,$mac,$if_id) = ('','','','',''); + } + $if = (split(/\s+/, $_))[0]; + $if =~ s/:$//; # em0: flags=8843 + $if_id = $if; + $if = (split(':', $if))[0] if $if; + if ($if =~ /^lo/){ + $b_skip = 1; + $if = ''; + $if_id = ''; + next; + } + $b_skip = 0; + } + elsif (!$b_skip && $bsd_type && /^\s+(address|ether|media|status|lladdr)/){ + $_ =~ s/^\s+//; + # freebsd 7.3: media: Ethernet 100baseTX + # Freebsd 8.2/12.2: media: Ethernet autoselect (1000baseT ) + # Netbsd 9.1: media: Ethernet autoselect (1000baseT full-duplex) + # openbsd: media: Ethernet autoselect (1000baseT full-duplex) + if (/^media/){ + if ($_ =~ /[\s\(]([1-9][^\(\s]+)?\s<([^>]+)>/){ + $speed = $1 if $1; + $duplex = $2; + } + if (!$duplex && $_ =~ /\s\(([\S]+)\s([^\s<]+)\)/){ + $speed = $1; + $duplex = $2; + } + if (!$speed && $_ =~ /\s\(([1-9][\S]+)\s/){ + $speed = $1; + } + } + # lladdr openbsd/address netbsd/ether freebsd + elsif (!$mac && /^(address|ether|lladdr)/){ + $mac = (split(/\s+/, $_))[1]; + } + elsif (/^status:\s*(.*)/){ + $state = $1; + } + } + elsif (!$b_skip && /^\s+inet/){ + # print "$_\n"; + $_ =~ s/^\s+//; + $_ =~ s/addr:\s/addr:/; + @temp = split(/\s+/, $_); + ($broadcast,$ip,$scope,$type) = ('','','',''); + $ip = $temp[1]; + # fe80::225:90ff:fe13:77ce%em0 +# $ip =~ s/^addr:|%([\S]+)//; + if ($1 && $1 ne $if_id){ + $if_id = $1; + } + $type = ($temp[0] eq 'inet') ? 4 : 6 ; + if (/(Bcast:|broadcast\s)([\S]+)/){ + $broadcast = $2; + } + if (/(scopeid\s[^<]+<|Scope:|scopeid\s)([^>]+)[>]?/){ + $scope = $2; + } + $scope = 'link' if $ip =~ /^fe80/; + push(@ips,[$type,$ip,$broadcast,$scope,$if_id]); + # print Data::Dumper::Dumper \@ips; + } + } + if (@ips){ + push(@ifs,($if,[@ips])); + } + if ($mac){ + push(@ifs_bsd,($if,[$state,$speed,$duplex,$mac])); + ($state,$speed,$duplex,$mac) = ('','','',''); + } + print 'ifconfig: ', Data::Dumper::Dumper \@ifs if $dbg[3]; + print 'ifconfig bsd: ', Data::Dumper::Dumper \@ifs_bsd if $dbg[3]; + main::log_data('dump','@ifs',\@ifs) if $b_log; + main::log_data('dump','@ifs_bsd',\@ifs_bsd) if $b_log; + eval $end if $b_log; +} +} + +sub get_kernel_bits { + eval $start if $b_log; + my $bits = ''; + if (my $program = check_program('getconf')){ + # what happens with future > 64 bit kernels? we'll see in the future! + if ($bits = (grabber("$program _POSIX_V6_LP64_OFF64 2>/dev/null"))[0]){ + if ($bits =~ /^(-1|undefined)$/i){ + $bits = 32; + } + # no docs for true state, 1 is usually true, but probably can be others + else { + $bits = 64; + } + } + # returns long bits if we got nothing on first test + $bits = (grabber("$program LONG_BIT 2>/dev/null"))[0] if !$bits; + } + # fallback test + if (!$bits && $bits_sys){ + $bits = $bits_sys; + } + $bits ||= 'N/A'; + eval $end if $b_log; + return $bits; +} + +# arg: 0: $cs_curr, by ref; 1: $cs_avail, by ref. +sub get_kernel_clocksource { + eval $start if $b_log; + if (-r '/sys/devices/system/clocksource/clocksource0/current_clocksource'){ + ${$_[0]} = reader('/sys/devices/system/clocksource/clocksource0/current_clocksource','',0); + if ($b_admin && + -r '/sys/devices/system/clocksource/clocksource0/available_clocksource'){ + ${$_[1]} = reader('/sys/devices/system/clocksource/clocksource0/available_clocksource','',0); + if (${$_[0]} && ${$_[1]}){ + my @temp = split(/\s+/,${$_[1]}); + @temp = grep {$_ ne ${$_[0]}} @temp; + ${$_[1]} = join(',', @temp); + } + } + } + eval $end if $b_log; +} + +sub get_kernel_data { + eval $start if $b_log; + my ($ksplice) = (''); + my $kernel = []; + # Linux; yawn; 4.9.0-3.1-liquorix-686-pae; #1 ZEN SMP PREEMPT liquorix 4.9-4 (2017-01-14); i686 + # FreeBSD; siwi.pair.com; 8.2-STABLE; FreeBSD 8.2-STABLE #0: Tue May 31 14:36:14 EDT 2016 erik5@iddhi.pair.com:/usr/obj/usr/src/sys/82PAIRx-AMD64; amd64 + if (@uname){ + $kernel->[0] = $uname[2]; + if ((my $program = check_program('uptrack-uname')) && $kernel->[0]){ + $ksplice = qx($program -rm); + $ksplice = trimmer($ksplice); + $kernel->[0] = $ksplice . ' (ksplice)' if $ksplice; + } + $kernel->[1] = $uname[-1]; + } + # we want these to have values to save validation checks for output + $kernel->[0] ||= 'N/A'; + $kernel->[1] ||= 'N/A'; + log_data('data',"kernel: " . join('; ', $kernel) . " ksplice: $ksplice") if $b_log; + log_data('dump','perl @uname', \@uname) if $b_log; + eval $end if $b_log; + return $kernel; +} + +## KernelParameters +{ +package KernelParameters; + +sub get { + eval $start if $b_log; + my ($parameters); + if (my $file = $system_files{'proc-cmdline'}){ + $parameters = parameters_linux($file); + } + elsif ($bsd_type){ + $parameters = parameters_bsd(); + } + eval $end if $b_log; + return $parameters; +} + +sub parameters_linux { + eval $start if $b_log; + my ($file) = @_; + # unrooted android may have file only root readable + my $line = main::reader($file,'',0) if -r $file; + $line =~ s/\s\s+/ /g; + eval $end if $b_log; + return $line; +} + +sub parameters_bsd { + eval $start if $b_log; + my ($parameters); + eval $end if $b_log; + return $parameters; +} +} + +## LsblkData - set/get +{ +package LsblkData; + +# args: 0: partition name +sub get { + eval $start if $b_log; + my $item = $_[0]; + return if !@lsblk; + my $result; + foreach my $device (@lsblk){ + if ($device->{'name'} eq $item){ + $result = $device; + last; + } + } + eval $start if $b_log; + return ($result) ? $result : {}; +} + +sub set { + eval $start if $b_log; + $loaded{'lsblk'} = 1; + if ($alerts{'lsblk'} && $alerts{'lsblk'}->{'path'}){ + # check to see if lsblk removes : - separators from accepted input syntax + my $cmd = $alerts{'lsblk'}->{'path'} . ' -bP --output NAME,TYPE,RM,FSTYPE,'; + $cmd .= 'SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,'; + $cmd .= 'MAJ:MIN,PKNAME 2>/dev/null'; + print "cmd: $cmd\n" if $dbg[32]; + my @working = main::grabber($cmd); + print Data::Dumper::Dumper \@working if $dbg[32]; + # note: lsblk 2.37 changeed - and : to _ in the output. + my $pattern = 'NAME="([^"]*)"\s+TYPE="([^"]*)"\s+RM="([^"]*)"\s+'; + $pattern .= 'FSTYPE="([^"]*)"\s+SIZE="([^"]*)"\s+LABEL="([^"]*)"\s+'; + $pattern .= 'UUID="([^"]*)"\s+SERIAL="([^"]*)"\s+MOUNTPOINT="([^"]*)"\s+'; + $pattern .= 'PHY[_-]SEC="([^"]*)"\s+LOG[_-]SEC="([^"]*)"\s+'; + $pattern .= 'PARTFLAGS="([^"]*)"\s+MAJ[:_-]MIN="([^"]*)"\s+PKNAME="([^"]*)"'; + foreach (@working){ + if (/$pattern/){ + my $size = ($5) ? $5/1024: 0; + # some versions of lsblk do not return serial, fs, uuid, or label + push(@lsblk, { + 'name' => $1, + 'type' => $2, + 'rm' => $3, + 'fs' => $4, + 'size' => $size, + 'label' => $6, + 'uuid' => $7, + 'serial' => $8, + 'mount' => $9, + 'block-physical' => $10, + 'block-logical' => $11, + 'partition-flags' => $12, + 'maj-min' => $13, + 'parent' => $14, + }); + # must be below assignments!! otherwise the result of the match replaces values + # note: for bcache and luks, the device that has that fs is the parent!! + if ($show{'logical'}){ + $use{'logical-lvm'} = 1 if !$use{'logical-lvm'} && $2 && $2 eq 'lvm'; + if (!$use{'logical-general'} && (($4 && + ($4 eq 'crypto_LUKS' || $4 eq 'bcache')) || + ($2 && ($2 eq 'dm' && $1 =~ /veracrypt/i) || $2 eq 'crypto' || + $2 eq 'mpath' || $2 eq 'multipath'))){ + $use{'logical-general'} = 1; + } + } + } + } + } + print Data::Dumper::Dumper \@lsblk if $dbg[32]; + main::log_data('dump','@lsblk',\@lsblk) if $b_log; + eval $end if $b_log; +} +} + +sub set_mapper { + eval $start if $b_log; + $loaded{'mapper'} = 1; + return if ! -d '/dev/mapper'; + foreach ((globber('/dev/mapper/*'))){ + my ($key,$value) = ($_,Cwd::abs_path("$_")); + next if !$value; + $key =~ s|^/.*/||; + $value =~ s|^/.*/||; + $mapper{$key} = $value; + } + %dmmapper = reverse %mapper if %mapper; + eval $end if $b_log; +} + +## MemoryData +{ +package MemoryData; + +sub get { + eval $start if $b_log; + my ($type) = @_; + $loaded{'memory'} = 1; + my ($memory); + # netbsd 8.0 uses meminfo, but it uses it in a weird way + if (!$force{'vmstat'} && (!$bsd_type || ($force{'meminfo'} && $bsd_type)) && + (my $file = $system_files{'proc-meminfo'})){ + $memory = linux_data($type,$file); + } + else { + $memory = bsd_data($type); + } + eval $end if $b_log; + return $memory; +} + +# $memory: +# 0: available (not reserved or iGPU) +# 1: used (of available) +# 2: used % +# 3: gpu (raspberry pi only) +# Linux only, but could be extended if anyone wants to do the work for BSDs +# 4: array ref: sys_memory [total, blocks, block-size, count factor] +# 5: array ref: proc/iomem [total, reserved, gpu] +# +# args: 0: source, the caller; 1: $row hash ref; 2: $num ref; 3: indent +sub row { + eval $start if $b_log; + my ($source,$row,$num,$indent) = @_; + $loaded{'memory'} = 1; + my ($available,$gpu_ram,$note,$total,$used); + my $memory = get('full'); + if ($memory){ + # print Data::Dumper::Dumper $memory; + if ($memory->[3]){ + $gpu_ram = $memory->[3]; + } + elsif ($memory->[5] && $memory->[5][2]){ + $gpu_ram = $memory->[5][2]; + } + # Great, we have the real RAM data. + if ($show{'ram'} && ($total = RamItem::ram_total())){ + $total = main::get_size($total,'string'); + } + elsif ($memory->[4] || $memory->[5]){ + process_total($memory,\$total,\$note); + } + if ($gpu_ram){ + $gpu_ram = main::get_size($gpu_ram,'string'); + } + $available = main::get_size($memory->[0],'string') if $memory->[0]; + $used = main::get_size($memory->[1],'string') if $memory->[1]; + $used .= " ($memory->[2]%)" if $memory->[2]; + } + my $field = ($source eq 'info') ? 'Memory' : 'System RAM'; + $available ||= 'N/A'; + $total ||= 'N/A'; + $used ||= 'N/A'; + $row->{main::key($$num++,1,$indent,$field)} = ''; + $row->{main::key($$num++,1,$indent+1,'total')} = $total; + $row->{main::key($$num++,0,$indent+2,'note')} = $note if $note; + $row->{main::key($$num++,0,$indent+1,'available')} = $available; + $row->{main::key($$num++,0,$indent+1,'used')} = $used; + $row->{main::key($$num++,0,$indent+1,'igpu')} = $gpu_ram if $gpu_ram; + eval $end if $b_log; +} + +## LINUX DATA ## +sub linux_data { + eval $start if $b_log; + my ($type,$file) = @_; + my ($available,$buffers,$cached,$free,$gpu,$not_used,$total_avail) = (0,0,0,0,0,0,0); + my ($iomem,$memory,$sys_memory,$total); + my @data = main::reader($file); + # Note: units kB should mean 1000x8 bits, but actually means KiB! Confusing + foreach (@data){ + # Not actual total, it's total physical minus reserved/kernel/system. + if ($_ =~ /^MemTotal:/){ + $total_avail = main::get_piece($_,2); + } + elsif ($_ =~ /^MemFree:/){ + $free = main::get_piece($_,2); + } + elsif ($_ =~ /^Buffers:/){ + $buffers = main::get_piece($_,2); + } + elsif ($_ =~ /^Cached:/){ + $cached = main::get_piece($_,2); + } + elsif ($_ =~ /^MemAvailable:/){ + $available = main::get_piece($_,2); + } + } + $gpu = gpu_ram_arm() if $risc{'arm'}; + if ($type ne 'short' && ($fake{'sys-mem'} || -d '/sys/devices/system/memory')){ + sys_memory(\$sys_memory); + } + if ($type ne 'short' && ($fake{'iomem'} || ($b_root && -r '/proc/iomem'))){ + proc_iomem(\$iomem); + } + # $gpu = main::translate_size('128M'); + # $total_avail += $gpu; # not using because this ram is not available to system + if ($available){ + $not_used = $available; + } + # Seen fringe cases, where total - free+buff+cach < 0 + # The idea is that the OS must be using 10MiB of ram or more + elsif (($total_avail - ($free + $buffers + $cached)) > 10000){ + $not_used = ($free + $buffers + $cached); + } + # Netbsd goes < 0, but it's wrong, so dump the cache + elsif (($total_avail - ($free + $buffers)) > 10000){ + $not_used = ($free + $buffers); + } + else { + $not_used = $free; + } + my $used = ($total_avail - $not_used); + my $percent = ($used && $total_avail) ? sprintf("%.1f", ($used/$total_avail)*100) : ''; + if ($type eq 'short'){ + $memory = short_data($total_avail,$used,$percent); + } + else { + # raw return in KiB + $memory = [$total_avail,$used,$percent,$gpu,$sys_memory,$iomem]; + } + # print "$total_avail, $used, $percent, $gpu\n"; + # print Data::Dumper::Dumper $memory; + main::log_data('data',"memory ref: $memory") if $b_log; + eval $end if $b_log; + return $memory; +} + +# All values 0 if not root, but it is readable. +# See inxi-perl/dev/code-snippets.pl for original attempt, with pci/reserved +# args: 0: $iomem by ref +sub proc_iomem { + eval $start if $b_log; + my $file = '/proc/iomem'; + my ($buffer,$gpu,$pci,$reserved,$rom,$system) = (0,0,0,0,0,0); + my $b_reserved; + no warnings 'portable'; + if ($fake{'iomem'}){ + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-128gb-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-544mb-igpu.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-64mb-vram-stolen.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-rh-1-matrox.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-2-vram.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-512mb-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-518mb-reserved-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-512mb-2-onboardgpu-active.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-512mb-system-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-257.18gb-system-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-192gb-system-1.txt"; + $file = "$ENV{'HOME'}/bin/scripts/inxi/data/memory/proc-iomem-1012mb-igpu.txt"; + } + foreach ((main::reader($file),'EOF')){ + if ($dbg[54]){ + if (/^\s*([0-9a-f]+)-([^\s]+) : /){ + print $_,"\n",' size: '; + print main::get_size(((hex($2) - hex($1) + 1)/1024),'string'), "\n"; + } + } + # Get everythign solidly System RAM + if (/^([0-9a-f]+)-([^\s]+) : (System RAM)$/i){ + $system += hex($2) - hex($1) + 1; + } + elsif (/^([0-9a-f]+)-([^\s]+) : (Ram buffer)$/i){ + $buffer += hex($2) - hex($1) + 1; + } + # Sometimes primary Reserved block contains PCI and other non RAM devices, + # but also can contain non RAM addresses, maybe NVMe? + elsif (/^([0-9a-f]+)-([^\s]+) : (Reserved)$/i){ + $reserved += hex($2) - hex($1) + 1; + } + # Legacy System ROM not in a Reserved block, primary item. + elsif (/^\s*([0-9a-f]+)-([^\s]+) : (System ROM)$/i){ + $rom += hex($2) - hex($1) + 1; + } + elsif (/^([0-9a-f]+)-([^\s]+) : (ACPI Tables)$/i){ + $rom += hex($2) - hex($1) + 1; + } + # Incomplete because sometimes Reserved blocks contain PCI etc devices + elsif (/^([0-9a-f]+)-([^\s]+) : (PCI .*)$/){ + $pci += hex($2) - hex($1) + 1; + } + # Graphics stolen memory/Video RAM area, but legacy had inside PCI blocks, + # not reserved, or as primary. That behavior seems to have changed. + if (/^\s*([0-9a-f]+)-([^\s]+) : (?:(Video RAM|Graphics).*)$/i){ + $gpu += hex($2) - hex($1) + 1; + } + } + if ($dbg[54] || $b_log){ + my $d = ['iomem:','System: ' . main::get_size(($system/1024),'string'), + 'Reserved: ' . main::get_size(($reserved/1024),'string'), + 'Buffer: ' . main::get_size(($buffer/1024),'string'), + 'iGPU: ' . main::get_size(($gpu/1024),'string'), + 'ROM: ' . main::get_size(($rom/1024),'string'), + 'System+iGPU+buffer+rom: ' . main::get_size((($system+$gpu+$buffer+$rom)/1024),'string'), + ' Raw GiB: ' . ($system+$gpu+$buffer+$rom)/1024**3, + 'System+reserved: ' . main::get_size((($system+$reserved)/1024),'string'), + ' Raw GiB: ' . ($system+$reserved)/1024**3, + 'System+reserved+buffer: ' . main::get_size((($system+$reserved+$buffer)/1024),'string'), + ' Raw GiB: ' . ($system+$reserved+$buffer)/1024**3, + 'Reserved-iGPU: ' . main::get_size((($reserved-$gpu)/1024),'string'), + 'PCI Bus: ' . main::get_size(($pci/1024),'string')]; + main::log_data('dump','$d iomem',$d) if $b_log; + print "\n",join("\n",@$d),"\n\n" if $dbg[54]; + } + if ($gpu || $system || $reserved){ + # This combination seems to provide the bwest overall result + $system += $gpu + $rom + $buffer; + ${$_[0]} = [$system/1024,$reserved/1024,$gpu/1024]; + } + main::log_data('dump','$iomem',$_[0]) if $b_log; + print 'proc/iomem: ', Data::Dumper::Dumper $_[0] if $dbg[53]; + eval $end if $b_log; +} + +# Note: seen case where actual 128 GiB, result here 130, 65x2GiB. Also cases +# where blocks under expected total, this may be related to active onboard gpu. +sub sys_memory { + eval $start if $b_log; + return if !$fake{'sys-mem'} && ! -r '/sys/devices/system/memory/block_size_bytes'; + my ($count,$factor,$size,$total) = (0,1,0,0); + # state = off,online; online = 1/0 + foreach my $online (main::globber('/sys/devices/system/memory/memory*/online')){ + $count++ if main::reader($online,'',0); # content 1/0, so will read as t/f + } + if ($count){ + $size = main::reader('/sys/devices/system/memory/block_size_bytes','',0); + if ($size){ + $size = hex($size)/1024; # back to integer KiB + $total = $count * $size; + } + } + if ($fake{'sys-mem'}){ + # ($total,$count,$size) = (,,); # + # ($total,$count,$size) = (4194304,32,131072); # 4gb + # ($total,$count,$size) = (7864320,60,131072); # 7.5 gb, -4 blocks + # ($total,$count,$size) = (136314880,65,2097152); # 130 gb, +1 block + # ($total,$count,$size) = (8126464,62,131072); # 7.75 gb, -2 blocks, vram? + # ($total,$count,$size) = (33554432,256,131072); # 32 gb + # ($total,$count,$size) = (8388608,64,131072); # 8gb + # ($total,$count,$size) = (270532608,129,2097152); # 258 gb, +1 block + # ($total,$count,$size) = (17563648,134,131072); # 16.75 gb, +6 block + # ($total,$count,$size) = (3801088,29,131072); # 3.62 gb, -3 blocks + # ($total,$count,$size) = (67108864,32,2097152); # 64 gb + # ($total,$count,$size) = (524288,4,131072); # 512 mb, maybe -4 blocks, vm + } + # Max stick size assumed: 64 blocks: 8 GiB/128 GiB min module: 2 GiB/32 GiB + # 128 blocks: 16 GiB/256 GiB min module: 4 GiB/64 GiB but no way to know + # Note: 128 MiB blocks; > 32 GiB, 2 GiB blocks, I think. + # 64: 8 GiB/256 GiB, min module: 2 GiB/32 GiB + if ($count > 32){ + $factor = 16;} + # 32: 4 GiB/64 GiB, min module: 1 GiB/16 GiB + elsif ($count > 16){ + $factor = 8;} + # 16: 2 GiB, min module: 512 MiB + elsif ($count > 8){ + $factor = 4;} + # 8: 1 GiB, min module: 256 MiB + elsif ($count > 4){ + $factor = 2;} + # 4: 512 MiB, min module: 128 MiB + else { + $factor = 1;} + if ($total || $count || $size){ + ${$_[0]} = [$total,$count,$size,$factor]; + } + if ($dbg[54] || $b_log){ + my $d = ['/sys:','Total: ' . main::get_size($total,'string'), + 'Blocks: ' . $count, + 'Block-size: ' . main::get_size($size,'string'), + "Count-factor: $count % $factor: " . $count % $factor]; + main::log_data('dump','$d sys-mem',$d) if $b_log; + print "\n",join("\n",@$d),"\n\n" if $dbg[54]; + } + main::log_data('dump','$sys_memory',$_[0]) if $b_log; + print 'sys memory: ', Data::Dumper::Dumper $_[0] if $dbg[53]; + eval $end if $b_log; +} + +# These are hacks since the phy ram real data is not available in clear form +# args: 0: memory array ref; 1: $total ref; 2: $note ref. +sub process_total { + eval $start if $b_log; + my ($memory,$total,$note) = @_; + my ($d,$b_vm,@info); + my $src = ''; + $b_vm = MachineItem::is_vm() if $show{'machine'}; + # Seen case where actual 128 GiB, result here 130, 65x2GiB. Maybe nvme? + # This can be over or under phys ram + if ($memory->[4] && $memory->[4][0]){ + @info = main::get_size($memory->[4][0]); + # We want to show note for probably wrong results + if ((!$fake{'sys-mem'} && $memory->[0] && $memory->[4][0] < $memory->[0]) || + (!$b_vm && $memory->[4][1] % $memory->[4][3] != 0)){ + $$note = main::message('note-check'); + } + $src = 'sys'; + } + # Note: this is a touch under the real ram amount, varies, igpu/vram can eat it. + # This working total will only be under phys ram. + if ($memory->[5] && $memory->[5][0] && + (!$memory->[4] || !$memory->[4][0] || ($memory->[4][0] != $memory->[5][0]))){ + @info = main::get_size($memory->[5][0]); + $src = 'iomem'; + } + if (@info){ + $$note = ''; + if (!$b_vm){ + # $info[0] = 384; + # $info[1] = 'MiB'; + my ($factor,$factor2) = (1,0.5); + # For M, assume smallest is 128, anything older won't even work probably. + # For T RAM, the system ram is going to be 99.9% of physical because the + # reserved stuff is going to be tiny, I believe. We will see. + # T array stick sizes: 128/256/512/1024 G + # Note: samsung ships 1T modules (2024?), 512G (2023). + if ($info[0] > 512){ + $factor = ($info[1] eq 'MiB') ? 256 : 64; + } + elsif ($info[0] > 256){ + $factor = ($info[1] eq 'MiB') ? 128 : 32; + } + elsif ($info[0] > 128){ + $factor = ($info[1] eq 'MiB') ? 64 : 16; + } + elsif ($info[0] > 64){ + $factor = 8; + } + elsif ($info[0] > 16){ + $factor = 4; + } + elsif ($info[0] > 8){ + $factor = 4; + } + elsif ($info[0] > 4){ + $factor = 2; + } + elsif ($info[0] > 3){ + $factor = 1; + } + elsif ($info[0] > 2){ + $factor = ($info[1] eq 'TiB') ? 0.25 : 0.5; + } + # Note: get_size returns 1 as 1024, so we never actually see 1 + elsif ($info[0] > 1){ + $factor = ($info[1] eq 'TiB') ? 0.125 : 0.25; + } + my $result = $info[0] / $factor; + my $mod = ((100 * $result) % 100); + if ($b_log || $dbg[54]){ + push(@$d,"src: $src result: $info[0] / $factor: $result math-modulus: $mod"); + } + if ($mod > 0){ + my ($check,$working) = (0,0); + # Sometimes Perl generates a tiny value over 0.1: 0.100000000000023 + # but also we want to be a little loose here. Note that when high + # numbers, like 1012 M, we want the math much looser. + # Within ~ 5% + if ($info[1] eq 'MiB'){ + if ($info[0] > 768){ + $check = 64; + } + elsif ($info[0] > 512){ + $check = 32; + } + elsif ($info[0] > 256){ + $check = 16; + } + else { + $check = 4; + } + } + # Within ~ 1% + elsif ($info[1] eq 'GiB'){ + if ($info[0] > 512){ + $check = 4; + } + elsif ($info[0] > 256){ + $check = 2; + } + elsif ($info[0] > 3){ + $check = 0.25; + } + else { + $check = 0.1; + } + } + # Will need to verify this T assumption on real data one day, but keep + # in mind how much reserved ram this would be! + elsif ($info[1] eq 'TiB'){ + if ($info[0] > 16){ + $check = 0.25; + } + elsif ($info[0] > 8){ + $check = 0.15; + } + elsif ($info[0] > 2){ + $check = 0.1; + } + else { + $check = 0.05; + } + } + # iomem is always under, sys can be over or under. we want fractional + # corresponding value over or under result. + # sys has block sizes: 128M, 2G, 32G, so sizes will always be divisible + if ($src eq 'sys'){ + if ($info[0] > 64){ + $factor2 = 0.25; + } + } + if ($src eq 'sys' && int($result + $factor2) == int($result)){ + $working = int($result) * $factor; + } + else { + $working = POSIX::ceil($result) * $factor; + } + if ($b_log || $dbg[54]){ + push(@$d, "factor2: $factor2 floor_res+fact2: " . int($result + $factor2), + "ceil_result * factor: " . (POSIX::ceil($result) * $factor), + "floor_result * factor: " . (int($result) * $factor)); + } + if (abs(($working - $info[0])) < $check){ + if ($src eq 'sys' && $info[0] != $working){ + $$note = main::message('note-est'); + } + if ($b_log || $dbg[54]){ + push(@$d,"check less: ($working - $info[0]) < $check: ", + "result: inside ceil < $check, clean"); + } + } + else { + if ($b_log || $dbg[54]){ + push(@$d,"check not less: ($working - $info[0]) < $check: ", + "set: $info[0] = $working"); + } + $$note = main::message('note-est'); + } + $info[0] = $working; + } + else { + if ($b_log || $dbg[54]){ + push(@$d,"result: clean match, no change: $info[0] $info[1]"); + } + } + } + else { + my $dec = ($info[1] eq 'MiB') ? 1: 2; + $info[0] = sprintf("%0.${dec}f",$info[0]) + 0; + if ($b_log || $dbg[54]){ + push(@$d,"result: vm, using size: $info[0] $info[1]"); + } + } + $$total = $info[0] . ' ' . $info[1]; + } + if ($b_log || $dbg[54]){ + main::log_data('dump','debugger',$d) if $b_log; + print Data::Dumper::Dumper $d if $dbg[54]; + } + eval $end if $b_log; +} + +## BSD DATA ## +## openbsd/linux +# procs memory page disks traps cpu +# r b w avm fre flt re pi po fr sr wd0 wd1 int sys cs us sy id +# 0 0 0 55256 1484092 171 0 0 0 0 0 2 0 12 460 39 3 1 96 +## openbsd 6.3? added in M/G/T etc, sigh... +# 2 57 55M 590M 789 0 0 0... +## freebsd: +# procs memory page disks faults cpu +# r b w avm fre flt re pi po fr sr ad0 ad1 in sy cs us sy id +# 0 0 0 21880M 6444M 924 32 11 0 822 827 0 0 853 832 463 8 3 88 +# with -H +# 2 0 0 14925812 936448 36 13 10 0 84 35 0 0 84 30 42 11 3 86 +## dragonfly: V1, supported -H +# procs memory page disks faults cpu +# r b w avm fre flt re pi po fr sr ad0 ad1 in sy cs us sy id +# 0 0 0 0 84060 30273993 2845 12742 1164 407498171 320960902 0 0 .... +## dragonfly: V2, no avm, no -H support +sub bsd_data { + eval $start if $b_log; + my ($type) = @_; + my ($avm,$av_pages,$cnt,$fre,$free_mem,$mult,$real_mem,$total) = (0,0,0,0,0,0,0,0); + my (@data,$memory,$message); + # my $arg = ($bsd_type ne 'openbsd' && $bsd_type ne 'dragonfly') ? '-H' : ''; + if (my $program = main::check_program('vmstat')){ + # See above, it's the last line. -H makes it hopefully all in kB so no need + # for K/M/G tests, note that -H not consistently supported, so don't use. + my @vmstat = main::grabber("vmstat 2>/dev/null",'\n','strip'); + main::log_data('dump','@vmstat',\@vmstat) if $b_log; + my @header = split(/\s+/, $vmstat[1]); + foreach (@header){ + if ($_ eq 'avm'){$avm = $cnt} + elsif ($_ eq 'fre'){$fre = $cnt} + elsif ($_ eq 'flt'){last;} + $cnt++; + } + my $row = $vmstat[-1]; + if ($row){ + @data = split(/\s+/, $row); + # Openbsd 6.3, dragonfly 5.x introduced an M / G character, sigh. + if ($avm > 0 && $data[$avm] && $data[$avm] =~ /^([0-9\.]+[KGMT])(iB|B)?$/){ + $data[$avm] = main::translate_size($1); + } + if ($fre > 0 && $data[$fre] && $data[$fre] =~ /^([0-9\.]+[KGMT])(iB|B)?$/){ + $data[$fre] = main::translate_size($1); + } + # Dragonfly can have 0 avg, or no avm, sigh, but they may fix that so make test dynamic + if ($avm > 0 && $data[$avm] != 0){ + $av_pages = ($bsd_type !~ /^(net|open)bsd$/) ? sprintf('%.1f',$data[$avm]/1024) : $data[$avm]; + } + if ($fre > 0 && $data[$fre] != 0){ + $free_mem = sprintf('%.1f',$data[$fre]); + } + } + } + # Code to get total goes here: + if ($alerts{'sysctl'}->{'action'} eq 'use'){ + # For dragonfly, we will use free mem, not used because free is 0 + my @working; + if ($sysctl{'memory'}){ + foreach (@{$sysctl{'memory'}}){ + # Freebsd seems to use bytes here + if (!$real_mem && /^hw.physmem:/){ + @working = split(/:\s*/, $_); + # if ($working[1]){ + $working[1] =~ s/^[^0-9]+|[^0-9]+$//g; + $real_mem = sprintf("%.1f", $working[1]/1024); + # } + last if $free_mem; + } + # But, it uses K here. Openbsd/Dragonfly do not seem to have this item + # This can be either: Free Memory OR Free Memory Pages + elsif (/^Free Memory:/){ + @working = split(/:\s*/, $_); + $working[1] =~ s/[^0-9]+//g; + $free_mem = sprintf("%.1f", $working[1]); + last if $real_mem; + } + } + } + } + else { + $message = "sysctl $alerts{'sysctl'}->{'action'}" + } + # Not using, but leave in place for a bit in case we want it + # my $type = ($free_mem) ? ' free':'' ; + # Hack: temp fix for openbsd/darwin: in case no free mem was detected but we have physmem + if (($av_pages || $free_mem) && !$real_mem){ + my $error = ($message) ? $message: 'total N/A'; + my $used = (!$free_mem) ? $av_pages : $real_mem - $free_mem; + if ($type eq 'short'){ + $memory = short_data($error,$used); + } + else { + $memory = [$error,$used,undef]; + } + } + # Use openbsd/dragonfly avail mem data if available + elsif (($av_pages || $free_mem) && $real_mem){ + my $used = (!$free_mem) ? $av_pages : $real_mem - $free_mem; + my $percent = ($used && $real_mem) ? sprintf("%.1f", ($used/$real_mem)*100) : ''; + if ($type eq 'short'){ + $memory = short_data($real_mem,$used,$percent); + } + else { + $memory = [$real_mem,$used,$percent,0]; + } + } + eval $end if $b_log; + return $memory; +} + +## TOOLS ## +# args: 0: avail memory; 1: used memory; 2: percent used +sub short_data { + # some BSDs, no available + my @avail = (main::is_numeric($_[0])) ? main::get_size($_[0]) : ($_[0]); + my @used = main::get_size($_[1]); + my $string = ''; + if ($avail[1] && $used[1]){ + if ( $avail[1] eq $used[1]){ + $string = "$used[0]/$avail[0] $used[1]"; + } + else { + $string = "$used[0] $used[1]/$avail[0] $avail[1]"; + } + } + elsif ($used[1]){ + $string = "$used[0]/[$avail[0]] $used[1]"; + } + $string .= " ($_[2]%)" if $_[2]; + return $string; +} + +# Raspberry pi only +sub gpu_ram_arm { + eval $start if $b_log; + my ($gpu_ram) = (0); + if (my $program = main::check_program('vcgencmd')){ + # gpu=128M + # "VCHI initialization failed" - you need to add video group to your user + my $working = (main::grabber("$program get_mem gpu 2>/dev/null"))[0]; + $working = (split(/\s*=\s*/, $working))[1] if $working; + $gpu_ram = main::translate_size($working) if $working; + } + main::log_data('data',"gpu ram: $gpu_ram") if $b_log; + eval $end if $b_log; + return $gpu_ram; +} +} + +# args: 0: module to get version of +sub get_module_version { + eval $start if $b_log; + my ($module) = @_; + return if !$module; + my ($version); + my $path = "/sys/module/$module/version"; + if (-r $path){ + $version = reader($path,'',0); + } + elsif (-f "/sys/module/$module/uevent"){ + $version = 'kernel'; + } + # print "version:$version\n"; + if (!$version){ + if (my $path = check_program('modinfo')){ + my @data = grabber("$path $module 2>/dev/null"); + $version = awk(\@data,'^version',2,':\s+') if @data; + } + } + $version ||= ''; + eval $end if $b_log; + return $version; +} + +## PackageData +# Note: this outputs the key/value pairs ready to go and is +# called from either -r or -Ix, -r precedes. +{ +package PackageData; +my ($count,$num,%pms,$type); +$pms{'total'} = 0; + +sub get { + eval $start if $b_log; + # $num passed by reference to maintain incrementing where requested + ($type,$num) = @_; + $loaded{'package-data'} = 1; + my $output = {}; + package_counts(); + appimage_counts(); + create_output($output); + eval $end if $b_log; + return $output; +} + +sub create_output { + eval $start if $b_log; + my $output = $_[0]; + my $total = ''; + if ($pms{'total'}){ + $total = $pms{'total'}; + } + else { + if ($type eq 'inner' || $pms{'note'}){ + $total = 'N/A' if $extra < 2; + } + else { + $total = main::message('package-data'); + } + } + if ($pms{'total'} && $extra > 1){ + delete $pms{'total'}; + my $b_mismatch; + foreach (keys %pms){ + next if $_ eq 'note'; + if ($pms{$_}->{'pkgs'} && $pms{$_}->{'pkgs'} != $total){ + $b_mismatch = 1; + last; + } + } + $total = '' if !$b_mismatch; + } + $output->{main::key($$num++,1,1,'Packages')} = $total; + # if blocked pm secondary, only show if no total or improbable total + if ($pms{'note'} && $extra < 2 && (!$pms{'total'} || $total < 100)){ + $output->{main::key($$num++,0,2,'note')} = $pms{'note'}; + } + if ($extra > 1 && %pms){ + foreach my $pm (sort keys %pms){ + my ($cont,$ind) = (1,2); + # if package mgr command returns error, this will not be a hash + next if ref $pms{$pm} ne 'HASH'; + if ($pms{$pm}->{'pkgs'} || $b_admin || ($extra > 1 && $pms{$pm}->{'note'})){ + my $type = $pm; + $type =~ s/^zzz-//; # get rid of the special sorters for items to show last + $output->{main::key($$num++,$cont,$ind,'pm')} = $type; + ($cont,$ind) = (0,3); + $pms{$pm}->{'pkgs'} = 'N/A' if $pms{$pm}->{'note'}; + $output->{main::key($$num++,($cont+1),$ind,'pkgs')} = $pms{$pm}->{'pkgs'}; + if ($pms{$pm}->{'note'}){ + $output->{main::key($$num++,$cont,$ind,'note')} = $pms{$pm}->{'note'}; + } + if ($b_admin ){ + if ($pms{$pm}->{'libs'}){ + $output->{main::key($$num++,$cont,($ind+1),'libs')} = $pms{$pm}->{'libs'}; + } + if ($pms{$pm}->{'tools'}){ + $output->{main::key($$num++,$cont,$ind,'tools')} = $pms{$pm}->{'tools'}; + } + } + } + } + } + # print Data::Dumper::Dumper \%output; + eval $end if $b_log; +} + +sub package_counts { + eval $start if $b_log; + my ($type) = @_; + # note: there is a program called discover which has nothing to do with kde + # apt systems: plasma-discover, non apt, discover, but can't use due to conflict + # my $disc = 'plasma-discover'; + my $gs = 'gnome-software'; + # 0: key; 1: program; 2: p/d; 3: arg/path; 4: 0/1 use lib; + # 5: lib slice; 6: lib splitter; 7 - optional eval test; + # 8: optional installed tool tests for -ra + # needed: cards [nutyx], urpmq [mageia] + my @pkg_managers = ( + ['alps','alps','p','showinstalled',1,0,''], + ['apk','apk','p','info',1,0,''], + # ['aptd','dpkg-query','d','/usr/lib/*',1,3,'\\/'], + # mutyx. do cards test because there is a very slow pkginfo python pkg mgr + ['cards','pkginfo','p','-i',1,1,'','main::check_program(\'cards\')'], + # older dpkg-query do not support -f values consistently: eg ${binary:Package} + ['dpkg','dpkg-query','p','-W --showformat=\'${Package}\n\'',1,0,'','', + ['apt','apt-get','aptitude','deb-get','nala','synaptic']], + ['emerge','emerge','d','/var/db/pkg/*/*/',1,5,'\\/'], + ['eopkg','eopkg','d','/var/lib/eopkg/package/*',1,5,'\\/'], + ['guix-sys','guix','p','package -p "/run/current-system/profile" -I',1,0,''], + ['guix-usr','guix','p','package -I',1,0,''], + ['kiss','kiss','p','list',1,0,''], + ['mport','mport','p','list',1,0,''], + # netpkg puts packages in same place as slackpkg, only way to tell apart + ['netpkg','netpkg','d','/var/lib/pkgtools/packages/*',1,5,'\\/', + '-d \'/var/netpkg\' && -d \'/var/lib/pkgtools/packages\'', + ['netpkg','sbopkg','sboui','slackpkg','slapt-get','slpkg','swaret']], + ['nix-sys','nix-store','p','-qR /run/current-system/sw',1,1,'-'], + ['nix-usr','nix-store','p','-qR ~/.nix-profile',1,1,'-'], + ['nix-default','nix-store','p','-qR /nix/var/nix/profiles/default',1,2,'-'], + ['opkg','opkg','p','list',1,0,''], # ubuntu based Security Onion + ['pacman','pacman','p','-Qq --color never',1,0,'', + '!main::check_program(\'pacman-g2\')', # pacman-g2 has sym link to pacman + # these may need to be trimmed down depending on how useful/less some are + ['argon','aura','aurutils','cylon','octopi','pacaur','pakku','pamac','paru', + 'pikaur','trizen','yaourt','yay','yup']], + ['pacman-g2','pacman-g2','p','-Q',1,0,'','',], + ['pkg','pkg','d','/var/db/pkg/*',1,0,''], # 'pkg list' returns non programs + ['pkg_add','pkg_info','p','',1,0,''], # OpenBSD has set of tools, not 1 pm + # like cards, avoid pkginfo directly due to python pm being so slow + # but pkgadd is also found in scratch + ['pkgutils','pkginfo','p','-i',1,0,'','main::check_program(\'pkgadd\')'], + # slack 15 moves packages to /var/lib/pkgtools/packages but links to /var/log/packages + ['pkgtool','installpkg','d','/var/lib/pkgtools/packages/*',1,5,'\\/', + '!-d \'/var/netpkg\' && -d \'/var/lib/pkgtools/packages\'', + ['sbopkg','sboui','slackpkg','slapt-get','slpkg','swaret']], + ['pkgtool','installpkg','d','/var/log/packages/*',1,4,'\\/', + '! -d \'/var/lib/pkgtools/packages\' && -d \'/var/log/packages/\'', + ['sbopkg','sboui','slackpkg','slapt-get','slpkg','swaret']], + # rpm way too slow without nodigest/sig!! confirms packages exist + # but even with, MASSIVELY slow in some cases, > 20, 30 seconds!!!! + # find another way to get rpm package counts or don't show this feature for rpm!! + ['rpm','rpm','force','-qa --nodigest --nosignature',1,0,'','', + ['dnf','packagekit','up2date','urpmi','yast','yum','zypper']], + # scratch is a programming language too, with software called scratch + ['scratch','pkgbuild','d','/var/lib/scratchpkg/index/*/.pkginfo',1,5,'\\/', + '-d \'/var/lib/scratchpkg\''], + # note: slackpkg, slapt-get, spkg, and pkgtool all return the same count + # ['slackpkg','pkgtool','slapt-get','slpkg','swaret']], + # ['slapt-get','slapt-get','p','--installed',1,0,''], + # ['spkg','spkg','p','--installed',1,0,''], + ['tce','tce-status','p','-i',1,0,'','',['apps','tce-load']], + # note: I believe mageia uses rpm internally but confirm + # ['urpmi','urpmq','p','??',1,0,''], + ['xbps','xbps-query','p','-l',1,1,''], + # ['xxx-brew','brew','p','--cellar',0,0,''], # verify how this works + ['zzz-flatpak','flatpak','p','list',0,0,''], + ['zzz-snap','snap','p','list',0,0,'','@ps_cmd && (grep {/\bsnapd\b/} @ps_cmd)'], + ); + my ($program); + foreach my $pm (@pkg_managers){ + if ($program = main::check_program($pm->[1])){ + next if $pm->[7] && !eval $pm->[7]; + my ($error,$libs,@list,$pmts); + if ($pm->[2] eq 'p' || ($pm->[2] eq 'force' && check_run($pm))){ + chomp(@list = qx($program $pm->[3] 2>/dev/null)); + } + elsif ($pm->[2] eq 'd'){ + @list = main::globber($pm->[3]); + } + else { + # update message() if pm other than rpm disabled by default + $error = main::message('pm-' . $pm->[1] . '-disabled'); + } + $count = scalar @list if !$error; + # print Data::Dumper::Dumper \@list; + if (!$error){ + if ($b_admin && $count && $pm->[4]){ + $libs = count_libs(\@list,$pm->[5],$pm->[6]); + } + } + else { + $pms{'note'} = $error; + } + # if there is ambiguity about actual program installed, use this loop + if ($b_admin && $pm->[8]){ + my @tools; + foreach my $tool (@{$pm->[8]}){ + if (main::check_program($tool)){ + push(@tools,$tool); + } + } + # only show gs if tools found, and if not added before + if (@tools){ + if ($gs && main::check_program($gs)){ + push(@tools,$gs); + $gs = ''; + } + } + $pmts = join(',',sort @tools) if @tools; + } + $pms{$pm->[0]} = { + 'pkgs' => $count, + 'libs' => $libs, + 'note' => $error, + 'tools' => $pmts, + }; + $pms{'total'} += $count if defined $count; + # print Data::Dumper::Dumper \%pms; + } + } + # print Data::Dumper::Dumper \%pms; + main::log_data('dump','Package managers: %pms',\%pms) if $b_log; + eval $end if $b_log; +} + +sub appimage_counts { + if (@ps_cmd && (grep {/\bappimage(d|launcher)\b/} @ps_cmd)){ + my @list = main::globber($ENV{'HOME'} . '/.{appimage/,local/bin/}*.[aA]pp[iI]mage'); + $count = scalar @list; + $pms{'zzz-appimage'} = { + 'pkgs' => $count, + 'libs' => undef, + }; + $pms{'total'} += $count; + } +} + +sub check_run { + if ($force{'pkg'}){ + return 1; + } + elsif (${_[0]}->[1] eq 'rpm'){ + # testing for core wrappers for rpm, these should not be present in non + # redhat/suse based systems. mageia has urpmi, dnf, yum + foreach my $tool (('dnf','up2date','urpmi','yum','zypper')){ + return 0 if main::check_program($tool); + } + # Note: test fails: apt-rpm (pclinuxos,alt linux), unknown how to detect + # Add pm test if known to have rpm available. + foreach my $tool (('dpkg','pacman','pkgtool','tce-load')){ + return 1 if main::check_program($tool); + } + } +} + +sub count_libs { + my ($items,$pos,$split) = @_; + my (@data); + my $i = 0; + $split ||= '\\s+'; + # print scalar @$items, '::', $split, '::', $pos, "\n"; + foreach (@$items){ + @data = split(/$split/, $_); + # print scalar @data, '::', $data[$pos], "\n"; + $i++ if $data[$pos] && $data[$pos] =~ m%^lib%; + } + return $i; +} +} + +## ParseEDID +{ +package ParseEDID; +# CVT_ratios: +my @known_ratios = qw(5/4 4/3 3/2 16/10 15/9 16/9); + +# Set values +my @edid_info = ( + ['a8', '_header'], + ['a2', 'manufacturer_name'], + ['v', 'product_code'], + ['V', 'serial_number'], + ['C', 'week'], + ['C', 'year'], + ['C', 'edid_version'], + ['C', 'edid_revision'], + ['a', 'video_input_definition'], + ['C', 'max_size_horizontal'], # in cm, 0 on projectors + ['C', 'max_size_vertical'], # in cm, 0 on projectors + ['C', 'gamma'], + ['a', 'feature_support'], + ['a10', 'color_characteristics'], + ['a3' , 'established_timings'], + ['a16', 'standard_timings'], + ['a72', 'monitor_details'], + ['C', 'extension_flag'], + ['C', 'checksum'], +); +my %subfields = ( + manufacturer_name => [ + [1, ''], + [5, '1'], + [5, '2'], + [5, '3'], + ], + video_input_definition => [ + [1, 'digital'], + [1, 'separate_sync'], + [1, 'composite_sync'], + [1, 'sync_on_green'], + [2, ''], + [2, 'voltage_level'], + ], + feature_support => [ + [1, 'DPMS_standby'], + [1, 'DPMS_suspend'], + [1, 'DPMS_active_off'], + [1, 'rgb'], + [1, ''], + [1, 'sRGB_compliance'], + [1, 'has_preferred_timing'], + [1, 'GTF_compliance'], + ], + # these are VESA timings, basically: VESA-EEDID-A2.pdf + established_timings => [ + # byte 1, 23h + [1, '720x400_70'], + [1, '720x400_88'], + [1, '640x480_60'], + [1, '640x480_67'], + [1, '640x480_72'], + [1, '640x480_75'], + [1, '800x600_56'], + [1, '800x600_60'], + # byte 2, 24h + [1, '800x600_72'], + [1, '800x600_75'], + [1, '832x624_75'], + [1, '1024x768_87i'], + [1, '1024x768_60'], + [1, '1024x768_70'], + [1, '1024x768_75'], + [1, '1280x1024_75'], + # byte 3, 25h + # 7: [1, '1152x870_75'], # apple macII + # 6-0: manufacturer's timings + ], + detailed_timing => [ + [8, 'horizontal_active'], + [8, 'horizontal_blanking'], + [4, 'horizontal_active_hi'], + [4, 'horizontal_blanking_hi'], + [8, 'vertical_active'], + [8, 'vertical_blanking'], + [4, 'vertical_active_hi'], + [4, 'vertical_blanking_hi'], + [8, 'horizontal_sync_offset'], + [8, 'horizontal_sync_pulse_width'], + [4, 'vertical_sync_offset'], + [4, 'vertical_sync_pulse_width'], + [2, 'horizontal_sync_offset_hi'], + [2, 'horizontal_sync_pulse_width_hi'], + [2, 'vertical_sync_offset_hi'], + [2, 'vertical_sync_pulse_width_hi'], + [8, 'horizontal_image_size'], # in mm + [8, 'vertical_image_size'], # in mm + [4, 'horizontal_image_size_hi'], + [4, 'vertical_image_size_hi'], + [8, 'horizontal_border'], + [8, 'vertical_border'], + [1, 'interlaced'], + [2, 'stereo'], + [2, 'digital_composite'], + [1, 'horizontal_sync_positive'], + [1, 'vertical_sync_positive'], + [1, ''], + ], + # 16 bytes, up to 8 additional timings, each identified by a unique 2 byte + # code derived from the horizontal active pixel count, the image aspect ratio + # and field refresh rate as described in Table 3.19 + standard_timing => [ + [8, 'X'], + [2, 'aspect'], + [6, 'vfreq'], + ], + monitor_range => [ + [8, 'vertical_min'], + [8, 'vertical_max'], + [8, 'horizontal_min'], + [8, 'horizontal_max'], + [8, 'pixel_clock_max'], + ], + manufacturer_specified_range_timing => [ + # http://www.spwg.org/salisbury_march_19_2002.pdf + # for the glossary: http://www.vesa.org/Public/PSWG/PSWG15v1.pdf + [8, 'horizontal_sync_pulse_width_min'], # HSPW (Horizontal Sync Pulse Width) + [8, 'horizontal_sync_pulse_width_max'], + [8, 'horizontal_back_porch_min'], # t_hbp + [8, 'horizontal_back_porch_max'], + [8, 'vertical_sync_pulse_width_min'], # VSPW (Vertical Sync Pulse Width) + [8, 'vertical_sync_pulse_width_max'], + [8, 'vertical_back_porch_min'], # t_vbp (Vertical Back Porch) + [8, 'vertical_back_porch_max'], + [8, 'horizontal_blanking_min'], # t_hp (Horizontal Period) + [8, 'horizontal_blanking_max'], + [8, 'vertical_blanking_min'], # t_vp + [8, 'vertical_blanking_max'], + [8, 'module_revision'], + ], + cea_data_block_collection => [ + [3, 'type'], + [5, 'size'], + ], + cea_video_data_block => [ + [1, 'native'], + [7, 'mode'], + ], + # Section 3.7 in VESA-EEDID-A2.pdf specs + color_characteristics => [ + # Rx1 Rx0 Ry1 Ry0 Gx1 Gx0 Gy1 Gy0 + [8, 'white_point_red_green'], + # Bx1 Bx0 By1 By0 Wx1 Wx0 Wy1 Wy0 + [8, 'white_point_blue_white'], + [8, 'red_x'], + [8, 'red_y'], + [8, 'green_x'], + [8, 'green_y'], + [8, 'blue_x'], + [8, 'blue_y'], + [8, 'white_x'], + [8, 'white_y'], + ], +); +my @cea_video_mode_to_detailed_timing = ( + 'pixel_clock', + 'horizontal_active', + 'vertical_active', + 'aspect', + 'horizontal_blanking', + 'horizontal_sync_offset', + 'horizontal_sync_pulse_width', + 'vertical_blanking', + 'vertical_sync_offset', + 'vertical_sync_pulse_width', + 'horizontal_sync_positive', + 'vertical_sync_positive', + 'interlaced' +); +my @cea_video_modes = ( +# [0] pixel clock, [1] X, [2] Y, [3] aspect, [4] Hblank, [5] Hsync_offset, [6] Hsync_pulse_width, +# [7] Vblank, [8] Vsync_offset, [9] Vsync_pulse_width, [10] Hsync+, [11] Vsync+, [12] interlaced +# 59.94/29.97 and similar modes also have a 60.00/30.00 counterpart by raising the pixel clock + [ 25.175, 640, 480, "4/3", 160, 16, 96, 45, 10, 2, 0, 0, 0 ], # 1: 640x 480@59.94 + [ 27.000, 720, 480, "4/3", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 2: 720x 480@59.94 + [ 27.000, 720, 480, "16/9", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 3: 720x 480@59.94 + [ 74.250, 1280, 720, "16/9", 370, 110, 40, 30, 5, 5, 1, 1, 0 ], # 4: 1280x 720@60.00 + [ 74.250, 1920, 1080, "16/9", 280, 88, 44, 45, 4, 10, 1, 1, 1 ], # 5: 1920x1080@30.00 + [ 27.000, 1440, 480, "4/3", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 6: 1440x 480@29.97 + [ 27.000, 1440, 480, "16/9", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 7: 1440x 480@29.97 + [ 27.000, 1440, 240, "4/3", 276, 38, 124, 22, 4, 3, 0, 0, 0 ], # 8: 1440x 240@60.05 + [ 27.000, 1440, 240, "16/9", 276, 38, 124, 22, 4, 3, 0, 0, 0 ], # 9: 1440x 240@60.05 + [ 54.000, 2880, 480, "4/3", 552, 76, 248, 45, 8, 6, 0, 0, 1 ], # 10: 2880x 480@29.97 + [ 54.000, 2880, 480, "16/9", 552, 76, 248, 45, 8, 6, 0, 0, 1 ], # 11: 2880x 480@29.97 + [ 54.000, 2880, 240, "4/3", 552, 76, 248, 22, 4, 3, 0, 0, 0 ], # 12: 2880x 240@60.05 + [ 54.000, 2880, 240, "16/9", 552, 76, 248, 22, 4, 3, 0, 0, 0 ], # 13: 2880x 240@60.05 + [ 54.000, 1440, 480, "4/3", 276, 32, 124, 45, 9, 6, 0, 0, 0 ], # 14: 1440x 480@59.94 + [ 54.000, 1440, 480, "16/9", 276, 32, 124, 45, 9, 6, 0, 0, 0 ], # 15: 1440x 480@59.94 + [ 148.500, 1920, 1080, "16/9", 280, 88, 44, 45, 4, 5, 1, 1, 0 ], # 16: 1920x1080@60.00 + [ 27.000, 720, 576, "4/3", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 17: 720x 576@50.00 + [ 27.000, 720, 576, "16/9", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 18: 720x 576@50.00 + [ 74.250, 1280, 720, "16/9", 700, 440, 40, 30, 5, 5, 1, 1, 0 ], # 19: 1280x 720@50.00 + [ 74.250, 1920, 1080, "16/9", 720, 528, 44, 45, 4, 10, 1, 1, 1 ], # 20: 1920x1080@25.00 + [ 27.000, 1440, 576, "4/3", 288, 24, 126, 49, 4, 6, 0, 0, 1 ], # 21: 1440x 576@25.00 + [ 27.000, 1440, 576, "16/9", 288, 24, 126, 49, 4, 6, 0, 0, 1 ], # 22: 1440x 576@25.00 + [ 27.000, 1440, 288, "4/3", 288, 24, 126, 24, 2, 3, 0, 0, 0 ], # 23: 1440x 288@50.08 + [ 27.000, 1440, 288, "16/9", 288, 24, 126, 24, 2, 3, 0, 0, 0 ], # 24: 1440x 288@50.08 + [ 54.000, 2880, 576, "4/3", 576, 48, 252, 49, 4, 6, 0, 0, 1 ], # 25: 2880x 576@25.00 + [ 54.000, 2880, 576, "16/9", 576, 48, 252, 49, 4, 6, 0, 0, 1 ], # 26: 2880x 576@25.00 + [ 54.000, 2880, 288, "4/3", 576, 48, 252, 24, 2, 3, 0, 0, 0 ], # 27: 2880x 288@50.08 + [ 54.000, 2880, 288, "16/9", 576, 48, 252, 24, 2, 3, 0, 0, 0 ], # 28: 2880x 288@50.08 + [ 54.000, 1440, 576, "4/3", 288, 24, 128, 49, 5, 5, 0, 0, 0 ], # 29: 1440x 576@50.00 + [ 54.000, 1440, 576, "16/9", 288, 24, 128, 49, 5, 5, 0, 0, 0 ], # 30: 1440x 576@50.00 + [ 148.500, 1920, 1080, "16/9", 720, 528, 44, 45, 4, 5, 1, 1, 0 ], # 31: 1920x1080@50.00 + [ 74.250, 1920, 1080, "16/9", 830, 638, 44, 45, 4, 5, 1, 1, 0 ], # 32: 1920x1080@24.00 + [ 74.250, 1920, 1080, "16/9", 720, 528, 44, 45, 4, 5, 1, 1, 0 ], # 33: 1920x1080@25.00 + [ 74.250, 1920, 1080, "16/9", 280, 88, 44, 45, 4, 5, 1, 1, 0 ], # 34: 1920x1080@30.00 + [ 108.000, 2880, 480, "4/3", 552, 64, 248, 45, 9, 6, 0, 0, 0 ], # 35: 2880x 480@59.94 + [ 108.000, 2880, 480, "16/9", 552, 64, 248, 45, 9, 6, 0, 0, 0 ], # 36: 2880x 480@59.94 + [ 108.000, 2880, 576, "4/3", 576, 48, 256, 49, 5, 5, 0, 0, 0 ], # 37: 2880x 576@50.00 + [ 108.000, 2880, 576, "16/9", 576, 48, 256, 49, 5, 5, 0, 0, 0 ], # 38: 2880x 576@50.00 + [ 72.000, 1920, 1080, "16/9", 384, 32, 168, 170, 46, 10, 1, 0, 1 ], # 39: 1920x1080@25.00 + [ 148.500, 1920, 1080, "16/9", 720, 528, 44, 45, 4, 10, 1, 1, 1 ], # 40: 1920x1080@50.00 + [ 148.500, 1280, 720, "16/9", 700, 440, 40, 30, 5, 5, 1, 1, 0 ], # 41: 1280x 720@100.00 + [ 54.000, 720, 576, "4/3", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 42: 720x 576@100.00 + [ 54.000, 720, 576, "16/9", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 43: 720x 576@100.00 + [ 54.000, 1440, 576, "4/3", 288, 24, 126, 49, 4, 6, 0, 0, 0 ], # 44: 1440x 576@50.00 + [ 54.000, 1440, 576, "16/9", 288, 24, 126, 49, 4, 6, 0, 0, 0 ], # 45: 1440x 576@50.00 + [ 148.500, 1920, 1080, "16/9", 280, 88, 44, 45, 4, 10, 1, 1, 1 ], # 46: 1920x1080@60.00 + [ 148.500, 1280, 720, "16/9", 370, 110, 40, 30, 5, 5, 1, 1, 0 ], # 47: 1280x 720@120.00 + [ 54.000, 720, 480, "4/3", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 48: 720x 480@119.88 + [ 54.000, 720, 480, "16/9", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 49: 720x 480@119.88 + [ 54.000, 1440, 480, "4/3", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 50: 1440x 480@59.94 + [ 54.000, 1440, 480, "16/9", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 51: 1440x 480@59.94 + [ 108.000, 720, 576, "4/3", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 52: 720x 576@200.00 + [ 108.000, 720, 576, "16/9", 144, 12, 64, 49, 5, 5, 0, 0, 0 ], # 53: 720x 576@200.00 + [ 108.000, 1440, 576, "4/3", 288, 24, 126, 49, 4, 6, 0, 0, 1 ], # 54: 1440x 576@100.00 + [ 108.000, 1440, 576, "16/9", 288, 24, 126, 49, 4, 6, 0, 0, 1 ], # 55: 1440x 576@100.00 + [ 108.000, 720, 480, "4/3", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 56: 720x 480@239.76 + [ 108.000, 720, 480, "16/9", 138, 16, 62, 45, 9, 6, 0, 0, 0 ], # 57: 720x 480@239.76 + [ 108.000, 1440, 480, "4/3", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 58: 1440x 480@119.88 + [ 108.000, 1440, 480, "16/9", 276, 38, 124, 45, 8, 6, 0, 0, 1 ], # 59: 1440x 480@119.88 + [ 59.400, 1280, 720, "16/9", 2020, 1760, 40, 30, 5, 5, 1, 1, 0 ], # 60: 1280x 720@24.00 + [ 74.250, 1280, 720, "16/9", 2680, 2420, 40, 30, 5, 5, 1, 1, 0 ], # 61: 1280x 720@25.00 + [ 74.250, 1280, 720, "16/9", 2020, 1760, 40, 30, 5, 5, 1, 1, 0 ], # 62: 1280x 720@30.00 + [ 297.000, 1920, 1080, "16/9", 280, 88, 44, 45, 4, 5, 1, 1, 0 ], # 63: 1920x1080@120.00 + [ 297.000, 1920, 1080, "16/9", 720, 528, 44, 45, 4, 10, 1, 1, 0 ], # 64: 1920x1080@100.00 +); +# Exist but IDs Unknown: Pixio, AOpen (AON?), AORUS [probably GBT], Deco Gear, +# Eyoyo, GAEMS, GeChic, KOORUI, Lilliput, Mobile Pixels, Nexanic, SunFounder, +# TECNII, TPEKKA, V7/VSEVEN, +# Guesses: KYY=KYY, MSI=MSI, KOE=Kaohsiung Opto Electronics +# PGS: Princeton Graphic Systems; SDC: Samsung Display Co; +# SIS: Silicon Integrated Systems; STN: Samsung Electronics America; +# BDS: Barco Display Systems +# TAI: Toshiba America +# HIQ: Hitachi ImageQuest or Kaohsiung Opto Electronics? or does Imagequest make hitachi: +# NVD: Nvidia or NewVisionDisplay? +my %vendors = ( +'AAC' => 'AcerView', 'ACI' => 'Asus', 'ACR' => 'Acer', 'ACT' => 'Targa', 'ADI' => 'ADI', +'AIC' => 'AG Neovo', 'AMW' => 'AMW', 'ANX' => 'Acer Netxix', 'AOC' => 'AOC', 'API' => 'A Plus Info', +'APP' => 'Apple', 'ART' => 'ArtMedia', 'AST' => 'AST Research', 'AUO' => 'AU Optronics', +'BEL' => 'Beltronic', 'BMM' => 'BMM', 'BNQ' => 'BenQ', 'BOE' => 'BOE Display', 'BDS' => 'Barco', +'CHO' => 'Sichuang Changhong', 'CMN' => 'ChiMei InnoLux', 'CMO' => 'Chi Mei Optoelectronics', +'CPL' => 'Compal/ALFA', 'CPQ' => 'Compaq', 'CPT' => 'Chungwa Picture Tubes', 'CTX' => 'CTX (Chuntex)', 'CVT' => 'DGM', +'DEC' => 'DEC', 'DEL' => 'Dell', 'DON' => 'Denon', 'DPC' => 'Delta', 'DPL' => 'Digital Projection', 'DWE' => 'Daewoo', +'ECS' => 'Elitegroup', 'EIZ' => 'EIZO', 'ELS' => 'ELSA', 'ENC' => 'EIZO NANAO', 'EPI' => 'Envision', 'ETR' => 'Rotel', +'FCM' => 'Funai', 'FUJ' => 'Fujitsu', 'FUS' => 'Fujitsu Siemens', +'GBT' => 'Gigabyte', 'GFN' => 'Gefen', 'GSM' => 'LG (GoldStar)', 'GWY' => 'Gateway 2000', +'HEI' => 'Hyundai.', 'HIQ' => 'Hyundai ImageQuest', 'HIT' => 'Hitachi', 'HPN' => 'HP', +'HSD' => 'HannSpree/HannStar', 'HSL' => 'Hansol', 'HTC' => 'Hitachi/Nissei', 'HVR' => 'Hitachi', +'HWP' => 'HP', 'HWV' => 'Huawei', +'IBM' => 'IBM', 'ICL' => 'Fujitsu ICL', 'IFS' => 'InFocus', 'INO' => 'Innolab Pte', 'IQT' => 'Hyundai', +'IVM' => 'Idek Iiyama', 'IVO' => 'InfoVision Optronics/Kunshan', +'KDS' => 'Korea Data Systems (KDS)', 'KFC' => 'KFC Computek', 'KOE' => 'Kaohsiung OptoElectronics', +'KTC' => 'Kingston', 'KYY' => 'KYY', +'LCD' => 'Toshiba Matsushita', 'LEN' => 'Lenovo', 'LGD' => 'LG Display', 'LKM' => 'Adlas/Azalea', +'LNK' => 'LINK', 'LPL' => 'LG Philips', 'LTN' => 'Lite-On', +'MAG' => 'MAG InnoVision', 'MAX' => 'Belinea/Maxdata', 'MED' => 'Medion', +'MEI' => 'Panasonic', 'MEL' => 'Mitsubishi', 'MIR' => 'Miro', 'MSI' => 'MSI', 'MTC' => 'MITAC', +'NAN' => 'NANAO/EIZO', 'NEX' => 'Nexgen Mediatech', 'NCP' => 'Najing CEC Panda', 'NEC' => 'NEC', +'NOK' => 'Nokia', 'NVD' => 'Nvidia', +'ONK' => 'Onkyo', 'OPT' => 'Optoma','OQI' => 'ViewSonic Optiquest', 'ORN' => 'Orion', +'PBN' => 'Packard Bell', 'PCK' => 'Daewoo', 'PDC' => 'Polaroid', 'PGS' => 'Princeton', +'PHL' => 'Philips', 'PIO' => 'Pioneer', 'PNR' => 'Planar', 'PRT' => 'Princeton', +'QDI' => 'Quantum Data', 'QDS' => 'Quanta Display', 'REL' => 'Relisys', 'REN' => 'Renesas', +'SAM' => 'Samsung', 'SAN' => 'Sanyo', 'SBI' => 'Smarttech', 'SDC' => 'Samsung', 'SEC' => 'Seiko Epson', +'SEN' => 'Sensics', 'SHP' => 'Sharp', 'SGD' => 'Sigma Designs', 'SGI' => 'SGI', 'SHI' => 'Jiangsu Shinco', +'SII' => 'Silicon Image', 'SIS' => 'SIS', 'SKM' => 'Guangzhou Teclast', 'SMC' => 'Samtron', +'SMI' => 'Smile', 'SNI' => 'Siemens Nixdorf', 'SNY' => 'Sony', 'SPT' => 'Sceptre', +'SRC' => 'Shamrock', 'STN' => 'Samsung', 'STP' => 'Sceptre', 'SUN' => 'Sun Microsystems', 'SYN' => 'Synaptics', +'TAI' => 'Toshiba', 'TAT' => 'Tatung', 'TOS' => 'Toshiba', 'TRL' => 'Royal Information', +'TSB' => 'Toshiba', 'UEG' => 'EliteGroup', 'UNM' => 'Unisys', +'VIT' => 'Visitech', 'VLV' => 'Valve', 'VSC' => 'ViewSonic', 'VTK' => 'Viewteck', 'VTS' => 'VTech', +'WTC' => 'Wen Technology', 'XLX' => 'Xilinx', 'YMH' => 'Yamaha', 'ZCM' => 'Zenith', +); + +sub _within_limit { + my ($value, $type, $limit) = @_; + $type eq 'min' ? $value >= $limit : $value <= $limit; +} + +sub _get_many_bits { + my ($s, $field_name) = @_; + my @bits = split('', unpack('B*', $s)); + my %h; + foreach (@{$subfields{$field_name}}) { + my ($size, $field) = @$_; + my @l = ('0' x (8 - $size), splice(@bits, 0, $size)); + if ($field && $field !~ /^_/){ + $h{$field} = unpack("C", pack('B*', join('', @l))); + # spec: chromacity: 0.xyz: white_point see color_characteristics + if ($h{$field} && $field_name eq 'color_characteristics'){ + $h{$field} = ($field =~ /_[xy]$/) ? sprintf('%0.3f',$h{$field}/255) : [@l[1..8]]; + } + } + } + \%h; +} + +sub _build_detailed_timing { + my ($pixel_clock, $vv) = @_; + my $h = _get_many_bits($vv, 'detailed_timing'); + $h->{pixel_clock} = $pixel_clock / 100; # to have it in MHz + my %detailed_timing_field_size = map { $_->[1], $_->[0] } @{$subfields{detailed_timing}}; + foreach my $field (keys %detailed_timing_field_size) { + $field =~ s/_hi$// or next; + my $hi = delete($h->{$field . '_hi'}); + $h->{$field} += $hi << $detailed_timing_field_size{$field}; + } + $h; +} + +sub _add_standard_timing_modes { + my ($edid, $v) = @_; + my @aspect2ratio = ( + $edid->{edid_version} > 1 || $edid->{edid_revision} > 2 ? '16/10' : '1/1', + '4/3', '5/4', '16/9', + ); + $v = [ map { + my $h = _get_many_bits($_, 'standard_timing'); + $h->{X} = ($h->{X} + 31) * 8; + if ($_ ne "\x20\x20" && $h->{X} > 256){ # cf VALID_TIMING in Xorg edid.h + $h->{vfreq} += 60; + if ($h->{ratio} = $aspect2ratio[$h->{aspect}]){ + delete $h->{aspect}; + $h->{Y} = $h->{X} / eval($h->{ratio}); + } + $h; + } + else { () } + } unpack('a2' x (length($v) / 2), $v) ]; + $v; +} + +sub parse_edid { + eval $start if $b_log; + my ($raw_edid, $verbose) = @_; + my (%edid, @warnings); + my ($main_edid, @eedid_blocks) = unpack("a128" x (length($raw_edid) / 128), $raw_edid); + my @vals = unpack(join('', map { $_->[0] } @edid_info), $main_edid); + my $i = 0; + foreach (@edid_info) { + my ($field, $v) = ($_->[1], $vals[$i++]); + if ($field eq 'year'){ + $v += 1990; + } + elsif ($field eq 'manufacturer_name'){ + my $h = _get_many_bits($v, 'manufacturer_name'); + $v = join('', map { chr(ord('A') + $h->{$_} - 1) } 1 .. 3); + $v = "" if $v eq "@@@"; + $edid{'manufacturer_name_nice'} = ($v && $vendors{$v}) ? $vendors{$v} : ''; + } + elsif ($field eq 'video_input_definition'){ + $v = _get_many_bits($v, 'video_input_definition'); + } + elsif ($field eq 'feature_support'){ + $v = _get_many_bits($v, 'feature_support'); + } + elsif ($field eq 'color_characteristics'){ + $v = _get_many_bits($v, 'color_characteristics'); + } + elsif ($field eq 'established_timings'){ + my $h = _get_many_bits($v, 'established_timings'); + $v = [ + sort { $a->{X} <=> $b->{X} || $a->{vfreq} <=> $b->{vfreq} } + map { /(\d+)x(\d+)_(\d+)(i?)/ ? { X => $1, Y => $2, vfreq => $3, $4 ? (interlace => 1) : () } : () } + grep { $h->{$_} } keys %$h ]; + } + elsif ($field eq 'standard_timings'){ + $v = _add_standard_timing_modes(\%edid, $v); + } + elsif ($field eq 'monitor_details'){ + while ($v){ + (my $pixel_clock, my $vv, $v) = unpack("v a16 a*", $v); + if ($pixel_clock){ + # detailed timing + my $h = _build_detailed_timing($pixel_clock, $vv); + push @{$edid{detailed_timings}}, $h + if $h->{horizontal_active} > 1 && $h->{vertical_active} > 1; + } + else { + (my $flag, $vv) = unpack("n x a*", $vv); + if ($flag == 0xfd){ + # range + $edid{monitor_range} = _get_many_bits($vv, 'monitor_range'); + if ($edid{monitor_range}{pixel_clock_max} == 0xff){ + delete $edid{monitor_range}{pixel_clock_max}; + } + else { + $edid{monitor_range}{pixel_clock_max} *= 10; #- to have it in MHz + } + } + elsif ($flag == 0xf){ + my $range = _get_many_bits($vv, 'manufacturer_specified_range_timing'); + my $e = $edid{detailed_timings}[0]; + my $valid = 1; + foreach my $m ('min', 'max') { + my %total; + foreach my $dir ('horizontal', 'vertical'){ + $range->{$dir . '_sync_pulse_width_' . $m} *= 2; + $range->{$dir . '_back_porch_' . $m} *= 2; + $range->{$dir . '_blanking_' . $m} *= 2; + if ($e && $e->{$dir . '_active'} + && _within_limit($e->{$dir . '_blanking'}, $m, $range->{$dir . '_blanking_' . $m}) + && _within_limit($e->{$dir . '_sync_pulse_width'}, $m, $range->{$dir . '_sync_pulse_width_' . $m}) + && _within_limit($e->{$dir . '_blanking'} - $e->{$dir . '_sync_offset'} - $e->{$dir . '_sync_pulse_width'}, + $m, $range->{$dir . '_back_porch_' . $m})){ + $total{$dir} = $e->{$dir . '_active'} + $range->{$dir . '_blanking_' . $m}; + } + } + if ($total{horizontal} && $total{vertical}){ + my $hfreq = $e->{pixel_clock} * 1000 / $total{horizontal}; + my $vfreq = $hfreq * 1000 / $total{vertical}; + $range->{'horizontal_' . ($m eq 'min' ? 'max' : 'min')} = _round($hfreq); + $range->{'vertical_' . ($m eq 'min' ? 'max' : 'min')} = _round($vfreq); + } + else { + $valid = 0; + } + } + $edid{$valid ? 'monitor_range' : 'manufacturer_specified_range_timing'} = $range; + } + elsif ($flag == 0xfa){ + push @{$edid{standard_timings}}, _add_standard_timing_modes(\%edid, unpack('a12', $vv)); + } + elsif ($flag == 0xfc){ + my $prev = $edid{monitor_name}; + $edid{monitor_name} = ($prev ? "$prev " : '') . unpack('A13', $vv); + } + elsif ($flag == 0xfe){ + push @{$edid{monitor_text}}, unpack('A13', $vv); + } + elsif ($flag == 0xff){ + push @{$edid{serial_number2}}, unpack('A13', $vv); + } + elsif ($vv ne "\0" x 13 && $vv ne " " x 13){ + push(@warnings, "parse_edid: unknown flag $flag"); + warn "$warnings[-1]\n" if $verbose; + } + } + } + } + $edid{$field} = $v if $field && $field !~ /^_/; + } + foreach (@eedid_blocks){ + my ($tag, $v) = unpack("C a*", $_); + if ($tag == 0x02){ # CEA EDID + my $dtd_offset; + ($dtd_offset, $v) = unpack("x C x a*", $v); + next if $dtd_offset < 4; + $dtd_offset -= 4; + while ($dtd_offset > 0){ + if (!$v){ + push(@warnings, "parse_edid: DTD offset outside of available data"); + warn "$warnings[-1]\n" if $verbose; + last; + } + my $h = _get_many_bits($v, 'cea_data_block_collection'); + $dtd_offset -= $h->{size} + 1; + my $vv; + ($vv, $v) = unpack("x a$h->{size} a*", $v); + if ($h->{type} == 0x02){ # Video Data Block + my @vmodes = unpack("a" x $h->{size}, $vv); + foreach my $vmode (@vmodes){ + $h = _get_many_bits($vmode, 'cea_video_data_block'); + my $cea_mode = $cea_video_modes[$h->{mode} - 1]; + if (!$cea_mode){ + push(@warnings, "parse_edid: unhandled CEA mode $h->{mode}"); + warn "$warnings[-1]\n" if $verbose; + next; + } + my %det_mode = (source => 'cea_vdb'); + @det_mode{@cea_video_mode_to_detailed_timing} = @$cea_mode; + push @{$edid{detailed_timings}}, \%det_mode; + } + } + } + while (length($v) >= 18){ + (my $pixel_clock, my $vv, $v) = unpack("v a16 a*", $v); + last if !$pixel_clock; + my $h = _build_detailed_timing($pixel_clock, $vv); + push @{$edid{detailed_timings}}, $h + if $h->{horizontal_active} > 1 && $h->{vertical_active} > 1; + } + } + else { + push(@warnings, "parse_edid: unknown tag $tag"); + warn "$warnings[-1]\n" if $verbose; + } + } + $edid{max_size_precision} = 'cm'; + if ($edid{product_code}){ + $edid{product_code_h} = sprintf('%04x', $edid{product_code}); + if ($edid{manufacturer_name}){ + $edid{EISA_ID} = $edid{manufacturer_name} . $edid{product_code_h}; + } + $edid{product_code_h} = '0x'. $edid{product_code_h}; + } + if ($edid{monitor_range}){ + $edid{HorizSync} = $edid{monitor_range}{horizontal_min} . '-' . $edid{monitor_range}{horizontal_max}; + $edid{VertRefresh} = $edid{monitor_range}{vertical_min} . '-' . $edid{monitor_range}{vertical_max}; + } + if ($edid{max_size_vertical}){ + $edid{ratio} = $edid{max_size_horizontal} / $edid{max_size_vertical}; + $edid{ratio_name} = _ratio_name($edid{max_size_horizontal}, $edid{max_size_vertical}, 'cm'); + $edid{ratio_precision} = 'cm'; + } + if ($edid{feature_support}{has_preferred_timing} && $edid{detailed_timings}[0]){ + $edid{detailed_timings}[0]{preferred} = 1; + } + foreach my $h (@{$edid{detailed_timings}}){ + # EDID standard is ambiguous on how interlaced modes should be + # specified; workaround clearly broken modes: + if ($h->{interlaced}){ + foreach ("720x480", "1440x480", "2880x480", "720x576", "1440x576", "2880x576", "1920x1080"){ + if ($_ eq $h->{horizontal_active} . 'x' . $h->{vertical_active} * 2){ + $h->{vertical_active} *= 2; + $h->{vertical_blanking} *= 2; + $h->{vertical_sync_offset} *= 2; + $h->{vertical_sync_pulse_width} *= 2; + $h->{vertical_blanking} |= 1; + } + } + } + # if the mm size given in the detailed_timing is not far from the cm size + # put it as a more precise cm size + my %in_cm = ( + horizontal => _define($h->{horizontal_image_size}) / 10, + vertical => _define($h->{vertical_image_size}) / 10, + ); + my ($error) = sort { $b <=> $a } map { abs($edid{'max_size_' . $_} - $in_cm{$_}) } keys %in_cm; + if ($error <= 0.5){ + $edid{'max_size_' . $_} = $in_cm{$_} foreach keys %in_cm; + $edid{max_size_precision} = 'mm'; + } + if ($error < 1 && $in_cm{vertical}){ + # using it for the ratio + $edid{ratio} = $in_cm{horizontal} / $in_cm{vertical}; + $edid{ratio_name} = _ratio_name($in_cm{horizontal}, $in_cm{vertical}, 'mm'); + $edid{ratio_precision} = 'mm'; + } + if ($edid{ratio_precision} && + abs($edid{ratio} - $h->{horizontal_active} / $h->{vertical_active}) > ($edid{ratio_precision} eq 'mm' ? 0.02 : 0.2)){ + $h->{bad_ratio} = 1; + } + if ($edid{ratio_name}){ + $edid{ratios} = $edid{ratio_name}; + $edid{ratios} =~ s|/|:|g; + $edid{ratios} = [split(/ or /, $edid{ratios})]; # "3/2 or 16/10" + } + if ($edid{max_size_vertical}){ + $h->{vertical_dpi} = $h->{vertical_active} / $edid{max_size_vertical} * 2.54; + } + if ($edid{max_size_horizontal}){ + $h->{horizontal_dpi} = $h->{horizontal_active} / $edid{max_size_horizontal} * 2.54; + } + if ($h->{horizontal_image_size}){ + $h->{horizontal_image_size_i} = sprintf('%.2f',($h->{horizontal_image_size}/25.4)) + 0; + } + if ($h->{vertical_image_size}){ + $h->{vertical_image_size_i} = sprintf('%.2f',($h->{vertical_image_size}/25.4)) + 0; + } + my $dpi_string = ''; + if ($h->{vertical_dpi} && $h->{horizontal_dpi}){ + $dpi_string = + abs($h->{vertical_dpi} / $h->{horizontal_dpi} - 1) < 0.05 ? + sprintf("%d dpi", $h->{horizontal_dpi}) : + sprintf("%dx%d dpi", $h->{horizontal_dpi}, $h->{vertical_dpi}); + } + my $horizontal_total = $h->{horizontal_active} + $h->{horizontal_blanking}; + my $vertical_total = $h->{vertical_active} + $h->{vertical_blanking}; + no warnings 'uninitialized'; + $h->{ModeLine_comment} = sprintf(qq(# Monitor %s%s modeline (%.1f Hz vsync, %.1f kHz hsync, %sratio %s%s)), + $h->{preferred} ? "preferred" : "supported", + $h->{source} eq 'cea_vdb' ? " CEA" : '', + $h->{pixel_clock} / $horizontal_total / $vertical_total * 1000 * 1000 * ($h->{interlaced} ? 2 : 1), + $h->{pixel_clock} / $horizontal_total * 1000, + $h->{interlaced} ? "interlaced, " : '', + _nearest_ratio($h->{horizontal_active} / $h->{vertical_active}, 0.01) || sprintf("%.2f", $h->{horizontal_active} / $h->{vertical_active}), + $dpi_string ? ", $dpi_string" : ''); + + $h->{ModeLine} = sprintf(qq("%dx%d" $h->{pixel_clock} %d %d %d %d %d %d %d %d %shsync %svsync%s), + $h->{horizontal_active}, $h->{vertical_active}, + $h->{horizontal_active}, + $h->{horizontal_active} + $h->{horizontal_sync_offset}, + $h->{horizontal_active} + $h->{horizontal_sync_offset} + $h->{horizontal_sync_pulse_width}, + $horizontal_total, + $h->{vertical_active}, + $h->{vertical_active} + $h->{vertical_sync_offset}, + $h->{vertical_active} + $h->{vertical_sync_offset} + $h->{vertical_sync_pulse_width}, + $vertical_total, + $h->{horizontal_sync_positive} ? '+' : '-', + $h->{vertical_sync_positive} ? '+' : '-', + $h->{interlaced} ? ' Interlace' : ''); + } + $edid{diagonal_size} = sqrt(_sqr($edid{max_size_horizontal}) + _sqr($edid{max_size_vertical})) / 2.54; + # we want to use null data found tests so only return errors/warnings if + # %edid or if verbose, since then we want to know no matter what. + if (%edid || $verbose){ + _edid_errors(\%edid); + $edid{edid_warnings} = \@warnings if @warnings; + } + eval $end if $b_log; + \%edid; +} + +sub _edid_errors { + my $edid = shift @_; + if (!defined $edid->{edid_version}){ + _edid_error($edid,'edid-version','undefined'); + } + elsif ($edid->{edid_version} < 1 || $edid->{edid_version} > 2){ + _edid_error($edid,'edid-version',$edid->{edid_version}); + } + if (!defined $edid->{edid_revision}){ + _edid_error($edid,'edid-revision','undefined'); + } + elsif ($edid->{edid_revision} == 0xff){ + _edid_error($edid,'edid-revision',$edid->{edid_revision}); + } + if ($edid->{monitor_range}){ + if (!$edid->{monitor_range}{horizontal_min}){ + _edid_error($edid,'edid-sync','no horizontal'); + } + elsif ($edid->{monitor_range}{horizontal_min} > $edid->{monitor_range}{horizontal_max}){ + _edid_error($edid,'edid-sync', + "bad horizontal values: min: $edid->{monitor_range}{horizontal_min} max: $edid->{monitor_range}{horizontal_max}"); + } + if (!$edid->{monitor_range}{vertical_min}){ + _edid_error($edid,'edid-sync','no vertical'); + } + elsif ($edid->{monitor_range}{vertical_min} > $edid->{monitor_range}{vertical_max}){ + _edid_error($edid,'edid-sync', + "bad vertical values: min: $edid->{monitor_range}{vertical_min} max: $edid->{monitor_range}{vertical_max}"); + } + } +} + +sub _edid_error { + my ($edid,$error,$data) = @_; + $edid->{edid_errors} = [] if !$edid->{edid_errors}; + push(@{$edid->{edid_errors}},main::message($error,$data)); +} + +sub _nearest_ratio { + my ($ratio, $max_error) = @_; + my @sorted = + sort { $a->[1] <=> $b->[1] } + map { + my $error = abs($ratio - eval($_)); + $error > $max_error ? () : [ $_, $error ]; + } @known_ratios; + $sorted[0][0]; +} + +sub _ratio_name { + my ($horizontal, $vertical, $precision) = @_; + if ($precision eq 'mm'){ + _nearest_ratio($horizontal / $vertical, 0.1); + } + else { + my $error = 0.5; + my $ratio1 = _nearest_ratio(($horizontal + $error) / ($vertical - $error), 0.2); + my $ratio2 = _nearest_ratio(($horizontal - $error) / ($vertical + $error), 0.2); + $ratio1 && $ratio2 or return; + if ($ratio1 eq $ratio2){ + $ratio1; + } + else { + my $ratio = _nearest_ratio($horizontal / $vertical, 0.2); + join(' or ', $ratio, $ratio eq $ratio1 ? $ratio2 : $ratio1); + } + } +} + +sub _define { + defined $_[0] ? $_[0] : 0; +} + +sub _sqr { + $_[0] * $_[0]; +} + +sub _round { + int($_[0] + 0.5); +} +} + +## PartitionData - set/get +# for /proc/partitions only, see DiskDataBSD for BSD partition data. +{ +package PartitionData; + +sub set { + my ($type) = @_; + $loaded{'partition-data'} = 1; + if (my $file = $system_files{'proc-partitions'}){ + proc_data($file); + } +} + +# args: 0: partition name, without /dev, like sda1, sde +sub get { + eval $start if $b_log; + my $item = $_[0]; + return if !@proc_partitions; + my $result; + foreach my $device (@proc_partitions){ + if ($device->[3] eq $item){ + $result = $device; + last; + } + } + eval $start if $b_log; + return ($result) ? $result : []; +} + +sub proc_data { + eval $start if $b_log; + my $file = $_[0]; + if ($fake{'partitions'}){ + # $file = "$fake_data_dir/block-devices/proc-partitions/proc-partitions-1.txt"; + } + my @parts = main::reader($file,'strip'); + # print Data::Dumper::Dumper \@parts; + shift @parts if @parts; # get rid of headers + for (@parts){ + my @temp = split(/\s+/, $_); + next if !defined $temp[2]; + push (@proc_partitions,[$temp[0],$temp[1],$temp[2],$temp[3]]); + } + eval $end if $b_log; +} +} + +# args: 0: pci device string; 1: pci cleaned subsystem string +sub get_pci_vendor { + eval $start if $b_log; + my ($device, $subsystem) = @_; + return if !$subsystem; + my ($vendor,$sep) = ('',''); + # get rid of any [({ type characters that will make regex fail + # and similar matches show as non-match + my @data = split(/\s+/, clean_regex($subsystem)); + foreach my $word (@data){ + # AMD Tahiti PRO [Radeon HD 7950/8950 OEM / R9 280] + # PC Partner Limited / Sapphire Technology Tahiti PRO [Radeon HD 7950/8950 OEM / R9 280] + # $word =~ s/(\+|\$|\?|\^|\*)/\\$1/g; + if (length($word) == 1 || $device !~ m|\b\Q$word\E\b|i){ + $vendor .= $sep . $word; + $sep = ' '; + } + else { + last; + } + } + # just in case we had a standalone last character after done + $vendor =~ s| [/\(\[\{a\.,-]$|| if $vendor; + eval $end if $b_log; + return $vendor; +} + +# $rows, $num by ref. +sub get_pcie_data { + eval $start if $b_log; + my ($bus_id,$j,$rows,$num,$type) = @_; + $type ||= ''; + # see also /sys/class/drm/ + my $path_start = '/sys/bus/pci/devices/0000:'; + return if !$bus_id || ! -d $path_start . $bus_id; + $path_start .= $bus_id; + my $path = $path_start . '/{max_link_width,current_link_width,max_link_speed'; + $path .= ',current_link_speed}'; + my @files = globber($path); + if ($type eq 'gpu'){ + $path = $path_start . '/0000*/0000*/{mem_info_vram_used,mem_info_vram_total}'; + push(@files,globber($path)); + } + # print @files,"\n"; + return if !@files; + my (%data,$name); + my %gen = ( + '2.5 GT/s' => 1, + '5 GT/s' => 2, + '8 GT/s' => 3, + '16 GT/s' => 4, + '32 GT/s' => 5, + '64 GT/s' => 6, + ); + foreach (@files){ + if (-r $_){ + $name = $_; + $name =~ s|^/.*/||; + $data{$name} = reader($_,'strip',0); + if ($name eq 'max_link_speed' || $name eq 'current_link_speed'){ + $data{$name} =~ s/\.0\b| PCIe$//g; # trim .0 off in 5.0, 8.0 + } + } + } + # print Data::Dumper::Dumper \%data; + # Maximum PCIe Bandwidth = SPEED * WIDTH * (1 - ENCODING) - 1Gb/s. + if ($data{'current_link_speed'} && $data{'current_link_width'}){ + $$rows[$j]->{key($$num++,1,2,'pcie')} = ''; + if ($b_admin && $gen{$data{'current_link_speed'}}){ + $$rows[$j]{key($$num++,0,3,'gen')} = $gen{$data{'current_link_speed'}}; + } + $$rows[$j]{key($$num++,0,3,'speed')} = $data{'current_link_speed'}; + $$rows[$j]->{key($$num++,0,3,'lanes')} = $data{'current_link_width'}; + if ($b_admin && (($data{'max_link_speed'} && + $data{'max_link_speed'} ne $data{'current_link_speed'}) || + ($data{'max_link_width'} && + $data{'max_link_width'} ne $data{'current_link_width'}))){ + $$rows[$j]->{key($$num++,1,3,'link-max')} = ''; + if ($data{'max_link_speed'} && + $data{'max_link_speed'} ne $data{'current_link_speed'}){ + $$rows[$j]{key($$num++,0,4,'gen')} = $gen{$data{'max_link_speed'}}; + $$rows[$j]->{key($$num++,0,4,'speed')} = $data{'max_link_speed'}; + } + if ($data{'max_link_width'} && + $data{'max_link_width'} ne $data{'current_link_width'}){ + $$rows[$j]->{key($$num++,0,4,'lanes')} = $data{'max_link_width'}; + } + } + } + if ($type eq 'gpu' && $data{'mem_info_vram_used'} && $data{'mem_info_vram_total'}){ + $$rows[$j]->{key($$num++,1,2,'vram')} = ''; + $$rows[$j]->{key($$num++,0,3,'total')} = get_size($data{'mem_info_vram_total'}/1024,'string'); + my $used = get_size($data{'mem_info_vram_used'}/1024,'string'); + $used .= ' (' . sprintf('%0.1f',($data{'mem_info_vram_used'}/$data{'mem_info_vram_total'}*100)) . '%)'; + $$rows[$j]->{key($$num++,0,3,'used')} = $used; + + } + eval $end if $b_log; +} + +sub set_ps_aux { + eval $start if $b_log; + my ($header,$ps,@temp); + # note: some ps cut off output based on terminal width + # ww sets width unlimited + $loaded{'ps-aux'} = 1; + $ps = grabber("ps wwaux 2>/dev/null",'','strip','ref'); + if (@$ps){ + $header = shift @$ps; # get rid of header row + # handle busy box, which has 3 columns, regular ps aux has 11 + # avoid deprecated implicit split error in older Perls + @temp = split(/\s+/, $header); + } + $ps_cols = $#temp; # the indexes, not the scalar count + if ($ps_cols < 10){ + my $version = qx(ps --version 2>&1); + $b_busybox_ps = 1 if $version =~ /busybox/i; + } + return if !@$ps; # note: mips/openwrt ps has no 'a' + for (@$ps){ + next if !$_; + next if $self_name eq 'inxi' && /\/$self_name\b/; + $_ = lc; + push (@ps_aux,$_); + my @split = split(/\s+/, $_); + # slice out 10th to last elements of ps aux rows + my $final = $#split; + # some stuff has a lot of data, chrome for example + $final = ($final > ($ps_cols + 2)) ? $ps_cols + 2 : $final; + # handle case of ps wrapping lines despite ww unlimited width, which + # should NOT be happening, but is. + next if !defined $split[$ps_cols]; + if ($split[$ps_cols] !~ /^\[/){ + push(@ps_cmd,join(' ', @split[$ps_cols .. $final])); + } + } + # never, because ps loaded before option handler + # print Dumper \@ps_cmd; # if $dbg[5]; + eval $end if $b_log; +} + +sub set_ps_gui { + eval $start if $b_log; + $loaded{'ps-gui'} = 1; + my ($b_wl,$working,@match,@temp); + # desktops / wm (some wm also compositors) + if ($show{'system'}){ + @temp=qw(razor-desktop razor-session lxsession lxqt-session + tdelauncher tdeinit_phase1); + push(@match,@temp); + @temp=qw(2bwm 3dwm 9wm afterstep aewm aewm\+\+ amiwm antiwm awesome + blackbox bspwm calmwm catwm cde (sh|c?lisp).*clfswm ctwm (openbsd-)?cwm + dwm evilwm + fluxbox flwm flwm_topside fvwm.*-crystal fvwm1 fvwm2 fvwm3 fvwm95 fvwm + herbstluftwm i3 icewm instantwm ion3 jbwm jwm larswm leftwm lwm + matchbox-window-manager mcwm mini monsterwm musca mwm nawm notion + openbox nscde pekwm penrose python.*qtile qvwm ratpoison + sawfish scrotwm snapwm spectrwm (sh|c?lisp).*stumpwm + tinywm tvtwm twm uwm windowlab WindowMaker wingo wm2 wmfs wmfs2 wmii2 wmii + wmx xfdesktop xmonad yeahwm); + push(@match,@temp); + $b_wl = 1; + } + # wm: note that for all but the listed wm, the wm and desktop would be the + # same, particularly with all smaller wayland wm/compositors. + if ($show{'system'} && $extra > 1){ + @temp=qw(budgie-wm compiz deepin-wm gala gnome-shell + twin kwin_wayland kwin_x11 kwinft kwin marco + deepin-metacity metacity metisse mir muffin deepin-mutter mutter + ukwm xfwm[45]?); + push(@match,@temp); + # startx: /bin/sh /usr/bin/startx + @temp=qw(ly .*startx xinit); # possible dm values + push(@match,@temp); + } + # info: NOTE: glx-dock is cairo-dock + if ($show{'system'} && $extra > 2){ + @temp=qw(alltray awn bar bmpanel bmpanel2 budgie-panel + cairo-dock dde-dock dmenu dockbarx docker docky dzen dzen2 + fbpanel fspanel glx-dock gnome-panel hpanel i3bar i3-status(-rs)? icewmtray + kdocker kicker latte latte-dock lemonbar ltpanel luastatus lxpanel lxqt-panel + matchbox-panel mate-panel nwg-bar nwg-dock nwg-panel ourico + perlpanel plank plasma-desktop plasma-netbook polybar pypanel + razor-panel razorqt-panel rootbar + sfwbar stalonetray swaybar taskbar tint2 trayer + ukui-panel vala-panel wapanel waybar wbar wharf wingpanel witray + xfce[45]?-panel xmobar yambar yabar); + push(@match,@temp); + } + # compositors (for wayland these are also the server, note. + # for wayland always show, so always load these + if ($show{'graphic'}){ + @temp=qw(3dwm budgie-wm cairo compiz compton cosmic-comp deepin-wm dcompmgr + enlightenment gala gnome-shell kmscon kwin_wayland kwin_x11 kwinft kwin + marco metisse mir moblin muffin mutter picom steamcompmgr + ukwm unagi unity-system-compositor wayland xcompmgr xfwm[45]?); + push(@match,@temp); + $b_wl = 1; + } + uniq(\@match); + my $matches = join('|', @match); + if ($b_wl){ + # wayland compositors generally are compositors and wm. + # These will be used globally to avoid having to redo it over and over. + $wl_compositors = '|' . join('|',qw(asc awc + cage cagebreak cardboard chameleonwm clayland comfc + dwc dwl epd-wm fireplace feathers fenestra glass gamescope greenfield grefson + hikari hopalong hyprland inaban japokwm kiwmi labwc laikawm lipstick liri + mahogany marina maze motorcar newm nucleus orbital perceptia phoc pywm qtile + river rootston rustland simulavr skylight smithay sommelier sway swc swvkc + tabby taiwins tinybox tinywl trinkster velox vimway vivarium + wavy waybox way-?cooler wayfire wayhouse waymonad westeros westford + weston wio\+? wxr[cd] xuake)); + $matches .= $wl_compositors; + } + $matches = qr/$matches/; # remember qr/../i only added perl 5.014 + foreach (@ps_cmd){ + if (/^(|[\S]*\/)($matches)(\/|\s|$)/){ + $working = $2; + push(@ps_gui, $working); # deal with duplicates with uniq + } + } + uniq(\@ps_gui) if @ps_gui; + print Dumper \@ps_gui if $dbg[5]; + log_data('dump','@ps_gui',\@ps_gui) if $b_log; + eval $end if $b_log; +} + +sub get_self_version { + eval $start if $b_log; + my $patch = $self_patch; + if ($patch ne ''){ + # for cases where it was for example: 00-b1 clean to -b1 + $patch =~ s/^[0]+-?//; + $patch = "-$patch" if $patch; + } + eval $end if $b_log; + return $self_version . $patch; +} + +## ServiceData +{ +package ServiceData; +my ($key,$service,$type); + +sub get { + eval $start if $b_log; + ($type,$service) = @_; + my $value; + set() if !$loaded{'service-tool'}; + $key = (keys %service_tool)[0] if %service_tool; + if ($key){ + if ($type eq 'status'){ + $value = process_status(); + } + elsif ($type eq 'tool'){ + $value = $service_tool{$key}->[1]; + } + } + eval $end if $b_log; + return $value; +} + +sub process_status { + eval $start if $b_log; + my ($cmd,$status,@data); + my ($result,$value) = ('',''); + my %translate = ( + 'active' => 'running', + 'down' => 'stopped', + 'fail' => 'not found', + 'failed' => 'not found', + 'inactive' => 'stopped', + 'ok' => 'running', + 'not running' => 'stopped', + 'run' => 'running', + 'started' => 'running', + ); + if ($key eq 'systemctl'){ + $cmd = "$service_tool{$key}->[0] status $service"; + } + # can be /etc/init.d or /etc/rc.d; ghostbsd/gentoo have this + elsif ($key eq 'rc-service'){ + $cmd = "$service_tool{$key}->[0] $service status"; + } + elsif ($key eq 'rcctl'){ + $cmd = "$service_tool{$key}->[0] check $service"; + } + # dragonfly/netbsd/freebsd have this. We prefer service over following since + # if it is present, the assumption is that it is being used, though multi id + # is probably better. + elsif ($key eq 'service'){ + $cmd = "$service_tool{$key}->[0] $service status"; + } + # upstart, legacy, and finit, needs more data + elsif ($key eq 'initctl' || $key eq 'dinitctl'){ + $cmd = "$service_tool{$key}->[0] status $service"; + } + # runit + elsif ($key eq 'sv'){ + $cmd = "$service_tool{$key}->[0] status $service"; + } + # s6: note, shows s6-rc but uses s6-svstat; -n makes human-readable. Needs + # real data samples before adding. + # elsif ($key eq 's6-rc'){ + # $cmd = "$service_tool{$key}->[0] $service"; + # } + # check or status or onestatus (netbsd) + elsif ($key eq 'rc.d'){ + if (-e "$service_tool{$key}->[0]$service"){ + $status = ($bsd_type && $bsd_type =~ /(dragonfly)/) ? 'status' : 'check'; + $cmd = "$service_tool{$key}->[0]$service check"; + } + else { + $result = 'not found'; + } + } + elsif ($key eq 'init.d'){ + if (-e "$service_tool{$key}->[0]$service"){ + $cmd = "$service_tool{$key}->[0]$service status"; + } + else { + $result = 'not found'; + } + } + @data = main::grabber("$cmd 2>&1",'','strip') if $cmd; + # @data = ('bluetooth is running.'); + print "key: $key\n", Data::Dumper::Dumper \@data if $dbg[29]; + main::log_data('dump','service @data',\@data) if $b_log; + for my $row (@data){ + my @working = split(/\s*:\s*/,$row); + ($value) = (''); + # print "$working[0]::$working[1]\n"; + # Loaded: masked (Reason: Unit sddm.service is masked.) + if ($working[0] eq 'Loaded'){ + # note: sshd shows ssh for ssh.service + $working[1] =~ /^(.+?)\s*\(.*?\.service;\s+(\S+?);.*/; + $result = lc($1) if $1; + $result = lc($2) if $2; # this will be enabled/disabled + } + # Active: inactive (dead) + elsif ($working[0] eq 'Active'){ + $working[1] =~ /^(.+?)\s*\((\S+?)\).*/; + $value = lc($1) if $1 && (!$result || $result ne 'disabled'); + $value = $translate{$value} if $value && $translate{$value}; + $result .= ",$value" if ($result && $value); + last; + } + # Status : running + elsif ($working[0] eq 'Status' || $working[0] eq 'State'){ + $result = lc($working[1]); + $result = $translate{$result} if $translate{$result}; + last; + } + # valid syntax, but service does not exist + # * rc-service: service 'ntp' does not exist :: + # dinitctl: service not loaded [whether exists or not] + elsif ($row =~ /$service.*?(not (exist|(be )?found|loaded)|no such (directory|file)|unrecognized)/i){ + $result = 'not found'; + last; + } + # means command directive doesn't exist, we don't know if service exists or not + # * ntpd: unknown function 'disable' :: + elsif ($row =~ /unknown (directive|function)|Usage/i){ + last; + } + # rc-service: * status: started :: * status: stopped, fail handled in not exist test + elsif ($working[0] eq '* status' && $working[1]){ + $result = lc($working[1]); + $result = $translate{$result} if $translate{$result}; + last; + } + ## start exists status detections + elsif ($working[0] =~ /\b$service is ([a-z\s]+?)(\s+as\s.*|\s+\.\.\..*)?\.?$/){ + $result = lc($1); + $result = $translate{$result} if $translate{$result}; + last; + } + # runit sv: run/down/fail - fail means not found + # run: udevd: (pid 631) 641s :: down: sshd: 9s, normally up + elsif ($working[1] && $working[1] eq $service && $working[0] =~ /^([a-z]+)$/){ + $result = lc($1); + $result = $translate{$result} if $translate{$result}; + $result = "enabled,$result" if $working[2] && $working[2] =~ /normally up/i; + } + # OpenBSD: sshd(ok) + elsif ($working[0] =~ /\b$service\s*\(([^\)]+)\)/){ + $result = lc($1); + $result = $translate{$result} if $translate{$result}; + last; + } + } + print "service result: $result\n" if $dbg[29]; + main::log_data('data',"result: $result") if $b_log; + eval $end if $b_log; + return $result; +} + +sub set { + eval $start if $b_log; + $loaded{'service-tool'} = 1; + my ($path); + if ($path = main::check_program('systemctl')){ + # systemctl status ssh :: Loaded: / Active: + %service_tool = ('systemctl' => [$path,'systemctl']); + } + elsif ($path = main::check_program('rc-service')){ + # rc-service ssh status :: * status: stopped + %service_tool = ('rc-service' => [$path,'rc-service']); + } + elsif ($path = main::check_program('rcctl')){ + # rc-service ssh status :: * status: stopped + %service_tool = ('rcctl' => [$path,'rcctl']); + } + elsif ($path = main::check_program('service')){ + # service sshd status + %service_tool = ('service' => [$path,'service']); + } + elsif ($path = main::check_program('sv')){ + %service_tool = ('sv' => [$path,'sv']); + } + # needs data, never seen output, but report if present + elsif ($path = main::check_program('s6-svstat')){ + %service_tool = ('s6-rc' => [$path,'s6-rc']); + } + elsif ($path = main::check_program('dinitctl')){ + %service_tool = ('dinitctl' => [$path,'dinitctl']); + } + # make it last in tools, need more data + elsif ($path = main::check_program('initctl')){ + %service_tool = ('initctl' => [$path,'initctl']); + } + # freebsd does not have 'check', netbsd does not have status + elsif (-d '/etc/rc.d/'){ + # /etc/rc.d/ssh check :: ssh(ok|failed) + %service_tool = ('rc.d' => ['/etc/rc.d/','/etc/rc.d']); + } + elsif (-d '/etc/init.d/'){ + # /etc/init.d/ssh status :: Loaded: loaded (...)/ Active: active (...) + %service_tool = ('init.d' => ['/etc/init.d/','/etc/init.d']); + } + eval $end if $b_log; +} +} +# $dbg[29] = 1; set_path(); print ServiceData::get('status','bluetooth'),"\n"; + +## ShellData +{ +package ShellData; +my $b_debug = 0; # disable all debugger output in case forget to comment out! + +# Public. This does not depend on using ps -jfp, open/netbsd do not at this +# point support it, so we only want to use -jp to get parent $ppid set in +# initialize(). shell_launcher will use -f so it only runs in case we got +# $pppid. $client{'pppid'} will be used to trigger launcher tests. If started +# with sshd via ssh user@address 'pinxi -Ia' will show sshd as shell, which is +# fine, that's what it is. +sub set { + eval $start if $b_log; + my ($cmd,$parent,$pppid,$shell); + $loaded{'shell-data'} = 1; + $cmd = "ps -wwp $ppid -o comm= 2>/dev/null"; + $shell = qx($cmd); + # we'll be using these $client pppid/parent values in shell_launcher() + $pppid = $client{'pppid'} = get_pppid($ppid); + $pppid ||= ''; + $client{'pppid'} ||= ''; + # print "sh: $shell\n"; + main::log_data('cmd',$cmd) if $b_log; + chomp($shell); + if ($shell){ + # print "shell pre: $shell\n"; + # when run in debugger subshell, would return sh as shell, + # and parent as perl, that is, pinxi itself, which is actually right. + # trim leading /.../ off just in case. ps -p should return the name, not path + # but at least one user dataset suggests otherwise so just do it for all. + $shell =~ s/^.*\///; + # NOTE: su -c "inxi -F" results in shell being su + # but: su - results in $parent being su + my $i=0; + $parent = $client{'parent'} = parent_name($pppid) if $pppid; + $parent ||= ''; + print "1: shell: $shell $ppid parent: $parent $pppid\n" if $b_debug; + # this will fail in this case: sudo su -c 'inxi -Ia' + if ($shell =~ /^(doas|login|sudo|su)$/){ + $client{'su-start'} = $shell if $shell ne 'login'; + $shell = $parent if $parent; + } + # eg: su to root, then sudo + elsif ($parent && $client{'parent'} =~ /^(doas|sudo|su)$/){ + $client{'su-start'} = $parent; + $parent = ''; + } + print "2: shell: $shell parent: $parent\n" if $b_debug; + my $working = $ENV{'SHELL'}; + if ($working){ + $working =~ s/^.*\///; + # a few manual changes for known + # Note: parent when fizsh shows as zsh but SHELL is fizsh, but other times + # SHELL is default shell, but in zsh, SHELL is default shell, not zfs + if ($shell eq 'zsh' && $working eq 'fizsh'){ + $shell = $working; + } + } + # print "3: shell post: $shell working: $working\n"; + # since there are endless shells, we'll keep a list of non program value + # set shells since there is little point in adding those to program values + if (shell_test($shell)){ + # do nothing, just leave $shell as is + } + # note: not all programs return version data. This may miss unhandled shells! + elsif ((@app = main::program_data(lc($shell),lc($shell),1)) && $app[0]){ + $shell = $app[0]; + $client{'version'} = $app[1] if $app[1]; + print "3: app test $shell v: $client{'version'}\n" if $b_debug; + } + else { + # NOTE: we used to guess here with position 2 --version but this cuold lead + # to infinite loops when inxi called from a script 'infos' that is in PATH and + # script does not have any start arg handlers or bad arg handlers: + # eg: shell -> infos -> inxi -> sh -> infos --version -> infos -> inxi... + # Basically here we are hoping that the grandparent is a shell, or at least + # recognized as a known possible program + # print "app not shell?: $shell\n"; + if ($shell){ + print "shell 4: $shell StartClientVersionType: $parent\n" if $b_debug; + if ($parent){ + if (shell_test($parent)){ + $shell = $parent; + } + elsif ((@app = main::program_data(lc($parent),lc($parent),0)) && $app[0]){ + $shell = $app[0]; + $client{'version'} = $app[1] if $app[1]; + } + print "shell 5: $shell version: $client{'version'}\n" if $b_debug; + } + } + else { + $client{'version'} = main::message('unknown-shell'); + } + print "6: shell not app version: $client{'version'}\n" if $b_debug; + } + $client{'version'} ||= ''; + $client{'version'} =~ s/(\(.*|-release|-version)// if $client{'version'}; + $shell =~ s/^[\s-]+|[\s-]+$//g if $shell; # sometimes will be like -sh + $client{'name'} = lc($shell); + $client{'name-print'} = $shell; + print "7: shell: $client{'name-print'} version: $client{'version'}\n" if $b_debug; + if ($extra > 2 && $working && lc($shell) ne lc($working)){ + if (@app = main::program_data(lc($working))){ + $client{'default-shell'} = $app[0]; + $client{'default-shell-v'} = $app[1]; + $client{'default-shell-v'} =~ s/(\s*\(.*|-release|-version)// if $client{'default-shell-v'}; + } + else { + $client{'default-shell'} = $working; + } + } + } + else { + # last fallback to catch things like busybox shells + if (my $busybox = readlink(main::check_program('sh'))){ + if ($busybox =~ m|busybox$|){ + $client{'name'} = 'ash'; + $client{'name-print'} = 'ash (busybox)'; + } + } + print "8: shell: $client{'name-print'} version: $client{'version'}\n" if $b_debug; + if (!$client{'name'}) { + $client{'name'} = 'shell'; + # handling na here, not on output, so we can test for !$client{'name-print'} + $client{'name-print'} = 'N/A'; + } + } + if (!$client{'su-start'}){ + $client{'su-start'} = 'sudo' if $ENV{'SUDO_USER'}; + $client{'su-start'} = 'doas' if $ENV{'DOAS_USER'}; + } + if ($parent && $parent eq 'login'){ + $client{'su-start'} = ($client{'su-start'}) ? $client{'su-start'} . ',' . $parent: $parent; + } + eval $end if $b_log; +} + +# Public: returns shell launcher, terminal, program, whatever +# depends on $pppid so only runs if that is set. +sub shell_launcher { + eval $start if $b_log; + my (@data); + my ($msg,$pppid,$shell_parent) = ('','',''); + $pppid = $client{'pppid'}; + if ($b_log){ + $msg = ($ppid) ? "pppid: $pppid ppid: $ppid": "ppid: undefined"; + main::log_data('data',$msg); + } + # print "self parent: $pppid ppid: $ppid\n"; + if ($pppid){ + $shell_parent = $client{'parent'}; + # print "shell parent 1: $shell_parent\n"; + if ($b_log){ + $msg = ($shell_parent) ? "shell parent 1: $shell_parent": "shell parent 1: undefined"; + main::log_data('data',$msg); + } + # in case sudo starts inxi, parent is shell (or perl inxi if run by debugger) + # so: perl (2) started pinxi with sudo (3) in sh (4) in terminal + my $shells = 'ash|bash|busybox|cicada|csh|dash|doas|elvish|fish|fizsh|ksh|'; + $shells .= 'ksh93|lksh|login|loksh|mksh|nash|oh|oil|osh|pdksh|perl|posh|'; + $shells .= 'su|sudo|tcsh|xonsh|yash|zsh'; + $shells .= shell_test('return'); + my $i = 0; + print "self::pppid-0: $pppid :: $shell_parent\n" if $b_debug; + # note that new shells not matched will keep this loop spinning until it ends. + # All we really can do about that is update with new shell name when we find them. + while ($i < 8 && $shell_parent && $shell_parent =~ /^($shells)$/){ + # bash > su > parent + $i++; + $pppid = get_pppid($pppid); + $shell_parent = parent_name($pppid); + print "self::pppid-${i}: $pppid :: $shell_parent\n" if $b_debug; + if ($b_log){ + $msg = ($shell_parent) ? "parent-$i: $shell_parent": "shell parent $i: undefined"; + main::log_data('data',$msg); + } + } + } + if ($b_log){ + $pppid ||= ''; + $shell_parent ||= ''; + main::log_data('data',"parents: pppid: $pppid parent-name: $shell_parent"); + } + eval $end if $b_log; + return $shell_parent; +} + +# args: 0: parent id +# returns SID/start ID +sub get_pppid { + eval $start if $b_log; + my ($ppid) = @_; + return 0 if !$ppid; + # ps -j -fp : some bsds ps do not have -f for PPID, so we can't get the ppid + my $cmd = "ps -wwjfp $ppid 2>/dev/null"; + main::log_data('cmd',$cmd) if $b_log; + my @data = main::grabber($cmd); + # shift @data if @data; + my $pppid = main::awk(\@data,"$ppid",3,'\s+'); + eval $end if $b_log; + return $pppid; +} + +# args: 0: parent id +# returns parent command name +sub parent_name { + eval $start if $b_log; + my ($ppid) = @_; + return '' if !$ppid; + my ($parent_name); + my $cmd = "ps -wwjp $ppid 2>/dev/null"; + main::log_data('cmd',$cmd) if $b_log; + my @data = main::grabber($cmd,'','strip'); + # dump the headers if they exist + $parent_name = (grep {/$ppid/} @data)[0] if @data; + if ($parent_name){ + # we don't want to worry about column position, just slice off all + # the first part before the command + $parent_name =~ s/^.*[0-9]+:[0-9\.]+\s+//; + # then get the command + $parent_name = (split(/\s+/,$parent_name))[0]; + # get rid of /../ path info if present + $parent_name =~ s|^.*/|| if $parent_name; + # to work around a ps -p or gnome-terminal bug, which returns + # gnome-terminal- trim -/_ off start/end; _su, etc, which breaks detections + $parent_name =~ s/^[_-]|[_-]$//g; + } + eval $end if $b_log; + return $parent_name; +} + +# List of program_values non-handled shells, or known to have no version +# Move shell to set_program_values for print name, or version if available +# args: 0: return|[shell name to test +# returns test list OR shell name/'' +sub shell_test { + my ($test) = @_; + # these shells are not verified or tested + my $shells = 'apush|ccsh|ch|esh?|eshell|heirloom|hush|'; + $shells .= 'ion|imrsh|larryshell|mrsh|msh(ell)?|murex|nsh|nu(shell)?|'; + $shells .= 'oksh|psh|pwsh|pysh(ell)?|rush|sash|xsh?|'; + # these shells are tested and have no version info + $shells .= 'es|rc|scsh|sh'; + return '|' . $shells if $test eq 'return'; + return ($test =~ /^($shells)$/) ? $test : ''; +} + +# This will test against default IP like: (:0) vs full IP to determine +# ssh status. Surprisingly easy test? Cross platform +sub ssh_status { + eval $start if $b_log; + my ($b_ssh,$ssh); + # fred pts/10 2018-03-24 16:20 (:0.0) + # fred-remote pts/1 2018-03-27 17:13 (43.43.43.43) + if (my $program = main::check_program('who')){ + $ssh = (main::grabber("$program am i 2>/dev/null"))[0]; + # crude IP validation, v6 ::::::::, v4 x.x.x.x + if ($ssh && $ssh =~ /\(([:0-9a-f]{8,}|[1-9][\.0-9]{6,})\)$/){ + $b_ssh = 1; + } + } + eval $end if $b_log; + return $b_ssh; +} + +# If IRC: called if root for -S, -G, or if not in display for user. +sub console_irc_tty { + eval $start if $b_log; + $loaded{'con-irc-tty'} = 1; + # not set for root in or out of display + if (defined $ENV{'XDG_VTNR'}){ + $client{'con-irc-tty'} = $ENV{'XDG_VTNR'}; + } + else { + # ppid won't work with name, so this is assuming there's only one client running + # if in display, -G returns vt size, not screen dimensions in rowsxcols. + $client{'con-irc-tty'} = main::awk(\@ps_aux,'.*\b' . $client{'name'} . '\b.*',7,'\s+'); + $client{'con-irc-tty'} =~ s/^(tty|\?)// if defined $client{'con-irc-tty'}; + } + $client{'con-irc-tty'} = '' if !defined $client{'con-irc-tty'}; + main::log_data('data',"console-irc-tty:$client{'con-irc-tty'}") if $b_log; + eval $end if $b_log; +} + +sub tty_number { + eval $start if $b_log; + $loaded{'tty-number'} = 1; + # note: ttyname returns undefined if pinxi is > redirected output + # variants: /dev/pts/1 /dev/tty1 /dev/ttyp2 /dev/ttyra [hex number a] + $client{'tty-number'} = POSIX::ttyname(1); + # but tty direct works fine in that case + if (!defined $client{'tty-number'} && (my $program = main::check_program('tty'))){ + chomp($client{'tty-number'} = qx($program 2>/dev/null)); + if (defined $client{'tty-number'} && $client{'tty-number'} =~ /^not/){ + undef $client{'tty-number'}; + } + } + if (defined $client{'tty-number'}){ + $client{'tty-number'} =~ s/^\/dev\/(tty)?//; + } + else { + $client{'tty-number'} = ''; + } + # systemd only item, usually same as tty in console, not defined + # for root or non systemd systems. + if (defined $ENV{'XDG_VTNR'} && $client{'tty-number'} ne '' && + $ENV{'XDG_VTNR'} ne $client{'tty-number'}){ + $client{'tty-number'} = "$client{'tty-number'} (vt $ENV{'XDG_VTNR'})"; + } + elsif ($client{'tty-number'} eq '' && defined $ENV{'XDG_VTNR'}){ + $client{'tty-number'} = $ENV{'XDG_VTNR'}; + } + main::log_data('data',"tty:$client{'tty-number'}") if $b_log; + eval $end if $b_log; +} +} + +sub set_sysctl_data { + eval $start if $b_log; + return if !$alerts{'sysctl'} || $alerts{'sysctl'}->{'action'} ne 'use'; + my (@temp); + # darwin sysctl has BOTH = and : separators, and repeats data. Why? + if (!$fake{'sysctl'}){ + # just on odd chance we hit a bsd with /proc/cpuinfo, don't want to + # sleep 2x + if ($use{'bsd-sleep'} && !$system_files{'proc-cpuinfo'}){ + if ($b_hires){ + eval 'Time::HiRes::usleep($sleep)'; + } + else { + select(undef, undef, undef, $cpu_sleep); + } + } + @temp = grabber($alerts{'sysctl'}->{'path'} . " -a 2>/dev/null"); + } + else { + my $file; + # $file = "$fake_data_dir/bsd/sysctl/obsd_6.1_sysctl_soekris6501_root.txt"; + # $file = "$fake_data_dir/bsd/sysctl/obsd_6.1sysctl_lenovot500_user.txt"; + ## matches: compaq: openbsd-dmesg.boot-1.txt + # $file = "$fake_data_dir/bsd/sysctl/openbsd-5.6-sysctl-1.txt"; + ## matches: toshiba: openbsd-5.6-dmesg.boot-1.txt + # $file = "$fake_data_dir/bsd/sysctl/openbsd-5.6-sysctl-2.txt"; + # $file = "$fake_data_dir/bsd/sysctl/obsd-6.8-sysctl-a-battery-sensor-1.txt"; + # @temp = reader($file); + } + foreach (@temp){ + $_ =~ s/\s*=\s*|:\s+/:/; + $_ =~ s/\"//g; + push(@{$sysctl{'main'}}, $_); + # we're building these here so we can use these arrays per feature + if ($use{'bsd-audio'} && /^hw\.snd\./){ + push(@{$sysctl{'audio'}}, $_); # not used currently, just test data + } + # note: we could use ac0 to indicate plugged in but messes with battery output + elsif ($use{'bsd-battery'} && /^hw\.sensors\.acpi(bat|cmb)/){ + push(@{$sysctl{'battery'}}, $_); + } + # hw.cpufreq.temperature: 40780 :: dev.cpu0.temperature + # hw.acpi.thermal.tz2.temperature: 27.9C :: hw.acpi.thermal.tz1.temperature: 42.1C + # hw.acpi.thermal.tz0.temperature: 42.1C + elsif ($use{'bsd-sensor'} &&((/^hw\.sensors/ && !/^hw\.sensors\.acpi(ac|bat|cmb)/ && + !/^hw\.sensors\.softraid/) || /^hw\.acpi\.thermal/ || /^dev\.cpu\.[0-9]+\.temp/)){ + push(@{$sysctl{'sensor'}}, $_); + } + # Must go AFTER sensor because sometimes freebsd puts sensors in dev.cpu + # hw.l1dcachesize hw.l2cachesize + elsif ($use{'bsd-cpu'} && (/^hw\.(busfreq|clock|n?cpu|l[123].?cach|model|smt)/ || + /^dev\.cpu/ || /^machdep\.(cpu|hlt_logical_cpus)/)){ + push(@{$sysctl{'cpu'}}, $_); + } + # only activate if using the diskname feature in dboot!! note assign to $dboot. + elsif ($use{'bsd-disk'} && /^hw\.disknames/){ + push(@{$dboot{'disk'}}, $_); + } + elsif ($use{'bsd-kernel'} && /^kern.compiler_version/){ + push(@{$sysctl{'kernel'}}, $_); + } + elsif ($use{'bsd-machine'} && + /^(hw\.|machdep\.dmi\.(bios|board|system)-)(date|product|serial(no)?|uuid|vendor|version)/){ + push(@{$sysctl{'machine'}}, $_); + } + # let's rely on dboot, we really just want the hardware specs for solid ID + # elsif ($use{'bsd-machine'} && !$dboot{'machine-vm'} && + # /(\bhvm\b|innotek|\bkvm\b|microsoft.*virtual machine|openbsd[\s-]vmm|qemu|qumranet|vbox|virtio|virtualbox|vmware)/i){ + # push(@{$dboot{'machine-vm'}}, $_); + # } + elsif ($use{'bsd-memory'} && /^(hw\.(physmem|usermem)|Free Memory)/){ + push(@{$sysctl{'memory'}}, $_); + } + + elsif ($use{'bsd-raid'} && /^hw\.sensors\.softraid[0-9]\.drive[0-9]/){ + push(@{$sysctl{'softraid'}}, $_); + } + } + if ($dbg[7]){ + print("main\n", Dumper $sysctl{'main'}); + print("dboot-machine-vm\n", Dumper $dboot{'machine-vm'}); + print("audio\n", Dumper $sysctl{'audio'}); + print("battery\n", Dumper $sysctl{'battery'}); + print("cpu\n", Dumper $sysctl{'cpu'}); + print("kernel\n", Dumper $sysctl{'kernel'}); + print("machine\n", Dumper $sysctl{'machine'}); + print("memory\n", Dumper $sysctl{'memory'}); + print("sensors\n", Dumper $sysctl{'sensor'}); + print("softraid\n", Dumper $sysctl{'softraid'}); + } + # this thing can get really long. + if ($b_log){ + main::log_data('dump','$sysctl{main}',$sysctl{'main'}); + main::log_data('dump','$dboot{machine-vm}',$sysctl{'machine-vm'}); + main::log_data('dump','$sysctl{audio}',$sysctl{'audio'}); + main::log_data('dump','$sysctl{battery}',$sysctl{'battery'}); + main::log_data('dump','$sysctl{cpu}',$sysctl{'cpu'}); + main::log_data('dump','$sysctl{kernel}',$sysctl{'kernel'}); + main::log_data('dump','$sysctl{machine}',$sysctl{'machine'}); + main::log_data('dump','$sysctl{memory}',$sysctl{'memory'}); + main::log_data('dump','$sysctl{sensors}',$sysctl{'sensor'}); + main::log_data('dump','$sysctl{softraid}',$sysctl{'softraid'}); + } + eval $end if $b_log; +} + +sub get_uptime { + eval $start if $b_log; + my ($days,$hours,$minutes,$seconds,$sys_time,$uptime) = ('','','','','',''); + if (check_program('uptime')){ + $uptime = qx(uptime); + $uptime = trimmer($uptime); + if ($fake{'uptime'}){ + # $uptime = '2:58PM up 437 days, 8:18, 3 users, load averages: 2.03, 1.72, 1.77'; + # $uptime = '04:29:08 up 3:18, 3 users, load average: 0,00, 0,00, 0,00'; + # $uptime = '10:23PM up 5 days, 16:17, 1 user, load averages: 0.85, 0.90, 1.00'; + # $uptime = '05:36:47 up 1 day, 3:28, 4 users, load average: 1,88, 0,98, 0,62'; + # $uptime = '05:36:47 up 1 day, 3 min, 4 users, load average: 1,88, 0,98, 0,62'; + # $uptime = '04:41:23 up 2:16, load average: 7.13, 6.06, 3.41 # root openwrt'; + # $uptime = '9:51 PM up 2 mins, 1 user, load average: 0:58, 0.27, 0.11'; + # $uptime = '05:36:47 up 3 min, 4 users, load average: 1,88, 0,98, 0,62'; + # $uptime = '9:51 PM up 49 secs, 1 user, load average: 0:58, 0.27, 0.11'; + # $uptime = '04:11am up 0:00, 1 user, load average: 0.08, 0.03, 0.01'; # openSUSE 13.1 (Bottle) + # $uptime = '11:21:43 up 1 day 5:53, 4 users, load average: 0.48, 0.62, 0.48'; # openSUSE Tumbleweed 20210515 + } + if ($uptime){ + # trim off and store system time and up, and cut off user/load data + $uptime =~ s/^([0-9:])\s*([AP]M)?.+up\s+|,?\s*([0-9]+\suser|load).*$//gi; + # print "ut: $uptime\n"; + if ($1){ + $sys_time = $1; + $sys_time .= lc($2) if $2; + } + if ($uptime =~ /\b([0-9]+)\s+day[s]?\b/){ + $days = ($1 + 0) . 'd'; + } + if ($uptime =~ /\b([0-9]{1,2}):([0-9]{1,2})\b/){ + $hours = ($1 + 0) . 'h'; + $minutes = ($2 + 0) . 'm'; + } + else { + if ($uptime =~ /\b([0-9]+)\smin[s]?\b/){ + $minutes = ($1 + 0) . 'm'; + } + if ($uptime =~ /\b([0-9]+)\ssec[s]?\b/){ + $seconds = ($1 + 0) . 's'; + } + } + $days .= ' ' if $days && ($hours || $minutes || $seconds); + $hours .= ' ' if $hours && $minutes; + $minutes .= ' ' if $minutes && $seconds; + $uptime = $days . $hours . $minutes . $seconds; + } + } + $uptime ||= 'N/A'; + eval $end if $b_log; + return $uptime; +} + +## UsbData +# %usb array indexes +# 0: bus id / sort id +# 1: device id +# 2: path_id +# 3: path +# 4: class id +# 5: subclass id +# 6: protocol id +# 7: vendor:chip id +# 8: usb version +# 9: interfaces +# 10: ports +# 11: vendor +# 12: product +# 13: device-name +# 14: type string +# 15: driver +# 16: serial +# 17: speed (bits, Si base 10, [MG]bps) +# 18: configuration - not used +# 19: power mW bsd only, not used yet +# 20: product rev number +# 21: driver_nu [bsd only] +# 22: admin usb rev info +# 23: rx lanes +# 24: tx lanes +# 25: speed (Bytes, IEC base 2, [MG]iBs +# 26: absolute path +{ +package UsbData; +my (@working); +my (@asound_ids,$b_asound,$b_hub,$addr_id,$bus_id,$bus_id_alpha, +$chip_id,$class_id,$device_id,$driver,$driver_nu,$ids,$interfaces, +$name,$network_regex,$path,$path_id,$power,$product,$product_id,$protocol_id, +$mode,$rev,$serial,$speed_si,$speed_iec,$subclass_id,$type,$version, +$vendor,$vendor_id); +my $b_live = 1; # debugger file data + +sub set { + eval $start if $b_log; + ${$_[0]} = 1; # set checked boolean + # note: bsd package usbutils has lsusb in it, but we dont' want it for default + # usbdevs is best, has most data, and runs as user + if ($alerts{'usbdevs'}->{'action'} eq 'use'){ + usbdevs_data(); + } + # usbconfig has weak/poor output, and requires root, only fallback + elsif ($alerts{'usbconfig'}->{'action'} eq 'use'){ + usbconfig_data(); + } + # if user config sets USB_SYS you can override with --usb-tool + elsif ((!$force{'usb-sys'} || $force{'lsusb'}) && $alerts{'lsusb'}->{'action'} eq 'use'){ + lsusb_data(); + } + elsif (-d '/sys/bus/usb/devices'){ + sys_data('main'); + } + @{$usb{'main'}} = sort {$a->[0] cmp $b->[0]} @{$usb{'main'}} if $usb{'main'}; + if ($b_log){ + main::log_data('dump','$usb{audio}: ',$usb{'audio'}); + main::log_data('dump','$usb{bluetooth}: ',$usb{'bluetooth'}); + main::log_data('dump','$usb{disk}: ',$usb{'disk'}); + main::log_data('dump','$usb{graphics}: ',$usb{'graphics'}); + main::log_data('dump','$usb{network}: ',$usb{'network'}); + } + if ($dbg[55]){ + print '$usb{audio}: ', Data::Dumper::Dumper $usb{'audio'}; + print '$usb{bluetooth}: ', Data::Dumper::Dumper $usb{'bluetooth'}; + print '$usb{disk}: ', Data::Dumper::Dumper $usb{'disk'}; + print '$usb{graphics}: ', Data::Dumper::Dumper $usb{'graphics'}; + print '$usb{network}: ', Data::Dumper::Dumper $usb{'network'}; + } + eval $end if $b_log; +} + +sub lsusb_data { + eval $start if $b_log; + my (@temp); + my @data = usb_grabber('lsusb'); + foreach (@data){ + next if /^~$|^Couldn't/; # expensive second call: || /UNAVAIL/ + @working = split(/\s+/, $_); + next unless defined $working[1] && defined $working[3]; + $working[3] =~ s/:$//; + # Don't use this fix, the data is garbage in general! Seen FreeBSD lsusb with: + # Bus /dev/usb Device /dev/ugen0.3: ID 24ae:1003 Shenzhen Rapoo Technology Co., Ltd. + # hub, note incomplete data: Bus /dev/usb Device /dev/ugen0.1: ID 0000:0000 + # linux: + # Bus 005 Device 007: ID 0d8c:000c C-Media Electronics, Inc. Audio Adapter + # if ($working[3] =~ m|^/dev/ugen([0-9]+)\.([0-9]+)|){ + # $working[1] = $1; + # $working[3] = $2; + # } + next unless main::is_numeric($working[1]) && main::is_numeric($working[3]); + $addr_id = int($working[3]); + $bus_id = int($working[1]); + $path_id = "$bus_id-$addr_id"; + $chip_id = $working[5]; + @temp = @working[6..$#working]; + $name = main::remove_duplicates(join(' ', @temp)); + # $type = check_type($name,'',''); + $type ||= ''; + # do NOT set bus_id_alpha here!! + # print "$name\n"; + $working[0] = $bus_id; + $working[1] = $addr_id; + $working[2] = $path_id; + $working[3] = ''; + $working[4] = '00'; + $working[5] = ''; + $working[6] = ''; + $working[7] = $chip_id; + $working[8] = ''; + $working[9] = ''; + $working[10] = 0; + $working[11] = ''; + $working[12] = ''; + $working[13] = $name; + $working[14] = '';# $type; + $working[15] = ''; + $working[16] = ''; + $working[17] = ''; + $working[18] = ''; + $working[19] = ''; + $working[20] = ''; + push(@{$usb{'main'}},[@working]); + # print join("\n",@working),"\n\n=====\n"; + } + print 'lsusb-pre-sys: ', Data::Dumper::Dumper $usb{'main'} if $dbg[6]; + sys_data('lsusb') if $usb{'main'}; + print 'lsusb-w-sys: ', Data::Dumper::Dumper $usb{'main'} if $dbg[6]; + main::log_data('dump','$usb{main}: plain',$usb{'main'}) if $b_log; + eval $end if $b_log; +} + +# ugen0.1: at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=SAVE (0mA) +# ugen0.2: at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (160mA) +# note: tried getting driver/ports from dmesg, impossible, waste of time +sub usbconfig_data { + eval $start if $b_log; + my ($cfg,$hub_id,$ports); + my @data = usb_grabber('usbconfig'); + foreach (@data){ + if ($_ eq '~' && @working){ + $chip_id = ($vendor_id || $product_id) ? "$vendor_id:$product_id" : ''; + $working[7] = $chip_id; + $product ||= ''; + $vendor ||= ''; + $working[13] = main::remove_duplicates("$vendor $product") if $product || $vendor; + # leave the ugly vendor/product ids unless chip-ID shows! + $working[13] = $chip_id if $extra < 2 && $chip_id && !$working[13]; + if (defined $class_id && defined $subclass_id && defined $protocol_id){ + $class_id = hex($class_id); + $subclass_id = hex($subclass_id); + $protocol_id = hex($protocol_id); + $type = device_type("$class_id/$subclass_id/$protocol_id"); + } + if ($working[13] && (!$type || $type eq '')){ + $type = check_type($working[13],'',''); + } + $working[14] = $type; + push(@{$usb{'main'}},[@working]); + assign_usb_type([@working]); + undef @working; + } + elsif (/^([a-z_-]+)([0-9]+)\.([0-9]+):\s+<[^>]+>\s+at usbus([0-9]+)\b/){ + ($class_id,$cfg,$power,$rev,$mode,$speed_si,$speed_iec,$subclass_id, + $type) = (); + ($product,$product_id,$vendor,$vendor_id) = ('','','',''); + $hub_id = $2; + $addr_id = $3; + $bus_id = $4; + $path_id = "$bus_id-$hub_id.$addr_id"; + $bus_id_alpha = bus_id_alpha($path_id); + if (/\bcfg\s*=\s*([0-9]+)/){ + $cfg = $1; + } + if (/\bmd\s*=\s*([\S]+)/){ + # nothing + } + # odd, using \b after ) doesn't work as expected + # note that bsd spd=FULL has no interest since we get that from the speed + if (/\b(speed|spd)\s*=\s*([\S]+)\s+\(([^\)]+)\)/){ + $speed_si = $3; + } + if (/\b(power|pwr)\s*=\s*([\S]+)\s+\(([0-9]+mA)\)/){ + $power = $3; + process_power(\$power) if $power; + } + version_data('bsd',\$speed_si,\$speed_iec,\$rev,\$mode); + $working[0] = $bus_id_alpha; + $working[1] = $addr_id; + $working[2] = $path_id; + $working[3] = ''; + $working[8] = $rev; + $working[9] = ''; + $working[10] = $ports; + $working[15] = $driver; + $working[17] = $speed_si; + $working[18] = $cfg; + $working[19] = $power; + $working[20] = ''; + $working[21] = $driver_nu; + $working[22] = $mode; + $working[25] = $speed_iec; + } + elsif (/^bDeviceClass\s*=\s*0x00([a-f0-9]{2})\s*(<([^>]+)>)?/){ + $class_id = $1; + $working[4] = $class_id; + } + elsif (/^bDeviceSubClass\s*=\s*0x00([a-f0-9]{2})/){ + $subclass_id = $1; + $working[5] = $subclass_id; + } + elsif (/^bDeviceProtocol\s*=\s*0x00([a-f0-9]{2})/){ + $protocol_id = $1; + $working[6] = $protocol_id; + } + elsif (/^idVendor\s*=\s*0x([a-f0-9]{4})/){ + $vendor_id = $1; + } + elsif (/^idProduct\s*=\s*0x([a-f0-9]{4})/){ + $product_id = $1; + } + elsif (/^iManufacturer\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){ + $vendor = main::clean($3); + $vendor =~ s/^0x.*//; # seen case where vendor string was ID + $working[11] = $vendor; + } + elsif (/^iProduct\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){ + $product = main::clean($3); + $product =~ s/^0x.*//; # in case they put product ID in, sigh + $working[12] = $product; + } + elsif (/^iSerialNumber\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){ + $working[16] = main::clean($3); + } + } + main::log_data('dump','$usb{main}: usbconfig',$usb{'main'}) if $b_log; + print 'usbconfig: ', Data::Dumper::Dumper $usb{'main'} if $dbg[6]; + eval $end if $b_log; +} + +# Controller /dev/usb2: +# addr 1: full speed, self powered, config 1, UHCI root hub(0x0000), Intel(0x8086), rev 1.00 +# port 1 addr 2: full speed, power 98 mA, config 1, USB Receiver(0xc52b), Logitech(0x046d), rev 12.01 +# port 2 powered +sub usbdevs_data { + eval $start if $b_log; + my ($b_multi,$class,$config,$hub_id,$port,$port_value,$product_rev); + my ($ports) = (0); + my @data = usb_grabber('usbdevs'); + foreach (@data){ + if ($_ eq '~' && @working){ + $working[10] = $ports; + push(@{$usb{'main'}},[@working]); + assign_usb_type([@working]); + undef @working; + ($config,$driver,$power,$rev) = ('','','',''); + } + elsif (/^Controller\s\/dev\/usb([0-9]+)/){ + $bus_id = $1; + } + elsif (/^addr\s([0-9]+):\s([^,]+),[^,0-9]+([0-9]+ mA)?,\s+config\s+([0-9]+),\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){ + ($mode,$rev,$speed_si,$speed_iec) = (); + $hub_id = $1; + $addr_id = $1; + $speed_si = $2; # requires prep + $power = $3; + $chip_id = "$6:$8"; + $config = $4; + $name = main::remove_duplicates("$7 $5"); + # print "p1:$protocol\n"; + $path_id = "$bus_id-$hub_id"; + $bus_id_alpha = bus_id_alpha($path_id); + $ports = 0; + process_power(\$power) if $power; + $port_value = ''; + version_data('bsd',\$speed_si,\$speed_iec,\$rev,\$mode); + $working[0] = $bus_id_alpha; + $working[1] = $addr_id; + $working[2] = $path_id; + $working[3] = ''; + $working[4] = '09'; + $working[5] = ''; + $working[6] = ''; + $working[7] = $chip_id; + $working[8] = $rev; + $working[9] = ''; + $working[10] = $ports; + $working[13] = $name; + $working[14] = 'Hub'; + $working[15] = ''; + $working[16] = ''; + $working[17] = $speed_si; + $working[18] = $config; + $working[19] = $power; + $working[20] = ''; + $working[22] = $mode; + $working[25] = $speed_iec; + } + elsif (/^port\s([0-9]+)\saddr\s([0-9]+):\s([^,]+),[^,0-9]*([0-9]+\s?mA)?,\s+config\s+([0-9]+),\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){ + ($rev,$mode,$speed_iec,$speed_si) = (); + $port = $1; + $addr_id = $2; + $speed_si = $3; + $power = $4; + $config = $5; + $chip_id = "$7:$9"; + $name = main::remove_duplicates("$8 $6"); + $type = check_type($name,'',''); + $type ||= ''; + # print "p2:$protocol\n"; + $ports++; + $path_id = "$bus_id-$hub_id.$port"; + $bus_id_alpha = bus_id_alpha($path_id); + process_power(\$power) if $power; + version_data('bsd',\$speed_si,\$speed_iec,\$rev,\$mode); + $working[0] = $bus_id_alpha; + $working[1] = $addr_id; + $working[2] = $path_id; + $working[3] = ''; + $working[4] = '01'; + $working[5] = ''; + $working[6] = ''; + $working[7] = $chip_id; + $working[8] = $rev; + $working[9] = ''; + $working[10] = $ports; + $working[11] = ''; + $working[12] = ''; + $working[13] = $name; + $working[14] = $type; + $working[15] = ''; + $working[16] = ''; + $working[17] = $speed_si; + $working[18] = $config; + $working[19] = $power; + $working[20] = ''; + $working[22] = $mode; + $working[25] = $speed_iec; + } + elsif (/^port\s([0-9]+)\spowered/){ + $ports++; + } + # newer openbsd usbdevs totally changed their syntax and layout, but it is better... + elsif (/^addr\s*([0-9a-f]+):\s+([a-f0-9]{4}:[a-f0-9]{4})\s*([^,]+)?(,\s[^,]+?)?,\s+([^,]+)$/){ + $addr_id = $1; + $chip_id = $2; + $vendor = main::clean($3) if $3; + $vendor ||= ''; + $name = main::remove_duplicates("$vendor $5"); + $type = check_type($name,'',''); + $class_id = ($name =~ /hub/i) ? '09': '01'; + $path_id = "$bus_id-$addr_id"; + $bus_id_alpha = bus_id_alpha($path_id); + $ports = 0; + $b_multi = 1; + $working[0] = $bus_id_alpha; + $working[1] = $addr_id; + $working[2] = $path_id; + $working[3] = ''; + $working[4] = $class_id; + $working[5] = ''; + $working[6] = ''; + $working[7] = $chip_id; + $working[8] = ''; + $working[9] = ''; + $working[10] = $ports; + $working[11] = ''; + $working[12] = ''; + $working[13] = $name; + $working[14] = $type; + $working[15] = ''; + $working[16] = ''; + $working[17] = ''; + $working[18] = ''; + $working[19] = ''; + $working[20] = ''; + } + elsif ($b_multi && + /^([^,]+),\s+(self powered|power\s+([0-9]+\s+mA)),\s+config\s([0-9]+),\s+rev\s+([0-9\.]+)(,\s+i?Serial\s(\S*))?/i){ + ($mode,$rev,$speed_iec,$speed_si) = (); + $speed_si = $1; + $power = $3; + process_power(\$power) if $power; + version_data('bsd',\$speed_si,\$speed_iec,\$rev,\$mode); + $working[8] = $rev; + $working[16] = $7 if $7; + $working[17] = $speed_si; + $working[18] = $4; # config number + $working[19] = $power; + $working[20] = $5; # product rev + $working[22] = $mode; + $working[25] = $speed_iec; + } + # 1 or more drivers supported + elsif ($b_multi && /^driver:\s*([^,]+)$/){ + my $temp = $1; + $working[4] = '09' if $temp =~ /hub[0-9]/; + $temp =~ s/([0-9]+)$//; + $working[21] = $1; # driver nu + # drivers, note that when numbers trimmed off, drivers can have same name + $working[15] = ($working[15] && $working[15] !~ /\b$temp\b/) ? "$working[15],$temp" : $temp; + # now that we have the driver, let's recheck the type + if (!$type && $name && $working[15]){ + $type = check_type($name,$working[15],''); + $working[14] = $type if $type; + } + } + elsif ($b_multi && /^port\s[0-9]/){ + $ports++; + } + } + main::log_data('dump','$usb{main}: usbdevs',$usb{'main'}) if $b_log; + print 'usbdevs: ', Data::Dumper::Dumper $usb{'main'} if $dbg[6]; + eval $end if $b_log; +} + +sub usb_grabber { + eval $start if $b_log; + my ($program) = @_; + my ($args,$path,$pattern,@data,@working); + if ($program eq 'lsusb'){ + $args = ''; + $path = $alerts{'lsusb'}->{'path'}; + $pattern = '^Bus [0-9]'; + } + elsif ($program eq 'usbconfig'){ + $args = 'dump_device_desc'; + $path = $alerts{'usbconfig'}->{'path'}; + $pattern = '^[a-z_-]+[0-9]+\.[0-9]+:'; + } + elsif ($program eq 'usbdevs'){ + $args = '-vv'; + $path = $alerts{'usbdevs'}->{'path'}; + $pattern = '^(addr\s[0-9a-f]+:|port\s[0-9]+\saddr\s[0-9]+:)'; + } + if ($b_live && !$fake{'usbdevs'} && !$fake{'usbconfig'}){ + @data = main::grabber("$path $args 2>/dev/null",'','strip'); + } + else { + my $file; + if ($fake{'usbdevs'}){ + $file = "$fake_data_dir/usb/usbdevs/bsd-usbdevs-v-1.txt"; + } + elsif ($fake{'usbconfig'}){ + $file = "$fake_data_dir/usb/usbconfig/bsd-usbconfig-list-v-1.txt"; + } + else { + $file = "$fake_data_dir/usb/lsusb/mdmarmer-lsusb.txt"; + } + @data = main::reader($file,'strip'); + } + if (@data){ + $use{'usb-tool'} = 1 if scalar @data > 2; + foreach (@data){ + # this is the group separator and assign trigger + push(@working, '~') if $_ =~ /$pattern/i; + push(@working, $_); + } + push(@working, '~'); + } + print Data::Dumper::Dumper \@working if $dbg[30]; + eval $end if $b_log; + return @working; +} + +sub sys_data { + eval $start if $b_log; + my ($source) = @_; + my ($configuration,$lanes_rx,$lanes_tx,$ports,$mode,$rev); + my (@drivers,@uevent); + my $i = 0; + my @files = main::globber('/sys/bus/usb/devices/*'); + # we want to get rid of the hubs with x-0: syntax, those are hubs found in /usbx + @files = grep {!/\/[0-9]+-0:/} @files; + # print join("\n", @files); + foreach my $file (@files){ + # be careful, sometimes uevent is not readable + @uevent = (-r "$file/uevent") ? main::reader("$file/uevent") : undef; + if (@uevent && ($ids = main::awk(\@uevent,'^(DEVNAME|DEVICE\b)',2,'='))){ + ($b_hub,$class_id,$protocol_id,$subclass_id) = (0,0,0,0); + (@drivers,$lanes_rx,$lanes_tx,$mode,$rev,$speed_iec,$speed_si) = (); + ($configuration,$driver,$interfaces,$name,$ports,$product,$serial, + $type,$vendor) = ('','','','','','','','',''); + # print Cwd::abs_path($file),"\n"; + # print "f1: $file\n"; + $path_id = $file; + $path_id =~ s/^.*\///; + $path_id =~ s/^usb([0-9]+)/$1-0/; + # if DEVICE= then path = /proc/bus/usb/001/001 else: bus/usb/006/001 + $ids =~ s/^\///; + @working = split('/', $ids); + shift @working if $working[0] eq 'proc'; + $bus_id = int($working[2]); + $bus_id_alpha = bus_id_alpha($path_id); + $device_id = int($working[3]); + # this will be a hex number + $class_id = sys_item("$file/bDeviceClass"); + # $subclass_id = sys_item("$file/bDeviceSubClass"); + # $protocol_id = sys_item("$file/bDeviceProtocol"); + $class_id = hex($class_id) if $class_id; + # $subclass_id = hex($subclass_id) if $subclass_id; + # $protocol_id = hex($protocol_id) if $protocol_id; + # print "$path_id $class_id/$subclass_id/$protocol_id\n"; + $power = sys_item("$file/bMaxPower"); + process_power(\$power) if $power; + # this populates class, subclass, and protocol id with decimal numbers + @drivers = uevent_data("$file/[0-9]*/uevent"); + push(@drivers, uevent_data("$file/[0-9]*/*/uevent")) if !$b_hub; + $ports = sys_item("$file/maxchild") if $b_hub; + if (@drivers){ + main::uniq(\@drivers); + $driver = join(',', sort @drivers); + } + $interfaces = sys_item("$file/bNumInterfaces"); + $lanes_rx = sys_item("$file/rx_lanes"); + $lanes_tx = sys_item("$file/tx_lanes"); + $serial = sys_item("$file/serial"); + $rev = sys_item("$file/version"); + $speed_si = sys_item("$file/speed"); + version_data('sys',\$speed_si,\$speed_iec,\$rev,\$mode,$lanes_rx,$lanes_tx); + $configuration = sys_item("$file/configuration"); + $power = sys_item("$file/bMaxPower"); + process_power(\$power) if $power; + $class_id = sprintf("%02x", $class_id) if defined $class_id && $class_id ne ''; + $subclass_id = sprintf("%02x", $subclass_id) if defined $subclass_id && $subclass_id ne ''; + if ($source eq 'lsusb'){ + for ($i = 0; $i < scalar @{$usb{'main'}}; $i++){ + if ($usb{'main'}->[$i][0] eq $bus_id && $usb{'main'}->[$i][1] == $device_id){ + if (!$b_hub && $usb{'main'}->[$i][13] && (!$type || $type eq '')){ + $type = check_type($usb{'main'}->[$i][13],$driver,$type); + } + $usb{'main'}->[$i][0] = $bus_id_alpha; + $usb{'main'}->[$i][2] = $path_id; + $usb{'main'}->[$i][3] = $file; + $usb{'main'}->[$i][4] = $class_id; + $usb{'main'}->[$i][5] = $subclass_id; + $usb{'main'}->[$i][6] = $protocol_id; + $usb{'main'}->[$i][8] = $rev; + $usb{'main'}->[$i][9] = $interfaces; + $usb{'main'}->[$i][10] = $ports if $ports; + if ($type && $b_hub && (!$usb{'main'}->[$i][13] || + $usb{'main'}->[$i][13] =~ /^linux foundation/i)){ + $usb{'main'}->[$i][13] = "$type"; + } + $usb{'main'}->[$i][14] = $type if ($type && !$b_hub); + $usb{'main'}->[$i][15] = $driver if $driver; + $usb{'main'}->[$i][16] = $serial if $serial; + $usb{'main'}->[$i][17] = $speed_si if $speed_si; + $usb{'main'}->[$i][18] = $configuration; + $usb{'main'}->[$i][19] = $power; + $usb{'main'}->[$i][20] = ''; + $usb{'main'}->[$i][22] = $mode; + $usb{'main'}->[$i][23] = $lanes_rx; + $usb{'main'}->[$i][24] = $lanes_tx; + $usb{'main'}->[$i][25] = $speed_iec if $speed_iec; + $usb{'main'}->[$i][26] = Cwd::abs_path($file); + assign_usb_type($usb{'main'}->[$i]); + # print join("\n",@{$usb{'main'}->[$i]}),"\n\n";# if !$b_hub; + last; + } + } + } + else { + $chip_id = sys_item("$file/idProduct"); + $vendor_id = sys_item("$file/idVendor"); + # we don't want the device, it's probably a bad path in /sys/bus/usb/devices + next if !$vendor_id && !$chip_id; + $product = sys_item("$file/product"); + $product = main::clean($product) if $product; + $vendor = sys_item("$file/manufacturer"); + $vendor = main::clean($vendor) if $vendor; + if (!$b_hub && ($product || $vendor)){ + if ($vendor && $product && $product !~ /$vendor/){ + $name = "$vendor $product"; + } + elsif ($product){ + $name = $product; + } + elsif ($vendor){ + $name = $vendor; + } + } + elsif ($b_hub){ + $name = $type; + } + $name = main::remove_duplicates($name) if $name; + if (!$b_hub && $name && (!$type || $type eq '')){ + $type = check_type($name,$driver,$type); + } + # this isn't that useful, but save in case something shows up + # if ($configuration){ + # $name = ($name) ? "$name $configuration" : $configuration; + # } + $type = 'Hub' if $b_hub; + $usb{'main'}->[$i][0] = $bus_id_alpha; + $usb{'main'}->[$i][1] = $device_id; + $usb{'main'}->[$i][2] = $path_id; + $usb{'main'}->[$i][3] = $file; + $usb{'main'}->[$i][4] = $class_id; + $usb{'main'}->[$i][5] = $subclass_id; + $usb{'main'}->[$i][6] = $protocol_id; + $usb{'main'}->[$i][7] = "$vendor_id:$chip_id"; + $usb{'main'}->[$i][8] = $rev; + $usb{'main'}->[$i][9] = $interfaces; + $usb{'main'}->[$i][10] = $ports; + $usb{'main'}->[$i][11] = $vendor; + $usb{'main'}->[$i][12] = $product; + $usb{'main'}->[$i][13] = $name; + $usb{'main'}->[$i][14] = $type; + $usb{'main'}->[$i][15] = $driver; + $usb{'main'}->[$i][16] = $serial; + $usb{'main'}->[$i][17] = $speed_si; + $usb{'main'}->[$i][18] = $configuration; + $usb{'main'}->[$i][19] = $power; + $usb{'main'}->[$i][20] = ''; + $usb{'main'}->[$i][22] = $mode; + $usb{'main'}->[$i][23] = $lanes_rx; + $usb{'main'}->[$i][24] = $lanes_tx; + $usb{'main'}->[$i][25] = $speed_iec; + $usb{'main'}->[$i][26] = Cwd::abs_path($file); + assign_usb_type($usb{'main'}->[$i]); + $i++; + } + # print "$path_id ids: $bus_id:$device_id driver: $driver ports: $ports\n==========\n"; # if $dbg[6];; + } + } + print 'usb-sys: ', Data::Dumper::Dumper $usb{'main'} if $source eq 'main' && $dbg[6]; + main::log_data('dump','$usb{main}: sys',$usb{'main'}) if $source eq 'main' && $b_log; + eval $end if $b_log; +} + +# Get driver, interface [type:] data +sub uevent_data { + my ($path) = @_; + my ($interface,$interfaces,$temp,@interfaces,@drivers); + my @files = main::globber($path); + @files = grep {!/\/(subsystem|driver|ep_[^\/]+)\/uevent$/} @files if @files; + foreach (@files){ + last if $b_hub; + # print "f2: $_\n"; + ($interface) = (''); + @working = main::reader($_) if -r $_; + # print join("\n",@working), "\n"; + if (@working){ + $driver = main::awk(\@working,'^DRIVER',2,'='); + $interface = main::awk(\@working,'^INTERFACE',2,'='); + if ($interface){ + # for hubs, we need the specific protocol, which is in TYPE + if ($interface eq '9/0/0' && + (my $temp = main::awk(\@working,'^TYPE',2,'='))){ + $interface = $temp; + } + # print "$interface\n"; + $interface = device_type($interface); + if ($interface){ + if ($interface ne ''){ + push(@interfaces, $interface); + } + # networking requires more data but this test is reliable + elsif (!@interfaces){ + $temp = $_; + $temp =~ s/\/uevent$//; + push(@interfaces, 'Network') if -d "$temp/net/"; + } + if (!@interfaces){ + push(@interfaces, $interface); + } + } + } + } + # print "driver:$driver\n"; + $b_hub = 1 if $driver && $driver eq 'hub'; + $driver = '' if $driver && ($driver eq 'usb' || $driver eq 'hub'); + push(@drivers,$driver) if $driver; + } + if (@interfaces){ + main::uniq(\@interfaces); + # clear out values like: ,Printer + if (scalar @interfaces > 1 && (grep {!/^ device on the bus, + # although nested hubs of course can be > 1 too. No need to build these if + # none of lines are showing. + if (($row->[4] && $row->[4] eq '09') || + ($row->[14] && lc($row->[14]) eq 'hub') || $row->[1] <= 1 || + (!$show{'audio'} && !$show{'bluetooth'} && !$show{'disk'} && + !$show{'graphic'} && !$show{'network'})){ + return; + } + $row->[13] = '' if !defined $row->[13]; # product + $row->[14] = '' if !defined $row->[14]; # type + $row->[15] = '' if !defined $row->[15]; # driver + set_asound_ids() if $show{'audio'} && !$b_asound; + set_network_regex() if $show{'network'} && !$network_regex; + # NOTE: a device, like camera, can be audio+graphic + # NOTE: 13, 14 can be upper/lower case, so use i. + if ($show{'audio'} && ( + (@asound_ids && $row->[7] && (grep {$row->[7] eq $_} @asound_ids)) || + ($row->[14] && $row->[14] =~ /audio/i) || + ($row->[15] && $row->[15] =~ /audio/) || + ($row->[13] && lc($row->[13]) =~ /(audio|\bdac[0-9]*\b|headphone|\bmic(rophone)?\b)/i) + )){ + push(@{$usb{'audio'}},$row); + } + if ($show{'graphic'} && ( + ($row->[14] && $row->[14] =~ /video/i) || + ($row->[15] && $row->[15] =~ /video/) || + ($row->[13] && lc($row->[13]) =~ /(camera|\bdvb-t|\b(pc)?tv\b|video|webcam)/i) + )){ + push(@{$usb{'graphics'}},$row); + } + # we want to catch bluetooth devices, which otherwise can trip network regex + elsif (($show{'bluetooth'} || $show{'network'}) && ( + ($row->[14] && $row->[14] =~ /bluetooth/i) || + ($row->[15] && $row->[15] =~ /\b(btusb|ubt)\b/) || + ($row->[13] && $row->[13] =~ /bluetooth/i) + )){ + push(@{$usb{'bluetooth'}},$row); + } + elsif ($show{'disk'} && ( + ($row->[14] && $row->[14] =~ /mass storage/i) || + ($row->[15] && $row->[15] =~ /storage/) + )){ + push(@{$usb{'disk'}},$row); + } + elsif ($show{'network'} && ( + ($row->[14] && $row->[14] =~ /(ethernet|network|wifi)/i) || + ($row->[15] && $row->[15] =~ /(^ipw|^iwl|wifi)/) || + ($row->[13] && $row->[13] =~ /($network_regex)/i) + )){ + push(@{$usb{'network'}},$row); + } +} + +sub device_type { + my ($data) = @_; + my ($type); + # note: the 3/0/0 value passed will be decimal, not hex + my @types = split('/', $data) if $data; + # print @types,"\n"; + if (!@types || $types[0] eq '0' || scalar @types != 3){return '';} + elsif ($types[0] eq '255'){ return '';} + if (scalar @types == 3){ + $class_id = $types[0]; + $subclass_id = $types[1]; + $protocol_id = $types[2]; + } + if ($types[0] eq '1'){ + $type = 'audio';} + elsif ($types[0] eq '2'){ + if ($types[1] eq '2'){ + $type = 'abstract (modem)';} + elsif ($types[1] eq '6'){ + $type = 'ethernet network';} + elsif ($types[1] eq '10'){ + $type = 'mobile direct line';} + elsif ($types[1] eq '12'){ + $type = 'ethernet emulation';} + else { + $type = 'communication';} + } + elsif ($types[0] eq '3'){ + if ($types[2] eq '0'){ + $type = 'HID';} # actual value: None + elsif ($types[2] eq '1'){ + $type = 'keyboard';} + elsif ($types[2] eq '2'){ + $type = 'mouse';} + } + elsif ($types[0] eq '6'){ + $type = 'still imaging';} + elsif ($types[0] eq '7'){ + $type = 'printer';} + elsif ($types[0] eq '8'){ + $type = 'mass storage';} + # note: there is a bug in linux kernel that always makes hubs 9/0/0 + elsif ($types[0] eq '9'){ + if ($types[2] eq '0'){ + $type = 'full speed or root hub';} + elsif ($types[2] eq '1'){ + $type = 'hi-speed hub with single TT';} + elsif ($types[2] eq '2'){ + $type = 'hi-speed hub with multiple TTs';} + # seen protocol 3, usb3 type hub, but not documented on usb.org + elsif ($types[2] eq '3'){ + $type = 'super-speed hub';} + # this is a guess, never seen it + elsif ($types[2] eq '4'){ + $type = 'super-speed+ hub';} + } + elsif ($types[0] eq '10'){ + $type = 'CDC-data';} + elsif ($types[0] eq '11'){ + $type = 'smart card';} + elsif ($types[0] eq '13'){ + $type = 'content security';} + elsif ($types[0] eq '14'){ + $type = 'video';} + elsif ($types[0] eq '15'){ + $type = 'personal healthcare';} + elsif ($types[0] eq '16'){ + $type = 'audio-video';} + elsif ($types[0] eq '17'){ + $type = 'billboard';} + elsif ($types[0] eq '18'){ + $type = 'type-C bridge';} + elsif ($types[0] eq '88'){ + $type = 'Xbox';} + elsif ($types[0] eq '220'){ + $type = 'diagnostic';} + elsif ($types[0] eq '224'){ + if ($types[1] eq '1'){ + $type = 'bluetooth';} + elsif ($types[1] eq '2'){ + if ($types[2] eq '1'){ + $type = 'host wire adapter';} + elsif ($types[2] eq '2'){ + $type = 'device wire adapter';} + elsif ($types[2] eq '3'){ + $type = 'device wire adapter';} + } + } + # print "$data: $type\n"; + return $type; +} + +# Device name/driver string based test, return if not detected +# for linux based tests, and empty for bsd tests +sub check_type { + my ($name,$driver,$type) = @_; + $name = lc($name); + if (($driver && $driver =~ /hub/) || $name =~ /\b(hub)/i){ + $type = 'Hub'; + } + elsif ($name =~ /(audio|\bdac[0-9]*\b|(head|micro|tele)phone|hifi|\bmidi\b|\bmic\b|sound)/){ + $type = 'Audio'; + } + # Broadcom HP Portable SoftSailing + elsif (($driver && $driver =~ /\b(btusb|ubt)\b/) || $name =~ /(bluetooth)/){ + $type = 'Bluetooth' + } + elsif (($driver && $driver =~ /video/) || + $name =~ /(camera|display|\bdvb-t|\b(pc)?tv\bvideo|webcam)/){ + $type = 'Video'; + } + elsif ($name =~ /(wlan|wi-?fi|802\.1[15]|(11|54|108|240|300|433|450|900|1300)\s?mbps|(11|54|108|240)g\b|wireless[\s-][bgn]\b|wireless.*adapter)/){ + $type = 'WiFi'; + } + # note, until freebsd match to actual drivers, these top level driver matches aren't interesting + elsif (($driver && $bsd_type && $driver =~ /\b(muge)\b/) || + $name =~ /(ethernet|\blan|802\.3|100?\/1000?|gigabit|10\s?G(b|ig)?E)/){ + $type = 'Ethernet'; + } + # note: audio devices show HID sometimes, not sure why + elsif ($name =~ /(joystick|keyboard|mouse|trackball)/){ + $type = 'HID'; + } + elsif (($driver && $driver =~ /^(umass)$/) || + $name =~ /\b(disk|drive|flash)\b/){ + $type = 'Mass Storage'; + } + return $type; +} + +# linux only, will create a positive match to sound devices +sub set_asound_ids { + $b_asound = 1; + if (-d '/proc/asound'){ + # note: this will double the data, but it's easier this way. + # binxi tested for -L in the /proc/asound files, and used only those. + my @files = main::globber('/proc/asound/*/usbid'); + foreach (@files){ + my $id = main::reader($_,'',0); + push(@asound_ids, $id) if ($id && !(grep {/$id/} @asound_ids)); + } + } + main::log_data('dump','@asound_ids',\@asound_ids) if $b_log; +} + +# USB networking search string data, because some brands can have other products +# than wifi/nic cards, they need further identifiers, with wildcards. Putting +# the most common and likely first, then the less common, then some specifics +sub set_network_regex { + # belkin=050d; d-link=07d1; netgear=0846; ralink=148f; realtek=0bda; + # Atmel, Atheros make other stuff. NOTE: exclude 'networks': IMC Networks + # intel, ralink bluetooth as well as networking; (WG|WND?A)[0-9][0-9][0-9] netgear IDs + $network_regex = 'Ethernet|gigabit|\bISDN|\bLAN\b|Mobile\s?Broadband|'; + $network_regex .= '\bNIC\b|wi-?fi|Wireless[\s-][GN]\b|WLAN|'; + $network_regex .= '802\.(1[15]|3)|(10|11|54|108|240|300|450|1300)\s?Mbps|(11|54|108|240)g\b|100?\/1000?|'; + $network_regex .= '(100?|N)Base-?T\b|'; + $network_regex .= '(Actiontec|AirLink|Asus|Belkin|Buffalo|Dell|D-Link|DWA-|ENUWI-|'; + $network_regex .= 'Ralink|Realtek|Rosewill|RNX-|Samsung|Sony|TEW-|TP-Link|'; + $network_regex .= 'Zonet.*ZEW.*).*Wireless|'; + # Note: Intel Bluetooth wireless interface < should be caught by bluetooth tests + $network_regex .= '(\bD-Link|Network(ing)?|Wireless).*(Adapter|Interface)|'; + $network_regex .= '(Linksys|Netgear|Davicom)|'; + $network_regex .= 'Range(Booster|Max)|Samsung.*LinkStick|\b(WG|WND?A)[0-9][0-9][0-9]|'; + $network_regex .= '\b(050d:935b|0bda:8189|0bda:8197)\b'; +} + +# For linux, process rev, get mode. For bsds, get rev, speed. +# args: 0: sys/bsd; 1: speed_si; 2: speed_iec; 3: rev; 4: rev_info; 5: rx lanes; +# 6: tx lanes +# 1,2,3,4 passed by reference. +sub version_data { + return if !${$_[1]}; + if ($_[0] eq 'sys'){ + if (${$_[3]} && main::is_numeric(${$_[3]})){ + # as far as we know, 4 will not have subversions, but this may change, + # check how /sys reports this in coming year(s) + if (${$_[3]} =~ /^4/){ + ${$_[3]} = ${$_[3]} + 0; + } + else { + ${$_[3]} = sprintf('%.1f',${$_[3]}); + } + } + # BSD rev is synthetic, it's a hack. And no lane data, so not trying. + if ($b_admin && ${$_[1]} && ${$_[3]} && $_[5] && $_[6] && + ${$_[3]} =~ /^[1234]/){ + if (${$_[3]} =~ /^[12]/){ + if (${$_[1]} == 1.5){ + ${$_[4]} = '1.0';} + elsif (${$_[1]} == 12){ + ${$_[4]} = '1.1';} + elsif (${$_[1]} == 480){ + ${$_[4]} = '2.0';} + } + # Note: unless otherwise indicated, 1 lane is 1rx+1tx. + elsif (${$_[3]} =~ /^3/){ + if (${$_[1]} == 5000){ + ${$_[4]} = '3.2 gen-1x1';} # 1 lane + elsif (${$_[1]} == 10000){ + if ($_[6] == 1){ + ${$_[4]} = '3.2 gen-2x1';} # 1 lane + elsif ($_[6] == 2){ + ${$_[4]} = '3.2 gen-1x2';} # 2 lane + } + elsif (${$_[1]} == 20000){ + if ($_[6] == 1){ + ${$_[4]} = '3.2 gen-3x1';} # 1 lane + elsif ($_[6] == 2){ + ${$_[4]} = '3.2 gen-2x2';} # 2 lane + } + # just in case rev: 3.x shows these speeds + elsif (${$_[1]} == 40000){ + if ($_[6] == 1){ + ${$_[4]} = '4-v1 gen-4x1';} # 1 lane + elsif ($_[6] == 2){ + ${$_[4]} = '4-v1 gen-3x2';} # 2 lane + } + elsif (${$_[1]} == 80000){ + ${$_[4]} = '4-v2 gen-4x2'; # 2 lanes + } + ${$_[4]} = main::message('usb-mode-mismatch') if !${$_[4]}; + } + # NOTE: no realworld usb4 data, unclear if these gen are reliable. + # possible /sys will expose v1/v2/v3. Check future data. + elsif (${$_[3]} =~ /^4/){ + # gen 2: 10gb x 1 ln + if (${$_[1]} < 10001){ + ${$_[4]} = '4-v1 gen-2x1';} # 1 lane + # gen2: 10gb x 2 ln; gen3: 20gb x 1 ln. Confirm + elsif (${$_[1]} < 20001){ + if ($_[6] == 2){ + ${$_[4]} = '4-v1 gen-2x2';} # 2 lanes + elsif ($_[6] == 1){ + ${$_[4]} = '4-v1 gen-3x1';} # 1 lane + } + # gen3: 20gb x 2 ln; gen4 40gb x 1 ln. Confirm + elsif (${$_[1]} < 40001){ + if ($_[6] == 2){ + ${$_[4]} = '4-v1 gen-3x2';} # 2 lanes + elsif ($_[6] == 1){ + ${$_[4]} = '4-v2 gen-4x1';} # 1 lane + } + # 40gb x 2 ln + elsif (${$_[1]} < 80001){ + ${$_[4]} = '4-v2 gen-4x2';} # 2 lanes + # 3 lanes: 2 tx+tx @ 60gb, 1 rx+rx @ 40gb, wait for data + elsif (${$_[1]} < 120001){ + ${$_[4]} = '4-v2 gen-4x3-asym'; # 3 lanes, asymmetric + } + ${$_[4]} = main::message('usb-mode-mismatch') if !${$_[4]}; + } + } + } + else { + (${$_[1]},${$_[3]}) = prep_speed(${$_[1]}); + # bsd rev hardcoded. We want this set to undef if bad data + ${$_[3]} = usb_rev(${$_[1]}) if !${$_[3]}; + } + # Add Si/IEC units + if ($extra > 0 && ${$_[1]}){ + # 1 == 1000000 bits + my $si = ${$_[1]}; + if (${$_[1]} >= 1000){ + ${$_[1]} = (${$_[1]}/1000) . ' Gb/s'; + } + else { + ${$_[1]} = ${$_[1]} . ' Mb/s'; + } + if ($b_admin){ + $si = (($si*1000**2)/8); + if ($si < 1000000){ + ${$_[2]} = sprintf('%0.0f KiB/s',($si/1024)); + } + elsif ($si < 1000000000){ + ${$_[2]} = sprintf('%0.1f MiB/s',$si/1024**2); + } + else { + ${$_[2]} = sprintf('%0.2f GiB/s',($si/1024**3)); + } + } + } + # print Data::Dumper::Dumper \@_; +} + +## BSD SPEED/REV ## +# Mapping of speed string to known speeds. Unreliable, very inaccurate, and some +# unconfirmed. Without real data source can never be better than a decent guess. +# args: 0: speed string +sub prep_speed { + return if !$_[0]; + my $speed_si = $_[0]; + my $rev; + if ($_[0] =~ /^([0-9\.]+)\s*Mb/){ + $speed_si = $1; + } + elsif ($_[0] =~ /^([0-9\.]+)+\s*Gb/){ + $speed_si = $1 * 1000; + } + elsif ($_[0] =~ /usb4?\s?120/i){ + $speed_si = 120000;# 4 120Gbps + $rev = '4'; + } + elsif ($_[0] =~ /usb4?\s?80/i){ + $speed_si = 80000;# 4 80Gbps + $rev = '4'; + } + elsif ($_[0] =~ /usb4?\s?40/i){ + $speed_si = 40000;# 4 40Gbps + $rev = '4'; + } + elsif ($_[0] =~ /usb4?\s?20/i){ + $speed_si = 20000;# 4 20Gbps + $rev = '4'; + } + elsif ($_[0] =~ /usb\s?20|super[\s-]?speed\s?(\+|plus) gen[\s-]?2x2/i){ + $speed_si = 20000;# 3.2 20Gbps + $rev = '3.2'; + } + # could be 3.2, 20000 too, also superspeed+ + elsif ($_[0] =~ /super[\s-]?speed\s?(\+|plus)/i){ + $speed_si = 10000;# 3.1; # can't trust bsds to use superspeed+ but we'll hope + $rev = '3.1'; + } + elsif ($_[0] =~ /super[\s-]?speed/i){ + $speed_si = 5000;# 3.0; + $rev = '3.0'; + } + elsif ($_[0] =~ /hi(gh)?[\s-]?speed/i){ + $speed_si = 480; # 2.0, + $rev = '2.0'; + } + elsif ($_[0] =~ /full[\s-]?speed/i){ + $speed_si = 12; # 1.1 - could be full speed 1.1/2.0 + $rev = '1.1'; + } + elsif ($_[0] =~ /low?[\s-]?speed/i){ + $speed_si = 1.5; # 1.5 - could be 1.0, or low speed 1.1/2.0 + $rev = '1.0'; + } + else { + undef $speed_si; # we don't know what the syntax was + } + return ($speed_si,$rev); +} + +# Try to guess at usb rev version from speed. Unreliable, very inaccurate. +# Note: this will probably be so inaccurate with USB 3.2/4 that it might be best +# to remove this feature at some point, unless better data sources found. +# args: 0: speed +sub usb_rev { + return if !$_[0] || !main::is_numeric($_[0]); + my $rev; + if ($_[0] < 2){ + $rev = '1.0';} + elsif ($_[0] < 13) + {$rev = '1.1';} + elsif ($_[0] < 481){ + $rev = '2.0';} + # 5 Gbps + elsif ($_[0] < 5001) + {$rev = '3.0';} + # 10 Gbps, this can be 3.1, 3.2 or 4 + elsif ($_[0] < 10001){ + $rev = '3.1';} + # SuperSpeed 'USB 20Gbps', this can be 3.2 or 4 + elsif ($_[0] < 20001){ + $rev = '3.2';} + # 4 does not use 4.x syntax, and real lanes/rev/speed data source required. + # 4: 10-120 Gbps. Update once data available for USB 3.2/4 speed strings + elsif ($_[0] < 120001){ + $rev = '4';} + return $rev; +} + +## UTILITIES ## +# This is used to create an alpha sortable bus id for main $usb[0] +sub bus_id_alpha { + my ($id) = @_; + $id =~ s/^([1-9])-/0$1-/; + $id =~ s/([-\.:])([0-9])\b/${1}0$2/g; + return $id; +} + +sub process_power { + return if !${$_[0]}; + ${$_[0]} =~ s/\s//g; + # ${$_[0]} = '' if ${$_[0]} eq '0mA'; # better to handle on output +} +} + +# note: seen android instance where reading file wakeup_count hangs endlessly. +# Some systems also report > 1 wakeup events per wakeup with +# /sys/power/wakeup_count, thus, we are using /sys/power/suspend_stats/success +# which does not appear to have that issue. +sub get_wakeups { + eval $start if $b_log; + return if %risc; + my ($path,$wakeups); + # this increments on suspend, but you can't see it until wake, numbers work. + $path = '/sys/power/suspend_stats/success'; + $wakeups = reader($path,'strip',0) if -r $path; + eval $end if $b_log; + return $wakeups; +} + +######################################################################## +#### GENERATE OUTPUT +######################################################################## + +## OutputGenerator +# Also creates Short, Info, and System items +{ +package OutputGenerator; +my ($items,$subs); + +sub generate { + eval $start if $b_log; + my ($item,%checks); + main::set_ps_aux() if !$loaded{'ps-aux'}; + main::set_sysctl_data() if $use{'sysctl'}; + main::set_dboot_data() if $bsd_type && !$loaded{'dboot'}; + # note: ps aux loads before logging starts, so create debugger data here + if ($b_log){ + # I don't think we need to see this, it's long, but leave in case we do + # main::log_data('dump','@ps_aux',\@ps_aux); + main::log_data('dump','@ps_cmd',\@ps_cmd); + } + if ($show{'short'}){ + $item = short_output(); + assign_data($item); + } + else { + if ($show{'system'}){ + $item = system_item(); + assign_data($item); + } + if ($show{'machine'}){ + DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'}; + $item = item_handler('Machine','machine'); + assign_data($item); + } + if ($show{'battery'}){ + DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'}; + $item = item_handler('Battery','battery'); + if ($item || $show{'battery-forced'}){ + assign_data($item); + } + } + if ($show{'ram'}){ + DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'}; + $item = item_handler('Memory','ram'); + assign_data($item); + } + if ($show{'slot'}){ + DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'}; + $item = item_handler('PCI Slots','slot'); + assign_data($item); + } + if ($show{'cpu'} || $show{'cpu-basic'}){ + DeviceData::set(\$checks{'device'}) if %risc && !$checks{'device'}; + DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'}; + my $arg = ($show{'cpu-basic'}) ? 'basic' : 'full' ; + $item = item_handler('CPU','cpu',$arg); + assign_data($item); + } + if ($show{'graphic'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + DeviceData::set(\$checks{'device'}) if !$checks{'device'}; + $item = item_handler('Graphics','graphic'); + assign_data($item); + } + if ($show{'audio'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + DeviceData::set(\$checks{'device'}) if !$checks{'device'}; + $item = item_handler('Audio','audio'); + assign_data($item); + } + if ($show{'network'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + DeviceData::set(\$checks{'device'}) if !$checks{'device'}; + IpData::set() if ($show{'ip'} || ($bsd_type && $show{'network-advanced'})); + $item = item_handler('Network','network'); + assign_data($item); + } + if ($show{'bluetooth'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + DeviceData::set(\$checks{'device'}) if !$checks{'device'}; + $item = item_handler('Bluetooth','bluetooth'); + assign_data($item); + } + if ($show{'logical'}){ + $item = item_handler('Logical','logical'); + assign_data($item); + } + if ($show{'raid'}){ + DeviceData::set(\$checks{'device'}) if !$checks{'device'}; + $item = item_handler('RAID','raid'); + assign_data($item); + } + if ($show{'disk'} || $show{'disk-basic'} || $show{'disk-total'} || $show{'optical'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + $item = item_handler('Drives','disk'); + assign_data($item); + } + if ($show{'partition'} || $show{'partition-full'}){ + $item = item_handler('Partition','partition'); + assign_data($item); + } + if ($show{'swap'}){ + $item = item_handler('Swap','swap'); + assign_data($item); + } + if ($show{'unmounted'}){ + $item = item_handler('Unmounted','unmounted'); + assign_data($item); + } + if ($show{'usb'}){ + UsbData::set(\$checks{'usb'}) if !$checks{'usb'}; + $item = item_handler('USB','usb'); + assign_data($item); + } + if ($show{'sensor'}){ + $item = item_handler('Sensors','sensor'); + assign_data($item); + } + if ($show{'repo'}){ + $item = item_handler('Repos','repo'); + assign_data($item); + } + if ($show{'process'}){ + $item = item_handler('Processes','process'); + assign_data($item); + } + if ($show{'weather'}){ + $item = item_handler('Weather','weather'); + assign_data($item); + } + if ($show{'info'}){ + $item = info_item(); + assign_data($item); + } + } + if ($output_type ne 'screen'){ + main::output_handler($items); + } + eval $end if $b_log; +} + +## Short, Info, System Items ## +sub short_output { + eval $start if $b_log; + my $num = 0; + my $kernel_os = ($bsd_type) ? 'OS' : 'Kernel'; + my ($cpu_string,$speed,$speed_key,$type) = ('','','speed',''); + my $cpu = CpuItem::get('short'); + if (ref $cpu eq 'ARRAY' && scalar @$cpu > 1){ + $type = ($cpu->[2]) ? " (-$cpu->[2]-)" : ''; + ($speed,$speed_key) = ('',''); + if ($cpu->[6]){ + $speed_key = "$cpu->[3]/$cpu->[5]"; + $speed = "$cpu->[4]/$cpu->[6] MHz"; + } + else { + $speed_key = $cpu->[3]; + $speed = "$cpu->[4] MHz"; + } + $cpu->[1] ||= main::message('cpu-model-null'); + $cpu_string = $cpu->[0] . ' ' . $cpu->[1] . $type; + } + elsif ($bsd_type){ + if ($alerts{'sysctl'}->{'action'}){ + if ($alerts{'sysctl'}->{'action'} ne 'use'){ + $cpu_string = "sysctl $alerts{'sysctl'}->{'action'}"; + $speed = "sysctl $alerts{'sysctl'}->{'action'}"; + } + else { + $cpu_string = 'bsd support coming'; + $speed = 'bsd support coming'; + } + } + } + $speed ||= 'N/A'; # totally unexpected situation, what happened? + my $disk = DriveItem::get('short'); + # print Dumper \@disk; + my $disk_string = 'N/A'; + my ($size,$used) = ('',''); + my ($size_holder,$used_holder); + if (ref $disk eq 'ARRAY' && @$disk){ + $size = ($disk->[0]{'logical-size'}) ? $disk->[0]{'logical-size'} : $disk->[0]{'size'}; + # must be > 0 + if ($size && main::is_numeric($size)){ + $size_holder = $size; + $size = main::get_size($size,'string'); + } + $used = $disk->[0]{'used'}; + if ($used && main::is_numeric($disk->[0]{'used'})){ + $used_holder = $disk->[0]{'used'}; + $used = main::get_size($used,'string'); + } + # in some fringe cases size can be 0 so only assign 'N/A' if no percents etc + if ($size_holder && $used_holder){ + my $percent = ' (' . sprintf("%.1f", $used_holder/$size_holder*100) . '% used)'; + $disk_string = "$size$percent"; + } + else { + $size ||= main::message('disk-size-0'); + $disk_string = "$used/$size"; + } + } + my $memory = MemoryData::get('short'); + $memory = 'N/A' if !$memory; + # print join('; ', @cpu), " sleep: $cpu_sleep\n"; + if (!$loaded{'shell-data'} && $ppid && (!$b_irc || !$client{'name-print'})){ + ShellData::set(); + } + my $client = $client{'name-print'}; + my $client_shell = ($b_irc) ? 'Client' : 'Shell'; + if ($client{'version'}){ + $client .= ' ' . $client{'version'}; + } + my $data = [{ + main::key($num++,0,0,'CPU') => $cpu_string, + main::key($num++,0,0,$speed_key) => $speed, + main::key($num++,0,0,$kernel_os) => join(' ', @{main::get_kernel_data()}), + main::key($num++,0,0,'Up') => main::get_uptime(), + main::key($num++,0,0,'Mem') => $memory, + main::key($num++,0,0,'Storage') => $disk_string, + # could make -1 for ps aux itself, -2 for ps aux and self + main::key($num++,0,0,'Procs') => scalar @ps_aux, + main::key($num++,0,0,$client_shell) => $client, + main::key($num++,0,0,$self_name) => main::get_self_version(), + },]; + eval $end if $b_log; + return { + main::key($prefix,1,0,'SHORT') => $data, + }; +} + +sub info_item { + eval $start if $b_log; + my $num = 0; + my $gcc_alt = ''; + my $running_in = ''; + my $data_name = main::key($prefix++,1,0,'Info'); + my ($b_gcc,$gcc,$index); + my ($available,$gpu_ram,$parent,$percent,$used) = ('',0,'','',''); + my $gccs = main::get_gcc_data(); + if (@$gccs){ + $gcc = shift @$gccs; + if ($extra > 1 && @$gccs){ + $gcc_alt = join('/', @$gccs); + } + $b_gcc = 1; + $gcc ||= 'N/A'; # should not be needed after fix but leave in case undef + } + my $data = { + $data_name => [{ + main::key($num++,0,1,'Processes') => scalar @ps_aux, + main::key($num++,1,1,'Uptime') => main::get_uptime(), + },], + }; + $index = scalar(@{$data->{$data_name}}) - 1; + if ($extra > 2){ + my $wakeups = main::get_wakeups(); + $data->{$data_name}[$index]{main::key($num++,0,2,'wakeups')} = $wakeups if defined $wakeups; + } + if (!$loaded{'memory'}){ + my $row = {}; + main::MemoryData::row('info',$data->{$data_name}[$index],\$num,1); + } + if ($gpu_ram){ + $data->{$data_name}[$index]{main::key($num++,0,2,'gpu')} = $gpu_ram; + } + if ((!$b_display || $force{'display'}) || $extra > 0){ + my $init = InitData::get(); + my $init_type = ($init->{'init-type'}) ? $init->{'init-type'}: 'N/A'; + $data->{$data_name}[$index]{main::key($num++,1,1,'Init')} = $init_type; + if ($extra > 1){ + my $init_version = ($init->{'init-version'}) ? $init->{'init-version'}: 'N/A'; + $data->{$data_name}[$index]{main::key($num++,0,2,'v')} = $init_version; + } + if ($init->{'rc-type'}){ + $data->{$data_name}[$index]{main::key($num++,1,2,'rc')} = $init->{'rc-type'}; + if ($init->{'rc-version'}){ + $data->{$data_name}[$index]{main::key($num++,0,3,'v')} = $init->{'rc-version'}; + } + } + if ($init->{'runlevel'}){ + my $key = ($init->{'init-type'} && $init->{'init-type'} eq 'systemd') ? 'target' : 'runlevel'; + $data->{$data_name}[$index]{main::key($num++,1,2,$key)} = $init->{'runlevel'}; + } + if ($extra > 1){ + if ($init->{'default'}){ + $data->{$data_name}[$index]{main::key($num++,0,3,'default')} = $init->{'default'}; + } + if ($b_admin && (my $tool = ServiceData::get('tool',''))){ + $data->{$data_name}[$index]{main::key($num++,0,2,'tool')} = $tool; + undef %service_tool; + } + } + } + if ($extra > 0){ + my $b_clang; + my $clang_version = ''; + if (my $path = main::check_program('clang')){ + $clang_version = main::program_version($path,'clang',3,'--version'); + $clang_version ||= 'N/A'; + $b_clang = 1; + } + my $compiler = ($b_gcc || $b_clang) ? '': 'N/A'; + $data->{$data_name}[$index]{main::key($num++,1,1,'Compilers')} = $compiler; + if ($b_gcc){ + $data->{$data_name}[$index]{main::key($num++,1,2,'gcc')} = $gcc; + if ($extra > 1 && $gcc_alt){ + $data->{$data_name}[$index]{main::key($num++,0,3,'alt')} = $gcc_alt; + } + } + if ($b_clang){ + $data->{$data_name}[$index]{main::key($num++,0,2,'clang')} = $clang_version; + } + } + if ($extra > 0 && !$loaded{'package-data'}){ + my $packages = PackageData::get('inner',\$num); + for (keys %$packages){ + $data->{$data_name}[$index]{$_} = $packages->{$_}; + } + } + if (!$loaded{'shell-data'} && $ppid && (!$b_irc || !$client{'name-print'})){ + ShellData::set(); + } + my $client_shell = ($b_irc) ? 'Client' : 'Shell'; + my $client = $client{'name-print'}; + if (!$b_irc && $extra > 1){ + # some bsds don't support -f option to get PPPID + # note: root/su - does not have $DISPLAY usually + if ($b_display && !$force{'display'} && $ppid && $client{'pppid'}){ + $parent = ShellData::shell_launcher(); + } + else { + ShellData::tty_number() if !$loaded{'tty-number'}; + if ($client{'tty-number'} ne ''){ + my $tty_type = ''; + if ($client{'tty-number'} =~ /^[a-f0-9]+$/i){ + $tty_type = 'tty '; + } + elsif ($client{'tty-number'} =~ /pts/i){ + $tty_type = 'pty '; + } + $parent = "$tty_type$client{'tty-number'}"; + } + } + # can be tty 0 so test for defined + $running_in = $parent if $parent; + if ($extra > 2 && $running_in && ShellData::ssh_status()){ + $running_in .= ' (SSH)'; + } + if ($extra > 2 && $client{'su-start'}){ + $client .= " ($client{'su-start'})"; + } + } + $data->{$data_name}[$index]{main::key($num++,1,1,$client_shell)} = $client; + if ($extra > 0 && $client{'version'}){ + $data->{$data_name}[$index]{main::key($num++,0,2,'v')} = $client{'version'}; + } + if (!$b_irc){ + if ($extra > 2 && $client{'default-shell'}){ + $data->{$data_name}[$index]{main::key($num++,1,2,'default')} = $client{'default-shell'}; + $data->{$data_name}[$index]{main::key($num++,0,3,'v')} = $client{'default-shell-v'} if $client{'default-shell-v'}; + } + if ($running_in){ + $data->{$data_name}[$index]{main::key($num++,0,2,'running-in')} = $running_in; + } + } + $data->{$data_name}[$index]{main::key($num++,0,1,$self_name)} = main::get_self_version(); + eval $end if $b_log; + return $data; +} + +sub system_item { + eval $start if $b_log; + my ($cont_desk,$ind_dm,$num) = (1,2,0); + my ($index); + my $data_name = main::key($prefix++,1,0,'System'); + my ($desktop,$desktop_info,$desktop_key,$dm_key,$toolkit,$wm) = ('','','Desktop','dm','',''); + my (@desktop_data,$cs_curr,$cs_avail,$desktop_version,$tk_version,$wm_version); + my $data = { + $data_name => [{}], + }; + $index = scalar(@{$data->{$data_name}}) - 1; + if ($show{'host'}){ + $data->{$data_name}[$index]{main::key($num++,0,1,'Host')} = main::get_hostname(); + } + my $kernel_data = main::get_kernel_data(); + $data->{$data_name}[$index]{main::key($num++,1,1,'Kernel')} = $kernel_data->[0]; + $data->{$data_name}[$index]{main::key($num++,0,2,'arch')} = $kernel_data->[1]; + $data->{$data_name}[$index]{main::key($num++,0,2,'bits')} = main::get_kernel_bits(); + if ($extra > 0){ + my $compiler = CompilerVersion::get(); # get compiler data + if (scalar @$compiler != 2){ + @$compiler = ('N/A', ''); + } + $data->{$data_name}[$index]{main::key($num++,1,2,'compiler')} = $compiler->[0]; + # if no compiler, obviously no version, so don't waste space showing. + if ($compiler->[0] ne 'N/A'){ + $compiler->[1] ||= 'N/A'; + $data->{$data_name}[$index]{main::key($num++,0,3,'v')} = $compiler->[1]; + } + } + if ($extra > 2){ + main::get_kernel_clocksource(\$cs_curr,\$cs_avail); + $cs_curr ||= 'N/A'; + $data->{$data_name}[$index]{main::key($num++,1,2,'clocksource')} = $cs_curr; + if ($b_admin && $cs_avail){ + $data->{$data_name}[$index]{main::key($num++,0,3,'available')} = $cs_avail; + } + } + if ($b_admin && (my $params = KernelParameters::get())){ + # $index = scalar(@{$data{$data_name}}); # not on own line for now + # print "$params\n"; + if ($use{'filter-label'}){ + $params = main::filter_partition('system', $params, 'label'); + } + if ($use{'filter-uuid'}){ + $params = main::filter_partition('system', $params, 'uuid'); + } + $data->{$data_name}[$index]{main::key($num++,0,2,'parameters')} = $params; + $index = scalar(@{$data->{$data_name}}); + } + # note: tty can have the value of 0 but the two tools + # return '' if undefined, so we test for explicit '' + if ($b_display){ + my $desktop_data = DesktopEnvironment::get(); + $desktop = $desktop_data->[0] if $desktop_data->[0]; + $desktop_version = $desktop_data->[1] if $desktop_data->[1]; + if ($extra > 0 && $desktop_data->[3]){ + $toolkit = $desktop_data->[2]; + $tk_version = $desktop_data->[3]; + } + if ($extra > 2 && $desktop_data->[4]){ + $desktop_info = $desktop_data->[4]; + } + # don't print the desktop if it's a wm and the same + if ($extra > 1 && $desktop_data->[5] && + (!$desktop_data->[0] || $desktop_data->[5] =~ /^(deepin.+|gnome[\s_-]shell|budgie.+)$/i || + index(lc($desktop_data->[5]),lc($desktop_data->[0])) == -1)){ + $wm = $desktop_data->[5]; + $wm_version = $desktop_data->[6] if $extra > 2 && $desktop_data->[6]; + } + } + if (!$b_display || (!$desktop && $b_root)){ + ShellData::tty_number() if !$loaded{'tty-number'}; + my $tty = $client{'tty-number'}; + if (!$desktop){ + $desktop_info = ''; + } + # it is defined, as '' + if ($tty eq '' && $client{'console-irc'}){ + ShellData::console_irc_tty() if !$loaded{'con-irc-tty'}; + $tty = $client{'con-irc-tty'}; + } + if ($tty ne ''){ + my $tty_type = ''; + if ($tty =~ /^[a-f0-9]+$/i){ + $tty_type = 'tty '; + } + elsif ($tty =~ /pts/i){ + $tty_type = 'pty '; + } + $desktop = "$tty_type$tty"; + } + $desktop_key = 'Console'; + $dm_key = 'DM'; + $ind_dm = 1; + $cont_desk = 0; + } + $desktop ||= 'N/A'; + $data->{$data_name}[$index]{main::key($num++,$cont_desk,1,$desktop_key)} = $desktop; + if ($desktop_version){ + $data->{$data_name}[$index]{main::key($num++,0,2,'v')} = $desktop_version; + } + if ($toolkit){ + $data->{$data_name}[$index]{main::key($num++,1,2,'tk')} = $toolkit; + } + if ($tk_version){ + $data->{$data_name}[$index]{main::key($num++,0,3,'v')} = $tk_version; + } + if ($extra > 2){ + if ($desktop_info){ + $data->{$data_name}[$index]{main::key($num++,0,2,'info')} = $desktop_info; + } + } + if ($extra > 1){ + if ($wm){ + $data->{$data_name}[$index]{main::key($num++,1,2,'wm')} = $wm; + if ($wm_version){ + $data->{$data_name}[$index]{main::key($num++,0,3,'v')} = $wm_version; + } + } + if ($extra > 2 && $b_display && defined $ENV{'XDG_VTNR'}){ + $data->{$data_name}[$index]{main::key($num++,0,2,'vt')} = $ENV{'XDG_VTNR'}; + } + my $dms = main::get_display_manager(); + # note: version only present if proper extra level so no need to test again + if (@$dms || $desktop_key ne 'Console'){ + if (@$dms && scalar @$dms > 1){ + my $i = 0; + $data->{$data_name}[$index]{main::key($num++,1,$ind_dm,$dm_key)} = ''; + foreach my $dm_data (@{$dms}){ + $i++; + $data->{$data_name}[$index]{main::key($num++,1,($ind_dm + 1),$i)} = $dm_data->[0]; + if ($dm_data->[1]){ + $data->{$data_name}[$index]{main::key($num++,0,($ind_dm + 2),'v')} = $dm_data->[1]; + } + if ($dm_data->[2]){ + $data->{$data_name}[$index]{main::key($num++,0,($ind_dm + 2),'note')} = $dm_data->[2]; + } + } + } + else { + my $dm = ($dms && $dms->[0][0]) ? $dms->[0][0] : 'N/A'; + $data->{$data_name}[$index]{main::key($num++,1,$ind_dm,$dm_key)} = $dm; + if ($dms && $dms->[0][1]){ + $data->{$data_name}[$index]{main::key($num++,0,($ind_dm + 1),'v')} = $dms->[0][1]; + } + } + + } + } + # if ($extra > 2 && $desktop_key ne 'Console'){ + # my $tty = ShellData::tty_number() if !$loaded{'tty-number'}; + # $data->{$data_name}[$index]{main::key($num++,0,1,'vc')} = $tty if $tty ne ''; + # } + my $distro_key = ($bsd_type) ? 'OS': 'Distro'; + my @distro_data = DistroData::get(); + my $distro = $distro_data[0]; + $distro ||= 'N/A'; + $data->{$data_name}[$index]{main::key($num++,1,1,$distro_key)} = $distro; + if ($extra > 0 && $distro_data[1]){ + $data->{$data_name}[$index]{main::key($num++,0,2,'base')} = $distro_data[1]; + } + eval $end if $b_log; + return $data; +} + +## Item Processors ## +sub assign_data { + return if !$_[0] || ref $_[0] ne 'HASH'; + if ($output_type eq 'screen'){ + main::print_data($_[0]); + } + else { + push(@$items,$_[0]); + } +} + +sub item_handler { + eval $start if $b_log; + my ($key,$item,$arg) = @_; + set_subs() if !$subs; + my $rows = $subs->{$item}($arg); + eval $end if $b_log; + if (ref $rows eq 'ARRAY' && @$rows){ + return {main::key($prefix++,1,0,$key) => $rows}; + } +} + +sub set_subs { + $subs = { + 'audio' => \&AudioItem::get, + 'battery' => \&BatteryItem::get, + 'bluetooth' => \&BluetoothItem::get, + 'cpu' => \&CpuItem::get, + 'disk' => \&DriveItem::get, + 'graphic' => \&GraphicItem::get, + 'logical' => \&LogicalItem::get, + 'machine' => \&MachineItem::get, + 'network' => \&NetworkItem::get, + 'partition' => \&PartitionItem::get, + 'raid' => \&RaidItem::get, + 'ram' => \&RamItem::get, + 'repo' => \&RepoItem::get, + 'process' => \&ProcessItem::get, + 'sensor' => \&SensorItem::get, + 'slot' => \&SlotItem::get, + 'swap' => \&SwapItem::get, + 'unmounted' => \&UnmountedItem::get, + 'usb' => \&UsbItem::get, + 'weather' => \&WeatherItem::get, + }; +} +} + +####################################################################### +#### LAUNCH +######################################################################## + +main(); ## From the End comes the Beginning + +## note: this EOF is needed for self updater, triggers the full download ok +###**EOF**### diff --git a/bin/jq b/bin/jq new file mode 100755 index 0000000000000000000000000000000000000000..7bf9129a90605ead73fb7335208b1ccdc5206d98 GIT binary patch literal 2255816 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

yYgFrLCqm7hQN;s|nryyhO^T*6ZuVSyQ*S|M;;C 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

8-boIVOp{rO6K9$Hg|F1d!ph*y!a4dz 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

oBk4XqKqE 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

NS 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%0A9&zXyA03c3tRVUF`&+9;?oMkc68Hw*j@Zq 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�@Kw7v$A0%uU> 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%0Dnmd`N(dLK&yrJfQ+4$hj&NE+csHbow1`d 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 literal 0 HcmV?d00001