X-Git-Url: https://git.quassel-irc.org/?p=quassel.git;a=blobdiff_plain;f=data%2Fscripts%2Finxi;h=a444e66bfa8e0721c9d6b982663323342ae866af;hp=30f9eddaf89cd47e9592c5d2d4df252e2a463705;hb=be8eb9963bb6c0e6522f818c51f9b6930adc7876;hpb=baf2ec741b854de2f14adf96c5875c7f4a330f1b diff --git a/data/scripts/inxi b/data/scripts/inxi index 30f9edda..a444e66b 100755 --- a/data/scripts/inxi +++ b/data/scripts/inxi @@ -1,3447 +1,18318 @@ -#!/bin/bash -######################################################################## -#### Script Name: inxi -#### version: 1.0.8 -#### Date: 13 March 2009 -######################################################################## -#### SPECIAL THANKS -######################################################################## -#### Special thanks to all those in lsc for their tireless dedication -#### with helping test inxi modules -######################################################################## -#### ABOUT INXI -######################################################################## -#### inxi is a fork of infobash 3.02, the original bash sys info script by locsmif -#### As time permits functionality improvements and recoding will occur. -#### -#### inxi, the universal, portable, system info script for irc. -#### Tested with Irssi, Xchat, Konversation, BitchX, KSirc, ircII, -#### Gaim/Pidgin, Weechat, KVIrc and Kopete. -#### Original infobash author and copyright holder: -#### Copyright (C) 2005-2007 Michiel de Boer a.k.a. locsmif -#### inxi version: Copyright (C) 2008-9 Scott Rogers & Harald Hope -#### Further fixes (listed as known): Horst Tritremmel -#### Steven Barrett (aka: damentz) - usb audio patch; swap percent used patch -#### -#### Current script home page: http://techpatterns.com/forums/about1131.html -#### Script svn: http://code.google.com/p/inxi -#### -#### 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. -#### -#### This program is distributed in the hope that it will be useful, -#### but WITHOUT ANY WARRANTY; without even the implied warranty of -#### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -#### GNU General Public License for more details. -#### -#### You should have received a copy of the GNU General Public License -#### 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 -######################################################################## -#### DEPENDENCIES -#### bash >=3.0(bash), df;readlink;stty;tr;uname;wc(coreutils), -#### gawk(gawk), grep(grep), hostname(hostname), lspci(pciutils), -#### ps;uptime(procps), glxinfo;xdpyinfo;xrandr(xbase-clients) -#### Also the proc filesystem should be present and mounted -#### -#### Apparently unpatched bash 3.0 has arrays broken; bug reports: -#### http://ftp.gnu.org/gnu/bash/bash-3.0-patches/bash30-008 -#### http://lists.gnu.org/archive/html/bug-bash/2004-08/msg00144.html -#### -#### Arrays work in bash 2.05b, but "egrep -m" does not -#### -#### RECOMMENDS (Needed to run certain features) -#### For local interfaces/IP test: ifconfig (in net-tools for Debian systems) -#### runlevel(sysvinit): to view current runlevel while not in X window system -#### Bash 3.1 for proper array use -######################################################################## -#### CONVENTIONS: -#### Indentation: TABS -#### Do not use `....`, those are totally non-reabable, use $(....) -#### Do not use one liner flow controls. The ONLY time you should use ; is in -#### this single case: if [[ condition ]];then (ie, never: [[ condition ]] && statement) -#### Note: [[ -n $something ]] - double brackets do not require quotes: "$something" for variables -#### Always use quotes, double or single, for all string values -#### -#### All new code/methods must be in a function. -#### For all boolean tests, use 'true' / 'false'. Do NOT use 0 or 1 unless -#### it's a function return. Avoid complicated tests in the if condition itself. -#### -#### For gawk: use always if ( num_of_cores > 1 ) { hanging { starter for all blocks -#### This lets us use one method for all gawk structures, including BEGIN/END, if, for, etc -#### -#### VARIABLE/FUNCTION NAMING: -#### All variables should explain what they are, except counters like i, j -#### All variables MUST be initialized / declared explicitly -####, globals UPPER CASE, at top of script, SOME_VARIABLE='' (words separated by _ ). -#### Locals always with: local some_variable= (lower case, words separated by _ ) -#### Locals that will be inherited by child functions: Some_Variable (so you know they are inherited) -#### and at the top of the function. -#### -#### Booleans should start with b_ or B_ and state clearly what is being tested -#### Arrays should start with a_ or A_ -#### All functions should follow standard naming, ie, verb adjective noun, get_cpu_data -#### -#### SPECIAL NOTES: -#### The color variable ${C2} must always be followed by a space unless you know what -#### character is going to be next for certain. Otherwise irc color codes can be accidentally -#### activated or altered. -#### -#### For native script konversation support (check distro for correct konvi scripts path): -#### ln -s /usr/share/apps/konversation/scripts/inxi -#### DCOP doesn't like \n, so avoid using it for most output unless required, as in error messages. -######################################################################## -#### Valuable Resources -#### awk arrays: http://www.math.utah.edu/docs/info/gawk_12.html -######################################################################## -#### TESTING FLAGS -#### inxi supports advanced testing triggers to do various things, using -! -#### -! 1 - triggers default B_TESTING_1='true' to trigger some test or other -#### -! 2 - triggers default B_TESTING_2='true' to trigger some test or other -#### -! 3 - triggers B_TESTING_1='true' and B_TESTING_2='true' -#### -! 10 - triggers an update from the primary dev download server instead of svn -#### -! 11 - triggers an update from svn branch one - if present, of course -#### -! 12 - triggers an update from svn branch two - if present, of course -#### -! 13 - triggers an update from svn branch three - if present, of course -#### -! 14 - triggers an update from svn branch four - if present, of course -#### -! - Triggers an update from whatever server you list. -######################################################################## -#### VARIABLES -######################################################################## +#!/usr/bin/env perl +## infobash: Copyright (C) 2005-2007 Michiel de Boer aka locsmif +## inxi: Copyright (C) 2008-2018 Harald Hope +## Additional features (C) Scott Rogers - kde, cpu info +## 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 with the /sys +## +## 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 -## NOTE: we can use hwinfo if it's available in all systems, or most, to get -## a lot more data and verbosity levels going - -### Variable initializations: null values -CMDL_MAX='' -COLOR_SCHEME='' -COLOR_SCHEME_SET='' -IRC_CLIENT='' -IRC_CLIENT_VERSION='' - -### primary data array holders -A_AUDIO_DATA='' -A_CMDL='' -A_CPU_CORE_DATA='' -A_CPU_DATA='' -A_CPU_TYPE_PCNT_CCNT='' -A_DEBUG_BUFFER='' -A_GFX_CARD_DATA='' -A_GLX_DATA='' -A_HDD_DATA='' -A_INTERFACES_DATA='' -A_NETWORK_DATA='' -A_PARTITION_DATA='' -A_X_DATA='' - -### Boolean true/false globals -# flag to allow distro maintainers to turn off update features. If false, turns off -# -U and -! testing/advanced update options, as well as removing the -U help menu item -B_ALLOW_UPDATE='true' -# triggers full display of cpu flags -B_CPU_FLAGS_FULL='false' -# Debug flood override: make 'true' to allow long debug output -B_DEBUG_FLOOD='false' -# show extra output data -B_EXTRA_DATA='false' -B_SHOW_DISK='false' -# override certain errors due to currupted data -B_HANDLE_CORRUPT_DATA='false' -# Running in a shell? Defaults to false, and is determined later. -B_RUNNING_IN_SHELL='false' -# this sets the debug buffer -B_SCRIPT_UP='false' -# Show sound card data -B_SHOW_AUDIO='false' -B_SHOW_CPU='false' -# Show full hard disk output -B_SHOW_FULL_HDD='false' -B_SHOW_GRAPHICS='false' -# Set this to 'false' to avoid printing the hostname -B_SHOW_HOST='true' -B_SHOW_INFO='false' -B_SHOW_IP='false' -B_SHOW_LABELS='false' -B_SHOW_NETWORK='false' -# either -v > 3 or -P will show partitions -B_SHOW_PARTITIONS='false' -B_SHOW_PARTITIONS_FULL='false' -# triggers only short inxi output -B_SHOW_SHORT_OUTPUT='false' -B_SHOW_SYSTEM='false' -B_SHOW_UUIDS='false' -# triggers various debugging and new option testing -B_TESTING_1='false' -B_TESTING_2='false' -# Test for X running -B_X_RUNNING='false' - -### Directory/file exist flags; test as [[ $(boolean) ]] not [[ $boolean ]] -B_PROC='false' -B_CPUINFO='false' -B_MEMINFO='false' -B_ASOUND_CARDS='false' -B_ASOUND_VERSION='false' -B_BASH_ARRAY='false' -B_IFCONFIG='false' -B_LSB_DIR='false' -B_SCSI_DIR='false' -B_MODULES_DIR='false' # -B_MOUNTS_DIR='false' -B_PARTITIONS_DIR='false' # - -### Directory's used when present -DIR_CPUINFO='/proc/cpuinfo' -DIR_MEMINFO='/proc/meminfo' -DIR_ASOUND_DEVICE='/proc/asound/cards' -DIR_ASOUND_VERSION='/proc/asound/version' -DIR_LSB_RELEASE='/etc/lsb-release' -DIR_SCSI='/proc/scsi/scsi' -DIR_MODULES='/proc/modules' # -DIR_MOUNTS='/proc/mounts' -DIR_PARTITIONS='/proc/partitions' # -DIR_IFCONFIG='/sbin/ifconfig' - -### Variable initializations: constants -DCOPOBJ="default" -DEBUG=0 # Set debug levels from 1-10 -# Debug Buffer Index, index into a debug buffer storing debug messages until inxi is 'all up' -DEBUG_BUFFER_INDEX=0 -## note: the debugger rerouting to /dev/null has been moved to the end of the get_parameters function -## so -@[number] debug levels can be set if there is a failure, otherwise you can't even see the errors - -# Defaults to 2, make this 1 for normal, 0 for no colorcodes at all. Set to any other valid scheme you like. -# Same as runtime parameter. -DEFAULT_SCHEME=2 -# Default indentation level -INDENT=10 -# default to false, no konversation found, 1 is /cmd inxi start, 2 is native konvi script mode -KONVI=0 -# NO_CPU_COUNT=0 # Wether or not the string "dual" or similar is found in cpuinfo output. If so, avoid dups. -# This is a variable that controls how many parameters inxi will parse in a /proc//cmdline file before stopping. -PARAMETER_LIMIT=30 -SCHEME=0 # set default scheme -# SHOW_IRC=1 to avoid showing the irc client version number, or SHOW_IRC=0 to disable client information completely. -SHOW_IRC=2 -# Verbosity level defaults to 0, this can also be set with -v0, -v2, -v3, etc as a parameter. -VERBOSITY_LEVEL=0 -# Supported number of verbosity levels, including 0 -VERBOSITY_LEVELS=5 - -# Clear nullglob, because it creates unpredictable situations with IFS=$'\n' ARR=($VAR) IFS="$ORIGINAL_IFS" -# type constructs. Stuff like [rev a1] is now seen as a glob expansion pattern, and fails, and -# therefore results in nothing. Tricky as fuck. -shopt -u nullglob -## info on bash built in: $IFS - http://tldp.org/LDP/abs/html/internalvariables.html -# Backup the current Internal Field Separator -ORIGINAL_IFS="$IFS" -# These two determine separators in single line output, to force irc clients not to break off sections -SEP1='-' -SEP2='~' - -### Script names/paths -SCRIPT_NAME="inxi" -SCRIPT_PATH=$( dirname $0 ) -SCRIPT_VERSION_NUMBER=$( grep -im 1 'version:' $SCRIPT_PATH/$SCRIPT_NAME | gawk '{print $3}' ) -SCRIPT_DOWNLOAD='http://inxi.googlecode.com/svn/trunk/' -SCRIPT_DOWNLOAD_BRANCH_1='http://inxi.googlecode.com/svn/branches/one/' -SCRIPT_DOWNLOAD_BRANCH_2='http://inxi.googlecode.com/svn/branches/two/' -SCRIPT_DOWNLOAD_BRANCH_3='http://inxi.googlecode.com/svn/branches/three/' -SCRIPT_DOWNLOAD_BRANCH_4='http://inxi.googlecode.com/svn/branches/four/' -SCRIPT_DOWNLOAD_DEV='http://smxi.org/test/' -KONVI_CFG="konversation/scripts/$SCRIPT_NAME.conf" # relative path to $(kde-config --path data) - -### Script Localization -# Make sure every program speaks English. -LC_ALL="C" -export LC_ALL - -### Output Colors -# A more elegant way to have a scheme that doesn't print color codes (neither ANSI nor mIRC) at all. See below. -unset EMPTY -# DGREY BLACK RED DRED GREEN DGREEN YELLOW DYELLOW -ANSI_COLORS="       " -IRC_COLORS=" \x0314 \x0301 \x0304 \x0305 \x0309 \x0303 \x0308 \x0307" -# BLUE DBLUE MAGENTA DMAGENTA CYAN DCYAN WHITE GREY NORMAL -ANSI_COLORS="$ANSI_COLORS         " -IRC_COLORS=" $IRC_COLORS \x0312 \x0302 \x0313 \x0306 \x0311 \x0310 \x0300 \x0315 \x03" -#ANSI_COLORS=($ANSI_COLORS); IRC_COLORS=($IRC_COLORS) -A_COLORS_AVAILABLE=( DGREY BLACK RED DRED GREEN DGREEN YELLOW DYELLOW BLUE DBLUE MAGENTA DMAGENTA CYAN DCYAN WHITE GREY NORMAL ) -# See above for notes on EMPTY -A_COLOR_SCHEMES=( EMPTY,EMPTY,EMPTY NORMAL,NORMAL,NORMAL BLUE,NORMAL,NORMAL GREEN,YELLOW,NORMAL DYELLOW,NORMAL,NORMAL CYAN,BLUE,NORMAL RED,NORMAL,NORMAL GREEN,NORMAL,NORMAL YELLOW,NORMAL,NORMAL GREEN,DGREEN,NORMAL BLUE,RED,NORMAL BLUE,NORMAL,RED YELLOW,WHITE,GREEN BLUE,NORMAL,GREEN DCYAN,NORMAL,DMAGENTA ) -## Actual color variables -C1='' -C2='' -CN='' - -### Distro Data -# In cases of derived distros where the version file of the base distro can also be found under /etc, -# the derived distro's version file should go first. (Such as with Sabayon / Gentoo) -DISTROS_DERIVED="antix-version kanotix-version knoppix-version mandrake-release pardus-release sabayon-release sidux-version turbolinux-release zenwalk-version" -# debian_version excluded from DISTROS_PRIMARY so Debian can fall through to /etc/issue detection. Same goes for Ubuntu. -DISTROS_EXCLUDE_LIST="debian_version ubuntu_version" -DISTROS_PRIMARY="gentoo-release redhat-release slackware-version SuSE-release" -DISTROS_LSB_GOOD="mandrake-release mandriva-release mandrakelinux-release" -## Distros with known problems -# DSL (Bash 2.05b: grep -m doesn't work; arrays won't work) --> unusable output -# Puppy Linux 4.1.2 (Bash 3.0: arrays won't work) --> works partially - -### Bans Data -# Precede a banword with $'\2' to prevent it from being subject to automated escaping by the make_ban_lists routine -# $'\1' gets weird results : -# user@host $ ARR=($'\x01'"one two" three four); echo ${ARR[0]} | hd -v -# 00000000 01 01 6f 6e 65 20 74 77 6f 0a |..one two.| -A_NORMAL_BANS=( corporation communications gmbh technologies technology group $'\2'"\" ltd. $'\2'"\" inc. $'\2'\ co. "(tm)" "(r)" "®" $'\2'"\(rev ..\)" ) -A_CPU_BANS=( @ cpu deca 'dual core' dual-core 'tri core' tri-core 'quad core' quad-core ennea genuine hepta hexa multi octa penta 'processor' processor single triple $'\2'"[0-9.]+ *[MmGg][Hh][Zz]" ) -# after processing, the ban arrays will be put into these: -BAN_LIST_NORMAL='' -BAN_LIST_CPU='' - -### Source global config overrides -if [[ -s /etc/$SCRIPT_NAME.conf ]];then - source /etc/$SCRIPT_NAME.conf -fi -# Source user config overrides -if [[ -s $HOME/.$SCRIPT_NAME ]];then - source $HOME/.$SCRIPT_NAME -fi - -# WARNING: In the main part below (search for 'KONVI') -# there's a check for Konversation-specific config files. -# Any one of these can override the above if inxi is run -# from Konversation! +use strict; +use warnings; +# use diagnostics; +use 5.008; -######################################################################## -#### MAIN: Where it all begins -######################################################################## -main() -{ - # first init function must be set first for colors etc. Remember, no debugger - # stuff works on this function unless you set the debugging flag - # manually. Debugging flag -@ [number] will not work until get_parameters runs. - initialize_script_data - - ## this needs to run before the KONVI stuff is set below - get_start_client - - # Check for dependencies before running anything else except above functions - check_script_depends - check_script_suggested_apps - - # note: this only works if it's run from inside konversation as a script builtin or something - # only do this if inxi has been started as a konversation script, otherwise bypass this - if [[ $KONVI -eq 1 ]];then - DCPORT="$1" - DCSERVER="$2" - DCTARGET="$3" - shift 3 - # The section below is on request of Argonel from the Konversation developer team: - # it sources config files like $HOME/.kde/share/apps/konversation/scripts/inxi.conf - IFS=":" - for kde_config in $( kde-config --path data ) - do - if [[ -r ${kde_config}${KONVI_CFG} ]];then - source "${kde_config}${KONVI_CFG}" - break - fi - done - IFS="$ORIGINAL_IFS" - fi - ## leave this for debugging dcop stuff if we get that working - # print_screen_output "DCPORT: $DCPORT" - # print_screen_output "DCSERVER: $DCSERVER" - # print_screen_output "DCTARGET: $DCTARGET" - - # "$@" passes every parameter separately quoted, "$*" passes all parameters as one quoted parameter. - # must be here to allow debugger and other flags to be set. - get_parameters "$@" - - # If no colorscheme was set in the parameter handling routine, then set the default scheme - if [[ $COLOR_SCHEME_SET != 'true' ]];then - set_color_scheme "$DEFAULT_SCHEME" - fi - - # all the pre-start stuff is in place now - B_SCRIPT_UP='true' - script_debugger "Debugger: $SCRIPT_NAME is up and running..." - - # then create the output - print_it_out - - ## last steps - if [[ $B_RUNNING_IN_SHELL == 'true' && $SCHEME -gt 0 ]];then - echo -n "" - fi - - # 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 -} +use Cwd qw(abs_path); # qw(abs_path);#abs_path realpath getcwd +use Data::Dumper qw(Dumper); # print_r +use File::Find; +use Getopt::Long qw(GetOptions); +# Note: default auto_abbrev is enabled, that's fine +Getopt::Long::Configure ('bundling', 'no_ignore_case', +'no_getopt_compat', 'no_auto_abbrev','pass_through'); +use POSIX qw(uname strftime ttyname); +# use feature qw(state); -#### ------------------------------------------------------------------- -#### basic tests: set script data, booleans, PATH -#### ------------------------------------------------------------------- +## INXI INFO ## +my $self_name='inxi'; +my $self_version='3.0.18'; +my $self_date='2018-07-16'; +my $self_patch='00'; +## END INXI INFO ## -# Set PATH data so we can access all programs as user. Set BAN lists. -# initialize some boleans, these directories are used throughout the script -# some apps are used for extended functions any directory used, should be -# checked here first. -initialize_script_data() -{ - local path='' sys_path='' added_path='' b_path_found='' - # Extra path variable to make execute failures less likely, merged below - local extra_paths="/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin" - - # Fallback paths put into $extra_paths; This might, among others, help on gentoo. - # Now, create a difference of $PATH and $extra_paths and add that to $PATH: - IFS=":" - for path in $extra_paths - do - b_path_found='false' - for sys_path in $PATH - do - if [[ $path == $sys_path ]];then - b_path_found='true' - fi - done - if [[ $b_path_found == 'false' ]];then - added_path="$added_path:$path" - fi - done - IFS="$ORIGINAL_IFS" - PATH="${PATH}${added_path}" - ##echo "PATH='$PATH'" - ##/bin/sh -c 'echo "PATH in subshell=\"$PATH\""' - - # Do this after sourcing of config overrides so user can customize banwords - BAN_LIST_NORMAL=$( make_ban_lists "${A_NORMAL_BANS[@]}" ) # Contrary to my previous belief, "${ARR[@]}" passes a quoted list, not one string - BAN_LIST_CPU=$( make_ban_lists "${A_CPU_BANS[@]}" ) - ##echo "BAN_LIST_NORMAL='$BAN_LIST_NORMAL'" - - # now set the script BOOLEANS for files required to run features - if [[ -d "/proc/" ]];then - B_PROC='true' - else - error_handler 6 - fi - - if [[ -e $DIR_CPUINFO ]]; then - B_CPUINFO='true' - fi - - if [[ -e $DIR_MEMINFO ]];then - B_MEMINFO='true' - fi - - if [[ -e $DIR_ASOUND_DEVICE ]];then - B_ASOUND_CARDS='true' - fi - - if [[ -e $DIR_ASOUND_VERSION ]];then - B_ASOUND_VERSION='true' - fi - - if [[ -f $DIR_LSB_RELEASE ]];then - B_LSB_DIR='true' - fi - - if [[ -e $DIR_SCSI ]];then - B_SCSI_DIR='true' - fi - - # lack of ifconfig will throw an error only upon it's usage - if [[ -x ifconfig ]]; then - B_IFCONFIG='true' - DIR_IFCONFIG='ifconfig' # change from full path to use $PATH - elif [[ -x $DIR_IFCONFIG ]];then - B_IFCONFIG='true' - else - A_INTERFACES_DATA=( "Interfaces tool requires missing app: $DIR_IFCONFIG" ) - fi - - if [[ -n $DISPLAY ]];then - B_X_RUNNING='true' - fi - - if [[ -e $DIR_MODULES ]];then - B_MODULES_DIR='true' - fi - - if [[ -e $DIR_MOUNTS ]];then - B_MOUNTS_DIR='true' - fi - - if [[ -e $DIR_PARTITIONS ]];then - B_PARTITIONS_DIR='true' - fi -} - -check_script_suggested_apps() -{ - local bash_array_test=( "one" "two" ) - - # check for array ability of bash, this is only good for the warning at this time - # the boolean could be used later - # bash version 2.05b is used in DSL - # bash version 3.0 is used in Puppy Linux - # versions older than 3.1 don't handle arrays - # distro's using below 2.05b are unknown, released in 2002 - if [[ ${bash_array_test[1]} -eq "two" ]];then - B_BASH_ARRAY='true' - else - script_debugger "Suggestion: update to Bash v3.1 for optimal inxi output" - fi -} - -# Determine if any of the absolutely necessary tools are absent -check_script_depends() -{ - local app_name='' app_data='' - # bc removed from deps for now - local depends="df free gawk grep hostname lspci ps readlink tr uname uptime wc" - - if [[ $B_X_RUNNING == 'true' ]];then - for app_name in xrandr xdpyinfo glxinfo - do - app_data=$( type -p $app_name ) - if [[ -z $app_data ]];then - script_debugger "Resuming in non X mode: $app_name not found in path" - B_X_RUNNING='false' - break - fi - done - fi - - app_name='' - - for app_name in $depends - do - app_data=$( type -p $app_name ) - if [[ -z $app_data ]];then - error_handler 5 "$app_name" - fi - done -} - -## note: this is now running inside each gawk sequence directly to avoid exiting gawk -## looping in bash through arrays, then re-entering gawk to clean up, then writing back to array -## in bash. For now I'll leave this here because there's still some interesting stuff to get re methods -# Enforce boilerplate and buzzword filters -# args: $1 - BAN_LIST_NORMAL/BAN_LIST_CPU; $2 - string to sanitize -sanitize_characters() -{ - # Cannot use strong quotes to unquote a string with pipes in it! - # bash will interpret the |'s as usual and try to run a subshell! - # Using weak quotes instead, or use '"..."' - echo "$2" | gawk " - BEGIN { - IGNORECASE=1 - } - { - gsub(/${!1}/,\"\") - gsub(/ [ ]+/,\" \") ## ([ ]+) with (space) - gsub(/^ +| +$/,\"\") ## (pipe char) with (nothing) - print ## prints (returns) cleaned input - }" -} - -# Filter boilerplate & buzzwords. -# args: $1 - quoted: "$@" array of ban terms -make_ban_lists() -{ - local ban_list='' - # Iterate over $@ - ## note: this is a weird, non-intuitive method, needs some documentation or rewriting - ## if you declare ban_string it stops working, have to read up on this - for ban_string - do - # echo "term=\"$ban_string\"" # >&2 - if [[ ${ban_string:0:1} = $'\2' ]];then - ban_list="${ban_list}${ban_list+|}${ban_string:1:${#ban_string}-1}" - else - # Automatically escapes [ ] ( ) . and + - ban_list="${ban_list}${ban_list+|}$( echo "$ban_string" | gawk '{ - gsub(/([\[\]+().])/,"\\\\&") - print - }' )" - fi - done - - echo "$ban_list" -} -# make_ban_lists "${A_CPU_BANS[@]}";exit - -# Set the colorscheme -# args: $1 = |<"none"> -set_color_scheme() -{ - local i='' script_colors='' color_codes='' - - if [[ $1 -ge ${#A_COLOR_SCHEMES[@]} ]];then - set -- 1 - fi - # Set a global variable to allow checking for chosen scheme later - SCHEME="$1" - if [[ $B_RUNNING_IN_SHELL == 'true' ]];then - color_codes=( $ANSI_COLORS ) - else - color_codes=( $IRC_COLORS ) - fi - for (( i=0; i < ${#A_COLORS_AVAILABLE[@]}; i++ )) - do - eval "${A_COLORS_AVAILABLE[i]}=\"${color_codes[i]}\"" - done - IFS="," - script_colors=( ${A_COLOR_SCHEMES[$1]} ) - IFS="$ORIGINAL_IFS" - # then assign the colors globally - C1="${!script_colors[0]}" - C2="${!script_colors[1]}" - CN="${!script_colors[2]}" - # ((COLOR_SCHEME++)) ## note: why is this? ## +### INITIALIZE VARIABLES ### + +## Self data +my ($self_path, $user_config_dir, $user_config_file,$user_data_dir); + +## Debuggers +my $debug=0; +my (@t0,$end,$start,$fh_l,$log_file); # log file handle, file +my ($b_hires,$t1,$t2,$t3) = (0,0,0,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 +## Hashes +my ( %alerts,%client,%colors,%dl,%files,%rows,%system_files,%use ); -######################################################################## -#### UTILITY FUNCTIONS -######################################################################## +## Arrays +# ps_aux is full output, ps_cmd is only the last 10 columns to last +my (@app,@dmesg_boot,@dmi,@gpudata,@ifs,@ifs_bsd,@paths,@pci,@ps_aux, +@ps_cmd,@ps_gui,@sysctl,@sysctl_battery,@sysctl_sensors,@sysctl_machine, +@uname,@usb); +## Disk arrays +my (@dm_boot_disk,@dm_boot_optical,@glabel,@gpart,@hardware_raid,@labels, +@lsblk,@partitions,@raid,@sysctl_disks,@uuids); +my @test = (0,0,0,0,0); -#### ------------------------------------------------------------------- -#### error handler, debugger, script updater -#### ------------------------------------------------------------------- +## Booleans +my ($b_admin,$b_arm,$b_console_irc,$b_debug_gz,$b_debug_timers, +$b_display,$b_dmesg_boot_check, +$b_dmi,$b_dmidecode_force,$b_fake_bsd,$b_fake_dboot,$b_fake_pciconf, +$b_fake_sysctl,$b_fake_usbdevs,$b_force_display,$b_gpudata,$b_irc, +$b_log,$b_log_colors,$b_log_full,$b_man,$b_mem,$b_mips,$b_pci,$b_pci_tool, +$b_proc_debug,$b_ps_gui,$b_root,$b_running_in_display,$b_slot_tool, +$b_soc_audio,$b_soc_gfx,$b_soc_net,$b_sudo,$b_sysctl,$b_usb_check,$b_wmctrl); +## Disk checks +my ($b_dm_boot_disk,$b_dm_boot_optical,$b_glabel,$b_hardware_raid, +$b_label_uuid,$b_lsblk,$b_partitions,$b_raid); +my ($b_sysctl_disk,$b_update,$b_weather) = (1,1,1); -# Error handling -# args: $1 - error number; $2 - optional, extra information -error_handler() -{ - local error_message='' - - # assemble the error message - case $1 in - 2) error_message="large flood danger, debug buffer full!" - ;; - 3) error_message="unsupported color scheme number: $2" - ;; - 4) error_message="unsupported verbosity level: $2" - ;; - 5) error_message="dependency not met: $2 not found in path" - ;; - 6) error_message="/proc not found! Quitting..." - ;; - 7) error_message="One of the options you entered in your script parameters: $2\nIs not supported. For supported options, check the help menu: $SCRIPT_NAME -h" - ;; - 8) error_message="the self-updater failed, wget exited with error: $2.\nYou probably need to be root.\nHint, to make for easy updates without being root, do: chown $SCRIPT_PATH/$SCRIPT_NAME" - ;; - 9) error_message="unsupported debugging level: $2" - ;; - 10) - error_message="the alt download url you provided: $2\nappears to be wrong, download aborted. Please note, the url\nneeds to end in /, without $SCRIPT_NAME, like: http://yoursite.com/downloads/" - ;; - 11) - error_message="unsupported testing option argument: -! $2" - ;; - 12) - error_message="the svn branch download url: $2\nappears to be empty currently. Make sure there is an actual svn branch version\nactive before you try this again. Check http://code.google.com/p/inxi\nto verify the branch status." - ;; - *) error_message="error unknown: $@" - set -- 99 - ;; - esac - # then print it and exit - print_screen_output "Error $1: $error_message" - exit $1 -} - -# prior to script up set, pack the data into an array -# then we'll print it out later. -# args: $1 - $@ debugging string text -script_debugger() -{ - if [[ $B_SCRIPT_UP == 'true' ]];then - # only return if debugger is off and no pre start up errors have occured - if [[ $DEBUG -eq 0 && $DEBUG_BUFFER_INDEX -eq 0 ]];then - return 0 - # print out the stored debugging information if errors occured - elif [[ $DEBUG_BUFFER_INDEX -gt 0 ]];then - for (( DEBUG_BUFFER_INDEX=0; DEBUG_BUFFER_INDEX < ${#A_DEBUG_BUFFER[@]}; DEBUG_BUFFER_INDEX++ )) - do - print_screen_output "${A_DEBUG_BUFFER[$DEBUG_BUFFER_INDEX]}" - done - DEBUG_BUFFER_INDEX=0 - fi - # or print out normal debugger messages if debugger is on - if [[ $DEBUG -gt 0 ]];then - print_screen_output "$1" - fi - else - if [[ $B_DEBUG_FLOOD == 'true' && $DEBUG_BUFFER_INDEX -gt 10 ]];then - error_handler 2 - # this case stores the data for later printout, will print out only - # at B_SCRIPT_UP == 'true' if array index > 0 - else - A_DEBUG_BUFFER[$DEBUG_BUFFER_INDEX]="$1" - # increment count for next pre script up debugging error - (( DEBUG_BUFFER_INDEX++ )) - fi - fi -} - -# args: $1 - download url, not including file name; $2 - string to print out -# note that $1 must end in / to properly construct the url path -script_self_updater() -{ - local wget_error=0 - print_screen_output "Starting $SCRIPT_NAME self updater." - print_screen_output "Currently running $SCRIPT_NAME version number: $SCRIPT_VERSION_NUMBER" - print_screen_output "Updating $SCRIPT_NAME in $SCRIPT_PATH using $2 as download source..." - # first test if path is good, need to make sure it's good because we're -O overwriting file - wget -q --spider $1$SCRIPT_NAME || wget_error=$? - # then do the actual download - if [[ $wget_error -eq 0 ]];then - wget -q -O $SCRIPT_PATH/$SCRIPT_NAME $1$SCRIPT_NAME || wget_error=$? - if [[ $wget_error -eq 0 ]];then - SCRIPT_VERSION_NUMBER=$( grep -im 1 'version:' $SCRIPT_PATH/$SCRIPT_NAME | gawk '{print $3}' ) - print_screen_output "Successfully updated to $2 version: $SCRIPT_VERSION_NUMBER" - print_screen_output "To run the new version, just start $SCRIPT_NAME again." - exit 0 - fi - fi - # now run the error handlers on any wget failure - if [[ $wget_error -gt 0 ]];then - if [[ $2 == 'svn server' ]];then - error_handler 8 "$wget_error" - elif [[ $2 == 'alt server' ]];then - error_handler 10 "$1" - else - error_handler 12 "$1" - fi - fi -} +## System +my ($bsd_type,$language,$os) = ('','',''); +my ($bits_sys,$cpu_arch); +my ($cpu_sleep,$dl_timeout,$limit,$ps_count,$usb_level) = (0.35,4,10,5,0); +my $sensors_cpu_nu = 0; +my $weather_unit='mi'; -#### ------------------------------------------------------------------- -#### print / output cleaners -#### ------------------------------------------------------------------- +## Tools +my ($display,$ftp_alt,$tty_session); +my ($display_opt,$sudo) = ('',''); -# inxi speaks through here. When run by Konversation script alias mode, uses DCOP -# for dcop to work, must use 'say' operator, AND colors must be evaluated by echo -e -# note: dcop does not seem able to handle \n so that's being stripped out and replaced with space. -print_screen_output() -{ - # the double quotes are needed to avoid losing whitespace in data when certain output types are used - local print_data="$( echo -e "$1" )" - - if [[ $DEBUG -gt 5 ]];then - if [[ $KONVI -eq 1 ]];then - # konvi doesn't seem to like \n characters, it just prints them literally - print_data="$( tr '\n' ' ' <<< "$print_data" )" - dcop "$DCPORT" "$DCOPOBJ" say "$DCSERVER" "$DCTARGET" "konvi='$KONVI' saying : '$print_data'" - elif [[ $KONVI -eq 2 ]];then - echo "konvi='$KONVI' saying : '$print_data'" - else - echo "printing out: '$print_data'" - fi - fi - - if [[ $KONVI -eq 1 ]];then - print_data="$( tr '\n' ' ' <<< "$print_data" )" - dcop "$DCPORT" "$DCOPOBJ" say "$DCSERVER" "$DCTARGET" "$print_data" - else - # the -n is needed to avoid double spacing of output in terminal - echo -ne "$print_data\n" - fi -} - -## this handles all verbose line construction with indentation/line starter -## args: $1 - null (, actually: " ") or line starter; $2 - line content -create_print_line() -{ - printf "${C1}%-${INDENT}s${C2} %s" "$1" "$2" -} +## 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 primiary row hash key prefix -# this removes newline and pipes. -# args: $1 - string to clean -remove_erroneous_chars() -{ - ## RS is input record separator - ## gsub is substitute; - gawk ' - BEGIN { - RS="" - } - { - gsub(/\n$/,"") ## (newline; end of string) with (nothing) - gsub(/\n/," "); ## (newline) with (space) - gsub(/^ *| *$/, "") ## (pipe char) with (nothing) - gsub(/ +/, " ") ## ( +) with (space) - gsub(/ [ ]+/, " ") ## ([ ]+) with (space) - gsub(/^ +| +$/, "") ## (pipe char) with (nothing) - printf $0 - }' "$1" ## prints (returns) cleaned input -} +# these will 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' => ':', +); -#### ------------------------------------------------------------------- -#### parameter handling, print usage functions. -#### ------------------------------------------------------------------- +my %show = ('host' => 1); -# Get the parameters. Note: standard options should be lower case, advanced or testing, upper -# args: $1 - full script startup args: $@ -get_parameters() -{ - local opt='' wget_test='' update_flags='U!:' - local use_short='true' # this is needed to trigger short output, every v/d/F/line trigger sets this false - - if [[ $B_ALLOW_UPDATE == 'false' ]];then - update_flags='' - fi - - # the short form only runs if no args output args are used - # no need to run through these if there are no args - if [[ -n $1 ]];then - while getopts Ac:CdDfFGhHiIlNpPSuv:Vx%@:${update_flags} opt - do - case $opt in - A) B_SHOW_AUDIO='true' - use_short='false' - ;; - c) if [[ -n $( egrep '^[0-9][0-9]?$' <<< $OPTARG ) ]];then - COLOR_SCHEME_SET='true' - ## note: not sure about this, you'd think user values should be overridden, but - ## we'll leave this for now - if [[ -z $COLOR_SCHEME ]];then - set_color_scheme "$OPTARG" - fi - else - error_handler 3 "$OPTARG" - fi - ;; - C) B_SHOW_CPU='true' - use_short='false' - ;; - d) VERBOSITY_LEVEL=1 - use_short='false' - ;; - D) B_SHOW_DISK='true' - use_short='false' - ;; - f) B_SHOW_CPU='true' - B_CPU_FLAGS_FULL='true' - use_short='false' - ;; - F) VERBOSITY_LEVEL=$VERBOSITY_LEVELS - B_EXTRA_DATA='true' - B_SHOW_DISK='true' - B_SHOW_PARTITIONS='true' - B_SHOW_AUDIO='true' - use_short='false' - ;; - G) B_SHOW_GRAPHICS='true' - use_short='false' - ;; - i) B_SHOW_IP='true' - B_SHOW_NETWORK='true' - use_short='false' - ;; - I) B_SHOW_INFO='true' - use_short='false' - ;; - l) B_SHOW_LABELS='true' - B_SHOW_PARTITIONS='true' - use_short='false' - ;; - N) B_SHOW_NETWORK='true' - use_short='false' - ;; - p) B_SHOW_PARTITIONS_FULL='true' - B_SHOW_PARTITIONS='true' - use_short='false' - ;; - P) B_SHOW_PARTITIONS='true' - use_short='false' - ;; - S) B_SHOW_SYSTEM='true' - use_short='false' - ;; - u) B_SHOW_UUIDS='true' - B_SHOW_PARTITIONS='true' - use_short='false' - ;; - v) if [[ -n $( egrep "^[0-9][0-9]?$" <<< $OPTARG ) && $OPTARG -le $VERBOSITY_LEVELS ]];then - VERBOSITY_LEVEL="$OPTARG" - if [[ $OPTARG -gt 0 ]];then - use_short='false' - fi - else - error_handler 4 "$OPTARG" - fi - ;; - U) script_self_updater "$SCRIPT_DOWNLOAD" 'svn server' - ;; - V) print_version_info - exit 0 - ;; - x) B_EXTRA_DATA='true' - ;; - h) show_options - exit 0 - ;; - H) show_options 'full' - exit 0 - ;; - ## debuggers and testing tools - %) B_HANDLE_CORRUPT_DATA='true' - ;; - @) if [[ -n $( egrep "^([1-9]|10)$" <<< $OPTARG ) ]];then - DEBUG=$OPTARG - exec 2>&1 - else - error_handler 9 "$OPTARG" - fi - ;; - !) # test for various supported methods - case $OPTARG in - 1) B_TESTING_1='true' - ;; - 2) B_TESTING_2='true' - ;; - 3) B_TESTING_1='true' - B_TESTING_2='true' - ;; - 10) - script_self_updater "$SCRIPT_DOWNLOAD_DEV" 'dev server' - ;; - 11) - script_self_updater "$SCRIPT_DOWNLOAD_BRANCH_1" 'svn: branch one server' - ;; - 12) - script_self_updater "$SCRIPT_DOWNLOAD_BRANCH_2" 'svn: branch two server' - ;; - 13) - script_self_updater "$SCRIPT_DOWNLOAD_BRANCH_3" 'svn: branch three server' - ;; - 14) - script_self_updater "$SCRIPT_DOWNLOAD_BRANCH_4" 'svn: branch four server' - ;; - http*) - script_self_updater "$OPTARG" 'alt server' - ;; - *) error_handler 11 "$OPTARG" - ;; - esac - ;; - *) error_handler 7 "$1" - ;; - esac - done - fi - ## this must occur here so you can use the debugging flag to show errors - ## Reroute all error messages to the bitbucket (if not debugging) - if [[ $DEBUG -eq 0 ]];then - exec 2>/dev/null - fi - #((DEBUG)) && exec 2>&1 # This is for debugging konversation - - # after all the args have been processed, if no long output args used, run short output - if [[ $use_short == 'true' ]];then - B_SHOW_SHORT_OUTPUT='true' - fi -} - -## print out help menu, not including Testing or Debugger stuff because it's not needed -show_options() -{ - local color_scheme_count=${#A_COLOR_SCHEMES[@]} - - print_screen_output "$SCRIPT_NAME supports the following options. You can combine them, or list them" - print_screen_output "one by one: Examples: $SCRIPT_NAME -v4 -c6 OR $SCRIPT_NAME -dDc 6" - print_screen_output "" - print_screen_output "If you start $SCRIPT_NAME with no arguments, it will show the short form." - print_screen_output "The following options if used without -d or -v will show just that complete line:" - print_screen_output "A,C,D,G,I,N,P,S - you can use these together to show just the lines you want to see." - print_screen_output "If you use them with a -v level (or -d), it will show the full output for that line " - print_screen_output "along with the output for the chosen verbosity level." - print_screen_output "- - - - - - - - - - - - - - - - - - - - - - - - - - - - -" - print_screen_output "Output Control Options:" - print_screen_output "-A Show audio/sound card information." - print_screen_output "-c Available color schemes. Scheme number is required." - print_screen_output " Supported schemes: 0-$color_scheme_count Example: $SCRIPT_NAME -c 11" - print_screen_output "-C Show full CPU output, including per CPU clockspeed." - print_screen_output "-d Default output verbosity level, same as: $SCRIPT_NAME -v 1" - print_screen_output "-D Show full hard disk info, not only model, ie: /dev/sda ST380817AS 80.0GB." - print_screen_output "-f Show all cpu flags used, not just the short list. Not shown with -F to avoid spamming." - print_screen_output "-F Show Full, all possible, output for $SCRIPT_NAME." - print_screen_output "-G Show graphic card information (+ glx driver and version for non free video drivers)." - print_screen_output "-i Show Wan IP address, and shows local interfaces (requires ifconfig network tool)." - print_screen_output " Not shown with -F for user security reasons, you shouldn't paste your local/wan IP." - print_screen_output "-I Show information: processes, uptime, memory, irc client, inxi version." - print_screen_output "-l Show partition labels. Default: short partition -P. For full -p output, use: -pl (or -plu)." - print_screen_output "-N Show network card information." - print_screen_output "-p Show full partition information (-P plus all other detected partitions)." - print_screen_output " If any of your partitions have spaces in their names, they will not show with -p" - print_screen_output "-P Show partition information (shows what -v4 would show, but without extra data)." - print_screen_output " Shows, if detected: / /boot /home /tmp /usr /var. Use -p for All partitions." - print_screen_output "-S Show system information: host name, kernel, distro" - print_screen_output "-u Show partition UUIDs. Default: short partition -P. For full -p output, use: -pu (or -plu)." - print_screen_output "-v Script verbosity levels. Verbosity level number is required." - print_screen_output " Supported levels: 0-${VERBOSITY_LEVELS} Example: $SCRIPT_NAME -v 4" - print_screen_output " 0 - short output, same as: $SCRIPT_NAME" - print_screen_output " 1 - basic verbose, same as: $SCRIPT_NAME -d" - print_screen_output " 2 - Also show networking card data" - print_screen_output " 3 - Also show hard disk names as detected." - print_screen_output " 4 - Also show partition size/filled data for (if present):/, /home, /var/, /boot" - print_screen_output " 5 - For multicore systems, also show per core clock speeds; shows audio card." - print_screen_output "-x Show extra data: bogomips on cpu; driver version (if available) for network/audio;" - print_screen_output " direct rendering status for Graphics. Only works with verbose or line output." - print_screen_output "" - print_screen_output "Additional Options:" - print_screen_output "-h - this help menu." - if [[ $B_ALLOW_UPDATE == 'true' ]];then - print_screen_output "-U Auto-update script. Note: if you installed as root, you" - print_screen_output " must be root to update, otherwise user is fine." - fi - print_screen_output "-V $SCRIPT_NAME version information. Prints information then exits." - print_screen_output "-% Overrides defective or corrupted data." - print_screen_output "-@ Triggers debugger output. Requires debugging level 1-10." - if [[ $1 == 'full' ]];then - print_screen_output "" - print_screen_output "Developer and Testing Options (Advanced):" - print_screen_output "-! 1 - Sets testing flag B_TESTING_1='true' to trigger testing condition 1." - print_screen_output "-! 2 - Sets testing flag B_TESTING_2='true' to trigger testing condition 2." - print_screen_output "-! 3 - Sets flags B_TESTING_1='true' and B_TESTING_2='true'." - print_screen_output "-! 10 - Triggers an update from the primary dev download server instead of svn." - print_screen_output "-! 11 - Triggers an update from svn branch one - if present, of course." - print_screen_output "-! 12 - Triggers an update from svn branch two - if present, of course." - print_screen_output "-! 13 - Triggers an update from svn branch three - if present, of course." - print_screen_output "-! 14 - Triggers an update from svn branch four - if present, of course." - print_screen_output "-! - Triggers an update from whatever server you list." - print_screen_output "" - fi - print_screen_output "" -} - -## print out version information for -V/--version -print_version_info() -{ - local last_modified=$( grep -im 1 'date:' $SCRIPT_PATH/$SCRIPT_NAME | gawk '{print $3,$4,$5}' ) - - print_screen_output "$SCRIPT_NAME - the universal, portable, system info script for irc." - print_screen_output "Version: $SCRIPT_VERSION_NUMBER" - print_screen_output "Script Last Modified: $last_modified" - print_screen_output "Script Location: $SCRIPT_PATH" - print_screen_output "" - print_screen_output "Tested with Irssi, Xchat, Konversation, BitchX, KSirc, ircII," - print_screen_output "Gaim/Pidgin, Weechat, KVIrc and Kopete." - print_screen_output "" - print_screen_output "This script is a fork of Infobash 3.02, which is:" - print_screen_output "Copyright (C) 2005-2007 Michiel de Boer a.k.a. locsmif" - print_screen_output "Subsequent changes and modifications (after Infobash 3.02) are:" - print_screen_output "Copyright (C) 2008-9 Scott Rogers, Harald Hope, aka trash80 & h2" - print_screen_output "" - print_screen_output "This program is free software; you can redistribute it and/or modify" - print_screen_output "it under the terms of the GNU General Public License as published by" - print_screen_output "the Free Software Foundation; either version 3 of the License, or" - print_screen_output "(at your option) any later version." -} +my %size = ( +'console' => 115, +# Default indentation level. NOTE: actual indent is 1 greater to allow for +# spacing +'indent' => 11, +'indent-min' => 90, +'irc' => 100, # shorter because IRC clients have nick lists etc +'max' => 0, +'no-display' => 130, +# these will be set dynamically in set_display_width() +'term' => 80, +'term-lines' => 100, +); + +## debug temp tools +$client{'test-konvi'} = 0; ######################################################################## -#### MAIN FUNCTIONS +#### STARTUP ######################################################################## #### ------------------------------------------------------------------- -#### initial startup stuff +#### MAIN #### ------------------------------------------------------------------- -# Determine where inxi was run from, set IRC_CLIENT and IRC_CLIENT_VERSION -get_start_client() -{ - local irc_client_path='' irc_client_path_lower='' non_native_konvi='' i='' - local b_non_native_app='false' pppid='' app_working_name='' - - if tty >/dev/null;then - IRC_CLIENT='Shell' - unset IRC_CLIENT_VERSION - B_RUNNING_IN_SHELL='true' - elif [[ -n $PPID && -f /proc/$PPID/exe ]];then - irc_client_path=$( readlink /proc/$PPID/exe ) - irc_client_path_lower=$( tr '[:upper:]' '[:lower:]' <<< $irc_client_path ) - app_working_name=$( basename $irc_client_path_lower ) - # handles the xchat/sh/bash/dash cases, and the konversation/perl cases, where clients - # report themselves as perl or unknown shell. IE: when konversation starts inxi - # from inside itself, as a script, the parent is konversation/xchat, not perl/bash etc - # note: perl can report as: perl5.10.0, so it needs wildcard handling - case $app_working_name in - bash|dash|sh|perl*) # We want to know who wrapped it into the shell or perl. - pppid="$( ps -p $PPID -o ppid --no-headers | sed 's/ //g' )" - if [[ -n $pppid && -f /proc/$pppid/exe ]];then - irc_client_path="$( readlink /proc/$pppid/exe )" - irc_client_path_lower="$( tr '[:upper:]' '[:lower:]' <<< $irc_client_path )" - app_working_name=$( basename $irc_client_path_lower ) - b_non_native_app='true' - fi - ;; - esac - # replacing loose detection with tight detection, bugs will be handled with app names - # as they appear. - case $app_working_name in - irssi-text|irssi) - IRC_CLIENT_VERSION=" $( $irc_client_path -v | gawk 'NR == 1 { - print $2 - }' )" - IRC_CLIENT="Irssi" - ;; - konversation) - # this is necessary to avoid the dcop errors from starting inxi as a /cmd started script - if [[ $b_non_native_app == 'true' ]];then - KONVI=2 - else - KONVI=1 - fi - IRC_CLIENT_VERSION=" $( $irc_client_path -v | gawk ' - /Konversation:/ { - for ( i=2; i<=NF; i++ ) { - if (i == NF) { - print $i - } - else { - printf $i" " - } - } - exit - }' )" - - T=($IRC_CLIENT_VERSION) - if [[ ${T[0]} == *+* ]];then - # < Sho_> locsmif: The version numbers of SVN versions look like this: - # "+ #= 20090226)) - # Quassel IRC: v0.4.0 [+60] (git-22effe5) - # note: early < 0.4.1 quassels do not have -v - IRC_CLIENT_VERSION=" $( $irc_client_path -v 2>/dev/null | gawk -F ': ' ' - BEGIN { - IGNORECASE=1 - clientVersion="" - } - /Quassel IRC/ { - clientVersion = $2 - } - END { - # this handles pre 0.4.1 cases with no -v - if ( clientVersion == "" ) { - clientVersion = "(pre v0.4.1)" - } - print clientVersion - }' )" - # now handle primary, client, and core. quasselcore doesn't actually - # handle scripts with exec, but it's here just to be complete - case $app_working_name in - quassel) - IRC_CLIENT="Quassel [M]" - ;; - quasselclient) - IRC_CLIENT="Quassel" - ;; - quasselcore) - IRC_CLIENT="Quassel (core)" - ;; - esac - ;; - weechat-curses) - IRC_CLIENT_VERSION=" $( $irc_client_path -v ) " - IRC_CLIENT="Weechat" - ;; - kvirc) - IRC_CLIENT_VERSION=" $( $irc_client_path -v 2>&1 | gawk '{ - for ( i=2; i<=NF; i++) { - if ( i == NF ) { - print $i - } - else { - printf $i" " - } - } - exit - }' )" - IRC_CLIENT="KVIrc" - ;; - kopete) - IRC_CLIENT_VERSION=" $( kopete -v | gawk ' - /Kopete:/ { - print $2 - exit - }' )" - IRC_CLIENT="Kopete" - ;; - perl*|ksirc|dsirc) - unset IRC_CLIENT_VERSION - # KSirc is one of the possibilities now. KSirc is a wrapper around dsirc, a perl client - get_cmdline $PPID - for (( i=0; i <= $CMDL_MAX; i++ )) - do - case ${A_CMDL[i]} in - *dsirc*) - IRC_CLIENT="KSirc" - # 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 ;) - IRC_CLIENT_VERSION=" $( ksirc -v | gawk ' - /KSirc:/ { - print $2 - exit - }' )" - break - ;; - esac - done - if [[ -z $IRC_CLIENT_VERSION ]];then - IRC_CLIENT="Unknown Perl client" - fi - ;; - bash|dash|sh) - unset IRC_CLIENT_VERSION - IRC_CLIENT="Shell wrapper" - ;; - *) - IRC_CLIENT="Unknown : ${irc_client_path##*/}" - unset IRC_CLIENT_VERSION - ;; - esac - if [[ $SHOW_IRC -lt 2 ]];then - unset IRC_CLIENT_VERSION - fi - else - IRC_CLIENT="PPID=\"$PPID\" - empty?" - unset IRC_CLIENT_VERSION - fi -} - -# Parse the null separated commandline under /proc//cmdline -# args: $1 - $PPID -get_cmdline() -{ - local i=0 ppid=$1 - - if [[ ! -e /proc/$ppid/cmdline ]];then - echo 0 - return - fi - ##print_screen_output "Marker" - ##print_screen_output "\$ppid='$ppid' -=- $(< /proc/$ppid/cmdline)" - unset A_CMDL - ## note: need to figure this one out, and ideally clean it up and make it readable - while read -d $'\0' L && [[ $i -lt 32 ]] - do - A_CMDL[i++]="$L" ## note: make sure this is valid - What does L mean? ## - done < /proc/$ppid/cmdline - ##print_screen_output "\$i='$i'" - if [[ $i -eq 0 ]];then - A_CMDL[0]=$(< /proc/$ppid/cmdline) - if [[ -n ${A_CMDL[0]} ]];then - i=1 - fi - fi - CMDL_MAX=$i +sub main { +# print Dumper \@ARGV; + eval $start if $b_log; + initialize(); + ## use for start client debugging + # $debug = 3; # 3 prints timers + # set_debugger(); # for debugging of konvi issues + #my $ob_start = StartClient->new(); + #$ob_start->get_client_data(); + StartClient::get_client_data(); + # print_line( Dumper \%client); + get_options(); + set_debugger(); # right after so it's set + check_tools(); + set_colors(); + set_sep(); + # print download_file('stdout','https://') . "\n"; + generate_lines(); + 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; } #### ------------------------------------------------------------------- -#### get data types +#### INITIALIZE #### ------------------------------------------------------------------- -## create array of sound cards installed on system, and if found, use asound data as well -get_audio_data() -{ - local i='' alsa_data='' alsa_driver='' device_count='' - local usb_proc_file='' array_count='' usb_id='' usb_data='' - IFS=$'\n' - # this first step handles the drivers for cases where the second step fails to find one - device_count=$( echo "$Lspci_Data" | egrep -ic '(multimedia audio controller|audio device)' ) - if [[ $device_count -eq 1 ]] && [[ $B_ASOUND_CARDS == 'true' ]];then - alsa_driver=$( gawk -F ']: ' ' - BEGIN { - IGNORECASE=1 - } - # filtering out modems and usb devices like webcams, this might get a - # usb audio card as well, this will take some trial and error - $0 !~ /modem/ || $0 !~ /usb/ { - driver=gensub( /^(.+)( - )(.+)$/, "\\1", 1, $2 ) - gsub(/^ +| +$/,"",driver) - if ( driver != "" ){ - print driver - } - }' $DIR_ASOUND_DEVICE ) - fi +sub initialize { + set_os(); + set_path(); + set_user_paths(); + set_basics(); + system_files('set'); + get_configs(); + # set_downloader(); + set_display_width('live'); +} - # this is to safeguard against line breaks from results > 1, which if inserted into following - # array will create a false array entry. This is a hack, not a permanent solution. - alsa_driver=$( echo $alsa_driver ) - # now we'll build the main audio data, card name, driver, and port. If no driver is found, - # and if the first method above is not null, and one card is found, it will use that instead. - A_AUDIO_DATA=( $( echo "$Lspci_Data" | gawk -F ': ' -v alsaDriver="$alsa_driver" ' - BEGIN { - IGNORECASE=1 +sub check_tools { + my ($action,$program,$message,@data,%commands,%hash); + if ( $b_dmi ){ + $action = 'use'; + if ($program = check_program('dmidecode')) { + @data = grabber("$program -t chassis -t baseboard -t processor 2>&1"); + if (scalar @data < 15){ + if ($b_root) { + foreach (@data){ + if ($_ =~ /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/i } @data){ + $action = 'permissions'; + } + else { + $action = 'unknown-error'; + } + } + } + } + else { + $action = 'missing'; + } + %hash = ( + 'dmidecode' => { + 'action' => $action, + 'missing' => 'Required program dmidecode not available', + 'permissions' => 'Unable to run dmidecode. Are you root?', + 'smbios' => 'No SMBIOS data for dmidecode to process', + 'no-data' => 'dmidecode is not allowed to read /dev/mem', + 'unknown-error' => 'dmidecode was unable to generate data', + }, + ); + %alerts = (%alerts, %hash); } - /multimedia audio controller|audio device/ { - audioCard=gensub(/^[0-9a-f:.]+ [^:]+: (.+)$/,"\\1","g",$0) - # The doublequotes are necessary because of the pipes in the variable. - gsub(/'"$BAN_LIST_NORMAL"'/, "", audioCard) - gsub(/,/, " ", audioCard) - gsub(/^ +| +$/, "", audioCard) - gsub(/ [ \t]+/, " ", audioCard) - - cards[audioCard]++ - - # loop until you get to the end of the data block - while (getline && !/^$/) { - if (/driver in use/) { - drivers[audioCard] = drivers[audioCard] gensub(/(.*): (.*)/,"\\2","g",$0) "" + # 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 ($b_pci || $b_sysctl){ + if (!$bsd_type){ + if ($b_pci ){ + %hash = ('lspci' => '-n',); + %commands = (%commands,%hash); } - else if (/kernel modules:/) { - modules[audioCard] = modules[audioCard] gensub(/(.*): (.*)/,"\\2","g",$0) "" + } + else { + if ($b_pci ){ + %hash = ('pciconf' => '-l',); + %commands = (%commands,%hash); } - else if (/I\/O/) { - portsTemp = gensub(/\t*I\/O ports at (.*) \[.*\]/,"\\1","g",$0) - ports[audioCard] = ports[audioCard] portsTemp " " + if ($b_sysctl ){ + # note: there is a case of kernel.osrelease but it's a linux distro + %hash = ('sysctl' => 'kern.osrelease',); + %commands = (%commands,%hash); } } - } - - END { - j=0 - for (i in cards) { - useDrivers="" - useModules="" - usePorts="" - if (cards[i]>1) { - a[j]=cards[i]"x "i - if (drivers[i] != "") { - useDrivers=drivers[i] - } - if (ports[i] != "") { - usePorts = ports[i] - } - if (modules[i] != "" ) { - useModules = modules[i] - } + foreach ( keys %commands ){ + $action = 'use'; + if ($program = check_program($_)) { + # > 0 means error in shell + #my $cmd = "$program $commands{$_} >/dev/null"; + #print "$cmd\n"; + $action = 'permissions' if system("$program $commands{$_} >/dev/null 2>&1"); } else { - a[j]=i - # little trick here to try to catch the driver if there is - # only one card and it was null, from the first test of asound/cards - if (drivers[i] != "") { - useDrivers=drivers[i] - } - else if ( alsaDriver != "" ) { - useDrivers=alsaDriver - } - if (ports[i] != "") { - usePorts=ports[i] - } - if (modules[i] != "" ) { - useModules = modules[i] - } - } - # create array primary item for master array - print a[j] "," useDrivers "," usePorts "," useModules - j++ - } - }') ) - - # in case of failure of first check do this instead - if [[ ${#A_AUDIO_DATA[@]} -eq 0 ]] && [[ $B_ASOUND_CARDS == 'true' ]];then - A_AUDIO_DATA=( $( gawk -F ']: ' ' - BEGIN { - IGNORECASE=1 - } - $1 !~ /modem/ && $2 !~ /modem/ { - card=gensub( /^(.+)( - )(.+)$/, "\\3", 1, $2 ) - driver=gensub( /^(.+)( - )(.+)$/, "\\1", 1, $2 ) - if ( card != "" ){ - print card","driver - } - }' $DIR_ASOUND_DEVICE ) ) - fi - - # alsa usb detection by damentz - # for every sound card symlink in /proc/asound - display information about it - for usb_proc_file in /proc/asound/* - do - # if lsusb exists, the file is a symlink, and contains an important usb exclusive file: continue - if [[ -n $( which lsusb ) && -L $usb_proc_file && -e $usb_proc_file/usbid ]]; then - # send error messages of lsusb to /dev/null as it will display a bunch if not a super user - # also, find the contents of usbid in lsusb and print everything after the 7th word on the - # corresponding line. Finally, strip out commas as they will change the driver :) - usb_id=$( cat $usb_proc_file/usbid ) - usb_data=$( lsusb -v 2>/dev/null | grep "$usb_id" ) - usb_data=$( gawk '{ - gsub( /,/, " ", $0 ) - for( i=7; i <= NF; i++ ) { - printf( $i " " ) - } - }' <<< "$usb_data" ) - # this method is interesting, it shouldn't work but it does - #A_AUDIO_DATA=( "${A_AUDIO_DATA[@]}" "$usb_data,snd-usb-audio,," ) - # but until we learn why the above worked, I'm using this one, which is safer - if [[ -n $usb_data ]];then - array_count=${#A_AUDIO_DATA[@]} - A_AUDIO_DATA[$array_count]="$usb_data,snd-usb-audio,," - fi - fi - done - IFS="$ORIGINAL_IFS" - - # handle cases where card detection fails, like in PS3, where lspci gives no output, or headless boxes.. - if [[ ${#A_AUDIO_DATA[@]} -eq 0 ]];then - A_AUDIO_DATA[0]='Failed to Detect Sound Card!' - fi -} - -get_audio_alsa_data() -{ - local alsa_data='' - - # now we'll get the alsa data if the file exists - if [[ $B_ASOUND_VERSION == 'true' ]];then - alsa_data=$( gawk ' - BEGIN { - IGNORECASE=1 - } - # some alsa strings have the build date in (...) - # remove trailing . and remove possible second line if compiled by user - $0 !~ /compile/ { - gsub( "Driver | [(].*[)]|\.$","",$0 ) - gsub(/,/, " ", $0) - gsub(/^ +| +$/, "", $0) - gsub(/ [ \t]+/, " ", $0) - if ( $0 != "" ){ - print $0 - } - }' $DIR_ASOUND_VERSION ) - fi - echo "$alsa_data" -} - -## create A_CPU_CORE_DATA, currently with two values: integer core count; core string text -## return value cpu core count string, this helps resolve the multi redundant lines of old style output -get_cpu_core_count() -{ - if [[ $B_CPUINFO == 'true' ]]; then - # load the A_CPU_TYPE_PCNT_CCNT core data array - get_cpu_ht_multicore_smp_data - ## Because of the upcoming release of cpus with core counts over 6, a count of cores is given after Deca (10) - # count the number of processors given - local cpu_physical_count=${A_CPU_TYPE_PCNT_CCNT[1]} - local cpu_core_count=${A_CPU_TYPE_PCNT_CCNT[2]} - local cpu_type=${A_CPU_TYPE_PCNT_CCNT[0]} - - # match the numberic value to an alpha value - case $cpu_core_count in - 1) cpu_alpha_count='Single';; - 2) cpu_alpha_count='Dual';; - 3) cpu_alpha_count='Triple';; - 4) cpu_alpha_count='Quad';; - 5) cpu_alpha_count='Penta';; - 6) cpu_alpha_count='Hexa';; - 7) cpu_alpha_count='Hepta';; - 8) cpu_alpha_count='Octa';; - 9) cpu_alpha_count='Ennea';; - 10) cpu_alpha_count='Deca';; - *) cpu_alpha_count='Multi';; - esac - # create array, core count integer; core count string - # A_CPU_CORE_DATA=( "$cpu_core_count" "$cpu_alpha_count Core$cpu_type" ) - A_CPU_CORE_DATA=( "$cpu_physical_count" "$cpu_alpha_count" "$cpu_type" "$cpu_core_count" ) - fi -} - -## main cpu data collector -get_cpu_data() -{ - local i='' j='' cpu_array_nu='' a_cpu_working='' multi_cpu='' bits='' - - if [[ $B_CPUINFO == 'true' ]];then - IFS=$'\n' - A_CPU_DATA=( $( gawk -F': ' ' - BEGIN { - IGNORECASE=1 + $action = 'missing'; + } + %hash = ( + $_ => { + 'action' => $action, + 'missing' => "Missing system tool: $_. Output will be incomplete", + 'permissions' => "Unable to run $_. Root required?", + }, + ); + %alerts = (%alerts, %hash); } - # TAKE STRONGER NOTE: \t+ does NOT always work, MUST be [ \t]+ - # TAKE NOTE: \t+ will work for $DIR_CPUINFO, but SOME ARBITRARY FILE used for TESTING might contain SPACES! - # Therefore PATCH to use [ \t]+ when TESTING! - /^processor[ \t]+:/ { - nr = $NF + } + %commands = (); + if ( $show{'sensor'} ){ + %commands = ('sensors' => 'linux',); + } + # note: lsusb ships in FreeBSD ports sysutils/usbutils + if ( $usb_level ){ + %hash = ('lsusb' => 'all',); + %commands = (%commands,%hash); + %hash = ('usbdevs' => 'bsd',); + %commands = (%commands,%hash); + } + if ($show{'ip'} || ($bsd_type && $show{'network-advanced'})){ + %hash = ( + 'ip' => 'linux', + 'ifconfig' => 'all', + ); + %commands = (%commands,%hash); + } + foreach ( keys %commands ){ + $action = 'use'; + $message = 'Present and working'; + if ( ($commands{$_} eq 'linux' && $os ne 'linux' ) || ($commands{$_} eq 'bsd' && $os eq 'linux' ) ){ + $message = "No " . ucfirst($os) . " support. Is a comparable $_ tool available?"; + $action = 'platform'; } - - /^model name|^cpu\t+:/ { - gsub(/'"$BAN_LIST_NORMAL"'/, "", $NF ) - gsub(/'"$BAN_LIST_CPU"'/, "", $NF ) - gsub(/,/, " ", $NF) - gsub(/^ +| +$/, "", $NF) - gsub(/ [ \t]+/, " ", $NF) - cpu[nr, "model"] = $NF + elsif (!check_program($_)){ + $message = "Required tool $_ not installed. Check --recommends"; + $action = 'missing'; } + %hash = ( + $_ => { + 'action' => $action, + 'missing' => $message, + 'platform' => $message, + }, + ); + %alerts = (%alerts, %hash); + } + # print Dumper \%alerts; + # only use 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 + # important: -n makes it non interactive, no prompt for password + if (!$b_root && $b_sudo && (my $path = main::check_program('sudo') )) { + my @data = program_values('sudo'); + my $version = program_version($path,$data[0],$data[1],$data[2],$data[5]); + $version =~ s/^([0-9]+\.[0-9]+).*/$1/; + $sudo = "$path -n " if $version >= 1.7; + } + set_fake_tools() if $b_fake_bsd; +} - /^cpu MHz|^clock\t+:/ { - if (!min) { - min = $NF - } - else { - if ($NF < min) { - min = $NF - } - } +# args: 1 - desktop/app command for --version; 2 - search string; +# 3 - space print number; 4 - [optional] version arg: -v, version, etc +# 5 - [optional] exit first find 0/1; 6 - [optional] 0/1 stderr output +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!!! + $b_irc = ( system('tty >/dev/null') ) ? 1 : 0; + # print "birc: $b_irc\n"; + $b_display = ( $ENV{'DISPLAY'} ) ? 1 : 0; + $b_root = ( $ENV{'HOME'} eq '/root' ) ? 1 : 0; + $dl{'dl'} = 'curl'; + $dl{'curl'} = 1; + $dl{'tiny'} = 1; # note: two modules needed, tested for in set_downloader + $dl{'wget'} = 1; + $dl{'fetch'} = 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'} = ''; + $colors{'default'} = 2; +} - if ($NF > max) { - max = $NF +# args: $1 - default OR override default cols max integer count. $_[0] +# is the display width override. +sub set_display_width { + my ($width) = @_; + if ( $width eq 'live' ){ + ## sometimes tput will trigger an error (mageia) if irc client + if ( ! $b_irc ){ + if ( check_program('tput') ) { + # trips error if use qx()... + chomp($size{'term'}=qx{tput cols}); + chomp($size{'term-lines'}=qx{tput lines}); + $size{'term-cols'} = $size{'term'}; + } + # print "tc: $size{'term'} cmc: $size{'console'}\n"; + # double check, just in case it's missing functionality or whatever + if ( $size{'term'} == 0 || $size{'term'} !~ /\d/ ){ + $size{'term'}=80; + # we'll be using this for terminal dimensions later so don't set default. + # $size{'term-lines'}=100; } - gsub(/MHZ/,"",$NF) ## clears out for cell cpu - gsub(/.00[0]+$/,".00",$NF) ## clears out excessive zeros - cpu[nr, "speed"] = $NF - } - - /^cache size/ { - cpu[nr, "cache"] = $NF } - - /^flags/ { - cpu[nr, "flags"] = $NF + # this lets you set different size for in or out of display server + # if ( ! $b_running_in_display && $configs{'COLS_MAX_NO_DISPLAY'} != 0 ){ + # $size{'console'}=$configs{'COLS_MAX_NO_DISPLAY'}; + # } + # term_cols is set in top globals, using tput cols + # print "tc: $size{'term'} cmc: $size{'console'}\n"; + if ( $size{'term'} < $size{'console'} ){ + $size{'console'}=$size{'term'}; } - - /^bogomips/ { - cpu[nr, "bogomips"] = $NF + # adjust, some terminals will wrap if output cols == term cols + $size{'console'}=( $size{'console'} - 2 ); + # echo cmc: $size{'console'} + # comes after source for user set stuff + if ( ! $b_irc ){ + $size{'max'}=$size{'console'}; } - - /vendor_id/ { - gsub(/genuine|authentic/,"",$NF) - cpu[nr, "vendor"] = tolower( $NF ) + else { + $size{'max'}=$size{'irc'}; } - - END { - #if (!nr) { print ",,,"; exit } # <- should this be necessary or should bash handle that - for ( i = 0; i <= nr; i++ ) { - print cpu[i, "model"] "," cpu[i, "speed"] "," cpu[i, "cache"] "," cpu[i, "flags"] "," cpu[i, "bogomips"] "," cpu[nr, "vendor"] - } - if (!min) { - print "not found" - exit - } - if (min != max) { - printf("Min:%s%s Max:%s%s\n", min, "Mhz", max, "Mhz") - } - else { - printf("%s %s\n", max, "Mhz") - } - }' $DIR_CPUINFO ) ) - fi - - IFS="$ORIGINAL_IFS" -# echo getMainCpu: ${[@]} + } + else { + $size{'max'}=$width; + } + # print "tc: $size{'term'} cmc: $size{'console'} cm: $size{'max'}\n"; } -## this is for counting processors and finding HT types -get_cpu_ht_multicore_smp_data() -{ - # in /proc/cpuinfo - # if > 1 processor && processor id == core id then Hyperthreaded (HT) - # if > 1 processor && different processor ids then Multiple Processors (SMP) - # if > 1 processor && processor id != core id then Multi-Core Processors (MCP) - # if = 1 processor then single core/processor Uni-Processor (UP) +# only for dev/debugging BSD +sub set_fake_tools { + $system_files{'dmesg-boot'} = '/var/run/dmesg.boot' if $b_fake_dboot; + $alerts{'pciconf'} = ({'action' => 'use'}) if $b_fake_pciconf; + $alerts{'sysctl'} = ({'action' => 'use'}) if $b_fake_sysctl; + if ($b_fake_usbdevs ){ + $alerts{'usbdevs'} = ({'action' => 'use'}); + $alerts{'lsusb'} = ({ + 'action' => 'missing', + 'missing' => 'Required program lsusb not available', + }); + } +} - if [[ $B_CPUINFO == 'true' ]]; then - A_CPU_TYPE_PCNT_CCNT=( $( - gawk ' - BEGIN { - FS=": " - IGNORECASE = 1 - core_count = 0 - i = 0 - index_temp = "" - num_of_cores = 0 - physical_cpu_count = 0 - processor_logical_count = 0 - processors = 1 - type = "UP" - } - # counts logical processors, both HT and physical - /^processor/ { - processor_logical_count = $NF + 1 - } - # counts physical cores (not used currently) - /^cpu cores/ { - num_of_cores = $NF - } - # array of physical cpus ids - /^physical/ { - a_physical_id[i] = $NF - } - # array of core ids - /^core id/ { - a_core_id[i] = $NF - i++ - } - END { - # look for the largest id number, and assign it - for ( j = 0; j < processor_logical_count; j++ ) { - if ( a_core_id[j] > core_count ) { - core_count = a_core_id[j] - } - } - core_count = core_count + 1 - # trick, set the index equal to value, if the same, it will overwrite - # this lets us create the actual array of true cpu physical ids - for ( j in a_physical_id ) { - index_temp = a_physical_id[j] - a_cpu_physical_working[index_temp] = a_physical_id[j] - } - # note that length() is a gawk >= 3.1.5 only method, better to do it manually - for ( j in a_cpu_physical_working ) { - ++physical_cpu_count - } - - # looking at logical processor counts over 1, which means either HT, SMP or MCP - # http://en.wikipedia.org/wiki/Symmetric_multiprocessing - if ( processor_logical_count > 1 ) { - if ( processor_logical_count > core_count && physical_cpu_count > 1 ) { - type = "SMP-HT" # could be Xeon/P4 HT dual cpu - } - else if ( processor_logical_count > core_count ) { - type = "HT" # this is more than likely a P4 w/HT or an Atom 270 - } - else { - type = "SMP" - } - } - # make sure to handle up cpus too - else { - core_count = 1 - physical_cpu_count = 1 - } - print type " " physical_cpu_count " " core_count +# NOTE: most tests internally are against !$bsd_type +sub set_os { + @uname = uname(); + $os = lc($uname[0]); + $cpu_arch = lc($uname[-1]); + if ($cpu_arch =~ /arm|aarch/){$b_arm = 1} + elsif ($cpu_arch =~ /mips/) {$b_mips = 1} + # aarch32 mips32 intel/amd handled in cpu + if ($cpu_arch =~ /(armv[1-7]|32|sparc_v9)/){ + $bits_sys = 32; + } + elsif ($cpu_arch =~ /(alpha|64)/){ + $bits_sys = 64; + } + if ( $os =~ /(bsd|dragonfly|darwin)/ ){ + if ( $os =~ /openbsd/ ){ + $os = 'openbsd'; + } + elsif ($os =~ /darwin/){ + $os = 'darwin'; } - ' $DIR_CPUINFO - ) ) - fi + if ($os =~ /kfreebsd/){ + $bsd_type = 'debian-bsd'; + } + else { + $bsd_type = $os; + } + } } -# for more on distro id, please reference this python thread: http://bugs.python.org/issue1322 -## return distro name/id if found -get_distro_data() -{ - local i='' j='' distro='' distro_file='' a_distro_glob='' - - # get the wild carded array of release/version /etc files if present - shopt -s nullglob - cd /etc - a_distro_glob=(*[-_]{release,version}) - cd "$OLDPWD" - shopt -u nullglob - - if [[ ${#a_distro_glob[@]} -eq 1 ]];then - distro_file="${a_distro_glob}" - # use the file if it's in the known good lists - elif [[ ${#a_distro_glob[@]} -gt 1 ]];then - for i in $DISTROS_DERIVED $DISTROS_PRIMARY - do - # Only echo works with ${var[@]}, not print_screen_output() or script_debugger() - # This is a known bug, search for the word "strange" inside comments - # echo "i='$i' a_distro_glob[@]='${a_distro_glob[@]}'" - if [[ " ${a_distro_glob[@]} " == *" $i "* ]];then - # 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 - # this is for only those distro's with self named release/version files - # because Mint does not use such, it must be done as below - ## this if statement requires the spaces and * as it is, else it won't work - ## - if [[ " $DISTROS_LSB_GOOD " == *" ${i} "* ]] && [[ $B_LSB_DIR == 'true' ]];then - distro_file='lsb-release' - else - distro_file="${i}" - fi - break - fi - done - fi - - # first test for the legacy antiX distro id file - if [[ -e /etc/antiX ]];then - distro="$( egrep -oi 'antix.*\.iso' <<< $( remove_erroneous_chars '/etc/antiX' ) | sed 's/\.iso//' )" - # 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. - elif [[ $distro_file == 'lsb-release' ]];then - distro=$( get_distro_lsb_data ) - # then if the distro id file was found and it's not in the exluded primary distro file list, read it - elif [[ -n $distro_file && -s /etc/$distro_file && " $DISTROS_EXCLUDE_LIST " != *" $distro_file "* ]];then - distro=$( remove_erroneous_chars "/etc/$distro_file" ) - # otherwise try the default debian/ubuntu /etc/issue file - elif [[ -f /etc/issue ]];then - # lsb gives more manageable and accurate output than issue, but mint should use issue for now - # some bashism, boolean must be in parenthesis to work correctly, ie [[ $(boolean) ]] not [[ $boolean ]] - if [[ $B_LSB_DIR == 'true' ]] && [[ -z $( grep -i 'mint' /etc/issue ) ]];then - distro=$( get_distro_lsb_data ) - else - distro=$( gawk ' - BEGIN { - RS="" - } - { - gsub(/\\[a-z]/, "") - gsub(/,/, " ") - gsub(/^ +| +$/, "") - gsub(/ [ \t]+/, " ") - print - }' /etc/issue ) - fi - fi - - if [[ ${#distro} -gt 80 ]] && [[ $B_HANDLE_CORRUPT_DATA != 'true' ]];then - distro="${RED}/etc/${distro_file} corrupted, use -% to override${NORMAL}" - fi - ## note: would like to actually understand the method even if it's not used - # : ${distro:=Unknown distro o_O} - ## 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 [[ -z $distro ]] && [[ $B_LSB_DIR == 'true' ]];then - distro=$( get_distro_lsb_data ) - fi - ## finally, if all else has failed, give up - if [[ -z $distro ]];then - distro='Unknown distro o_O' - fi - - # this handles an arch bug where /etc/arch-release is empty and /etc/issue is corrupted - if [[ -n $( grep -i 'arch linux' <<< $distro ) ]];then - distro='Arch Linux' - fi - - echo "$distro" +# This data is hard set top of program but due to a specific project's +# foolish idea that ignoring the FSH totally is somehow a positive step +# forwards for free software, we also have to padd the results with PATH. +sub set_path { + # Extra path variable to make execute failures less likely, merged below + my (@path); + @paths = qw(/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin /usr/X11R6/bin); + @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 ( !(grep { /^$id$/ } @paths) && $id !~ /(game)/ ){ + push @paths, $id; + } + } + # print "paths: @paths\n"; } -# args: $1 - optional, app, uses the app test, not being used now -get_distro_lsb_data() -{ - local distro='' - - if [[ $B_LSB_DIR == 'true' ]] && [[ $1 != 'app' ]];then - distro=$( gawk -F '=' ' - BEGIN { - IGNORECASE=1 - } - # note: adding the spacing directly to variable to make sure distro output is null if not found - /^DISTRIB_ID/ { - gsub(/^ +| +$/, "", $NF) - # this is needed because grep for "arch" is too loose to be safe - if ( $NF == "arch" ) { - distroId = "Arch Linux" - } - else if ( $NF != "n/a" ) { - distroId = $NF " " - } - } - /^DISTRIB_RELEASE/ { - gsub(/^ +| +$/, "", $NF) - if ( $NF != "n/a" ) { - distroRelease = $NF " " - } - } - /^DISTRIB_CODENAME/ { - gsub(/^ +| +$/, "", $NF) - if ( $NF != "n/a" ) { - distroCodename = $NF " " - } - } - END { - print distroId distroRelease distroCodename - }' $DIR_LSB_RELEASE ) - fi - # this is HORRIBLY slow, but I don't know why, it runs fast in shell -# if [[ -n $( which lsb_release ) && $1 == 'app' ]];then -# distro=$( echo "$( lsb_release -irc )" | gawk ' -# { IGNORECASE=1 } -# /^Distributor ID/ { -# gsub(/^ +| +$/, "", $NF) -# distroId = $NF -# } -# /^Release/ { -# gsub(/^ +| +$/, "", $NF) -# distroRelease = $NF -# } -# /^Codename/ { -# gsub(/^ +| +$/, "", $NF) -# distroCodename = $NF -# } -# END { -# print distroId " " distroRelease " (" distroCodename ")" -# }' ) -# fi - - echo $distro +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'}; + } } -## create array of gfx cards installed on system -get_graphics_card_data() -{ - local i='' - - IFS=$'\n' - A_GFX_CARD_DATA=( $( echo "$Lspci_Data" | gawk -F': ' ' - BEGIN { - IGNORECASE=1 - } - /vga compatible controller/ { - gsub(/'"$BAN_LIST_NORMAL"'/, "", $NF) - gsub(/,/, " ", $NF) - gsub(/^ +| +$/, "", $NF) - gsub(/ [ \t]+/, " ", $NF) - print $NF - }' ) ) - IFS="$ORIGINAL_IFS" -# for (( i=0; i < ${#A_GFX_CARD_DATA[@]}; i++ )) -# do -# A_GFX_CARD_DATA[i]=$( sanitize_characters BAN_LIST_NORMAL "${A_GFX_CARD_DATA[i]}" ) -# done - # handle cases where card detection fails, like in PS3, where lspci gives no output, or headless boxes.. - if [[ ${#A_GFX_CARD_DATA[@]} -eq 0 ]];then - A_GFX_CARD_DATA[0]='Failed to Detect Video Card!' - fi - - # GFXMEM is UNUSED at the moment, because it shows AGP aperture size, which is not necessarily equal to GFX memory.. - # GFXMEM="size=[$(echo "$Lspci_Data" | gawk '/VGA/{while (!/^$/) {getline;if (/size=[0-9][0-9]*M/) {size2=gensub(/.*\[size=([0-9]+)M\].*/,"\\1","g",$0);if (size 1 cases of detections I guess - function join( arr, sep ) { - s="" - i=flag=0 - for ( i in arr ) { - if ( flag++ ) { - s = s sep - } - s = s i - } - return s - } - - BEGIN { - IGNORECASE=1 - } - /opengl renderer/ { - if ( $2 ~ /mesa/ ) { - # Allow all mesas -# if ( $2 ~ / r[3-9][0-9][0-9] / ) { - gsub(/'"$BAN_LIST_NORMAL"'/, "", $2) - a[$2] - # this counter failed in one case, a bug, and is not needed now -# f++ -# } - next - } - $2 && a[$2] - } - # 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 - # /opengl version/ && ( f || $2 !~ /mesa/ ) { - /opengl version/ { - $2 && b[$2] - } - /direct rendering/ { - $2 && c[$2] - } - END { - printf( "%s\n%s\n%s\n", join( a, ", " ), join( b, ", " ), join( c, ", " ) ) - }' ) ) - IFS="$ORIGINAL_IFS" - - # GLXR=$(glxinfo | gawk -F ': ' 'BEGIN {IGNORECASE=1} /opengl renderer/ && $2 !~ /mesa/ {seen[$2]++} END {for (i in seen) {printf("%s ",i)}}') - # GLXV=$(glxinfo | gawk -F ': ' 'BEGIN {IGNORECASE=1} /opengl version/ && $2 !~ /mesa/ {seen[$2]++} END {for (i in seen) {printf("%s ",i)}}') - fi -} - -## return screen resolution / tty resolution -get_graphics_res_data() -{ - local screen_resolution='' - - if [[ $B_X_RUNNING == 'true' ]];then - # Added the two ?'s , because the resolution is now reported without spaces around the 'x', as in - # 1400x1050 instead of 1400 x 1050. Change as of X.org version 1.3.0 - screen_resolution=$( xrandr | gawk ' - /\*/ { - res[++m] = gensub(/^.* ([0-9]+) ?x ?([0-9]+)[_ ].* ([0-9\.]+)\*.*$/,"\\1x\\2@\\3hz","g",$0) - } - END { - for (n in res) { - if (res[n] ~ /^[[:digit:]]+x[[:digit:]]+/) { - line = line ? line ", " res[n] : res[n] - } - } - if (line) { - print(line) - } - }' ) - if [[ -z $screen_resolution ]];then - screen_resolution=$( xdpyinfo | gawk ' - /dimensions/ { - print $2 - }' ) - fi - else - screen_resolution=$( stty -F $( readlink /proc/$PPID/fd/0 ) size | gawk '{ print $2"x"$1 }' ) - fi - echo "$screen_resolution" -} - -## for possible future data, not currently used -get_graphics_agp_data() -{ - local agp_module='' - - if [[ B_MODULES_DIR == 'true' ]];then - ## not used currently - agp_module=$( gawk ' - /agp/ && !/agpgart/ && $3 > 0 { - print(gensub(/(.*)_agp.*/,"\\1","g",$1)) - }' $DIR_MODULES ) - fi +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"; + } + $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"; } -## create array of x vendor/version data -get_graphics_x_data() -{ - local x_vendor='' x_version='' - - if [[ $B_X_RUNNING == 'true' ]];then - # X vendor and version detection. - x_vendor=$( xdpyinfo | gawk -F': +' ' - BEGIN { - IGNORECASE=1 - } - /vendor string/ { - gsub(/the|inc|foundation|project|corporation/, "", $2) - gsub(/,/, " ", $2) - gsub(/^ +| +$/, "", $2) - gsub(/ [ \t]+/, " ", $2) - print $2 - }' ) - - # 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 $x_version is empty, the release number is parsed instead) - x_version=$( xdpyinfo | gawk '/version:/ { print $NF }' ) - if [[ -z $x_version ]];then - x_version=$(xdpyinfo | gawk -F': +' ' - BEGIN { - IGNORECASE=1 - } - /vendor release number/ { - gsub(/0+$/, "", $2) - gsub(/0+/, ".", $2) - print $2 - }' ) - fi - A_X_DATA[0]="$x_vendor" - A_X_DATA[1]="$x_version" - - #X -version 2>&1 | gawk '/^X Window System Version/ { print $5 }' - #This method could be used in the future to detect X when X is not running, - #however currently inxi ignores X checks when X is not found. - fi -} - -# this gets just the raw data, total space/percent used and disk/name/per disk capacity -get_hdd_data_basic() -{ - local hdd_used='' - - hdd_used=$( df --exclude-type=aufs --exclude-type=tmpfs --exclude-type=iso9660 | gawk ' - # also handles odd dm-1 type, from lvm - /^\/dev\/(mapper\/|[hs]d[a-z][0-9]+|dm[-]?[0-9]+)/ { - # this handles the case where the first item is too long - # and makes df wrap output to next line, so here we advance - # it to the next line for that single case - if ( NF < 5 && $0 !~ /.*\%/ ) { - getline - } - # if the first item caused a wrap, use one less than standard - # testing for the field with % in it, ie: 34%, then go down from there - # this also protects against cases where the mount point has a space in the - # file name, thus breaking going down from $NF directly. - if ( $4 ~ /.*\%/ ) { - used += $2 - } - # otherwise use standard - else if ( $5 ~ /.*\%/ ) { - used += $3 - } - # and if this is not detected, give up, we need user data to debug - else { - next - } - } - END { - print used - }' ) - - if [[ -z $hdd_used ]];then - hdd_used='na' - fi - - # create the initial array strings: - # disk-dev, capacity, name, usb or not - # final item is the total of the disk - IFS=$'\n' - - if [[ $B_PARTITIONS_DIR == 'true' ]];then - A_HDD_DATA=( $( - gawk -v hddused="$hdd_used" ' - /[hs]d[a-z]$/ { - driveSize = $(NF - 1)*1024/1000**3 - gsub(/,/, " ", driveSize) - gsub(/^ +| +$/, "", driveSize) - printf( $NF",%.1fGB,,\n", driveSize ) - } - # See http://lanana.org/docs/device-list/devices-2.6+.txt for major numbers used below - # $1 ~ /^(3|22|33|8)$/ && $2 % 16 == 0 { - # size += $3 - # } - # special case from this data: 8 0 156290904 sda - $1 ~ /^(3|22|33|8)$/ && $NF ~ /[hs]d[a-z]$/ && ( $2 % 16 == 0 || $2 % 16 == 8 ) { - size += $3 +# args: 1: set|hash key to return either null or path +sub system_files { + my ($file) = @_; + if ( $file eq 'set'){ + %files = ( + 'asound-cards' => '/proc/asound/cards', + 'asound-modules' => '/proc/asound/modules', + 'asound-version' => '/proc/asound/version', + 'cpuinfo' => '/proc/cpuinfo', + 'dmesg-boot' => '/var/run/dmesg.boot', + 'lsb-release' => '/etc/lsb-release', + 'mdstat' => '/proc/mdstat', + 'meminfo' => '/proc/meminfo', + 'modules' => '/proc/modules', + 'mounts' => '/proc/mounts', + 'os-release' => '/etc/os-release', + 'partitions' => '/proc/partitions', + 'scsi' => '/proc/scsi/scsi', + 'version' => '/proc/version', + 'xorg-log' => '/var/log/Xorg.0.log' + ); + foreach ( keys %files ){ + $system_files{$_} = ( -e $files{$_} ) ? $files{$_} : ''; } - - END { - size = size*1024/1000**3 # calculate size in GB size - workingUsed = hddused*1024/1000**3 # calculate workingUsed in GB used - # this handles a special case with livecds where no hdd_used is detected - if ( size > 0 && hddused == "na" ) { - size = sprintf( "%.1f", size ) - print size "GB,-" - } - else if ( size > 0 && workingUsed > 0 ) { - diskUsed = workingUsed*100/size # calculate used percentage - diskUsed = sprintf( "%.1f", diskUsed ) - size = sprintf( "%.1f", size ) - print size "GB," diskUsed "% used" - } - else { - print "NA,-" # print an empty array, this will be further handled in the print out function + if ( ! $system_files{'xorg-log'} && check_program('xset') ){ + my $data = qx(xset q 2>/dev/null); + foreach ( split /\n/, $data){ + if ($_ =~ /Log file/i){ + $system_files{'xorg-log'} = get_piece($_,3); + last; + } } - }' $DIR_PARTITIONS - ) ) - fi - IFS="$ORIGINAL_IFS" + } + } + else { + return $system_files{$file}; + } } -## fills out the A_HDD_DATA array with disk names -get_hard_drive_data_advanced() -{ - local a_temp_working='' a_temp_scsi='' temp_holder='' temp_name='' i='' j='' - local sd_ls_by_id='' ls_disk_by_id='' - - ## check for all ide type drives, non libata, only do it if hdx is in array - if [[ -n $( egrep 'hd[a-z]' <<< ${A_HDD_DATA[@]} ) ]];then - # remember, we're using the last array item to store the total size of disks - for (( i=0; i < ${#A_HDD_DATA[@]} - 1; i++ )) - do - IFS="," - a_temp_working=( ${A_HDD_DATA[i]} ) - IFS="$ORIGINAL_IFS" - if [[ -n $( egrep '^hd[a-z]' <<< ${a_temp_working[0]} ) ]];then - if [[ -e /proc/ide/${a_temp_working[0]}/model ]];then - a_temp_working[2]="$( remove_erroneous_chars /proc/ide/${a_temp_working[0]}/model )" - else - a_temp_working[2]="Name n/a" - fi - # these loops are to easily extend the cpu array created in the awk script above with more fields per cpu. - for (( j=0; j < ${#a_temp_working[@]}; j++ )) - do - if [[ $j -gt 0 ]];then - A_HDD_DATA[i]="${A_HDD_DATA[i]},${a_temp_working[$j]}" - else - A_HDD_DATA[i]="${a_temp_working[$j]}" - fi - done - fi - done - fi - - ## then handle libata names - # first get the ata device names, put them into an array - IFS=$'\n' - if [[ $B_SCSI_DIR == 'true' ]]; then - a_temp_scsi=( $( gawk ' - BEGIN { - IGNORECASE=1 - } - /host/ { - getline a[$0] - getline b[$0] - } - END { - for (i in a) { - if (b[i] ~ / *type: *direct-access.*/) { - #c=gensub(/^ *vendor: (.+) +model: (.+) +rev: (.+)$/,"\\1 \\2 \\3","g",a[i]) - #c=gensub( /^ *vendor: (.+) +model: (.+) +rev:.*$/,"\\1 \\2","g",a[i] ) - # the vendor: string is useless, and is a bug, ATA is not a vendor for example - c=gensub( /^ *vendor: (.+) +model: (.+) +rev:.*$/, "\\2", "g", a[i] ) - gsub(/,/, " ", c) - gsub(/^ +| +$/, "", c) - gsub(/ [ \t]+/, " ", c) - #print a[i] - # we actually want this data, so leaving this off for now -# if (c ~ /\|\|memory stick|memory card/) { -# continue -# } - print c - } - } - }' $DIR_SCSI ) ) - fi - IFS="$ORIGINAL_IFS" - - ## then we'll loop through that array looking for matches. - if [[ -n $( egrep 'sd[a-z]' <<< ${A_HDD_DATA[@]} ) ]];then - # first pack the main ls variable so we don't have to keep using ls /dev... - ls_disk_by_id="$( ls -l /dev/disk/by-id )" - for (( i=0; i < ${#A_HDD_DATA[@]} - 1; i++ )) - do - if [[ -n $( egrep '^sd[a-z]' <<< ${A_HDD_DATA[$i]} ) ]];then - IFS="," - a_temp_working=( ${A_HDD_DATA[$i]} ) - IFS="$ORIGINAL_IFS" - if [[ ${#a_temp_scsi[@]} -gt 0 ]];then - for (( j=0; j < ${#a_temp_scsi[@]}; j++ )) - do - ## 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 - temp_name=$( tr ' ' '_' <<< ${a_temp_scsi[$j]} | cut -d '-' -f 1 ) - sd_ls_by_id=$( egrep -m1 ".*$temp_name.*${a_temp_working[0]}$" <<< "$ls_disk_by_id" ) - - if [[ -n $sd_ls_by_id ]];then - a_temp_working[2]=${a_temp_scsi[$j]} - if [[ -n $( grep 'usb-' <<< $sd_ls_by_id ) ]];then - a_temp_working[3]='USB' - fi - break - else - a_temp_working[2]="Name n/a" - fi - done - else - a_temp_working[2]="Name n/a" - fi - - # these loops are to easily extend the cpu array created in the awk script above with more fields per cpu. - for (( j=0; j < ${#a_temp_working[@]}; j++ )) - do - if [[ $j -gt 0 ]];then - A_HDD_DATA[i]="${A_HDD_DATA[i]},${a_temp_working[$j]}" - else - A_HDD_DATA[i]="${a_temp_working[$j]}" - fi - done - fi - done - unset ls_disk_by_id # and then let's dump the data we don't need - fi -} - -get_lspci_data() -{ - echo "$( lspci -v | gawk '{ - gsub(/\(prog-if[^)]*\)/,"") - print - }' )" -} +######################################################################## +#### UTILITIES +######################################################################## -## return memory used/installed -get_memory_data() -{ - local memory='' +#### ------------------------------------------------------------------- +#### COLORS +#### ------------------------------------------------------------------- - memory=$( gawk ' - /^MemTotal:/ { - tot = $2 +## arg: 1 - the type of action, either integer, count, or full +sub get_color_scheme { + my ($type) = @_; + eval $start if $b_log; + 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)], + ); + if ($type eq 'count' ){ + return scalar @color_schemes; } - /^(MemFree|Buffers|Cached):/ { - notused+=$2 + if ($type eq 'full' ){ + return @color_schemes; } - END { - used = tot-notused - printf("%.1f/%.1fMB\n", used/1024, tot/1024) - }' $DIR_MEMINFO ) - - echo "$memory" + else { + return @{$color_schemes[$type]}; + # print Dumper $color_schemes[$scheme_nu]; + } + eval $end if $b_log; } -# process and return module version data -get_module_version_number() -{ - local module_version='' - - if [[ -n $( which modinfo ) ]];then - module_version=$( modinfo $1 | gawk ' - BEGIN { - IGNORECASE=1 - } - /^version/ { - gsub(/,/, " ", $2) - gsub(/^ +| +$/, "", $2) - gsub(/ [ \t]+/, " ", $2) - print $2 - } - ' ) - fi - - echo "$module_version" +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 @scheme = get_color_scheme($colors{'scheme'}); + $colors{'c1'} = $color_palette{$scheme[0]}[$index]; + $colors{'c2'} = $color_palette{$scheme[1]}[$index]; + $colors{'cn'} = $color_palette{$scheme[2]}[$index]; + # print Dumper \@scheme; + # print "$colors{'c1'}here$colors{'c2'} we are!$colors{'cn'}\n"; + eval $end if $b_log; } -## create array of network cards -get_networking_data() -{ - IFS=$'\n' - A_NETWORK_DATA=( $( echo "$Lspci_Data" | gawk ' - BEGIN { - IGNORECASE=1 +sub set_colors { + eval $start if $b_log; + # it's already been set with -c 0-43 + if ( exists $colors{'c1'} ){ + return 1; } - /^[0-9a-f:.]+ (ethernet|network) (controller|bridge)/ || /^[0-9a-f:.]+ [^:]+: .*(ethernet|network).*$/ { - nic=gensub(/^[0-9a-f:.]+ [^:]+: (.+)$/,"\\1","g",$0) - gsub(/realtek semiconductor/, "Realtek", nic) - gsub(/davicom semiconductor/, "Davicom", nic) - # The doublequotes are necessary because of the pipes in the variable. - gsub(/'"$BAN_LIST_NORMAL"'/, "", nic) - gsub(/,/, " ", nic) - gsub(/^ +| +$/, "", nic) - gsub(/ [ \t]+/, " ", nic) - - eth[nic]++ - while (getline && !/^$/) { - if (/I\/O/) { - ports[nic] = ports[nic] $4 " " + # 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'}; } - if (/driver in use/) { - drivers[nic] = drivers[nic] gensub(/(.*): (.*)/,"\\2","g",$0) "" + elsif (defined $colors{'irc-console'} && !$b_display){ + $color_scheme = $colors{'irc-console'}; } - else if (/kernel modules/) { - modules[nic] = modules[nic] gensub(/(.*): (.*)/,"\\2","g",$0) "" + elsif ( defined $colors{'irc-gui'}) { + $color_scheme = $colors{'irc-gui'}; } } - } - - END { - j=0 - for (i in eth) { - useDrivers="" - usePorts="" - useModules="" - if (eth[i]>1) { - a[j]=eth[i]"x "i - ## note: this loses the plural ports case, is it needed anyway? - if (ports[i] != "") { - usePorts=ports[i] - } - if (drivers[i] != "") { - useDrivers=drivers[i] - } - if (modules[i] != "" ) { - useModules = modules[i] - } + else { + if (defined $colors{'console'} && !$b_display){ + $color_scheme = $colors{'console'}; } - else { - a[j]=i - if (ports[i] != "") { - usePorts=ports[i] - } - if (drivers[i] != "") { - useDrivers=drivers[i] - } - if (modules[i] != "" ) { - useModules = modules[i] - } + elsif (defined $colors{'virt-term'}){ + $color_scheme = $colors{'virt-term'}; } - # create array primary item for master array - print a[j] "," useDrivers "," usePorts "," useModules - j++ } - }') ) - IFS="$ORIGINAL_IFS" + } + # force 0 for | or > output, all others prints to irc or screen + if (!$b_irc && ! -t STDOUT ){ + $color_scheme = 0; + } + set_color_scheme($color_scheme); + eval $end if $b_log; } -get_networking_wan_ip_data() +## SelectColors { - local ip='' - - # get ip using wget redirect to stdout. This is a clean, text only IP output url. - ip=$( wget -q -O - http://smxi.org/opt/ip.php | awk -F 'is: ' '{ - #gsub("\n","",$2") - print $2 - }' ) +package SelectColors; - if [[ -z $ip ]];then - ip='None Detected!' - fi - echo "$ip" -} - -get_networking_local_ip_data() -{ - if [[ $B_IFCONFIG == 'true' ]];then - IFS=$'\n' - A_INTERFACES_DATA=( $( $DIR_IFCONFIG | gawk ' - BEGIN { - IGNORECASE=1 - } - $0 !~ /^lo/ { - # not clear on why inet is coming through, but this gets rid of it - # as first line item. - interface = $1 - gsub(/,/, " ", interface) - gsub(/^ +| +$/, "", interface) - gsub(/ [ \t]+/, " ", interface) +# use warnings; +# use strict; +# use diagnostics; +# use 5.008; - aInterfaces[interface]++ - while (getline && !/^$/) { - if (/inet addr:/) { - ipAddresses[interface] = gensub( /addr:([0-9\.]+)/, "\\1", "g", $2 ) - } - } - } +my (@data,@rows,%configs,%status); +my ($type,$w_fh); +my $safe_color_count = 12; # null/normal + default color group +my $count = 0; - END { - j=0 - for (i in aInterfaces) { - useInterfaceIp = "" - a[j] = i - if (ipAddresses[i] != "") { - useInterfaceIp = ipAddresses[i] - } - # create array primary item for master array - # tested needed to avoid bad data from above, if null it is garbage - # this is the easiest way to handle junk I found, improve if you want - if ( useInterfaceIp != "" ) { - print a[j] "," useInterfaceIp - } - j++ - } - }') ) - IFS="$ORIGINAL_IFS" - fi +# args: 1 - type +sub new { + my $class = shift; + ($type) = @_; + my $self = {}; + return bless $self, $class; } - -get_partition_data() -{ - #local excluded_file_types='--exclude-type=aufs --exclude-type=tmpfs --exclude-type=iso9660' - # df doesn't seem to work in script with variables like at the command line - - IFS=$'\n' - # sample line: /dev/sda2 ext3 15G 8.9G 4.9G 65% /home - # $NF = partition name; $(NF - 4) = partition size; $(NF - 3) = used, in gB; $(NF - 1) = percent used - ## note: by subtracting from the last field number NF, we avoid a subtle issue with LVM df output, where if - ## the first field is too long, it will occupy its own line, this way we are getting only the needed data - A_PARTITION_DATA=( $( df -h -T --exclude-type=aufs --exclude-type=tmpfs --exclude-type=iso9660 | gawk ' - BEGIN { - IGNORECASE=1 - } - # this has to be nulled for every iteration so it does not retain value from last iteration - devBase="" - # this is required because below we are subtracting from NF, so it has to be > 4 - # the real issue is long file system names that force the wrap of df output: //fileserver/main - # but we still need to handle more dynamically long space containing file names, but later. - ( NF < 5 ) && ( $0 !~ /[0-9]+\%/ ) { - # set the dev location here for cases of wrapped output - if ( NF == 1 ){ - devBase=gensub( /^(\/dev\/)(.+)$/, "\\2", 1, $1 ) - } - getline - } - # this handles yet another fredforfaen special case where a mounted drive - # has the search string in its name - $NF ~ /^\/$|^\/boot$|^\/var$|^\/home$|^\/tmp$|^\/usr$/ { - print $NF "," $(NF - 4) "," $(NF - 3) "," $(NF - 1) ",main," devBase - } - # skip all these, including the first, header line. Use the --exclude-type - # to handle new filesystems types we do not want listed here - $NF !~ /^\/$|^\/boot$|^\/var$|^\/home$|^\/tmp$|^\/usr$|^filesystem/ { - # this is to avoid file systems with spaces in their names, that will make - # the test show the wrong data in each of the fields, if no x%, then do not use - # using 3 cases, first default, standard, 2nd, 3rd, handles one and two spaces in name - if ( $(NF - 1) ~ /[0-9]+\%/ ) { - print $NF "," $(NF - 4) "," $(NF - 3) "," $(NF - 1) ",secondary," devBase - } - # these two cases construct the space containing name - else if ( $(NF - 2) ~ /[0-9]+\%/ ) { - print $(NF - 1) " " $NF "," $(NF - 5) "," $(NF - 4) "," $(NF - 2) ",secondary," devBase - } - else if ( $(NF - 3) ~ /[0-9]+\%/ ) { - print $(NF - 2) " " $(NF - 1) " " $NF "," $(NF - 6) "," $(NF - 5) "," $(NF - 3) ",secondary," devBase - } - } - ' ) - # 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 - $( swapon -s | gawk ' - BEGIN { - swapCounter = 1 - } - /^\/dev/ { - size = sprintf( "%.2f", $3*1024/1000**3 ) - devBase = gensub( /^(\/dev\/)(.+)$/, "\\2", 1, $1 ) - used = sprintf( "%.2f", $4*1024/1000**3 ) - percentUsed = sprintf( "%.0f", ( $4/$3 )*100 ) - print "swap-" swapCounter "," size "GB," used "GB," percentUsed "\%,main," devBase - swapCounter = ++swapCounter - }' ) ) - IFS="$ORIGINAL_IFS" - - if [[ $B_SHOW_LABELS == 'true' || $B_SHOW_UUIDS == 'true' ]];then - get_partition_data_advanced - fi -} - -# first get the locations of the mount points for label/uuid detection -get_partition_data_advanced() -{ - local a_partition_working='' dev_partition_data='' - local dev_disk_label='' dev_disk_uuid='' dev_item='' dev_label='' dev_uuid='' - local mount_point='' - - if [[ -d /dev/disk/by-label ]];then - dev_disk_label="$( ls -l /dev/disk/by-label )" - fi - if [[ -d /dev/disk/by-uuid ]];then - dev_disk_uuid="$( ls -l /dev/disk/by-uuid )" - fi - - if [[ $B_MOUNTS_DIR == 'true' ]];then - for (( i=0; i < ${#A_PARTITION_DATA[@]}; i++ )) - do - IFS="," - a_partition_working=( ${A_PARTITION_DATA[i]} ) - IFS="$ORIGINAL_IFS" - # note: for swap this will already be set - if [[ -z ${a_partition_working[5]} ]];then - mount_point=$( sed 's|/|\\/|g' <<< ${a_partition_working[0]} ) - #echo mount_point $mount_point - dev_partition_data=$( gawk ' - BEGIN { - IGNORECASE = 1 - partition = "" - partTemp = "" - } - # trying to handle space in name -# gsub( /\\040/, " ", $0 ) - /[ \t]'$mount_point'[ \t]/ && $1 != "rootfs" { - # initialize the variables - label = "" - uuid = "" - - # slice out the /dev - partition=gensub( /^(\/dev\/)(.+)$/, "\\2", 1, $1 ) - # label and uuid can occur for root, set partition to null now - if ( partition ~ /by-label/ ) { - label=gensub( /^(\/dev\/disk\/by-label\/)(.+)$/, "\\2", 1, $1 ) - partition = "" - } - if ( partition ~ /by-uuid/ ) { - uuid=gensub( /^(\/dev\/disk\/by-uuid\/)(.+)$/, "\\2", 1, $1 ) - partition = "" - } - - # handle /dev/root for / id - if ( partition == "root" ) { - # if this works, great, otherwise, just set this to null values - partTemp="'$( readlink /dev/root 2>/dev/null )'" - if ( partTemp != "" ) { - if ( partTemp ~ /[hs]d[a-z][0-9]/ ) { - partition=gensub( /^(\/dev\/)(.+)$/, "\\2", 1, partTemp ) - } - else if ( partTemp ~ /by-uuid/ ) { - uuid=gensub( /^(\/dev\/disk\/by-uuid\/)(.+)$/, "\\2", 1, partTemp ) - partition="" # set null to let real location get discovered - } - else if ( partTemp ~ /by-label/ ) { - label=gensub( /^(\/dev\/disk\/by-label\/)(.+)$/, "\\2", 1, partTemp ) - partition="" # set null to let real location get discovered - } - } - else { - partition = "" - label = "" - uuid = "" - } - } - print partition "," label "," uuid - }' $DIR_MOUNTS ) - -# echo dev_partition_data: $dev_partition_data - # assemble everything we could get for dev/h/dx, label, and uuid - IFS="," - A_PARTITION_DATA[i]=${a_partition_working[0]}","${a_partition_working[1]}","${a_partition_working[2]}","${a_partition_working[3]}","${a_partition_working[4]}","$dev_partition_data - IFS="$ORIGINAL_IFS" - fi - ## now we're ready to proceed filling in the data - IFS="," - a_partition_working=( ${A_PARTITION_DATA[i]} ) - IFS="$ORIGINAL_IFS" - - dev_item=${a_partition_working[5]} - dev_label=${a_partition_working[6]} - dev_uuid=${a_partition_working[7]} - - # then if dev data/uuid is incomplete, try to get missing piece - # it's more likely we'll get a uuid than a label. But this should get the - # dev item set no matter what, so then we can get the rest of any missing data - # first we'll get the dev_item if it's missing - if [[ -n $dev_disk_uuid ]] && [[ -z $dev_item && -n $dev_uuid ]];then - dev_item=$( echo "$dev_disk_uuid" | gawk ' - /'$dev_uuid'/ { - item=gensub( /..\/..\/(.+)/, "\\1", 1, $NF ) - print item - }' ) - elif [[ -n $dev_disk_label ]] && [[ -z $dev_item && -n $dev_label ]];then - dev_item=$( echo "$dev_disk_label" | gawk ' - # first we need to change space x20 in by-label back to a real space - #gsub( /x20/, " ", $0 ) - # then we can see if the string is there - /'$dev_label'/ { - item=gensub( /..\/..\/(.+)/, "\\1", 1, $NF ) - print item - }' ) - fi - if [[ -n $dev_disk_uuid ]] && [[ -n $dev_item && -z $dev_uuid ]];then - dev_uuid=$( echo "$dev_disk_uuid" | gawk ' - /'$dev_item'$/ { - print $(NF - 2) - }' ) - fi - if [[ -n $dev_disk_label ]] && [[ -n $dev_item && -z $dev_label ]];then - dev_label=$( echo "$dev_disk_label" | gawk ' - /'$dev_item'/ { - print $(NF - 2) - }' ) - - fi - # assemble everything we could get for dev/h/dx, label, and uuid - IFS="," - A_PARTITION_DATA[i]=${a_partition_working[0]}","${a_partition_working[1]}","${a_partition_working[2]}","${a_partition_working[3]}","${a_partition_working[4]}","$dev_item","$dev_label","$dev_uuid - IFS="$ORIGINAL_IFS" - done - fi -} - -## return uptime string -get_uptime() -{ - ## note: removing gsub(/ /,"",a); to get get space back in there, goes right before print a - echo $( uptime | gawk '{ - a = gensub(/^.*up *([^,]*).*$/,"\\1","g",$0) - print a - }' ) +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 ){ + main::check_config_file(); + get_selection(); + } + else { + print_irc_message(); + } + eval $end if $b_log; } -#### ------------------------------------------------------------------- -#### special data handling for specific options and conditions -#### ------------------------------------------------------------------- +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'; +} -## multiply the core count by the data to be calculated, bmips, cache -# args: $1 - string to handle; $2 - cpu count -calculate_multicore_data() -{ - local string_number=$1 string_data='' - - if [[ -n $( egrep -i '( mb| kb)' <<< $1 ) ]];then - string_data=" $( gawk '{print $2}' <<< $1 )" # add a space for output - string_number=$( gawk '{print $1}' <<< $1 ) - fi - # handle weird error cases where it's not a number - if [[ -n $( egrep '^[0-9\.,]+$' <<< $string_number ) ]];then - string_number=$( echo $string_number $2 | gawk '{ - total = $1*$2 - print total - }' ) - elif [[ $string_number == '' ]];then - string_number='Not Available' - else - # I believe that the above returns 'unknown' by default so no need for extra text - string_number="$string_number " - fi - echo "$string_number$string_data" -} - -# prints out shortened list of flags, the main ones of interest -# args: $1 - string of cpu flags to process -process_cpu_flags() -{ - local cpu_flags="$1" - - # nx = AMD stack protection extensions - # lm = Intel 64bit extensions - # sse, sse2, pni = sse1,2,3 gfx extensions - # svm = AMD pacifica virtualization extensions - # vmx = Intel IVT (vanderpool) virtualization extensions - cpu_flags=$( echo "$cpu_flags" | gawk ' - BEGIN { - RS=" " - ssel["sse"] = 1 - ssel["sse2"] = 2 - ssel["pni"] = 3 - sses[1] = "sse" - sses[2] = "sse2" - sses[3] = "sse3" - } - /^(nx|lm|svm|vmx)$/ { - if (s) { - s = s " " $0 - } - else { - s = $0 - } +sub assign_selectors { + if ($type == 94){ + $configs{'variable'} = 'CONSOLE_COLOR_SCHEME'; + $configs{'selection'} = 'console'; } - /^(sse2?|pni)$/ { - if (ssel[$0] > sse) { - sse = ssel[$0] - } + elsif ($type == 95){ + $configs{'variable'} = 'VIRT_TERM_COLOR_SCHEME'; + $configs{'selection'} = 'virt-term'; } - END { - if (sse) { - if (s) { - s = sses[sse] " " s - } + 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."], + ); + } + @rows = ( + [ 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, '', '', ""], + ); + push @data, @rows; + if ( ! $b_irc ){ + @rows = ( + [ 0, '', '', "Please note that this will set the $configs{'selection'} + preferences only for user: $whoami"], + ); + push @data, @rows; + } + @rows = ( + [ 0, '', '', "$line1"], + ); + push @data, @rows; + 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 ); + for 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); + @rows = ( + [0, '', '', "$i)$spacer$colors{'c1'}Card:$colors{'c2'}^nVidia^GT218 + $colors{'c1'}Display^Server$colors{'c2'}^x11^(X.Org^1.7.7)$colors{'cn'}"], + ); + push @data, @rows; + } + 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 = (); + my $response = ; + chomp $response; + if ($response =~ /([^0-9]|^$)/ || ( $response =~ /^[0-9]+$/ && $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); + } +} +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 1; + } + 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_config_colors(); + } + set_config_color_scheme($response); + } +} +sub delete_all_config_colors { + my @file_lines = main::reader( $user_config_file ); + open( $w_fh, '>', $user_config_file ) or 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 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 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 1; +} + +} + +#### ------------------------------------------------------------------- +#### CONFIGS +#### ------------------------------------------------------------------- + +sub check_config_file { + $user_config_file = "$user_config_dir/$self_name.conf"; + if ( ! -f $user_config_file ){ + open( my $fh, '>', $user_config_file ) or error_handler('create', $user_config_file, $!); + } +} + +sub get_configs { + my (@configs) = @_; + my ($key, $val,@config_files); + if (!@configs){ + @config_files = ( + qq(/etc/$self_name.conf), + qq($user_config_dir/$self_name.conf) + ); + } + else { + @config_files = (@configs); + } + # 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, '<', "$_"); + while (<$fh>) { + chomp; + s/#.*//; + s/^\s+//; + s/\s+$//; + s/'|"//g; + s/true/1/; # switch to 1/0 perl boolean + s/false/0/; # switch to 1/0 perl boolean + next unless length; + ($key, $val) = split(/\s*=\s*/, $_, 2); + get_config_item($key,$val); + # print "f: $file key: $key val: $val\n"; + } + close $fh; + } +} + +# args: 0: key; 1: value +sub get_config_item { + my ($key,$val) = @_; + if ($key eq 'ALLOW_UPDATE' || $key eq 'B_ALLOW_UPDATE') {$b_update = int($val)} + elsif ($key eq 'ALLOW_WEATHER' || $key eq 'B_ALLOW_WEATHER') {$b_weather = int($val)} + elsif ($key eq 'CPU_SLEEP') {$cpu_sleep = $val if $val =~ /^[0-9\.]$/} + elsif ($key eq 'DL_TIMEOUT') {$dl_timeout = 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 = set_perl_downloader($val); + %dl = ('dl' => $val, $val => 1); + }} + elsif ($key eq 'FILTER_STRING') {$filter_string = $val} + elsif ($key eq 'LANGUAGE') {$language = $val if $val =~ /^(en)$/} + elsif ($key eq 'LIMIT') {$limit = int($val)} + elsif ($key eq 'OUTPUT_TYPE') {$output_type = $val if $val =~ /^json|screen|xml/} + elsif ($key eq 'PS_COUNT') {$ps_count = int($val) } + elsif ($key eq 'SENSORS_CPU_NO') {$sensors_cpu_nu = int($val)} + elsif ($key eq 'SHOW_HOST' || $key eq 'B_SHOW_HOST') { $show{'host'} = 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; + } + } + # layout + elsif ($key eq 'CONSOLE_COLOR_SCHEME') {$colors{'console'} = int($val)} + elsif ($key eq 'GLOBAL_COLOR_SCHEME') {$colors{'global'} = int($val)} + elsif ($key eq 'IRC_COLOR_SCHEME') {$colors{'irc-gui'} = int($val)} + elsif ($key eq 'IRC_CONS_COLOR_SCHEME') {$colors{'irc-console'} = int($val)} + elsif ($key eq 'IRC_X_TERM_COLOR_SCHEME') {$colors{'irc-virt-term'} = int($val)} + elsif ($key eq 'VIRT_TERM_COLOR_SCHEME') {$colors{'virt-term'} = 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 'SEP[23]_IRC') {$sep{'s2-irc'} = $val} + elsif ($key eq 'SEP[23]_CONSOLE') {$sep{'s2-console'} = $val} + # size + elsif ($key eq 'COLS_MAX_CONSOLE') {$size{'console'} = int($val)} + elsif ($key eq 'COLS_MAX_IRC') {$size{'irc'} = int($val)} + elsif ($key eq 'COLS_MAX_NO_DISPLAY') {$size{'no-display'} = int($val)} + elsif ($key eq 'INDENT') {$size{'indent'} = int($val)} + elsif ($key eq 'INDENT_MIN') {$size{'indent-min'} = int($val)} + # print "mc: key: $key val: $val\n"; + # print Dumper (keys %size) . "\n"; +} + +#### ------------------------------------------------------------------- +#### 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 $b_debug_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: $1 - type [fs|fe|cat|dump|raw] OR data to log +# arg: $2 - +# arg: $one type (fs/fe/cat/dump/raw) or logged data; +# [$two is function name; [$three - function args]] +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'){ + my @temp = @$three; + # 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 $b_debug_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 $b_debug_timers; + } + elsif ( $one eq 'cat') { + if ( $b_log_full ){ + for 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 ($b_debug_timers){ + print $timer if $timer; + } + #print "d: $data"; + elsif ($data){ + print $fh_l "$spacer$data"; + } +} + +sub set_debugger { + if ( $debug >= 20){ + error_handler('not-in-irc', 'debug data generator') if $b_irc; + my $option = ( $debug > 22 ) ? 'main-full' : 'main'; + $b_debug_gz = 1 if ($debug == 22 || $debug == 24); + my $ob_sys = SystemDebugger->new($option); + $ob_sys->run_debugger(); + $ob_sys->upload_file($ftp_alt) if $debug > 20; + exit 0; + } + elsif ($debug >= 10 && $debug <= 12){ + $b_log = 1; + if ($debug == 11){ + $b_log_full = 1; + } + elsif ($debug == 12){ + $b_log_colors = 1; + } + begin_logging(); + } + elsif ($debug <= 3){ + if ($debug == 3){ + $b_log = 1; + $b_debug_timers = 1; + begin_logging(); + } + else { + $end = ''; + $start = ''; + } + } +} + +## SystemDebugger +{ +package SystemDebugger; + +# use File::Find q(find); +#no warnings 'File::Find'; +# use File::Spec::Functions; +#use File::Copy; +#use POSIX qw(strftime); + +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: 1 - type +# args: 2 - upload +sub new { + my $class = shift; + ($option) = @_; + my $self = {}; + # print "$f\n"; + # print "$option\n"; + return bless $self, $class; +} + +sub run_debugger { + require File::Copy; + import File::Copy; + require File::Spec::Functions; + import File::Spec::Functions; + + print "Starting $self_name debugging data collector...\n"; + create_debug_directory(); + print "Note: for dmidecode data you must be root.\n" if !$b_root; + print $line3; + if (!$b_debug){ + audio_data(); + disk_data(); + display_data(); + network_data(); + perl_modules(); + system_data(); + } + system_files(); + print $line3; + if (!$b_debug){ + if ( -d '/sys' && main::count_dir_files('/sys') ){ + build_tree('sys'); + sys_traverse_data(); + } + else { + print "Skipping /sys data collection. /sys not present, or empty.\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 ( (!$b_root || $b_proc_debug ) && -d '/proc' && main::count_dir_files('/proc') ){ + build_tree('proc'); + proc_traverse_data(); + } + else { + print "Skipping /proc data collection. /proc not present, or empty.\n"; + } + print $line3; + } + run_self(); + print $line3; + compress_dir(); +} + +sub create_debug_directory { + my $host = main::get_hostname(); + $host =~ s/ /-/g; + $host = 'no-host' if !$host || $host eq 'N/A'; + my ($alt_string,$bsd_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'; + } + $bsd_string = "-BSD-$bsd_type" if $bsd_type; + if ($b_arm ){$alt_string = '-ARM'} + elsif ($b_mips) {$alt_string = '-MIPS'} + $debug_dir = "$self_name$alt_string$bsd_string-$host-$today$root_string-$self_version"; + $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 "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', '-l'], # alsa + ['pactl', 'list'], # pulseaudio + ); + 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'); + @files = (@files,@files2) if @files2; + copy_files(\@files,'audio'); +} +## 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/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/*/*'); + @files = (@files, @ides) if @ides; + } + else { + push (@files, '/proc-ide-directory'); + } + copy_files(\@files, 'disk'); + my @cmds = ( + ['btrfs', 'filesystem show'], + ['btrfs', 'filesystem show --mounted'], + # ['btrfs', 'filesystem show --all-devices'], + ['df', '-h -T'], + ['df', '-h'], + ['df', '-k'], + ['df', '-k -T'], + ['df', '-k -T -P'], + ['df', '-P'], + ['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'], + ['lsblk', '-rb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT'], + ['lsblk', '-Pb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE'], + ['lsblk', '-Pb --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT'], + ['gpart', 'list'], + ['gpart', 'show'], + ['gpart', 'status'], + ['ls', '-l /dev'], + ['ls', '-l /dev/disk'], + ['ls', '-l /dev/disk/by-id'], + ['ls', '-l /dev/disk/by-label'], + ['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/disk/by-path'], + ['ls', '-l /dev/mapper'], + # LSI raid https://hwraid.le-vert.net/wiki/LSIMegaRAIDSAS + ['megacli', '-AdpAllInfo -aAll'], + ['megacli', '-LDInfo -L0 -a0'], + ['megacli', '-PDList -a0'], + ['megaclisas-status', ''], + ['megaraidsas-status', ''], + ['megasasctl', ''], + ['mount', ''], + ['nvme', 'present'], + ['readlink', '/dev/root'], + ['swapon', '-s'], + # 3ware-raid + ['tw-cli', 'info'], + ['zfs', 'list'], + ['zpool', 'list'], + ['zpool', 'list -v'], + ); + run_commands(\@cmds,'disk'); + @cmds = ( + ['atacontrol', 'list'], + ['camcontrol', 'devlist'], + ['glabel', 'status'], + ['swapctl', '-l -k'], + ['swapctl', '-l -k'], + ['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'); + } + push (@files, $files{'xorg-log'}); + push (@files, '/etc/X11/xorg.conf'); + copy_files(\@files,'display-xorg'); + print "Collecting X, xprop, glxinfo, xrandr, xdpyinfo data, wayland, weston...\n"; + %data = ( + 'desktop-session' => $ENV{'DESKTOP_SESSION'}, + '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: + 'xdg-session-type' => $ENV{'XDG_SESSION_TYPE'}, + 'wayland-display' => $ENV{'WAYLAND_DISPLAY'}, + '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-indent' => $size{'indent'}, + 'size-indent-min' => $size{'indent-min'}, + 'size-cols-max' => $size{'max'}, + ); + 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'], + ['glxinfo',''], + ['glxinfo','-B'], + ['kded','--version'], + ['kded1','--version'], + ['kded2','--version'], + ['kded3','--version'], + ['kded4','--version'], + ['kded5','--version'], + ['kded6','--version'], + ['kf4-config','--version'], + ['kf5-config','--version'], + ['kf6-config','--version'], + ['kwin_x11','--version'], + ['loginctl','--no-pager list-sessions'], + ['nvidia-settings','-q screens'], + ['nvidia-settings','-c :0.0 -q all'], + ['nvidia-smi','-q'], + ['nvidia-smi','-q -x'], + ['plasmashell','--version'], + ['vainfo',''], + ['vdpauinfo',''], + ['weston-info',''], + ['wmctrl','-m'], + ['weston','--version'], + ['xdpyinfo',''], + ['Xorg','-version'], + ['xprop','-root'], + ['xrandr',''], + ); + run_commands(\@cmds,'display'); +} +sub network_data { + print "Collecting networking data...\n"; +# no warnings 'uninitialized'; + my @cmds = ( + ['ifconfig',''], + ['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"; + my %data = ( + 'cc' => $ENV{'CC'}, + # @(#)MIRBSD KSH R56 2018/03/09: ksh and mksh + 'ksh-version' => system('echo -n $KSH_VERSION'), # shell, not env, variable + 'manpath' => $ENV{'MANPATH'}, + 'path' => $ENV{'PATH'}, + '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', '-b kern.geom.conftxt'], + ['sysctl', '-b kern.geom.confxml'], + ['usbdevs','-v'], + # freebsd + ['pciconf','-l -cv'], + ['pciconf','-vl'], + ['pciconf','-l'], + # openbsd + ['pcidump',''], + ['pcidump','-v'], + # netbsd + ['kldstat',''], + ['pcictl','list'], + ['pcictl','list -ns'], + ); + run_commands(\@cmds,'system-bsd'); + # diskinfo -v + # fdisk + @cmds = ( + ['clang','--version'], + ['dmidecode',''], + ['dmesg',''], + ['gcc','--version'], + ['hciconfig','-a'], + ['initctl','list'], + ['ipmi-sensors',''], + ['ipmi-sensors','--output-sensor-thresholds'], + ['ipmitool','sensor'], + ['lscpu',''], + ['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',''], + ['lsusb','-v'], + ['ps','aux'], + ['ps','-e'], + ['ps','-p 1'], + ['runlevel',''], + ['rc-status','-a'], + ['rc-status','-l'], + ['rc-status','-r'], + ['sensors',''], + # 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','list-units'], + ['systemctl','list-units --type=target'], + ['systemd-detect-virt',''], + ['upower','-e'], + ['uptime',''], + ['vcgencmd','get_mem arm'], + ['vcgencmd','get_mem gpu'], + ); + run_commands(\@cmds,'system'); + @files = main::globber('/dev/bus/usb/*/*'); + copy_files(\@files, 'system'); +} +sub system_files { + print "Collecting system files data...\n"; + my (%data,@files,@files2); + @files = RepoData::get($data_dir); + copy_files(\@files, 'repo'); + # chdir "/etc"; + @files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*'); + push (@files, '/etc/issue'); + push (@files, '/etc/lsb-release'); + push (@files, '/etc/os-release'); + copy_files(\@files,'system-distro'); + @files = main::globber('/etc/upstream[-_]{[rR]elease,[vV]ersion}/*'); + copy_files(\@files,'system-distro'); + @files = ( + '/proc/1/comm', + '/proc/cpuinfo', + '/proc/meminfo', + '/proc/modules', + '/proc/net/arp', + '/proc/version', + ); + @files2=main::globber('/sys/class/power_supply/*/uevent'); + if (@files2){ + @files = (@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'); +} +## 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 $i = ($option eq 'main-full')? ' -i' : ''; + my $cmd = "$self_path/$self_name -FRfrploudmxxx$i -c 0 --usb --slots --debug 10 -y 120 > $data_dir/$self_name-FRfrploudmxxxyusbslots120.txt 2>&1"; + 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 $_ ) { + 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 (@$cmds){ + my @rows = @$_; + 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 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) = (''); + # wd 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 == 5 ){ "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 - "; + } else { - s = sses[sse] + $type = "f - "; } + $working[9] ||= ''; + $working[10] ||= ''; + $output = $output . " $type$working[8] $working[9] $working[10]\n"; } - print s - }' ) + 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'; + $parse_src = 'proc'; + File::Find::find( \&wanted, "/proc"); + proc_traverse_processor(); + @content = (); +} +sub proc_traverse_processor { + 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"; +# } +} - #grep -oE '\<(nx|lm|sse[0-9]?|pni|svm|vmx)\>' | tr '\n' ' ')) - if [[ -z $cpu_flags ]];then - cpu_flags="-" - fi - echo "$cpu_flags" +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'; + $parse_src = 'sys'; + File::Find::find( \&wanted, "/sys"); + sys_traverse_processsor(); + @content = (); +} +sub sys_traverse_processsor { + 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; + 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"; +} +sub wanted { + 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'){ + # 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. Also do not need . files or __ starting files + # print $File::Find::name . "\n"; + # block maybe: cfgroup\/ + return if $File::Find::name =~ /\/(\.[a-z]|kernel\/|parameters\/|debug\/)/; + # 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 =~ /^\/proc\/[0-9]+\//; + return if $File::Find::name =~ /^\/proc\/bus\/pci\//; + return if $File::Find::name =~ /^\/proc\/(irq|spl|sys)\//; + # these choke on sudo/root: kmsg kcore kpage and we don't want keys or kallsyms + return if $File::Find::name =~ /^\/proc\/k/; + return if $File::Find::name =~ /(\/mb_groups|debug)$/; + } + # print $File::Find::name . "\n"; + push (@content, $File::Find::name); + return; +} +# args: 1 - path to file to be uploaded +# args: 2 - optional: alternate ftp upload url +# NOTE: must be in format: ftp.site.com/incoming +sub upload_file { + require Net::FTP; + import Net::FTP; + 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); + $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 ($b_debug_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"); + } +} } #### ------------------------------------------------------------------- -#### print and processing of output data +#### DOWNLOADER #### ------------------------------------------------------------------- -#### MASTER PRINT FUNCTION - triggers all line item print functions -## main function to print out, master for all sub print functions. -print_it_out() -{ - # note that print_it_out passes local variable values on to its children, - # and in some cases, their children, with Lspci_Data - local Lspci_Data='' # only for verbose - - if [[ $B_SHOW_SHORT_OUTPUT == 'true' ]];then - print_short_data - else - Lspci_Data="$( get_lspci_data )" - if [[ $VERBOSITY_LEVEL -ge 1 || $B_SHOW_SYSTEM == 'true' ]];then - print_system_data - fi - if [[ $VERBOSITY_LEVEL -ge 1 || $B_SHOW_CPU == 'true' ]];then - print_cpu_data - fi - if [[ $VERBOSITY_LEVEL -ge 1 || $B_SHOW_GRAPHICS == 'true' ]];then - print_gfx_data - fi - if [[ $VERBOSITY_LEVEL -ge 5 || $B_SHOW_AUDIO == 'true' ]];then - print_audio_data - fi - if [[ $VERBOSITY_LEVEL -ge 2 || $B_SHOW_NETWORK == 'true' ]];then - print_networking_data - fi - if [[ $VERBOSITY_LEVEL -ge 1 || $B_SHOW_DISK == 'true' ]];then - print_hard_disk_data - fi - if [[ $VERBOSITY_LEVEL -ge 4 || $B_SHOW_PARTITIONS == 'true' ]];then - print_partition_data - fi - if [[ $VERBOSITY_LEVEL -ge 1 || $B_SHOW_INFO == 'true' ]];then - print_info_data - fi - fi -} - -#### SHORT OUTPUT PRINT FUNCTION, ie, verbosity 0 -# all the get data stuff is loaded here to keep execution time down for single line print commands -# these will also be loaded in each relevant print function for long output -print_short_data() -{ - local current_kernel=$( uname -rm ) # | gawk '{print $1,$3,$(NF-1)}' ) - local processes="$(( $( ps aux | wc -l ) - 1 ))" - local short_data='' i='' b_background_black='false' - local memory=$( get_memory_data ) - local up_time="$( get_uptime )" - - # set A_CPU_CORE_DATA - get_cpu_core_count - local cpc_plural='' cpu_count_print='' model_plural='' - local cpu_physical_count=${A_CPU_CORE_DATA[0]} - local cpu_core_count=${A_CPU_CORE_DATA[3]} - local cpu_core_alpha=${A_CPU_CORE_DATA[1]} - local cpu_type=${A_CPU_CORE_DATA[2]} - - if [[ $cpu_physical_count -gt 1 ]];then - cpc_plural='(s)' - model_plural='s' - cpu_count_print="$cpu_physical_count " - fi - - local cpu_data_string="${cpu_count_print}${cpu_core_alpha} core" -# local cpu_core_count=${A_CPU_CORE_DATA[0]} - - # load A_HDD_DATA - get_hdd_data_basic - ## note: if hdd_model is declared prior to use, whatever string you want inserted will - ## be inserted first. In this case, it's desirable to print out (x) before each disk found. - local a_hdd_data_count=$(( ${#A_HDD_DATA[@]} - 1 )) - IFS="," - local a_hdd_basic_working=( ${A_HDD_DATA[$a_hdd_data_count]} ) - IFS="$ORIGINAL_IFS" - local hdd_capacity=${a_hdd_basic_working[0]} - local hdd_used=${a_hdd_basic_working[1]} - - # load A_CPU_DATA - get_cpu_data - - IFS="," - local a_cpu_working=(${A_CPU_DATA[0]}) - IFS="$ORIGINAL_IFS" - local cpu_model="${a_cpu_working[0]}" - ## assemble data for output - local cpu_clock="${a_cpu_working[1]}" # old CPU3 - # this gets that weird min/max final array item - local min_max_clock_nu=$(( ${#A_CPU_DATA[@]} - 1 )) - local min_max_clock=${A_CPU_DATA[$min_max_clock_nu]} - - #set_color_scheme 12 - if [[ $B_RUNNING_IN_SHELL == 'false' ]];then - for i in $C1 $C2 $CN - do - case "$i" in - "$GREEN"|"$WHITE"|"$YELLOW"|"$CYAN") - b_background_black='true' - ;; - esac - done - if [[ $b_background_black == 'true' ]];then - for i in C1 C2 CN - do - ## these need to be in quotes, don't know why - if [[ ${!i} == $NORMAL ]];then - declare $i="${!i}15,1" - else - declare $i="${!i},1" - fi - done - #C1="${C1},1"; C2="${C2},1"; CN="${CN},1" - fi - fi - short_data="${C1}CPU$cpc_plural${CN}[${C2}${SEP1}${cpu_data_string} ${cpu_model}$model_plural (${cpu_type}) clocked at ${min_max_clock}${SEP1}${CN}] ${C1}Kernel${CN}[${C2}${SEP1}${current_kernel}${SEP1}${CN}] ${C1}Up${CN}[${C2}${SEP1}${up_time}${SEP1}${CN}] ${C1}Mem${CN}[${C2}${SEP1}${memory}${SEP1}${CN}] ${C1}HDD${CN}[${C2}${SEP1}${hdd_capacity}($hdd_used)${SEP1}${CN}] ${C1}Procs${CN}[${C2}${SEP1}${processes}${SEP1}${CN}]" - - if [[ $SHOW_IRC -gt 0 ]];then - short_data="${short_data} ${C1}Client${CN}[${C2}${SEP1}${IRC_CLIENT}${IRC_CLIENT_VERSION}${SEP1}${CN}]" - fi - short_data="${short_data} ${C1}$SCRIPT_NAME${C2}${CN}[${C2}${SEP1}$SCRIPT_VERSION_NUMBER${SEP1}${CN}]" - if [[ $SCHEME -gt 0 ]];then - short_data="${short_data} $NORMAL" - fi - print_screen_output "$short_data" -} - -#### LINE ITEM PRINT FUNCTIONS - -# print sound card data -print_audio_data() -{ - local i='' card_one='Card-1 ' audio_data='' a_audio_data='' port_data='' - local a_audio_working='' alsa_driver='' alsa_data='' port_plural='' module_version='' - # set A_AUDIO_DATA and get alsa data - get_audio_data - alsa_data=$( get_audio_alsa_data ) - IFS="," - a_audio_working=(${A_AUDIO_DATA[0]}) - IFS="$ORIGINAL_IFS" - - if [[ -n ${A_AUDIO_DATA[@]} ]];then - # slightly complicated because 2nd array item could be the alsa data - if [[ ${#A_AUDIO_DATA[@]} -le 1 ]];then - card_one='Card ' - fi - -# if [[ -n ${a_audio_working[2]} ]];then -# port_data=" ${C1}at port${C2} ${a_audio_working[2]}" -# fi - # this should only trigger if the $DIR_ASOUND_DEVICE data is used, not lspci -nn - if [[ -n ${a_audio_working[3]} && $B_EXTRA_DATA == 'true' ]];then - module_version=$( print_module_version "${a_audio_working[3]}" ) - fi - if [[ -n ${a_audio_working[1]} ]];then - alsa_driver=" ${C1}driver${C2} ${a_audio_working[1]}$module_version" - fi - if [[ -n ${a_audio_working[2]} && $B_EXTRA_DATA == 'true' ]];then - if [[ $( wc -w <<< ${a_audio_working[2]} ) -gt 1 ]];then - port_plural='s' - fi - port_data=" ${C1}at port$port_plural${C2} ${a_audio_working[2]}" - fi - audio_data="${C1}$card_one${C2}${a_audio_working[0]}$alsa_driver$port_data" - audio_data=$( create_print_line "Audio:" "$audio_data" ) - print_screen_output "$audio_data" - i=0 ## loop starts with 1 by auto-increment so it only shows cards > 1 - while [[ -n ${A_AUDIO_DATA[++i]} ]] - do - IFS="," - a_audio_working=( ${A_AUDIO_DATA[i]} ) - IFS="$ORIGINAL_IFS" - port_data='' - alsa_driver='' - port_plural='' - module_version='' - if [[ -n ${a_audio_working[3]} && $B_EXTRA_DATA == 'true' ]];then - module_version=$( print_module_version "${a_audio_working[3]}" ) - fi - # we're testing for the presence of the 2nd array item here, which is the driver name - if [[ -n ${a_audio_working[1]} ]];then - alsa_driver="${C1}driver${C2} ${a_audio_working[1]}" - fi - if [[ -n ${a_audio_working[2]} && $B_EXTRA_DATA == 'true' ]];then - if [[ $( wc -w <<< ${a_audio_working[2]} ) -gt 1 ]];then - port_plural='s' - fi - port_data=" ${C1}at port$port_plural${C2} ${a_audio_working[2]}" - fi - if [[ -n ${a_audio_working[0]} ]];then - audio_data="${C1}Card-$(( $i + 1 )) ${C2}${a_audio_working[0]}$alsa_driver$port_data" - fi - if [[ -n $audio_data ]];then - audio_data=$( create_print_line " " "$audio_data" ) - print_screen_output "$audio_data" - fi - done - # alsa driver data only prints out if sound card data is found - if [[ -n $alsa_data ]];then - audio_data="${C1}Sound: ${C2}$alsa_data" - audio_data=$( create_print_line " " "$audio_data" ) - print_screen_output "$audio_data" - fi - fi -} - -print_cpu_data() -{ - local cpu_data='' i='' cpu_clock_speed='' cpu_multi_clock_data='' - local bmip_data='' cpu_cache='' cpu_vendor='' cpu_flags='' - - ##print_screen_output "A_CPU_DATA[0]=\"${A_CPU_DATA[0]}\"" - # Array A_CPU_DATA always has one extra element: max clockfreq found. - # that's why its count is one more than you'd think from cores/cpus alone - # load A_CPU_DATA - get_cpu_data - - IFS="," - local a_cpu_working=(${A_CPU_DATA[0]}) - IFS="$ORIGINAL_IFS" - local cpu_model="${a_cpu_working[0]}" - ## assemble data for output - local cpu_clock="${a_cpu_working[1]}" - - cpu_vendor=${a_cpu_working[5]} - - # set A_CPU_CORE_DATA - get_cpu_core_count - local cpc_plural='' cpu_count_print='' model_plural='' - local cpu_physical_count=${A_CPU_CORE_DATA[0]} - local cpu_core_count=${A_CPU_CORE_DATA[3]} - local cpu_core_alpha=${A_CPU_CORE_DATA[1]} - local cpu_type=${A_CPU_CORE_DATA[2]} - - if [[ $cpu_physical_count -gt 1 ]];then - cpc_plural='(s)' - cpu_count_print="$cpu_physical_count " - model_plural='s' - fi - - local cpu_data_string="${cpu_count_print}${cpu_core_alpha} core" - # Strange (and also some expected) behavior encountered. If print_screen_output() uses $1 - # as the parameter to output to the screen, then passing " ${ARR[@]} " - # will output only and first element of ARR. That "@" splits in elements and "*" _doesn't_, - # is to be expected. However, that text2 is consecutively truncated is somewhat strange, so take note. - # This has been confirmed by #bash on freenode. - # The above mentioned only emerges when using the debugging markers below - ## print_screen_output "a_cpu_working=\"***${a_cpu_working[@]} $hostName+++++++\"----------" - - if [[ -z ${a_cpu_working[2]} ]];then - a_cpu_working[2]="unknown" - fi - - cpu_data=$( create_print_line "CPU$cpc_plural:" "${C1}${cpu_data_string}${C2} ${a_cpu_working[0]}$model_plural (${cpu_type})" ) - if [[ $VERBOSITY_LEVEL -ge 3 || $B_SHOW_CPU == 'true' ]];then - # update for multicore, bogomips x core count. - if [[ $B_EXTRA_DATA == 'true' ]];then -# if [[ $cpu_vendor != 'intel' ]];then - bmip_data=$( calculate_multicore_data "${a_cpu_working[4]}" "$(( $cpu_core_count * $cpu_physical_count ))" ) -# else -# bmip_data="${a_cpu_working[4]}" -# fi - bmip_data=" ${C1}bmips${C2} $bmip_data" - fi - ## note: this handles how intel reports L2, total instead of per core like AMD does - # note that we need to multiply by number of actual cpus here to get true cache size - if [[ $cpu_vendor != 'intel' ]];then - cpu_cache=$( calculate_multicore_data "${a_cpu_working[2]}" "$(( $cpu_core_count * $cpu_physical_count ))" ) - else - cpu_cache=$( calculate_multicore_data "${a_cpu_working[2]}" "$cpu_physical_count" ) - fi - # only print shortened list - if [[ $B_CPU_FLAGS_FULL != 'true' ]];then - cpu_flags=$( process_cpu_flags "${a_cpu_working[3]}" ) - cpu_flags=" ${C1}flags${C2} ($cpu_flags)" - fi - cpu_data="$cpu_data${C2} ${C1}cache${C2} $cpu_cache$cpu_flags$bmip_data${CN}" - fi - # we don't this printing out extra line unless > 1 cpu core - if [[ ${#A_CPU_DATA[@]} -gt 2 ]] && [[ $B_SHOW_CPU == 'true' || $VERBOSITY_LEVEL -ge 5 ]];then - cpu_clock_speed='' # null < verbosity level 5 - else - cpu_data="$cpu_data ${C1}clocked at${C2} ${a_cpu_working[1]} MHz${CN}" - fi - - cpu_data="$cpu_data $cpu_clock_speed" - print_screen_output "$cpu_data" - - # we don't this printing out extra line unless > 1 cpu core - if [[ ${#A_CPU_DATA[@]} -gt 2 ]] && [[ $B_SHOW_CPU == 'true' || $VERBOSITY_LEVEL -ge 5 ]];then - for (( i=0; i < ${#A_CPU_DATA[@]}-1; i++ )) - do - IFS="," - a_cpu_working=(${A_CPU_DATA[i]}) - IFS="$ORIGINAL_IFS" - cpu_multi_clock_data="$cpu_multi_clock_data ${C1}($(( i + 1 )))${C2} ${a_cpu_working[1]} MHz${CN}" - if [[ $i -gt 10 ]];then - break - fi - done - if [[ -n $cpu_multi_clock_data ]];then - cpu_multi_clock_data=$( create_print_line " " "${C1}Clock Speeds:${C2}$cpu_multi_clock_data" ) - print_screen_output "$cpu_multi_clock_data" - fi - fi - if [[ $B_CPU_FLAGS_FULL == 'true' ]];then - print_cpu_flags_full "${a_cpu_working[3]}" - fi -} - -# takes list of all flags, split them and prints x per line -# args: $1 - cpu flag string -print_cpu_flags_full() -{ - local cpu_flags_full="$1" a_cpu_flags='' line_starter='' - local i=0 counter=0 max_length=18 max_length_add=18 flag='' flag_data='' - - # build the flag line array - for flag in $cpu_flags_full - do - a_cpu_flags[$counter]="${a_cpu_flags[$counter]}$flag " - if [[ $i -ge $max_length ]];then - (( counter++ )) - max_length=$(( $max_length + $max_length_add )) - fi - ((i++)) - done - # then print it out - for (( i=0; i < ${#a_cpu_flags[@]};i++ )) - do - if [[ $i -eq 0 ]];then - line_starter="${C1}CPU Flags${C2} " - else - line_starter='' - fi - flag_data=$( create_print_line " " "$line_starter${a_cpu_flags[$i]}" ) - print_screen_output "$flag_data" - done -} - -print_gfx_data() -{ - local gfx_data='' i='' card_one='Card ' - local screen_resolution="$( get_graphics_res_data )" - local b_is_mesa='false' - - # set A_GFX_CARD_DATA - get_graphics_card_data - # set A_X_DATA - get_graphics_x_data - local x_vendor=${A_X_DATA[0]} - local x_version=${A_X_DATA[1]} - # set A_GLX_DATA - get_graphics_glx_data - local glx_renderer="${A_GLX_DATA[0]}" - local glx_version="${A_GLX_DATA[1]}" - # this can contain a long No case debugging message, so it's being sliced off - # note: using grep -ioE '(No|Yes)' <<< ${A_GLX_DATA[2]} did not work in Arch, no idea why - local glx_direct_render=$( gawk '{ - print $1 - }' <<< "${A_GLX_DATA[2]}" ) - - if [[ ${#A_GFX_CARD_DATA[@]} -gt 1 ]];then - i=1 - while [[ -n ${A_GFX_CARD_DATA[i]} && $i -le 3 ]] - do - gfx_data=" ${C1}Card-$(($i+1))${C2} ${A_GFX_CARD_DATA[i]}" - ((i++)) - done - card_one='Card-1 ' - fi - gfx_data=$( create_print_line "Graphics:" "${C1}$card_one${C2}${A_GFX_CARD_DATA[0]}${gfx_data}" ) - - if [[ $B_X_RUNNING == 'true' ]];then - gfx_data="${gfx_data} ${C1}$x_vendor${C2} $x_version ${C1}Res${C2} ${screen_resolution}" - else - gfx_data="${gfx_data} ${C1} tty resolution ${CN}(${C2} ${screen_resolution} ${CN})" - fi - print_screen_output "$gfx_data" - -# if [[ -z $glx_renderer || -z $glx_version ]];then -# b_is_mesa='true' -# fi - - ## note: if glx render or version have no content, then mesa is true - # if [[ $B_X_RUNNING == 'true' ]] && [[ $b_is_mesa != 'true' ]];then - if [[ $B_X_RUNNING == 'true' ]];then - gfx_data=$( create_print_line " " "${C1}GLX Renderer${C2} ${glx_renderer} ${C1}GLX Version${C2} ${glx_version}${CN}" ) - if [[ $B_HANDLE_CORRUPT_DATA == 'true' || $B_EXTRA_DATA == 'true' ]];then - gfx_data="$gfx_data ${C1}Direct Rendering${C2} ${glx_direct_render}${CN}" - fi - print_screen_output "$gfx_data" - fi -} - -print_hard_disk_data() -{ - local hdd_data='' hdd_data_2='' a_hdd_working='' - local dev_data='' size_data='' hdd_model='' hdd_model_2='' hdd_model_3='' usb_data='' - - # load A_HDD_DATA - get_hdd_data_basic - ## note: if hdd_model is declared prior to use, whatever string you want inserted will - ## be inserted first. In this case, it's desirable to print out (x) before each disk found. - local a_hdd_data_count=$(( ${#A_HDD_DATA[@]} - 1 )) - IFS="," - local a_hdd_basic_working=( ${A_HDD_DATA[$a_hdd_data_count]} ) - IFS="$ORIGINAL_IFS" - local hdd_capacity=${a_hdd_basic_working[0]} - local hdd_used=${a_hdd_basic_working[1]} - - if [[ $VERBOSITY_LEVEL -ge 3 ]] || [[ $B_SHOW_DISK == 'true' ]];then - ## note: the output part of this should be in the print hdd data function, not here - get_hard_drive_data_advanced - for (( i=0; i < ${#A_HDD_DATA[@]} - 1; i++ )) - do - # this adds the (x) numbering in front of each disk found, and creates the full disk string - IFS="," - a_hdd_working=( ${A_HDD_DATA[i]} ) - IFS="$ORIGINAL_IFS" - if [[ $B_SHOW_DISK == 'true' ]];then - if [[ -n ${a_hdd_working[3]} ]];then - usb_data="${a_hdd_working[3]} " - else - usb_data='' - fi - dev_data="/dev/${a_hdd_working[0]} " - size_data=" ${a_hdd_working[1]}" - fi - # wrap to avoid long lines - - if [[ $i -gt 1 && $B_SHOW_DISK == 'true' ]] || [[ $i -gt 3 ]];then - hdd_model_2="${hdd_model_2}${hdd_model_2+${C1}$(($i+1)):${C2}} $usb_data$dev_data${a_hdd_working[2]}$size_data " - else - hdd_model="${hdd_model}${hdd_model+ ${C1}$(($i+1)):${C2}} $usb_data$dev_data${a_hdd_working[2]}$size_data" - fi - done - if [[ -z $hdd_model ]];then - hdd_model=' None Detected' - fi - if [[ -n $hdd_model_2 ]];then - hdd_data=$( create_print_line "Disks:" "${C1}HDD${C2} ${C1}Total Size:${C2} ${hdd_capacity} (${hdd_used})${hdd_model}" ) - hdd_data_2=$( create_print_line " " "${hdd_model_2}${CN}" ) - else - hdd_data=$( create_print_line "Disks:" "${C1}HDD${C2} ${C1}Total Size:${C2} ${hdd_capacity} (${hdd_used})${hdd_model}${CN}" ) - fi - else - hdd_data=$( create_print_line "Disks:" "${C1}HDD Total Size:${C2} ${hdd_capacity} (${hdd_used})${CN}" ) - fi - print_screen_output "$hdd_data" - if [[ -n $hdd_model_2 ]];then - print_screen_output "$hdd_data_2" - fi -} - -print_info_data() -{ - local suggested_app="runlevel" - local info_data='' - local runlvl='' - local memory="$( get_memory_data )" - local processes="$(( $( ps aux | wc -l ) - 1 ))" - local up_time="$( get_uptime )" - - # Some code could look superfluous but BitchX doesn't like lines not ending in a newline. F*&k that bitch! - # long_last=$( echo -ne "${C1}Processes${C2} ${processes}${CN} | ${C1}Uptime${C2} ${up_time}${CN} | ${C1}Memory${C2} ${MEM}${CN}" ) - info_data=$( create_print_line "Info:" "${C1}Processes${C2} ${processes} ${C1}Uptime${C2} ${up_time} ${C1}Memory${C2} ${memory}${CN}" ) - - # this only triggers if no X data is present - if [[ $B_X_RUNNING != 'true' ]];then - if [[ -e $suggested_app ]];then - runlvl="$( runlevel | gawk '{ print $2 }' )" - info_data="${info_data} ${C1}Runlevel${C2} ${runlvl}${CN}" - fi - fi - - if [[ $SHOW_IRC -gt 0 ]];then - info_data="${info_data} ${C1}Client${C2} ${IRC_CLIENT}${IRC_CLIENT_VERSION}${CN}" - fi - info_data="${info_data} ${C1}$SCRIPT_NAME${C2} $SCRIPT_VERSION_NUMBER${CN}" - - if [[ $SCHEME -gt 0 ]];then - info_data="${info_data} ${NORMAL}" - fi - print_screen_output "$info_data" -} - -# args: $1 - module name (could be > 1, so loop it ) -print_module_version() -{ - local module_versions='' module='' version='' +sub download_file { + my ($type, $url, $file) = @_; + my ($cmd,$args,$timeout) = ('','',''); + my $debug_data = ''; + my $result = 1; + $dl{'no-ssl-opt'} ||= ''; + $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-opt'}\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($type, $url, $file); + $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 + else { + if ($type eq 'stdout'){ + $args = $dl{'stdout'}; + $cmd = "$dl{'dl'} $dl{'no-ssl-opt'} $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-opt'} $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-opt'} $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 $test[1]; + log_data('data',"$cmd\nResult: $result") if $b_log; + return $result; +} + +sub get_file { + my ($type, $url, $file) = @_; + my $response = HTTP::Tiny->new->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 $test[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; + $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'}){ + if (check_module('HTTP::Tiny') && check_module('IO::Socket::SSL')){ + import HTTP::Tiny; + import IO::Socket::SSL; + $dl{'tiny'} = 1; + } + else { + $dl{'tiny'} = 0; + } + } + #print $dl{'tiny'} . "\n"; + if ($dl{'tiny'}){ + $dl{'dl'} = 'tiny'; + $dl{'file'} = ''; + $dl{'stdout'} = ''; + $dl{'timeout'} = ''; + } + elsif ( $dl{'curl'} && check_program('curl') ){ + $dl{'dl'} = 'curl'; + $dl{'file'} = ' -L -s -o '; + $dl{'no-ssl'} = ' --insecure'; + $dl{'stdout'} = ' -L -s '; + $dl{'timeout'} = ' -y '; + } + elsif ($dl{'wget'} && check_program('wget') ){ + $dl{'dl'} = 'wget'; + $dl{'file'} = ' -q -O '; + $dl{'no-ssl'} = ' --no-check-certificate'; + $dl{'spider'} = ' -q --spider'; + $dl{'stdout'} = ' -q -O -'; + $dl{'timeout'} = ' -T '; + } + elsif ($dl{'fetch'} && check_program('fetch')){ + $dl{'dl'} = 'fetch'; + $dl{'file'} = ' -q -o '; + $dl{'no-ssl'} = ' --no-verify-peer'; + $dl{'stdout'} = ' -q -o -'; + $dl{'timeout'} = ' -T '; + } + elsif ( $bsd_type eq 'openbsd' && check_program('ftp') ){ + $dl{'dl'} = 'ftp'; + $dl{'file'} = ' -o '; + $dl{'null'} = ' 2>/dev/null'; + $dl{'stdout'} = ' -o - '; + $dl{'timeout'} = ''; + } + else { + $dl{'dl'} = ''; + } + # no-ssl-opt is set to 1 with --no-ssl, so it is true, then assign + $dl{'no-ssl-opt'} = $dl{'no-ssl'} if $dl{'no-ssl-opt'}; + eval $end if $b_log; +} + +sub set_perl_downloader { + my ($downloader) = @_; + $downloader =~ s/perl/tiny/; + return $downloader; +} - for module in $1 - do - version=$( get_module_version_number $module ) - if [[ -n $version ]];then - module_versions="$module_versions $version" - fi - done +#### ------------------------------------------------------------------- +#### 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!" } + ## 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-login' ) { + $errno=71; "There was an error with login to ftp server: $one" } + elsif ( $err eq 'ftp-upload' ) { + $errno=72; "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" } + ## 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 0 if $b_exit; +} - if [[ -n $module_versions ]];then - echo " ${C1}v:${C2}$module_versions" - fi +sub error_defaults { + my ($type,$one) = @_; + $one ||= ''; + my %errors = ( + 'download-error' => "Download Failure:\n$one\n", + ); + return $errors{$type}; } -print_networking_data() -{ - local i='' card_one='Card-1' network_data='' a_network_working='' port_data='' driver_data='' - local card_string='' port_plural='' module_version='' - # set A_NETWORK_DATA - get_networking_data - - IFS="," - a_network_working=(${A_NETWORK_DATA[0]}) - IFS="$ORIGINAL_IFS" - - # will never be null because null is handled in get_network_data, but in case we change - # that leaving this test in place. - if [[ -n ${A_NETWORK_DATA[@]} ]];then - if [[ ${#A_NETWORK_DATA[@]} -le 1 ]];then - card_one='Card' - fi - if [[ -n ${a_network_working[3]} && $B_EXTRA_DATA == 'true' ]];then - module_version=$( print_module_version "${a_network_working[3]}" ) - fi - if [[ -n ${a_network_working[1]} ]];then - driver_data=" ${C1}driver${C2} ${a_network_working[1]}$module_version" - fi - if [[ -n ${a_network_working[2]} && $B_EXTRA_DATA == 'true' ]];then - if [[ $( wc -w <<< ${a_network_working[2]} ) -gt 1 ]];then - port_plural='s' - fi - port_data=" ${C1}at port$port_plural${C2} ${a_network_working[2]}" - fi - card_string='' - network_data="${C1}$card_one${C2} ${a_network_working[0]}$driver_data$port_data" - network_data=$( create_print_line "Network:" "$network_data" ) - print_screen_output "$network_data" - i=0 ## loop starts with 1 by auto-increment so it only shows cards > 1 - while [[ -n ${A_NETWORK_DATA[++i]} ]] - do - IFS="," - a_network_working=( ${A_NETWORK_DATA[i]} ) - IFS="$ORIGINAL_IFS" - port_data='' - driver_data='' - port_plural='' - module_version='' - if [[ -n ${a_network_working[3]} && $B_EXTRA_DATA == 'true' ]];then - module_version=$( print_module_version "${a_network_working[3]}" ) - fi - if [[ -n ${a_network_working[1]} ]];then - driver_data=" ${C1}driver${C2} ${a_network_working[1]}$module_version" - fi - if [[ -n ${a_network_working[2]} && $B_EXTRA_DATA == 'true' ]];then - if [[ $( wc -w <<< ${a_network_working[2]} ) -gt 1 ]];then - port_plural='s' - fi - port_data=" ${C1}at port$port_plural${C2} ${a_network_working[2]}" - fi - network_data="${C1}Card-$(( $i + 1 )) ${C2}${a_network_working[0]}$driver_data$port_data" - network_data=$( create_print_line " " "$network_data" ) - print_screen_output "$network_data" - done - fi - if [[ $B_SHOW_IP == 'true' ]];then - print_networking_ip_data - fi -} - -print_networking_ip_data() -{ - local ip=$( get_networking_wan_ip_data ) - local ip_data='' a_interfaces_working='' interfaces='' interfaces_2='' i='' - - # set A_INTERFACES_DATA - get_networking_local_ip_data - # first print output for wan ip line. Null is handled in the get function - ip_data=$( create_print_line " " "${C1}Wan IP:${C2} $ip" ) - - # then create the list of local interface/ip - interfaces=" ${C1}Interface:${C2}" - i=0 ## loop starts with 1 by auto-increment so it only shows cards > 1 - while [[ -n ${A_INTERFACES_DATA[i]} ]] - do - IFS="," - a_interfaces_working=(${A_INTERFACES_DATA[i]}) - IFS="$ORIGINAL_IFS" - if [[ $i -lt 3 ]];then - if [[ -n ${a_interfaces_working[0]} ]];then - interfaces="$interfaces ${C1}${a_interfaces_working[0]}${C2} ${a_interfaces_working[1]}" - fi - else - if [[ -n ${a_interfaces_working[0]} ]];then - # space on end here for lining up with line starter - interfaces_2="$interfaces_2${C1}${a_interfaces_working[0]}${C2} ${a_interfaces_working[1]} " - fi - fi - ((i++)) - done - print_screen_output "$ip_data$interfaces" - # then wrap it if needed - if [[ -n $interfaces_2 ]];then - interfaces_2=$( create_print_line " " "$interfaces_2" ) - print_screen_output "$interfaces_2" - fi -} - -print_partition_data() -{ - local a_partition_working='' partition_used='' partition_data='' - local counter=0 line_max=160 i=0 a_partition_data='' line_starter='' - local partitionIdClean='' part_dev='' full_dev='' part_label='' full_label='' - local part_uuid='' full_uuid='' dev_remote='' - - # this handles the different, shorter, irc colors strings embedded in variable data - if [[ $B_RUNNING_IN_SHELL != 'true' ]];then - line_max=130 - fi - # and no color string data at all - if [[ $SCHEME -eq 0 ]];then - line_max=75 - fi - if [[ $B_SHOW_LABELS == 'true' || $B_SHOW_UUIDS == 'true' ]];then - line_max=20 - fi - - # set A_PARTITION_DATA - get_partition_data - - for (( i=0; i < ${#A_PARTITION_DATA[@]}; i++ )) - do - IFS="," - a_partition_working=(${A_PARTITION_DATA[i]}) - IFS="$ORIGINAL_IFS" - full_label='' - full_uuid='' - if [[ $B_SHOW_PARTITIONS_FULL == 'true' ]] || [[ ${a_partition_working[4]} == 'main' ]];then - if [[ -n ${a_partition_working[2]} ]];then - partition_used=" ${C1}used:${C2} ${a_partition_working[2]} (${a_partition_working[3]})" - else - partition_used='' # reset partition used to null - fi - - # don't show user names in output - if [[ $B_SHOW_LABELS == 'true' || $B_SHOW_UUIDS == 'true' ]];then - if [[ -n ${a_partition_working[5]} ]];then - if [[ -z $( grep -E '(^//|:/)' <<< ${a_partition_working[5]} ) ]];then - part_dev="/dev/${a_partition_working[5]}" - dev_remote='dev' - else - part_dev="${a_partition_working[5]}" - dev_remote='remote' - fi - else - dev_remote='dev' - part_dev='N/A' - fi - full_dev=" ${C1}$dev_remote:${C2} $part_dev" - if [[ $B_SHOW_LABELS == 'true' && $dev_remote != 'remote' ]];then - if [[ -n ${a_partition_working[6]} ]];then - part_label="${a_partition_working[6]}" - else - part_label='N/A' - fi - full_label=" ${C1}label:${C2} $part_label" - fi - if [[ $B_SHOW_UUIDS == 'true' && $dev_remote != 'remote' ]];then - if [[ -n ${a_partition_working[7]} ]];then - part_uuid="${a_partition_working[7]}" - else - part_uuid='N/A' - fi - full_uuid=" ${C1}uuid:${C2} $part_uuid" - fi - fi - partitionIdClean=$( sed -r 's|/home/(.*)/(.*)|/home/##/\2|' <<< ${a_partition_working[0]} ) - # because these lines can vary widely, using dynamic length handling here - a_partition_data[$counter]="${a_partition_data[$counter]}${C1}ID:${C2}$partitionIdClean ${C1}size:${C2} ${a_partition_working[1]}$partition_used$full_dev$full_label$full_uuid " - - if [[ $( wc -c <<< ${a_partition_data[$counter]} ) -gt $line_max ]];then - ((counter++)) - fi - fi - done - # print out all lines, line starter on first line - for (( i=0; i < ${#a_partition_data[@]};i++ )) - do - if [[ $i -eq 0 ]];then - line_starter='Partition:' - else - line_starter=' ' - fi - partition_data=$( create_print_line "$line_starter" "${a_partition_data[$i]}" ) - print_screen_output "$partition_data" - done -} - -print_system_data() +#### ------------------------------------------------------------------- +#### RECOMMENDS +#### ------------------------------------------------------------------- + +## CheckRecommends { - local system_data='' bits='' - local host_name=$( hostname ) - local current_kernel=$( uname -rm ) # | gawk '{print $1,$3,$(NF-1)}' ) - local distro="$( get_distro_data )" - # check for 64 bit first - if [[ -n $( uname -m | grep -o 'x86_64' ) ]];then - bits="(64 bit)" - else - bits="(32 bit)" - fi - - if [[ $B_SHOW_HOST == 'true' ]];then - system_data=$( create_print_line "System:" "${C1}Host${C2} $host_name ${C1}Kernel${C2}" ) - else - system_data=$( create_print_line "System:" "${C1}Kernel${C2}" ) - fi - system_data="$system_data $current_kernel $bits ${C1}Distro${C2} $distro" - print_screen_output "$system_data" +package CheckRecommends; +sub run { + main::error_handler('not-in-irc', 'recommends') if $b_irc; + my (@data,@rows); + my $line = make_line(); + my $pm = get_pm(); + @data = basic_data($line); + push @rows,@data; + if (!$bsd_type){ + @data = check_items('required system directories',$line,$pm); + push @rows,@data; + } + @data = check_items('recommended system programs',$line,$pm); + push @rows,@data; + @data = check_items('recommended display information programs',$line,$pm); + push @rows,@data; + @data = check_items('recommended downloader programs',$line,$pm); + push @rows,@data; + @data = check_items('recommended Perl modules',$line,$pm); + push @rows,@data; + @data = check_items('recommended directories',$line,''); + push @rows,@data; + @data = check_items('recommended files',$line,''); + push @rows,@data; + @data = ( + ['0', '', '', "$line"], + ['0', '', '', "Ok, all done with the checks. Have a nice day."], + ['0', '', '', " "], + ); + push @rows,@data; + #print Data::Dumper::Dumper \@rows; + main::print_basic(@rows); + exit 1; } -######################################################################## -#### SCRIPT EXECUTION +sub basic_data { + my ($line) = @_; + my (@data,@rows); + 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); + @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" ], + ); + return @rows; +} +sub check_items { + my ($type,$line,$pm) = @_; + my (@data,%info,@missing,$row,@rows,$result,@unreadable); + my ($b_dir,$b_file,$b_module,$b_program,$item); + my ($about,$extra,$extra2,$extra3,$extra4,$info_os,$install) = ('','','','','','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 dmidecode fdisk file glabel gpart ifconfig ipmi-sensors + ipmitool lsusb sudo smartctl sysctl tree upower uptime usbdevs); + $info_os = 'info-bsd'; + } + else { + @data = qw(dig dmidecode fdisk file hddtemp ifconfig ip ipmitool ipmi-sensors + lsblk lsusb modinfo runlevel sensors 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(glxinfo wmctrl xdpyinfo xprop xrandr); + $info_os = 'info-bsd'; + } + else { + @data = qw(glxinfo wmctrl xdpyinfo xprop 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(HTTP::Tiny IO::Socket::SSL Time::HiRes Cpanel::JSON::XS JSON::XS XML::Dumper); + $b_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."; + } + 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); + } + $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."; + } + @rows = ( + ['0', '', '', "$line" ], + ['0', '', '', "Test: $type$extra:" ], + ['0', '', '', " " ], + ); + if ($extra2){ + $rows[scalar @rows] = (['0', '', '', $extra2]); + $rows[scalar @rows] = (['0', '', '', ' ']); + } + if ($extra3){ + $rows[scalar @rows] = (['0', '', '', $extra3]); + $rows[scalar @rows] = (['0', '', '', ' ']); + } + foreach (@data){ + $install = ''; + $about = ''; + %info = item_data($_); + $about = $info{$info_os}; + if ( ( $b_dir && -d $_ ) || ( $b_file && -r $_ ) || + ($b_program && main::check_program($_) ) || ($b_module && main::check_module($_)) ){ + $result = 'Present'; + } + elsif ($b_file && -f $_){ + $result = 'Unreadable'; + push @unreadable, "$_"; + } + else { + $result = 'Missing'; + $install = " ~ Install package: $info{$pm}" if (($b_program || $b_module) && $pm); + push @missing, "$_$install"; + } + $row = make_row($_,$about,$result); + $rows[scalar @rows] = (['0', '', '', $row]); + } + $rows[scalar @rows] = (['0', '', '', " "]); + if (@missing){ + $rows[scalar @rows] = (['0', '', '', "The following $type are missing$extra4:"]); + foreach (@missing) { + $rows[scalar @rows] = (['0', '', '', "$item: $_"]); + } + } + if (@unreadable){ + $rows[scalar @rows] = (['0', '', '', "The following $type are not readable: "]); + foreach (@unreadable) { + $rows[scalar @rows] = (['0', '', '', "$item: $_"]); + } + } + if (!@missing && !@unreadable){ + $rows[scalar @rows] = (['0', '', '', "All $type are present"]); + } + return @rows; +} + +sub item_data { + my ($type) = @_; + my %data = ( + # directory data + '/sys/class/dmi/id' => ({ + 'info' => '-M system, motherboard, bios', + }), + '/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' => '', + }), + # 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', + }), + # system tools + # apt-dpkg,apt-get; pm-arch,pacman; rpm-redhat,suse + 'curl' => ({ + 'info' => '-i (if no dig); -w,-W; -U', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => 'curl', + 'pacman' => 'curl', + 'rpm' => 'curl', + }), + 'camcontrol' => ({ + 'info' => '', + 'info-bsd' => '-R; -D; -P. Get actual gptid /dev path', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'dig' => ({ + 'info' => '-i wlan IP', + 'info-bsd' => '-i wlan IP', + 'apt' => 'dnsutils', + 'pacman' => 'dnsutils', + 'rpm' => 'bind-utils', + }), + 'dmidecode' => ({ + 'info' => '-M if no sys machine data; -m', + 'info-bsd' => '-M if null sysctl; -m; -B if null sysctl', + 'apt' => 'dmidecode', + 'pacman' => 'dmidecode', + 'rpm' => 'dmidecode', + }), + 'fdisk' => ({ + 'info' => '-D partition scheme (fallback)', + 'info-bsd' => '-D partition scheme', + 'apt' => 'fdisk', + 'pacman' => 'util-linux', + 'rpm' => 'util-linux', + }), + 'fetch' => ({ + 'info' => '', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'file' => ({ + 'info' => '-o unmounted file system (if no lsblk)', + 'info-bsd' => '-o unmounted file system', + 'apt' => 'file', + 'pacman' => 'file', + 'rpm' => 'file', + }), + 'ftp' => ({ + 'info' => '', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'glabel' => ({ + 'info' => '', + 'info-bsd' => '-R; -D; -P. Get actual gptid /dev path', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'gpart' => ({ + 'info' => '', + 'info-bsd' => '-p,-P file system, size', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'hciconfig' => ({ + 'info' => 'Experimental', + 'info-bsd' => '', + 'apt' => 'bluez', + 'pacman' => 'bluez-utils', + 'rpm' => 'bluez-utils', + }), + 'hddtemp' => ({ + 'info' => '-Dx show hdd temp', + 'info-bsd' => '-Dx show hdd temp', + 'apt' => 'hddtemp', + 'pacman' => 'hddtemp', + 'rpm' => 'hddtemp', + }), + 'ifconfig' => ({ + 'info' => '-i ip LAN (deprecated)', + 'info-bsd' => '-i ip LAN', + 'apt' => 'net-tools', + 'pacman' => 'net-tools', + 'rpm' => 'net-tools', + }), + 'ip' => ({ + 'info' => '-i ip LAN', + 'info-bsd' => '', + 'apt' => 'iproute', + 'pacman' => 'iproute2', + 'rpm' => 'iproute', + }), + 'ipmi-sensors' => ({ + 'info' => '-s IPMI sensors (servers)', + 'info-bsd' => '', + 'apt' => 'freeipmi-tools', + 'pacman' => 'freeipmi', + 'rpm' => 'freeipmi', + }), + 'ipmitool' => ({ + 'info' => '-s IPMI sensors (servers)', + 'info-bsd' => '-s IPMI sensors (servers)', + 'apt' => 'ipmitool', + 'pacman' => 'ipmitool', + 'rpm' => 'ipmitool', + }), + 'lsblk' => ({ + 'info' => '-o unmounted file system (best option)', + 'info-bsd' => '-o unmounted file system', + 'apt' => 'util-linux', + 'pacman' => 'util-linux', + 'rpm' => 'util-linux-ng', + }), + 'lsusb' => ({ + 'info' => '-A usb audio; -N usb networking; --usb', + 'info-bsd' => '-A; -N; --usb. Alternate to usbdevs', + 'apt' => 'usbutils', + 'pacman' => 'usbutils', + 'rpm' => 'usbutils', + }), + 'modinfo' => ({ + 'info' => 'Ax; -Nx module version', + 'info-bsd' => '', + 'apt' => 'module-init-tools', + 'pacman' => 'module-init-tools', + 'rpm' => 'module-init-tools', + }), + 'runlevel' => ({ + 'info' => '-I fallback to Perl', + 'info-bsd' => '', + 'apt' => 'systemd or sysvinit', + 'pacman' => 'systemd', + 'rpm' => 'systemd or sysvinit', + }), + 'sensors' => ({ + 'info' => '-s sensors output', + 'info-bsd' => '', + 'apt' => 'lm-sensors', + 'pacman' => 'lm-sensors', + 'rpm' => 'lm-sensors', + }), + 'smartctl' => ({ + 'info' => '-Dx show hdd temp', + 'info-bsd' => '-Dx show hdd temp', + 'apt' => '', + 'pacman' => '', + 'rpm' => '', + }), + 'strings' => ({ + 'info' => '-I sysvinit version', + 'info-bsd' => '', + 'apt' => 'binutils', + 'pacman' => '?', + 'rpm' => '?', + }), + 'sysctl' => ({ + 'info' => '', + 'info-bsd' => '-C; -I; -m; -tm', + 'apt' => '?', + 'pacman' => '?', + 'rpm' => '?', + }), + 'sudo' => ({ + 'info' => '-Dx hddtemp-user; -o file-user', + 'info-bsd' => '-Dx hddtemp-user; -o file-user', + 'apt' => 'sudo', + 'pacman' => 'sudo', + 'rpm' => 'sudo', + }), + 'tree' => ({ + 'info' => '--debugger 20,21 /sys tree', + 'info-bsd' => '--debugger 20,21 /sys tree', + 'apt' => 'tree', + 'pacman' => 'tree', + 'rpm' => 'tree', + }), + 'upower' => ({ + 'info' => '-sx attached device battery info', + 'info-bsd' => '-sx attached device battery info', + 'apt' => 'upower', + 'pacman' => 'upower', + 'rpm' => 'upower', + }), + 'uptime' => ({ + 'info' => '-I uptime', + 'info-bsd' => '-I uptime', + 'apt' => 'procps', + 'pacman' => 'procps', + 'rpm' => 'procps', + }), + 'usbdevs' => ({ + 'info' => '', + 'info-bsd' => '-A; -N; --usb;', + 'apt' => 'usbutils', + 'pacman' => 'usbutils', + 'rpm' => 'usbutils', + }), + 'wget' => ({ + 'info' => '-i (if no dig); -w,-W; -U', + 'info-bsd' => '-i (if no dig); -w,-W; -U', + 'apt' => 'wget', + 'pacman' => 'wget', + 'rpm' => 'wget', + }), + # display tools + 'glxinfo' => ({ + 'info' => '-G glx info', + 'info-bsd' => '-G glx info', + 'apt' => 'mesa-utils', + 'pacman' => 'mesa-demos', + 'rpm' => 'glx-utils (openSUSE 12.3 and later Mesa-demo-x)', + }), + 'wmctrl' => ({ + 'info' => '-S active window manager (fallback)', + 'info-bsd' => '-S active window managerr (fallback)', + 'apt' => 'wmctrl', + 'pacman' => 'wmctrl', + 'rpm' => 'wmctrl', + }), + 'xdpyinfo' => ({ + 'info' => '-G multi screen resolution', + 'info-bsd' => '-G multi screen resolution', + 'apt' => 'X11-utils', + 'pacman' => 'xorg-xdpyinfo', + 'rpm' => 'xorg-x11-utils', + }), + 'xprop' => ({ + 'info' => '-S desktop data', + 'info-bsd' => '-S desktop data', + 'apt' => 'X11-utils', + 'pacman' => 'xorg-xprop', + 'rpm' => 'x11-utils', + }), + 'xrandr' => ({ + 'info' => '-G single screen resolution', + 'info-bsd' => '-G single screen resolution', + 'apt' => 'x11-xserver-utils', + 'pacman' => 'xrandr', + 'rpm' => 'x11-server-utils', + }), + # Perl modules + 'Cpanel::JSON::XS' => ({ + 'info' => '--output json - required for export.', + 'info-bsd' => '--output json - required for export.', + 'apt' => 'libcpanel-json-xs-perl', + 'pacman' => 'perl-cpanel-json-xs', + 'rpm' => 'perl-Cpanel-JSON-XS', + }), + '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', + 'pacman' => 'Core Modules', + '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', + 'rpm' => 'perl-IO-Socket-SSL', + }), + 'JSON::XS' => ({ + 'info' => '--output json - required for export (legacy).', + 'info-bsd' => '--output json - required for export (legacy).', + 'apt' => 'libjson-xs-perl', + 'pacman' => 'perl-json-xs', + 'rpm' => 'perl-JSON-XS', + }), + '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', + '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', + 'rpm' => 'perl-XML-Dumper', + }), + ); + my $ref = $data{$type}; + my %values = %$ref; + return %values; +} +sub get_pm { + my ($pm) = (''); + if (main::check_program('dpkg')){ + $pm = 'apt'; + } + elsif (main::check_program('pacman')){ + $pm = 'pacman'; + } + elsif (main::check_program('rpm')){ + $pm = 'rpm'; + } + return $pm; +} +# 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'} - 16 - length("$start$middle"))){ + $dots .= '.'; + } + $line = "$start$sep$middle$dots $end"; + return $line; +} +sub make_line { + my $line = ''; + foreach (0 .. $size{'max'} - 2 ){ + $line .= '-'; + } + return $line; +} +} + +#### ------------------------------------------------------------------- +#### TOOLS +#### ------------------------------------------------------------------- + +# Duplicates the functionality of awk to allow for one liner +# type data parsing. note: -1 corresponds to awk NF +# args 1: array of data; 2: search term; 3: field result; 4: 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){ + 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; +} + +# $1 - Perl module to check +sub check_module { + my ($module) = @_; + my $b_present = 0; + eval "require $module"; + $b_present = 1 if !$@; + return $b_present; +} + +# arg: 1 - 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; + } + } +} + +# 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 .. + return $count; +} + +# args: 1 - the string to get piece of +# 2 - the position in string, starting at 1 for 0 index. +# 3 - 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]; + } +} + +# arg: 1 - command to turn into an array; 2 - optional: splitter +# 3 - 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) = @_; + $split ||= "\n"; + my @rows = split /$split/, qx($cmd); + if ($strip && @rows){ + @rows = grep {/^\s*[^#]/} @rows; + @rows = map {s/^\s+|\s+$//g; $_} @rows if @rows; + } + eval $end if $b_log; + return @rows; +} + +# args: 1 - string value to glob +sub globber { + eval $start if $b_log; + my @files = <$_[0]>; + eval $end if $b_log; + return @files; +} + +# 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: 1 - array ref; 2 - join string; 3 - default value, optional +sub joiner { + my ($ref,$join,$default) = @_; + my @arr = @$ref; + $default ||= ''; + my $string = ''; + foreach (@arr){ + if (defined $_){ + $string .= $_ . $join; + } + else { + $string .= $default . $join; + } + } + return $string; +} + +# returns array of: +# 0 - match string; 1 - search number; 2 - version string; 3 - Print name +# 4 - console 0/1; 5 - 0/1 exit version loop at first iteration; +# 6 - 0/1 write to stderr +# arg: 1 - program lower case name +sub program_values { + my ($app) = @_; + my (@client_data); + # note: setting index 1 and 2 to 0 will trip flags to not do version + my %data = ( + ## 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 + '3dwm' => ['^3dwm',0,'0','3dwm',0,1,0], # unknown syntax + '9wm' => ['^9wm',3,'-version','9wm',0,1,0], + 'afterstep' => ['^afterstep',3,'--version','AfterStep',0,1,0], + 'amiwm' => ['^amiwm',0,'0','AmiWM',0,1,0], + 'awesome' => ['^awesome',2,'--version','Awesome',0,1,0], + 'blackbox' => ['^Blackbox',2,'--version','Blackbox',0,1,0], + 'budgie' => ['^budgie-desktop',2,'--version','Budgie',0,1,0], + 'cinnamon' => ['^cinnamon',2,'--version','Cinnamon',0,1,0], + 'compiz' => ['^compiz',2,'--version','Compiz',0,1,0], + 'dwm' => ['^dwm',1,'-v','Dwm',0,1,1], + 'fluxbox' => ['^fluxbox',2,'--version','Fluxbox',0,1,0], + 'flwm' => ['^flwm',0,'0','FLWM',0,0,1], + 'fvwm' => ['^fvwm',2,'--version','FVWM',0,0,1], + 'fvwm2' => ['^fvwm',2,'--version','FVWM2',0,0,1], + # command: fvwm + 'fvwm-crystal' => ['^fvwm',2,'--version','FVWM-Crystal',0,0,0], + 'gala' => ['^gala',2,'--version','gala',0,1,0], # super slow result + 'gnome-about' => ['gnome',3,'--version','Gnome',0,1,0], + 'gnome-shell' => ['gnome',3,'--version','Gnome',0,1,0], + # fails to return version when in wm, but outside does. weird. + 'herbstluftwm' => ['^herbstluftwm',2,'--version','herbstluftwm',0,1,0], + 'jwm' => ['^jwm',2,'--version','JWM',0,1,0], + # i3 version 4.13 (2016-11-08) © 2009 Michael Stapelberg and contributors + 'i3' => ['^i3',3,'--version','i3',0,1,0], + 'icewm' => ['^icewm',2,'--version','IceWM',0,1,0], + 'kded' => ['^KDE Development Platform:',4,'--version','KDE',0,1,0], + 'kded1' => ['^KDE Development Platform:',4,'--version','KDE',0,1,0], + 'kded2' => ['^KDE Development Platform:',4,'--version','KDE',0,1,0], + 'kded3' => ['^KDE Development Platform:',4,'--version','KDE',0,1,0], + 'kded4' => ['^KDE Development Platform:',4,'--version','KDE',0,1,0], + 'lxde' => ['^lxpanel',2,'--version','LXDE',0,1,0], + # command: lxqt-panel + 'lxqt' => ['^lxqt-panel',2,'--version','LXQt',0,1,0], + '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], + 'metacity' => ['^metacity',2,'--version','Metacity',0,1,0], + 'muffin' => ['^muffin',2,'--version','muffin',0,1,0], + 'mwm' => ['^mwm',0,'0','mwm',0,1,0], + 'notion' => ['^.',1,'--version','notion',0,1,0], + 'openbox' => ['^openbox',2,'--version','Openbox',0,1,0], + 'pantheon' => ['^pantheon',0,'0','Pantheon',0,1,0], + 'pekwm' => ['^pekwm',3,'--version','PekWM',0,1,0], + 'plasmashell' => ['^plasmashell',2,'--version','KDE Plasma',0,1,0], + 'qtdiag' => ['^qt',2,'--version','Qt',0,1,0], + 'ratpoison' => ['^ratpoison',2,'--version','Ratpoison',0,1,0], + 'sawfish' => ['^sawfish',3,'--version','Sawfish',0,1,0], + 'scrotwm' => ['^scrotwm.*welcome.*',5,'-v','Scrotwm',0,1,1], + 'spectrwm' => ['^spectrwm.*welcome.*wm',5,'-v','Spectrwm',0,1,0], + 'twm' => ['^twm',0,'0','twm',0,1,0], + 'unity' => ['^unity',2,'--version','Unity',0,1,0], + 'windowlab' => ['^windowlab',2,'-about','WindowLab',0,1,0], + 'wm2' => ['^wm2',0,'0','wm2',0,1,0], + 'wmaker' => ['^Window[[:space:]]*Maker',-1,'--version','WindowMaker',0,1,0], + 'wmii' => ['^wmii',0,'0','wmii',0,1,0], # note: in debian, wmii is wmii3 + 'wmii2' => ['^wmii2',1,'--version','wmii2',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], + 'xmonad' => ['^xmonad',2,'--version','XMonad',0,1,0], + ## Shells + 'bash' => ['^GNU[[:space:]]bash,[[:space:]]version',4,'--version','Bash',1,0,0], + 'csh' => ['^tcsh',2,'--version','csh',1,0,0], + 'dash' => ['dash',3,'--version','Dash',1,0,0], # no version, uses dpkg query, sigh + # ksh/lksh/mksh/pdksh can't be handled with version but we'll use the search string to + # trigger version return and tests + 'ksh' => ['ksh',5,'-v','ksh',1,0,0], + 'lksh' => ['ksh',5,'-v','lksh',1,0,0], + 'loksh' => ['ksh',5,'-v','lksh',1,0,0], + 'mksh' => ['ksh',5,'-v','mksh',1,0,0], + 'pdksh' => ['ksh',5,'-v','pdksh',1,0,0], + 'tcsh' => ['^tcsh',2,'--version','tcsh',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 + ); + if ( defined $data{$app} ){ + my $ref = $data{$app}; + @client_data = @$ref; + } + #my $debug = main::Dumper \@client_data; + main::log_data('dump',"Client Data",\@client_data) if $b_log; + return @client_data; +} + +# args: 1 - desktop/app command for --version; 2 - search string; +# 3 - space print number; 4 - [optional] version arg: -v, version, etc +# 5 - [optional] exit first find 0/1; 6 - [optional] 0/1 stderr output +sub program_version { + eval $start if $b_log; + my ($app, $search, $num,$version,$exit,$b_stderr) = @_; + my ($cmd,$line,$output); + my $version_nu = ''; + my $count = 0; + #print "app:$app\n"; + $exit ||= 100; # basically don't exit ever + $version ||= '--version'; + # adjust to array index, not human readable + $num-- if (defined $num && $num > 0); + # ksh: Version JM 93t+ 2010--03-05 + # mksh: @(#)MIRBSD KSH R56 2018/03/09 + # loksh: @(#)PD KSH v5.2.14 99/07/13.2 + # --version opens a new ksh, sigh... This so far does not work + # because the ENV/Shell variable is not visible in subshells + if ($search eq 'ksh'){ + my $ksh = system('echo -n $KSH_VERSION'); + if ( $ksh ){ + my @temp = split /\s+/, $ksh; + if ($temp[2]){ + $temp[2] =~ s/^v//i; # trim off leading v + log_data('data',"Program *ksh array: @temp version: $temp[2]") if $b_log; + return $temp[2]; + } + } + return 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 ( $app !~ /^\// ){ + if (my $program = check_program($app) ){ + $app = $program; + } + else { + log_data('data',"$app not found in path."); + return 0; + } + } + # note, some wm/apps send version info to stderr instead of stdout + if ( $b_stderr ) { + $cmd = "$app $version 2>&1"; + } +# elsif ( $app eq 'csh' ){ +# $app = 'tcsh'; +# } + # quick debian/buntu hack until I find a universal way to get version for these + elsif ( $app eq 'dash' ){ + $cmd = "dpkg -l $app 2>/dev/null"; + } + else { + $cmd = "$app $version 2>/dev/null"; + } + log_data('data',"version: $version num: $num search: $search command: $cmd") if $b_log; + $output = qx($cmd); + # print "$cmd : $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 "$_ ::$num\n"; + 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/(\([^)]+\)|,|dwm-|wmii2-|wmii-|\||\(|\))//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"; + +# arg: 1 - full file path, returns array of file lines. +# 2 - optionsl, strip and clean data +# note: chomp has to chomp the entire action, not just <$fh> +sub reader { + eval $start if $b_log; + my ($file,$strip) = @_; + return if ! $file; + open( my $fh, '<', $file ) or error_handler('open', $file, $!); + chomp(my @rows = <$fh>); + if ($strip && @rows){ + @rows = grep {/^\s*[^#]/} @rows; + @rows = map {s/^\s+|\s+$//g; $_} @rows if @rows; + } + eval $end if $b_log; + return @rows; +} + +# args: 1 - 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 +# arg: 1 - 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: 1 - hash +# send array, assign to hash, return array, uniq values only. +sub uniq { + my %seen; + grep !$seen{$_}++, @_; +} + + +# arg: 1 file full path to write to; 2 - arrayof data to write. +# note: turning off strict refs so we can pass it a scalar or an array reference. +sub writer { + my ($path, $ref_content) = @_; + my ($content); + no strict 'refs'; + # print Dumper $ref_content, "\n"; + if (ref $ref_content eq 'ARRAY'){ + $content = join "\n", @$ref_content or die "failed with error $!"; + } + else { + $content = scalar $ref_content; + } + open(my $fh, ">", $path) or error_handler('open',"$path", "$!"); + print $fh $content; + close $fh; +} + +#### ------------------------------------------------------------------- +#### UPDATER +##### ------------------------------------------------------------------- + +# arg 1: type to return +sub get_defaults { + my ($type) = @_; + my %defaults = ( + 'ftp-upload' => 'ftp.techpatterns.com/incoming', + 'inxi-branch-1' => 'https://github.com/smxi/inxi/raw/one/', + 'inxi-branch-2' => 'https://github.com/smxi/inxi/raw/two/', + 'inxi-dev' => 'https://smxi.org/in/', + 'inxi-main' => 'https://github.com/smxi/inxi/raw/master/', + 'inxi-pinxi' => 'https://github.com/smxi/inxi/raw/inxi-perl/', + 'inxi-man' => "https://smxi.org/in/$self_name.1.gz", + 'inxi-man-gh' => "https://github.com/smxi/inxi/raw/master/$self_name.1", + 'pinxi-man' => "https://smxi.org/in/$self_name.1.gz", + 'pinxi-man-gh' => "https://github.com/smxi/inxi/raw/inxi-perl/$self_name.1", + ); + if ( exists $defaults{$type}){ + return $defaults{$type}; + } + else { + error_handler('bad-arg-int', $type); + } +} + +# args: 1 - download url, not including file name; 2 - string to print out +# 3 - update type option +# note that 1 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 + 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"; + $output .= "Starting download of man page file now.\n"; + print $output; + $output = ''; + if ($b_man){ + update_man($download_id); + } + else { + print "Skipping man download because branch version is being used.\n"; + } + exit 1; + } + 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 { + my ($download_id) = @_; + my $man_file_location=set_man_location(); + my $man_file_path="$man_file_location/$self_name.1" ; + my ($man_file_url,$output) = ('',''); + + my $b_downloaded = 0; + 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! Are you root?\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' ); + } + } + # first choice is inxi.1/pinxi.1 from gh, second gz from smxi.org + if ( $download_id ne 'dev server' && (my $program = check_program('gzip'))){ + $man_file_url=get_defaults($self_name . '-man-gh'); + print "Downloading Man page file...\n"; + $b_downloaded = download_file('file', $man_file_url, $man_file_path); + if ($b_downloaded){ + print "Download 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 manfile:\n"; + print "Local path: $man_file_path Error: $err\n"; + } + else { + print "Download and install of man page successful.\nCheck to make sure it works: man $self_name\n"; + } + } + } + else { + $man_file_url = get_defaults($self_name . '-man'); + # used to use spider tests, but only wget supports that, so no need + print "Downloading Man page file gz...\n"; + $man_file_path .= '.gz'; + # returns perl, 1 for true, 0 for false, even when using shell tool returns + $b_downloaded = download_file('file', $man_file_url, $man_file_path ); + if ($b_downloaded) { + print "Download and install of man page successful.\nCheck to make sure it works: man $self_name\n"; + } + } + if ( !$b_downloaded ){ + print "Oh no! Something went wrong downloading the Man file at:\n$man_file_url\n"; + print "Try -U with --dbg 1 for more information on the failure.\n"; + } +} + +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 +######################################################################## + +sub get_options{ + eval $start if $b_log; + my (@args) = @_; + $show{'short'} = 1; + my ($b_downloader,$b_help,$b_no_man,$b_no_man_force,$b_recommends,$b_updater,$b_version, + $b_use_man,$self_download, $download_id); + GetOptions ( + '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{'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 < get_color_scheme('count') ){ + set_color_scheme($arg); + } + elsif ( $arg >= 94 && $arg <= 99 ){ + $colors{'selector'} = $arg; + } + else { + error_handler('bad-arg', $opt, $arg); + } }, + 'C|cpu' => sub { + $show{'short'} = 0; + $show{'cpu'} = 1; }, + 'd|disk-full|optical' => sub { + $show{'short'} = 0; + $show{'disk'} = 1; + $show{'optical'} = 1; }, + 'D' => sub { + $show{'short'} = 0; + $show{'disk'} = 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{'cpu'} = 1; + $show{'disk'} = 1; + $show{'graphic'} = 1; + $show{'info'} = 1; + $show{'machine'} = 1; + $show{'network'} = 1; + $show{'network-advanced'} = 1; + $show{'partition'} = 1; + $show{'raid'} = 1; + $show{'sensor'} = 1; + $show{'system'} = 1; }, + 'G|graphics|graphic' => sub { + $show{'short'} = 0; + $show{'graphic'} = 1; }, + 'i|ip' => sub { + $show{'short'} = 0; + $show{'ip'} = 1; + $show{'network'} = 1; + $show{'network-advanced'} = 1; + $b_downloader = 1 if ! check_program('dig');}, + 'I|info' => sub { + $show{'short'} = 0; + $show{'info'} = 1; }, + 'l|labels|label' => sub { + $show{'short'} = 0; + $show{'label'} = 1; + $show{'partition'} = 1; }, + 'limit:i' => sub { + my ($opt,$arg) = @_; + if ($arg != 0){ + $limit = $arg; + } + else { + error_handler('bad-arg',$opt,$arg); + } }, + 'm|memory' => sub { + $show{'short'} = 0; + $show{'ram'} = 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' => sub { + $show{'short'} = 0; + $show{'partition'} = 0; + $show{'partition-full'} = 1; }, + 'P|partitions|partition' => sub { + $show{'short'} = 0; + $show{'partition'} = 1; }, + '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; }, + 'sleep:s' => sub { + my ($opt,$arg) = @_; + $arg ||= 0; + if ($arg >= 0){ + $cpu_sleep = $arg; + } + else { + 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 { + error_handler('bad-arg',$opt,$arg); + } }, + 'usb' => sub { + $show{'short'} = 0; + $show{'usb'} = 1; }, + 'u|uuid' => sub { + $show{'short'} = 0; + $show{'partition'} = 1; + $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{'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{'ram'} = 1; + $show{'label'} = 1; + $show{'optical-basic'} = 1; + $show{'ram'} = 1; + $show{'raid'} = 1; + $show{'sensor'} = 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 ){ + $b_downloader = 1 if ! check_program('dig'); + $show{'cpu-flag'} = 1; + $show{'ip'} = 1; + $show{'raid-forced'} = 1; + $extra = 3; + } + if ($arg >= 8 ){ + $b_downloader = 1; + $show{'slot'} = 1; + $show{'process'} = 1; + $show{'ps-cpu'} = 1; + $show{'ps-mem'} = 1; + $show{'repo'} = 1; + #$show{'weather'} = 1; + } + } + else { + error_handler('bad-arg',$opt,$arg); + } }, + 'w|weather' => sub { + my ($opt) = @_; + $show{'short'} = 0; + $b_downloader = 1; + if ( $b_weather ){ + $show{'weather'} = 1; + } + else { + error_handler('distro-block', $opt); + } }, + 'W|weather-location:s' => sub { + my ($opt,$arg) = @_; + $arg ||= ''; + $arg =~ s/\s//g; + $show{'short'} = 0; + $b_downloader = 1; + if ( $b_weather ){ + if ($arg){ + $show{'weather'} = 1; + $show{'weather-location'} = $arg; + } + else { + error_handler('bad-arg',$opt,$arg); + } + } + else { + error_handler('distro-block', $opt); + } }, + '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 { + 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) = @_; + $arg = 2000 if defined $arg && $arg == -1; + if ( $arg =~ /\d/ && $arg >= 80 ){ + set_display_width($arg); + } + else { + error_handler('bad-arg', $opt, $arg); + } }, + 'z|filter' => sub { + $show{'filter'} = 1; }, + 'Z|filter-override' => sub { + $show{'filter-override'} = 1; }, + ## Start non data options + 'alt:i' => sub { + my ($opt,$arg) = @_; + if ($arg == 40) { + $dl{'tiny'} = 0; + $b_downloader = 1;} + elsif ($arg == 41) { + $dl{'curl'} = 0; + $b_downloader = 1;} + elsif ($arg == 42) { + $dl{'fetch'} = 0; + $b_downloader = 1;} + elsif ($arg == 43) { + $dl{'wget'} = 0; + $b_downloader = 1;} + elsif ($arg == 44) { + $dl{'curl'} = 0; + $dl{'fetch'} = 0; + $dl{'wget'} = 0; + $b_downloader = 1;} + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'arm' => sub { + $b_arm = 1 }, + 'bsd:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(darwin|dragonfly|freebsd|openbsd|netbsd)$/i){ + $bsd_type = lc($arg); + $b_fake_bsd = 1; + } + else { + error_handler('bad-arg', $opt, $arg); + } + }, + 'bsd-data:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(dboot|pciconf|sysctl|usbdevs)$/i){ + $b_fake_dboot = 1 if $arg eq 'dboot'; + $b_fake_pciconf = 1 if $arg eq 'pciconf'; + $b_fake_sysctl = 1 if $arg eq 'sysctl'; + $b_fake_usbdevs = 1 if $arg eq 'usbdevs'; + } + else { + error_handler('bad-arg', $opt, $arg); + } + }, + 'dbg:i' => sub { + my ($opt,$arg) = @_; + if ($arg > 0) { + $test[$arg] = 1; + } + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'debug:i' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^[1-3]|1[0-3]|2[0-4]$/){ + $debug=$arg; + } + else { + error_handler('bad-arg', $opt, $arg); + } }, + 'display:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^:?([0-9]+)?$/){ + $display=$arg; + $display ||= ':0'; + $display = ":$display" if $display !~ /^:/; + $b_display = ($b_root) ? 0 : 1; + $b_force_display = 1; + $display_opt = "-display $display"; + } + else { + error_handler('bad-arg', $opt, $arg); + } }, + 'dmidecode' => sub { + $b_dmidecode_force = 1 }, + 'downloader:s' => sub { + my ($opt,$arg) = @_; + $arg = lc($arg); + if ($arg =~ /^(curl|fetch|ftp|perl|wget)$/){ + if ($arg eq 'perl' && (!check_module('HTTP::Tiny') || !check_module('IO::Socket::SSL') )){ + error_handler('missing-perl-downloader', $opt, $arg); + } + elsif ( !check_program($arg)) { + error_handler('missing-downloader', $opt, $arg); + } + else { + # this dumps all the other data and resets %dl for only the + # desired downloader. + $arg = set_perl_downloader($arg); + %dl = ('dl' => $arg, $arg => 1); + $b_downloader = 1; + } + } + else { + error_handler('bad-arg', $opt, $arg); + } }, + 'ftp:s' => sub { + my ($opt,$arg) = @_; + # pattern: ftp.x.x/x + if ($arg =~ /^ftp\..+\..+\/[^\/]+$/ ){ + $ftp_alt = $arg; + } + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'h|help|?' => sub { + $b_help = 1; }, + 'host|hostname' => sub { + $show{'host'} = 1 }, + 'indent-min:i' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^\d+$/){ + $size{'indent-min'} = 1; + } + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'irc' => sub { + $b_irc = 1; }, + 'man' => sub { + $b_use_man = 1; }, + 'output:s' => sub { + my ($opt,$arg) = @_; + if ($arg =~ /^(json|screen|xml)$/){ + if ($arg =~ /json|screen|xml/){ + $output_type = $arg; + } + else { + error_handler('option-feature-incomplete', $opt, $arg); + } + } + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'no-host|no-hostname' => sub { + $show{'host'} = 0 }, + 'no-man' => sub { + $b_no_man_force = 0; }, + 'no-ssl' => sub { + $dl{'no-ssl-opt'}=1 }, + 'output-file:s' => sub { + my ($opt,$arg) = @_; + if ($arg){ + if ($arg eq 'print' || check_output_path($arg)){ + $output_file = $arg; + } + else { + error_handler('output-file-bad', $opt, $arg); + } + } + else { + error_handler('bad-arg', $opt, $arg); + }}, + 'proc' => sub { + $b_proc_debug = 1; }, + 'recommends' => sub { + $b_recommends = 1; }, + 'U|update:s' => sub { # 1,2,3 OR http://myserver/path/inxi + my ($opt,$arg) = @_; + $b_downloader = 1; + if ( $b_update ){ + $b_updater = 1; + if (!$arg && $self_name eq 'pinxi'){ + $b_man = 1; + $download_id = 'inxi-perl branch'; + $self_download = get_defaults('inxi-pinxi'); + } + elsif ($arg && $arg eq '3'){ + $b_man = 1; + $download_id = 'dev server'; + $self_download = get_defaults('inxi-dev'); + } + else { + if (!$arg){ + $download_id = 'main branch'; + $self_download = get_defaults('inxi-main'); + $b_man = 1; + $b_use_man = 1; + } + elsif ( $arg =~ /^[12]$/){ + $download_id = "branch $arg"; + $self_download = get_defaults("inxi-branch-$arg"); + } + elsif ( $arg =~ /^http/){ + $download_id = 'alt server'; + $self_download = $arg; + } + } + if (!$self_download){ + error_handler('bad-arg', $opt, $arg); + } + } + else { + error_handler('distro-block', $opt); + } }, + 'V|version' => sub { + $b_version = 1 }, + 'wm' => sub { + $b_wmctrl = 1 }, + '<>' => sub { + my ($opt) = @_; + error_handler('unknown-option', "$opt", "" ); } + ) ; #or error_handler('unknown-option', "@ARGV", ''); + ## run all these after so that we can change widths, downloaders, etc + eval $end if $b_log; + CheckRecommends::run() if $b_recommends; + set_downloader() if $b_downloader; + show_version() if $b_version; + show_options() if $b_help; + $b_man = 0 if (!$b_use_man || $b_no_man_force); + update_me( $self_download, $download_id ) if $b_updater; + if ($output_type){ + if ($output_type ne 'screen' && ! $output_file){ + error_handler('bad-arg', '--output', '--output-file not provided'); + } + } + if ( $show{'ram'} || $show{'slot'} || + ( ( $bsd_type || $b_dmidecode_force ) && ($show{'machine'} || $show{'battery'}) ) ){ + $b_dmi = 1; + } + if ($show{'audio'} || $show{'graphic'} || $show{'network'} || $show{'raid'} || $show{'raid-forced'} ){ + $b_pci = 1; + } + if ($show{'usb'} || $show{'audio'} || $show{'network'} ){ + # to detect wan/lan, we have to use long form to get as much data as possible + $usb_level = ($show{'usb'} || $show{'network'}) ? 2 : 1; + } + if ($bsd_type && ($show{'short'} || $show{'battery'} || $show{'cpu'} || $show{'cpu-basic'} || + $show{'info'} || $show{'machine'} || $show{'process'} || $show{'ram'} || $show{'sensor'} ) ){ + $b_sysctl = 1; + } + if ($show{'filter-override'}){ + $show{'filter'} = 0; + } + $b_sudo = 1 if ( $show{'unmounted'} || ($extra > 0 && $show{'disk'}) ); + # 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'}; + if ($show{'disk'} || $show{'optical'} ){ + $show{'disk-basic'} = 0; + $show{'disk-total'} = 0; + } + if ($bsd_type && ($show{'short'} || $show{'disk-basic'} || $show{'disk-total'} || $show{'disk'})){ + $b_dm_boot_disk = 1; + } + if ($bsd_type && ($show{'optical-basic'} || $show{'optical'})){ + $b_dm_boot_optical = 1 + } +} + +sub show_options { + error_handler('not-in-irc', 'help') if $b_irc; + my (@row,@rows,@data); + my $line = ''; + my $color_scheme_count = get_color_scheme('count') - 1; + my $partition_string='partition'; + my $partition_string_u='Partition'; + my $flags = ($b_arm) ? 'features' : 'flags' ; + if ( $bsd_type ){ + $partition_string='slice'; + $partition_string_u='Slice'; + } + # fit the line to the screen! + for my $i ( 0 .. ( ( $size{'max'} / 2 ) - 2 ) ){ + $line = $line . '- '; + } + @rows = ( + ['0', '', '', "$self_name supports the following options. You can combine + these or list them one by one. For more detailed information, see man^$self_name. + Examples:^$self_name^-v4^-c6 OR + $self_name^-bDc^6. If you start $self_name with no arguments, it will display + a short system summary." ], + ['0', '', '', '' ], + ['0', '', '', "The following options, if used without -F, -b, or -v, will + show option line(s): A, B, C, D, G, I, M, N, P, R, S, W, d, f, i, l, m, n, + o, p, r, s, t, u, w, --slots, --usb - you can use these alone or together + to show just the line(s) you want to see. If you use them with -v [level], + -b or -F, $self_name will combine the outputs." ], + ['0', '', '', $line ], + ['0', '', '', "Output Control Options:" ], + ['1', '-A', '--audio', "Audio/sound card(s), driver, sound server." ], + ['1', '-b', '--basic', "Basic output, short form. Same as $self_name^-v^2." ], + ['1', '-B', '--battery', "System battery info, including charge and condition, plus + extra info (if battery present)." ], + ['1', '-c', '--color', "Set color scheme (0-42). 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', '-C', '--cpu', "CPU output, including per CPU clock speed and max + CPU speed (if available)." ], + ['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 size(s)." ], + ['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 -W, plus -s and -n. Does not show extra verbose options such + as -d -f -i -l -m -o -p -r -t -u -x, unless specified." ], + ['1', '-G', '--graphics', "Graphics info (card(s), driver, display protocol + (if available), display server, resolution, renderer, OpenGL version)." ], + ['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, + IRC client or shell type, $self_name version." ], + ['1', '-l', '--label', "$partition_string_u labels. Triggers -P. + For full -p output, use -pl." ], + ['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, size, speed, type (e.g. DDR3). + If neither -I nor -tm are selected, also shows RAM used/total." ], + ['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 card info. Triggers -N. Shows + interface, speed, MAC id, state, etc. " ], + ['1', '-N', '--network', "Network card(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)." ], + ['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 /var /var/log /var/tmp. Use -p to see all + mounted ${partition_string}s." ], + ['1', '-r', '--repos', "Distro repository data. Supported repo types: APK, + APT, EOPKG, PACMAN, PACMAN-G2, PISI, PORTAGE, PORTS (BSDs), SLACKPKG, + URPMQ, YUM/ZYPP." ], + ['1', '-R', '--raid', "RAID data. Shows RAID devices, states, levels, + 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. GPU temp only for Fglrx/Nvidia drivers. + 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', '', '--usb', "Show USB data: Hubs and Devices." ], + ['1', '-u', '--uuid', "$partition_string_u UUIDs. Triggers -P. For full -p + output, use -pu." ], + ['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 card (-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 card (-A), sensors (-s), memory/RAM (-m), + $partition_string label^(-l), UUID^(-u), short form of optical drives, + standard RAID data (-R). " ], + ['2', '6', '', "Full $partition_string (-p), unmounted $partition_string (-o), + optical drive (-d), USB (--usb), full RAID; triggers -xx." ], + ['2', '7', '', "Network IP data (-i); triggers -xxx."], + ['2', '8', '', "Everything available, including repos (-r), processes + (-tcm), PCI slots (--slots)."], + ); + push @data, @rows; + # if distro maintainers don't want the weather feature disable it + if ( $b_weather ){ + @rows = ( + ['1', '-w', '--weather', "Local weather data/time. To check an alternate + location, see -W."], + ['1', '-W', '--weather-location', "[location] Supported options for + [location]: postal code; city, state/country; 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"], + ['1', '', '--weather-unit', "Set weather units to metric (m), imperial (i), + metric/imperial (mi), or imperial/metric (im)."], + ); + push @data, @rows; + } + @rows = ( + ['1', '-x', '--extra', "Adds the following extra data (only works with + verbose or line output, not short form):" ], + ['2', '-B', '', "Vendor/model, status (if available); attached devices + (e.g. wireless mouse, keyboard, if present)." ], + ['2', '-C', '', "CPU $flags, 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 if you have hddtemp installed, + if you are root, or if you have added to /etc/sudoers (sudo v. 1.7 or newer). + Example:^^ALL^=^NOPASSWD:^/usr/sbin/hddtemp" ], + ['2', '-G', '', "Direct rendering status (in X); Screen number GPU is + running on (Nvidia only)." ], + ['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 (if available)." ], + ['2', '-m', '', "Max memory module size (if available), device type." ], + ['2', '-N -A', '', "Version/port(s)/driver version (if available)." ], + ['2', '-N -A -G', '', "PCI Bus ID/USB ID number of card." ], + ['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', '-t', '', "Adds memory use output to CPU (-xt c), and CPU use to + memory (-xt m)." ], + ['2', '--usb', '', "For Devices, shows USB version/speed." ], + ); + push @data, @rows; + if ( $b_weather ){ + @rows = (['2', '-w -W', '', "Wind speed and direction, humidity, pressure, + and (-w only) time zone." ]); + push @data, @rows; + } + @rows = ( + ['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." ], + ['2', '-B', '', "Serial number, voltage now/minimum (if available)." ], + ['2', '-C', '', "Minimum CPU speed, if available." ], + ['2', '-D', '', "Disk transfer speed; NVMe lanes; Disk serial number." ], + ['2', '-G', '', "Chip vendor:product ID for each video card; OpenGL + compatibility version, if free drivers and available; compositor (experimental); + alternate Xorg drivers (if available). Alternate means driver is on automatic + driver check list of Xorg for the card vendor, but is not installed on system." ], + ['2', '-I', '', "Other detected installed gcc versions (if present). System + default runlevel. Adds parent program (or tty) for shell info if not in + IRC. Adds Init version number, RC (if found)." ], + ['2', '-m', '', "Manufacturer, part number; single/double bank (if found)." ], + ['2', '-M', '', "Chassis info, BIOS ROM size (dmidecode only), if available." ], + ['2', '-N', '', "Chip vendor:product ID." ], + ['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." ], + ['2', '--usb', '', "Vendor:chip ID." ], + ); + push @data, @rows; + if ( $b_weather ){ + @rows = (['2', '-w -W', '', "Wind chill, dew point, heat index, if available." ]); + push @data, @rows; + } + @rows = ( + ['1', '-xxx', '--extra 3', "Show extra, extra, extra data (only works + with verbose or line output, not short form):" ], + ['2', '-A', '', "Specific vendor/product information (if relevant)." ], + ['2', '-B', '', "Chemistry, cycles, location (if available)." ], + ['2', '-C', '', "CPU boost (turbo) enabled/disabled, if present." ], + ['2', '-D', '', "Firmware rev. if available; partition scheme, in some cases; disk + rotation speed (if detected)." ], + ['2', '-G', '', "Specific vendor/product information (if relevant)." ], + ['2', '-I', '', "For 'Shell:' adds ([su|sudo|login]) to shell name if present; + for 'running in:' adds (SSH) if SSH session." ], + ['2', '-m', '', "Width of memory bus, data and total (if present and greater + than data); Detail for Type, if present; module voltage, if available; serial + number." ], + ['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', '', "Panel/shell info in desktop output, if in X (like lxpanel, + xfce4-panel, mate-panel); (if available) dm version number, window manager + version number." ] + ); + push @data, @rows; + if ( $b_weather ){ + @rows = (['2', '-w -W', '', "Location (uses -z/irc filter), weather observation + time, altitude (shows extra lines for data where relevant)." ] ); + push @data, @rows; + } + @rows = ( + ['1', '', '--admin', "Adds advanced sys admin data (only works with + verbose or line output, not short form):" ], + ['2', '-C', '', "If available: CPU errata (bugs); family, model-id, stepping - format: + hex (decimal) if greater than 9, otherwise hex; microcode - format: hex." ], + ['1', '-y', '--width', "Output line width max (integer >= 80). Overrides IRC/Terminal + settings or actual widths. Example:^inxi^-y^130" ], + ['1', '-z', '--filter', "Adds security filters for IP/MAC addresses, serial numbers, + location (-w), user home directory name. Default on for IRC clients." ], + ['1', '-Z', '--filter-override', "Absolute override for output filters. Useful for + debugging networking issues in IRC, for example." ], + [0, '', '', "$line" ], + [0, '', '', "Additional Options:" ], + ['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." ] + ); + push @data, @rows; + if ( $b_update ){ + @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" ], + ['2', '1', '', "Get the git branch one version." ], + ['2', '2', '', "Get the git branch two version." ], + ['3', '3', '', "Get the dev server (smxi.org) version." ], + ['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 @data, @rows; + } + @rows = ( + ['1', '-V', '--version', "Prints $self_name version info then exits." ], + ['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', '', '--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', '', '--host', "Turn on hostname for -S." ], + ['1', '', '--indent-min', "Set point where $self_name autowraps line starters." ], + ['1', '', '--limit', "[-1; 1-x] Set max output limit of IP addresses for -i + (default 10; -1 removes limit)." ], + ); + push @data, @rows; + if ( $b_update ){ + @rows = ( + ['1', '', '--man', "Install correct man version for dev branch (-U 3) or pinxi using -U." ], + ); + push @data, @rows; + } + @rows = ( + ['1', '', '--no-host', "Turn off hostname for -S. Useful if showing output from servers etc." ], + ); + push @data, @rows; + if ( $b_update ){ + @rows = ( + ['1', '', '--no-man', "Disable man install for all -U update actions." ], + ); + push @data, @rows; + } + @rows = ( + ['1', '', '--no-ssl', "Skip SSL certificate checks for all downloader actions + (Wget/Fetch/Curl only)." ], + ['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', '', '--proc', "Force debugger parsing of /proc as sudo/root." ], + ['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', '', '--wm', "Force wm: to use wmctrl as data source. Default uses ps." ], + ['0', '', '', $line ], + ['0', '', '', "Debugging Options:" ], + ['1', '', '--debug', "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.techpatterns.com: $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', '', '--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" ], + ); + push @data, @rows; + print_basic(@data); + exit 1; +} + +sub show_version { + require Cwd; + import Cwd; + # if not in PATH could be either . or directory name, no slash starting + my $working_path=$self_path; + my (@data, @row, @rows, $link, $self_string); + if ( $working_path eq '.' ){ + $working_path = getcwd(); + } + elsif ( $working_path !~ /^\// ){ + $working_path = getcwd() . "/$working_path"; + } + # 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%/\./%/%; + @row = ([ 0, '', '', "$self_name $self_version-$self_patch ($self_date)"],); + push @data, @row; + if ( ! $b_irc ){ + @row = ([ 0, '', '', ""],); + push @data, @row; + my $year = (split/-/, $self_date)[0]; + @row = [ 0, '', '', "Program Location: $working_path" ]; + push @data, @row; + if ( $link ){ + @row = [ 0, '', '', "Started via symbolic link: $link" ]; + push @data, @row; + } + @rows = ( + [ 0, '', '', "Website:^https://github.com/smxi/inxi^or^https://smxi.org/" ], + [ 0, '', '', "IRC:^irc.oftc.net channel:^#smxi" ], + [ 0, '', '', "Forums:^https://techpatterns.com/forums/forum-33.html" ], + [ 0, '', '', " " ], + [ 0, '', '', "$self_name - the universal, portable, system information tool + for console and irc." ], + [ 0, '', '', "Using Perl version: $]"], + [ 0, '', '', " " ], + [ 0, '', '', "This program started life as a fork of Infobash 3.02: + Copyright^(C)^2005-2007^Michiel^de^Boer^aka^locsmif." ], + [ 0, '', '', "Subsequent changes and modifications (after Infobash 3.02): + Copyright^(C)^2008-$year^Harald^Hope^aka^h2. + CPU/Konversation^fixes:^Scott^Rogers^aka^trash80. + USB^audio^fixes:^Steven^Barrett^aka^damentz." ], + [ 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)" ] + ); + push @data, @rows; + } + print_basic(@data); + exit 1; +} + +######################################################################## +#### STARTUP DATA +######################################################################## + +# StartClient +{ +package StartClient; + +# use warnings; +# use strict; + +my $ppid = ''; +my $pppid = ''; + +# NOTE: there's no reason to crete 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 get_client_data { + eval $start if $b_log; + $ppid = getppid(); + main::set_ps_aux() if ! @ps_aux; + if (!$b_irc){ + main::get_shell_data($ppid); + } + else { + $show{'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|dash|sh|python.*|perl.*$/){ + $pppid = (main::grabber("ps -p $ppid -o ppid"))[1]; + #my @temp = (main::grabber("ps -p $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"; + } + else { + if (! check_modern_konvi() ){ + $ppid = getppid(); + $client_name = (main::grabber("ps -p $ppid"))[1]; + if ($client_name){ + my @data = split /\s+/, $client_name if $client_name; + if ($bsd_type){ + $client_name = lc($data[5]); + } + # 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|dash|sh$/ ){ + $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'); + } + $client{'version'} = main::awk(\@data,'version',2,'\s*=\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/) { + @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(); + } + if (!$client{'name-print'}) { + $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; + $ppid = getppid(); + 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 = 0; + my $konvi_version = ''; + my $konvi = ''; + my $pid = ''; + my (@temp); + # 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 + if ( -d '/usr/share/kde4/apps/konversation' || -d '/usr/share/apps/konversation' ){ + $pid = main::awk(\@ps_aux,'konversation',2,'\s+'); + main::log_data('data',"pid: $pid") if $b_log; + $konvi = readlink ("/proc/$pid/exe"); + $konvi =~ s/^.*\///; # basename + @app = main::program_values('konversation'); + if ($konvi){ + @app = main::program_values('konversation'); + $konvi_version = main::program_version($konvi,$app[0],$app[1],$app[2],$app[5],$app[6]); + @temp = split /\./, $konvi_version; + $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 + $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: $konvi_qt4 verNum: $konvi_version pid: $pid ppid: $ppid" ); + eval $end if $b_log; + return $b_modern_konvi; +} + +sub set_konvi_data { + eval $start if $b_log; + my $config_tool = ''; + # 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; + # there's no current kde 5 konvi config tool that we're aware of. Correct if changes. + if ( main::check_program('kde4-config') ){ + $config_tool = 'kde4-config'; + } + elsif ( main::check_program('kde5-config') ){ + $config_tool = 'kde5-config'; + } + elsif ( main::check_program('kde-config') ){ + $config_tool = 'kde-config'; + } + # The section below is on request of Argonel from the Konversation developer team: + # it sources config files like $HOME/.kde/share/apps/konversation/scripts/inxi.conf + if ($config_tool){ + my @data = main::grabber("$config_tool --path data 2>/dev/null",':'); + main::get_configs(@data); + } + eval $end if $b_log; +} +} + +######################################################################## +#### OUTPUT +######################################################################## + +#### ------------------------------------------------------------------- +#### FILTERS AND TOOLS +#### ------------------------------------------------------------------- + +sub apply_filter { + my ($string) = @_; + if ($string){ + $string = ( $show{'filter'} ) ? $filter_string : $string; + } + else { + $string = 'N/A'; + } + return $string; +} +sub arm_cleaner { + 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/\(\)//; + $data =~ s/^\s+|\s+$//g; + return $data; +} +sub cleaner { + my ($item) = @_; + return $item if !$item;# handle cases where it was 0 or '' + # note: |nee trips engineering, but I don't know why nee was filtered + $item =~ s/chipset|company|components|computing|computer|corporation|communications|electronics|electrical|electric|gmbh|group|incorporation|industrial|international|\bnee\b|revision|semiconductor|software|technologies|technology|ltd\.||\bltd\b|inc\.||\binc\b|intl\.|co\.||corp\.||\(tm\)|\(r\)|®|\(rev ..\)|\'|\"|\sinc\s*$|\?//gi; + $item =~ s/,|\*/ /g; + $item =~ s/\s\s+/ /g; + $item =~ s/^\s+|\s+$//g; + return $item; +} + +sub disk_cleaner { + 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 dmi_cleaner { + my ($string) = @_; + my $cleaner = '^Base Board .*|^Chassis .*|empty|Undefined.*|.*O\.E\.M\..*|.*OEM.*|^Not .*'; + $cleaner .= '|^System .*|.*unknow.*|.*N\/A.*|none|^To be filled.*|^0x[0]+$'; + $cleaner .= '|\[Empty\]||Default string|^\.\.$|Manufacturer.*'; + $cleaner .= '|AssetTagNum|Manufacturer| Or Motherboard|PartNum.*|SerNum'; + $string =~ s/$cleaner//i; + $string =~ s/^\s+|\bbios\b|\bacpi\b|\s+$//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 remove_duplicates { + my ($string) = @_; + return if ! $string; + my $holder = ''; + my (@temp); + my @data = split /\s+/, $string; + foreach (@data){ + if ($holder ne $_){ + push @temp, $_; + } + $holder = $_; + } + $string = join ' ', @temp; + return $string; +} + +# args: $1 - size in KB, return KB, MB, GB, TB, PB, EB +sub get_size { + my ($size,$b_int) = @_; + my (@data); + return ('','') if ! defined $size; + if ($size !~ /^[0-9\.]+$/){ + $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] = int($data[0]) if $b_int && $data[0]; + 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 memory_data_full { + eval $start if $b_log; + my ($source) = @_; + my $num = 0; + my ($memory,@rows); + my ($gpu_ram,$percent,$total,$used) = (0,'','',''); + if (!$show{'info'}){ + $memory = get_memory_data('splits'); + if ($memory){ + my @temp = split /:/, $memory; + my @temp2 = get_size($temp[0]); + $gpu_ram = $temp[3] if $temp[3]; + $total = ($temp2[1]) ? $temp2[0] . ' ' . $temp2[1] : $temp2[0]; + @temp2 = get_size($temp[1]); + $used = ($temp2[1]) ? $temp2[0] . ' ' . $temp2[1] : $temp2[0]; + $used .= " ($temp[2]%)" if $temp[2]; + if ($gpu_ram){ + @temp2 = get_size($gpu_ram); + $gpu_ram = $temp2[0] . ' ' . $temp2[1] if $temp2[1]; + } + } + my $key = ($source eq 'process') ? 'System RAM': 'RAM'; + $rows[0]{main::key($num++,$key)} = ''; + $rows[0]{main::key($num++,'total')} = $total; + $rows[0]{main::key($num++,'used')} = $used; + $rows[0]{main::key($num++,'gpu')} = $gpu_ram if $gpu_ram; + } + $b_mem = 1; + eval $end if $b_log; + return @rows; +} + +sub pci_cleaner { + my ($string,$type) = @_; + #print "st1 $type:$string\n"; + my $filter = 'compatible\scontroller|\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 pci_cleaner_subsystem { + my ($string) = @_; + # we only need filters for features that might use vendor, -AGN + my $filter = 'adapter|(hd\s)?audio|definition|desktop|ethernet|gigabit|graphics|'; + $filter .= 'hdmi(\/[\S]+)?|high|integrated|motherboard|network|onboard|'; + $filter .= 'raid|pci\s?express'; + $string =~ s/\b($filter)\b//gi; + $string =~ s/\s\s+/ /g; + $string =~ s/^\s+|\s+$//g; + return $string; +} + +sub pci_long_filter { + my ($string) = @_; + if ($string =~ /\[AMD(\/ATI)?\]/){ + $string =~ s/Advanced\sMicro\sDevices\s\[AMD(\/ATI)?\]/AMD/; + } + return $string; +} + +sub row_defaults { + my ($type,$id) = @_; + $id ||= ''; + my %unfound = ( + 'arm-cpu-f' => 'Use -f option to see features', + 'arm-pci' => "No ARM data found for this feature.", + 'battery-data' => "No system battery data found. Is one present?", + 'battery-data-sys' => "No /sys data found. Old system?", + 'cpu-model-null' => "Model N/A", + 'cpu-speeds' => "No speed data found for $id cores.", + 'darwin-feature' => "Feature not supported iu Darwin/OSX.", + 'disk-data-bsd' => "No disk data found for this BSD system.", + 'disk-data' => "No Disk data was found.", + 'disk-size-0' => "Total N/A", + 'display-console' => 'No advanced graphics data found on this system in console.', + 'display-null' => 'No advanced graphics data found on this system.', + 'display-root' => 'Advanced graphics data unavailable in console for root.', + 'display-root-x' => 'Advanced graphics data unavailable for root. Old System?', + 'display-server' => "No display server data found. Headless machine?", + 'glxinfo-missing' => "Unable to show advanced data. Required tool glxinfo missing.", + 'display-try' => 'Advanced graphics data unavailable in console. Try -G --display', + 'dev' => 'Feature under development', + 'dmesg-boot-permissions' => 'dmesg.boot permissions', + 'dmesg-boot-missing' => 'dmesg.boot not found', + 'IP' => "No $id data found. Connected to the web? SSL issues?", + '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.", + 'mips-pci' => "No MIPS data found for this feature.", + 'optical-data' => "No Optical or Floppy data was found.", + 'optical-data-bsd' => "No floppy or optical data found for this BSD system.", + 'output-limit' => "Output throttled. IPs: $id; Limit: $limit; Override: --limit [1-x;-1 all]", + 'partition-data' => "No Partition data was found.", + 'pci-advanced-data' => 'bus/chip ids unavailable', + 'pci-card-data' => "No PCI card data found.", + 'pci-slot-data' => "No PCI slot data found. SBC?", + 'raid-data' => "No RAID data was found.", + 'ram-data' => "No RAM data was found. SBC?", + 'root-required' => "", + 'sensors-data-ipmi' => "No ipmi sensors data was found.", + 'sensors-data-linux' => "No sensors data was found. Is sensors configured?", + 'sensors-ipmi-root' => "Unable to run ipmi sensors. Are you root?", + 'unmounted-data' => "No unmounted partitions found.", + 'unmounted-data-bsd' => "No unmounted partition data found for this BSD system.", + 'unmounted-file' => "No /proc/partitions file found.", + 'usb-data' => "No USB data was found. Server?", + 'unknown-desktop-version' => "ERR-101", + 'unknown-dev' => "ERR-102", + 'unknown-shell' => "ERR-100", + 'weather-null' => "No $id found. Internet connection working?", + 'xdpyinfo-missing' => '', + ); + return $unfound{$type}; +} + +# convert string passed to KB, based on GB/MB/TB id +# NOTE: K 1024 KB 1000 +sub translate_size { + my ($working) = @_; + my $size = 0; + #print ":$working:\n"; + return if ! defined $working; + my $math = ( $working =~ /B$/) ? 1000: 1024; + if ( $working =~ /^([0-9\.]+)M[B]?$/i){ + $size = $1 * $math; + } + elsif ( $working =~ /^([0-9\.]+)G[B]?$/i){ + $size = $1 * $math**2; + } + elsif ( $working =~ /^([0-9\.]+)T[B]?$/i){ + $size = $1 * $math**3; + } + elsif ( $working =~ /^([0-9\.]+)P[B]?$/i){ + $size = $1 * $math**4; + } + elsif ( $working =~ /^([0-9\.]+)E[B]?$/i){ + $size = $1 * $math**5; + } + elsif ( $working =~ /^([0-9\.]+)K[B]?$/i){ + $size = $1; + } + $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; +} + +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); + } +} + +# NOTE: file has already been set and directory verified +sub generate_json { + eval $start if $b_log; + my (%data) = @_; + my ($json); + my $b_debug = 1; + my ($b_cpanel,$b_valid); + error_handler('not-in-irc', 'help') if $b_irc; + #print Dumper \%data if $b_debug; + if (check_module('Cpanel::JSON::XS')){ + import Cpanel::JSON::XS; + $json = Cpanel::JSON::XS::encode_json(\%data); + } + elsif (check_module('JSON::XS')){ + import JSON::XS; + $json = JSON::XS::encode_json(\%data); + } + else { + error_handler('required-module', 'json', '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_module('XML::Dumper')){ + import XML::Dumper; + $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", $_[0],$_[1]); +} + +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,$aref,$i,$j,$line); + + if ( $size{'max'} > 110 ){ + $indent_static = 22; + } + elsif ($size{'max'} < 90 ){ + $indent_static = 15; + } + # print $length . "\n"; + for my $i (0 .. $#data){ + $aref = $data[$i]; + #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]) ) < $size{'max'} ){ + $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 = ' '; + foreach my $word (split / /, $data[$i][3]){ + #print "$word\n"; + if ( ( $indent + length($holder) + length($word) ) < $size{'max'} ) { + $word =~ s/\^/ /g; + $holder .= $word . $sep; + #print "3-hold.\n"; + } + #elsif ( ( $indent + length($holder) + length($word) ) >= $size{'max'}){ + 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 $array = 0; + my $array_holder = 1; + my $counter=0; + my $split_count = 0; + my $hash = 0; + my $holder = ''; + my $id_holder = 0; + my $start = ''; + my $start2 = ''; + my $length = 0; + my $indent = $size{'indent'}; + my (@temp,@working,@values,%ids,$holder2,%row); + my ($key,$line,$val2,$val3); + # $size{'max'} = 88; + # NOTE: indent < 11 would break the output badly in some cases + if ($size{'max'} < $size{'indent-min'} || $size{'indent'} < 11 ){ + $indent = 2; + } + #foreach my $key1 (sort { (split/#/, $a)[0] <=> (split/#/, $b)[0] } keys %data) { + foreach my $key1 (sort { substr($a,0,3) <=> substr($b,0,3) } keys %data) { + #foreach my $key1 (sort { $a cmp $b } keys %data) { + $key = (split/#/, $key1)[1]; + if ($key ne 'SHORT' ) { + $start = sprintf("$colors{'c1'}%-${indent}s$colors{'cn'}","$key$sep{'s1'}"); + if ($indent < 10){ + $line = "$start\n"; + print_line($line); + $start = ''; + $line = ''; + } + } + else { + $indent = 0; + } + if (ref($data{$key1}) eq 'ARRAY'){ + # @working = @{$data{$key1}}; + %ids = ( + 'Array' => 1, + 'array' => 1, + 'Battery' => 1, + 'Card' => 1, + 'Device' => 1, + 'Floppy' => 1, + 'Hardware' => 1, # hardware raid report + 'ID' => 1, + 'IF-ID' => 1, + 'Optical' => 1, + 'variant' => 1, # arm > 1 cpu type + ); + $array_holder = 1; + foreach my $val1 (@{$data{$key1}}){ + $length = $indent; + if (ref($val1) eq 'HASH'){ + #%row = %$val1; + $counter=0; + $split_count = 0; + $hash = scalar %$val1; + #foreach my $key2 (sort { (split/#/, $a)[0] <=> (split/#/, $b)[0] } keys %$val1){ + foreach my $key2 (sort { substr($a,0,3) <=> substr($b,0,3) } keys %$val1){ + #foreach my $key2 (sort { $a cmp $b } keys %$val1){ + $key = (split/#/, $key2)[1]; + # for ram with > 1 system array, we want to reset device count to 1 for each + # new array + if ($key eq 'Array' && $array_holder != $ids{$key} ){ + $array_holder = $ids{$key}; + $ids{'Device'} = 1 if ($ids{'Device'} > 1); + } + if ($key eq 'Device' && $ids{'array'} > 1 && $id_holder != $ids{$key} ){ + $id_holder = $ids{$key}; + $ids{'array'} = 1 if ($ids{'array'} > 1); + } + if ($counter == 0 && defined $ids{$key}){ + $key .= '-' . $ids{$key}++; + } + $val2 = $$val1{$key2}; + # we have to handle cases where $val2 is 0 + if ($val2 || $val2 eq '0'){ + $val2 .= " "; + } + # see: Use of implicit split to @_ is deprecated. Only get this warning + # in Perl 5.08 oddly enough. + @temp = split/\s+/, $val2; + $split_count = scalar @temp; + if ( ( length( "$key$sep{'s2'} $val2" ) + $length ) < $size{'max'} ) { + $length += length("$key$sep{'s2'} $val2"); + $holder .= "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2"; + #print "one\n"; + } + # handle case where the opening 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 + elsif ( ( length( "$key$sep{'s2'} $val2" ) + $indent ) > $size{'max'} && + !defined $ids{$key} && $split_count > 2 ) { + @values = split/\s+/, $val2; + $val3 = shift @values; + # $length += length("$key$sep{'s2'} $val3 ") + $indent; + $start2 = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val3 "; + $holder2 = ''; + $length += length("$key$sep{'s2'} $val3 "); + # print scalar @values,"\n"; + foreach (@values){ + # my $l = (length("$_ ") + $length); + #print "$l\n"; + if ( (length("$_ ") + $length) < $size{'max'} ){ + #print "a\n"; + if ($start2){ + $holder2 .= "$start2$_ "; + $start2 = ''; + #$length += $length2; + #$length2 = 0; + } + else { + $holder2 .= "$_ "; + } + $length += length("$_ "); + } + else { + #print "three\n"; + if ($start2){ + $holder2 = "$start2$holder2"; + } + else { + $holder2 = "$colors{'c2'}$holder2"; + } + #print "xx:$holder"; + $line = sprintf("%-${indent}s%s$colors{'cn'}\n","$start","$holder$holder2"); + print_line($line); + $holder = ''; + + $holder2 = "$_ "; + #print "h2: $holder2\n"; + $length = length($holder2) + $indent; + $start2 = ''; + $start = ''; + #$length2 = 0; + } + } + if ($holder2 !~ /^\s*$/){ + #print "four\n"; + $holder2 = "$colors{'c2'}$holder2"; + $line = sprintf("%-${indent}s%s$colors{'cn'}\n","$start","$holder$holder2"); + print_line($line); + $holder = ''; + $holder2 = ''; + $length = $indent; + $start2 = ''; + $start = ''; + #$length2 = 0; + } + } + else { + #print "H: $counter $hash\n"; + if ($holder){ + #print "five\n"; + $line = sprintf("%-${indent}s%s$colors{'cn'}\n",$start,"$holder"); + $holder = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2"; + $length = length("$key$sep{'s2'} $val2") + $indent; + print_line($line); + $start = ''; + } + else { + #print "six\n"; + $holder = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2"; + #$line = sprintf("%-${indent}s%s$colors{'cn'}\n",$start,"$holder"); + $length = $indent; + #$holder = ''; + } + } + $counter++; + } + if ($holder !~ /^\s*$/){ + #print "seven\n"; + $line = sprintf("%-${indent}s%s$colors{'cn'}\n",$start,"$start2$holder"); + print_line($line); + $holder = ''; + $length = 0; + $start = ''; + } + } + # only for repos? + elsif (ref($val1) eq 'ARRAY'){ + #print "eight\n"; + $array=0; + foreach my $item (@$val1){ + $array++; + $line = "$colors{'c1'}$array$sep{'s2'} $colors{'c2'}$item$colors{'cn'}"; + $line = sprintf("%-${indent}s%s\n","","$line"); + print_line($line); + } + } + else { + + } + } + } + } +} + +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 $line; + } +} + +######################################################################## +#### DATA PROCESSORS +######################################################################## + +#### ------------------------------------------------------------------- +#### PRIMARY DATA GENERATORS +#### ------------------------------------------------------------------- +# 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 + +## AudioData +{ +package AudioData; + +sub get { + eval $start if $b_log; + my (@data,@rows); + my $num = 0; + if (($b_arm || $b_mips) && !$b_soc_audio && !$b_pci_tool){ + my $key = ($b_arm) ? 'ARM' : 'MIPS'; + @data = ({ + main::key($num++,$key) => main::row_defaults(lc($key) . '-pci',''), + },); + @rows = (@rows,@data); + } + else { + @data = card_data(); + @rows = (@rows,@data); + } + if ( ( (($b_arm || $b_mips) && !$b_soc_audio && !$b_pci_tool) || !@rows ) && + (my $file = main::system_files('asound-cards') ) ){ + @data = asound_data($file); + @rows = (@rows,@data); + } + @data = usb_data(); + @rows = (@rows,@data); + if (!@rows){ + my $key = 'Message'; + @data = ({ + main::key($num++,$key) => main::row_defaults('pci-card-data',''), + },); + @rows = (@rows,@data); + } + @data = sound_server_data(); + @rows = (@rows,@data); + eval $end if $b_log; + return @rows; +} + +sub card_data { + eval $start if $b_log; + my (@rows,@data); + my ($j,$num) = (0,1); + foreach (@pci){ + $num = 1; + my @row = @$_; + if ($row[0] =~ /^(audio|daudio|hdmi|multimedia)$/){ + $j = scalar @rows; + my $driver = $row[9]; + $driver ||= 'N/A'; + my $card = $row[4]; + $card = ($card) ? main::pci_cleaner($card,'output') : 'N/A'; + # have seen absurdly verbose card descriptions, with non related data etc + if (length($card) > 85 || $size{'max'} < 110){ + $card = main::pci_long_filter($card); + } + @data = ({ + main::key($num++,'Card') => $card, + },); + @rows = (@rows,@data); + if ($extra > 2 && $b_pci_tool && $row[11]){ + my $item = main::get_pci_vendor($row[4],$row[11]); + $rows[$j]{main::key($num++,'vendor')} = $item if $item; + } + $rows[$j]{main::key($num++,'driver')} = $driver; + if ($extra > 0 && !$bsd_type){ + if ($row[9] ){ + my $version = main::get_module_version($row[9]); + $rows[$j]{main::key($num++,'v')} = $version if $version; + } + } + if ($extra > 0){ + $rows[$j]{main::key($num++,'bus ID')} = (!$row[2] && !$row[3]) ? 'N/A' : "$row[2].$row[3]"; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = ($row[5]) ? "$row[5]:$row[6]" : $row[6]; + } + } + #print "$row[0]\n"; + } + #my $ref = $pci[-1]; + #print $$ref[0],"\n"; + eval $end if $b_log; + return @rows; +} +# 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_data { + eval $start if $b_log; + my ($file) = @_; + my (@asound,@rows,@data); + my ($card,$driver,$j,$num) = ('','',0,1); + @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+(.*)/; + $card = $2; + $driver = $1; + if ( $card ){ + $j = scalar @rows; + $driver ||= 'N/A'; + @data = ({ + main::key($num++,'Card') => $card, + main::key($num++,'driver') => $driver, + },); + @rows = (@rows,@data); + if ($extra > 0){ + my $version = main::get_module_version($driver); + $rows[$j]{main::key($num++,'v')} = $version if $version; + $rows[$j]{main::key($num++,'message')} = main::row_defaults('pci-advanced-data',''); + } + } + } + } + # print Data::Dumper:Dumper \s@rows; + eval $end if $b_log; + return @rows; +} +sub usb_data { + eval $start if $b_log; + my (@rows,@data,@ids,$driver,$product,$product2,@temp2,$vendor,$vendor2); + my ($j,$num) = (0,1); + if (-d '/proc/asound') { + # note: this will double the data, but it's easier this way. + # inxi 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 @ids, $id if ($id && ! grep {/$id/} @ids); + } + # lsusb is a very expensive operation + if (@ids){ + if (!$bsd_type && !$b_usb_check){ + main::set_usb_data(); + $b_usb_check = 1; + } + } + main::log_data('dump','@ids',\@ids) if $b_log; + return if !@usb; + foreach my $id (@ids){ + $j = scalar @rows; + foreach my $ref (@usb){ + my @row = @$ref; + # a device will always be the second or > device on the bus + if ($row[1] > 1 && $row[2] eq $id){ + $num = 1; + # makre sure to reset, or second device trips last flag + ($product,$product2,$vendor,$vendor2) = ('','','',''); + if ($usb_level == 1){ + $product = main::cleaner($row[3]); + } + else { + foreach my $line (@row){ + my @working = split /:/, $line; + if ($working[0] eq 'idVendor' && $working[2]){ + $vendor = main::cleaner($working[2]); + } + if ($working[0] eq 'idProduct' && $working[2]){ + $product = main::cleaner($working[2]); + } + if ($working[0] eq 'iManufacturer' && $working[2]){ + $vendor2 = main::cleaner($working[2]); + } + if ($working[0] eq 'iProduct' && $working[2]){ + $product2 = main::cleaner($working[2]); + } + if ($working[0] eq 'Descriptor_Configuration'){ + last; + } + } + } + if ($vendor && $product){ + $product = ($product =~ /$vendor/) ? $product: "$vendor $product" ; + } + elsif (!$product) { + if ($vendor && $product2){ + $product = ($product2 =~ /$vendor/) ? $product2: "$vendor $product2" ; + } + elsif ($vendor2 && $product2){ + $product = ($product2 =~ /$vendor2/) ? $product2: "$vendor2 $product2" ; + } + elsif ($vendor){ + $product = $vendor; + } + elsif ($vendor2){ + $product = $vendor2; + } + else { + $product = 'N/A'; + } + } + @temp2 = main::get_usb_drivers($row[0],$row[2]) if !$bsd_type && -d "/sys/devices"; + if (@temp2 && $temp2[0]){ + $driver = $temp2[0]; + } + $driver ||= 'snd-usb-audio'; + @data = ({ + main::key($num++,'Card') => $product, + main::key($num++,'type') => 'USB', + main::key($num++,'driver') => $driver, + },); + @rows = (@rows,@data); + if ($extra > 0){ + $rows[$j]{main::key($num++,'bus ID')} = "$row[0]:$row[1]"; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = $row[2]; + } + } + } + } + } + eval $end if $b_log; + return @rows; +} + +sub sound_server_data { + eval $start if $b_log; + my (@data,$server,$version); + my $num = 0; + if (my $file = main::system_files('asound-version') ){ + my $content = (main::reader($file))[0]; + # some alsa strings have the build date in (...) + # remove trailing . and remove possible second line if compiled by user +# foreach (@content){ +# if (!/compile/i){ + #$_ =~ s/Advanced Linux Sound Architecture/ALSA/; + $version = (split /\s+/, $content)[-1]; + $version =~ s/\.$//; # trim off period + $server = 'ALSA'; +# } +# } + } + elsif (my $program = main::check_program('oss')){ + $server = 'OSS'; + $version = main::program_version('oss','\S',2); + $version ||= 'N/A'; + } + if ($server){ + @data = ({ + main::key($num++,'Sound Server') => $server, + main::key($num++,'v') => $version, + },); + } + eval $end if $b_log; + return @data; +} +} + +## BatteryData +{ +package BatteryData; +my (@upower_items,$b_upower,$upower); +sub get { + eval $start if $b_log; + my (@rows,%battery,$key1,$val1); + my $num = 0; + if ($bsd_type || $b_dmidecode_force){ + my $ref = $alerts{'dmidecode'}; + if ( $$ref{'action'} ne 'use'){ + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + $key1 = ucfirst($key1); + @rows = ({main::key($num++,$key1) => $val1,}); + } + else { + %battery = battery_data_dmi(); + if (!%battery){ + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::row_defaults('battery-data',''); + @rows = ({main::key($num++,$key1) => $val1,}); + } + } + else { + @rows = create_output(%battery); + } + } + } + elsif (-d '/sys/class/power_supply/'){ + %battery = battery_data_sys(); + if (!%battery){ + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::row_defaults('battery-data',''); + @rows = ({main::key($num++,$key1) => $val1,}); + } + } + else { + @rows = create_output(%battery); + } + } + else { + if ($show{'battery-forced'}){ + $key1 = 'Message'; + $val1 = main::row_defaults('battery-data-sys',''); + @rows = ({main::key($num++,$key1) => $val1,}); + } + } + (@upower_items,$b_upower,$upower) = undef; + 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 create_output { + eval $start if $b_log; + my (%battery) = @_; + my ($key,@data,@rows); + my $num = 0; + my $j = 0; + # print Data::Dumper::Dumper \%battery; + foreach $key (sort keys %battery){ + $num = 0; + my ($charge,$condition,$model,$serial,$status,$volts) = ('','','','','',''); + 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 + $charge = (defined $battery{$key}{'energy_now'} && $battery{$key}{'energy_now'} ne '') ? "$battery{$key}{'energy_now'} Wh" : '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; + @data = ({ + main::key($num++,'ID') => $key, + main::key($num++,'charge') => $charge, + main::key($num++,'condition') => $condition, + },); + @rows = (@rows,@data); + if ($extra > 0){ + if ($extra > 1){ + if ($battery{$key}{'voltage_min_design'} || $battery{$key}{'voltage_now'}){ + $battery{$key}{'voltage_min_design'} ||= 'N/A'; + $battery{$key}{'voltage_now'} ||= 'N/A'; + $volts = "$battery{$key}{'voltage_now'}/$battery{$key}{'voltage_min_design'}"; + } + $volts ||= 'N/A'; + $rows[$j]{main::key($num++,'volts')} = $volts; + } + 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++,'model')} = $model; + if ($extra > 2){ + $chemistry = ( $battery{$key}{'technology'} ) ? $battery{$key}{'technology'}: 'N/A'; + $rows[$j]{main::key($num++,'type')} = $chemistry; + } + if ($extra > 1){ + $serial = main::apply_filter($battery{$key}{'serial_number'}); + $rows[$j]{main::key($num++,'serial')} = $serial; + } + $status = ($battery{$key}{'status'}) ? $battery{$key}{'status'}: 'N/A'; + $rows[$j]{main::key($num++,'status')} = $status; + if ($extra > 2){ + if ($battery{$key}{'cycle_count'}){ + $rows[$j]{main::key($num++,'cycles')} = $battery{$key}{'cycle_count'}; + } + if ($battery{$key}{'location'}){ + $rows[$j]{main::key($num++,'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) = ('','','','','',''); + my (%upower_data); + $j = scalar @rows; + %upower_data = upower_data($key) if $upower; + 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'}; + $status = ($battery{$key}{'status'} && lc($battery{$key}{'status'}) ne 'unknown') ? $battery{$key}{'status'}: 'N/A' ; + $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'; + } + @data = ({ + main::key($num++,'Device') => $key, + main::key($num++,'model') => $model, + },); + @rows = (@rows,@data); + if ($extra > 1){ + $serial = main::apply_filter($battery{$key}{'serial_number'}); + $rows[$j]{main::key($num++,'serial')} = $serial; + } + $rows[$j]{main::key($num++,'charge')} = $charge; + if ($extra > 2 && $upower_data{'rechargeable'}){ + $rows[$j]{main::key($num++,'rechargeable')} = $upower_data{'rechargeable'}; + } + $rows[$j]{main::key($num++,'status')} = $status; + } + } + eval $end if $b_log; + return @rows; +} + +# charge: mAh energy: Wh +sub battery_data_sys { + eval $start if $b_log; + my ($b_ma,%battery,$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 + 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); + foreach $item (@batteries){ + $b_ma = 0; + $id = $item; + $id =~ s%/sys/class/power_supply/%%g; + my $purpose = ($id =~ /^(BAT|CMB).*$/) ? 'primary': 'device'; + # don't create arrays of device data if it's not going to show + next if $extra == 0 && $purpose ne 'primary'; + $battery{$id} = ({}); + # NOTE: known ids: BAT[0-9] CMB[0-9] + $battery{$id}{'purpose'} = $purpose; + foreach $file (@items){ + $path = "$item/$file"; + $value = (-f $path) ? (main::reader($path))[0]: ''; + # mains + if ($file eq 'type' && $value && lc($value) ne 'battery' ){ + $battery{$id}{'purpose'} = 'mains'; + } + if ($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::dmi_cleaner($value); + } + elsif ($file eq 'model_name'){ + $value = main::dmi_cleaner($value); + } + } + elsif ($b_root && -e $path && ! -r $path ){ + $value = main::row_defaults('root-required'); + } + $battery{$id}{$file} = $value; + # print "$battery{$id}{$file}\n"; + } + # 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( "%.0f", $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'} ); + } + } + eval $end if $b_log; + return %battery; +} +# note, dmidecode does not have charge_now or charge_full +sub battery_data_dmi { + eval $start if $b_log; + my (%battery,$id); + my $i = 0; + foreach (@dmi){ + my @ref = @$_; + # Portable Battery + if ($ref[0] == 22){ + $id = "BAT$i"; + $i++; + $battery{$id} = ({}); + $battery{$id}{'purpose'} = 'primary'; + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + foreach my $item (@ref){ + 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::dmi_cleaner($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::dmi_cleaner($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 ($ref[0] > 22){ + last; + } + } + # print Data::Dumper::Dumper \%battery; + eval $end if $b_log; + return %battery; +} +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; + } + } + } + eval $end if $b_log; + return %data; +} + +} + +## CpuData +{ +package CpuData; + +sub get { + eval $start if $b_log; + my ($type) = @_; + my (@data,@rows,$single,$key1,$val1); + my $num = 0; + if ($type eq 'short' || $type eq 'basic'){ + @rows = data_short($type); + } + else { + @rows = create_output_full(); + } + eval $end if $b_log; + return @rows; +} +sub create_output_full { + eval $start if $b_log; + my $num = 0; + my ($b_flags,$b_speeds,$core_speeds_value,$flag_key,@flags,%cpu,@data,@rows); + my $sleep = $cpu_sleep * 1000000; + if ($b_hires){ + eval 'Time::HiRes::usleep( $sleep )'; + } + else { + select(undef, undef, undef, $cpu_sleep); + } + if (my $file = main::system_files('cpuinfo')){ + %cpu = data_cpuinfo($file,'full'); + } + elsif ($bsd_type ){ + my ($key1,$val1) = ('',''); + if ( $alerts{'sysctl'} ){ + if ( $alerts{'sysctl'}{'action'} eq 'use' ){ +# $key1 = 'Status'; +# $val1 = main::row_defaults('dev'); + %cpu = data_sysctl('full'); + } + else { + $key1 = ucfirst($alerts{'sysctl'}{'action'}); + $val1 = $alerts{'sysctl'}{$alerts{'sysctl'}{'action'}}; + @data = ({main::key($num++,$key1) => $val1,}); + return @data; + } + } + } + my %properties = cpu_properties(%cpu); + my $type = ($properties{'cpu-type'}) ? $properties{'cpu-type'}: ''; + my $ref = $cpu{'processors'}; + my @processors = @$ref; + my @speeds = cpu_speeds(@processors); + my $j = scalar @rows; + $cpu{'model_name'} ||= 'N/A'; + @data = ({ + main::key($num++,'Topology') => $properties{'cpu-layout'}, + main::key($num++,'model') => $cpu{'model_name'}, + },); + @rows = (@rows,@data); + if ($cpu{'arm-cpus'}){ + my $ref = $cpu{'arm-cpus'}; + my %arm_cpus = %$ref; + my $i = 1; + my $counter = ( %arm_cpus && scalar keys %arm_cpus > 1 ) ? '-' : ''; + foreach my $key (keys %arm_cpus){ + $counter = '-' . $i++ if $counter; + $rows[$j]{main::key($num++,'variant'.$counter)} = $key; + } + } + $properties{'bits-sys'} ||= 'N/A'; + $rows[$j]{main::key($num++,'bits')} = $properties{'bits-sys'}; + if ($type){ + $rows[$j]{main::key($num++,'type')} = $type; + } + if ($extra > 0){ + $cpu{'arch'} ||= 'N/A'; + $rows[$j]{main::key($num++,'arch')} = $cpu{'arch'}; + if ( !$b_admin && $cpu{'arch'} ne 'N/A' && $cpu{'rev'} ){ + $rows[$j]{main::key($num++,'rev')} = $cpu{'rev'}; + } + } + if ($b_admin){ + $rows[$j]{main::key($num++,'family')} = hex_and_decimal($cpu{'family'}); + $rows[$j]{main::key($num++,'model-id')} = hex_and_decimal($cpu{'model_id'}); + $rows[$j]{main::key($num++,'stepping')} = hex_and_decimal($cpu{'rev'}); + $cpu{'microcode'} ||= 'N/A'; + $rows[$j]{main::key($num++,'microcode')} = $cpu{'microcode'}; + } + $properties{'l2-cache'} ||= 'N/A'; + if (!$b_arm || ($b_arm && $properties{'l2-cache'} ne 'N/A')){ + $rows[$j]{main::key($num++,'L2 cache')} = $properties{'l2-cache'}; + } + if ($extra > 0 && !$show{'cpu-flag'}){ + $j = scalar @rows; + @flags = split /\s+/, $cpu{'flags'} if $cpu{'flags'}; + $flag_key = ($b_arm || $bsd_type) ? 'features': 'flags'; + my $flag = 'N/A'; + if (@flags){ + # failure to read dmesg.boot: dmesg.boot permissions + @flags = grep {/^(dmesg.boot|lm|nx|pae|permissions|pni|svm|vmx|(sss|ss)e([2-9])?([a-z])?(_[0-9])?)$/} @flags; + @flags = map {s/pni/sse3/; $_} @flags; + @flags = sort(@flags); + $flag = join ' ', @flags if @flags; + } + if ($b_arm && $flag eq 'N/A'){ + $flag = main::row_defaults('arm-cpu-f'); + } + @data = ({ + main::key($num++,$flag_key) => $flag, + },); + @rows = (@rows,@data); + $b_flags = 1; + } + if ($extra > 0 && !$bsd_type){ + my $bogomips = ($cpu{'bogomips'}) ? int($cpu{'bogomips'}) : 'N/A'; + $rows[$j]{main::key($num++,'bogomips')} = $bogomips; + } + $j = scalar @rows; + my $core_key = (scalar @speeds > 1) ? 'Core speeds (MHz)' : 'Core speed (MHz)'; + 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 = (defined $properties{'speed'}) ? $properties{'speed'}: 'N/A'; + # aren't able to get per core speeds in bsds yet + if (@speeds){ + if (grep {$_ ne '0'} @speeds){ + $core_speeds_value = ''; + $b_speeds = 1; + } + else { + $core_speeds_value = main::row_defaults('cpu-speeds',scalar @speeds); + } + } + else { + $core_speeds_value = 'N/A'; + } + $j = scalar @rows; + @data = ({ + main::key($num++,$speed_key) => $speed, + main::key($num++,$min_max_key) => $min_max, + }); + @rows = (@rows,@data); + if ($extra > 2){ + my $boost = get_boost_status(); + $rows[$j]{main::key($num++,'boost')} = $boost if $boost; + } + $rows[$j]{main::key($num++,$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++,$i++)} = $_; + } + } + if ($show{'cpu-flag'} && !$b_flags){ + $flag_key = ($b_arm || $bsd_type) ? 'Features': 'Flags'; + @flags = split /\s+/, $cpu{'flags'} if $cpu{'flags'}; + my $flag = 'N/A'; + if (@flags){ + @flags = sort(@flags); + $flag = join ' ', @flags if @flags; + } + @data = ({ + main::key($num++,$flag_key) => $flag, + },); + @rows = (@rows,@data); + } + if ($b_admin && $cpu{'bugs'}){ + my @bugs = split /\s+/, $cpu{'bugs'}; + @bugs = sort(@bugs); + my $bug = join ' ', @bugs; + @data = ({ + main::key($num++,'Errata') => $bug, + },); + @rows = (@rows,@data); + } + eval $end if $b_log; + return @rows; +} +sub create_output_short { + eval $start if $b_log; + my (@cpu) = @_; + my @data; + my $num = 0; + $cpu[1] ||= main::row_defaults('cpu-model-null'); + $cpu[2] ||= 'N/A'; + @data = ({ + main::key($num++,$cpu[0]) => $cpu[1], + main::key($num++,'type') => $cpu[2], + },); + if ($extra > 0){ + $data[0]{main::key($num++,'arch')} = $cpu[7]; + } + $data[0]{main::key($num++,$cpu[3])} = $cpu[4]; + if ($cpu[6]){ + $data[0]{main::key($num++,$cpu[5])} = $cpu[6]; + } + eval $end if $b_log; + return @data; +} +sub data_short { + eval $start if $b_log; + my ($type) = @_; + my $num = 0; + my (%cpu,@data,%speeds); + my $sys = '/sys/devices/system/cpu/cpufreq/policy0'; + my $sleep = $cpu_sleep * 1000000; + if ($b_hires){ + eval 'Time::HiRes::usleep( $sleep )'; + } + else { + select(undef, undef, undef, $cpu_sleep); + } + # NOTE: : Permission denied, ie, this is not always readable + # /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq + if (my $file = main::system_files('cpuinfo')){ + %cpu = data_cpuinfo($file,$type); + } + elsif ($bsd_type ){ + my ($key1,$val1) = ('',''); + if ( $alerts{'sysctl'} ){ + if ( $alerts{'sysctl'}{'action'} eq 'use' ){ +# $key1 = 'Status'; +# $val1 = main::row_defaults('dev'); + %cpu = data_sysctl($type); + } + else { + $key1 = ucfirst($alerts{'sysctl'}{'action'}); + $val1 = $alerts{'sysctl'}{$alerts{'sysctl'}{'action'}}; + @data = ({main::key($num++,$key1) => $val1,}); + return @data; + } + } + } + # $cpu{'cur-freq'} = $cpu[0]{'core-id'}[0]{'speed'}; + if ($type eq 'short' || $type eq 'basic'){ + @data = prep_short_data(%cpu); + } + if ($type eq 'basic'){ + @data = create_output_short(@data); + } + eval $end if $b_log; + return @data; +} + +sub prep_short_data { + eval $start if $b_log; + my (%cpu) = @_; + my %properties = cpu_properties(%cpu); + my ($cpu,$speed_key,$speed,$type) = ('','speed',0,''); + $cpu = $cpu{'model_name'} if $cpu{'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{'cpu-layout'}, + $cpu, + $type, + $speed_key, + $speed, + $properties{'min-max-key'}, + $properties{'min-max'}, + ); + if ($extra > 0){ + $cpu{'arch'} ||= 'N/A'; + $result[7] = $cpu{'arch'}; + } + eval $end if $b_log; + return @result; +} + +sub data_cpuinfo { + eval $start if $b_log; + my ($file,$type)= @_; + my ($arch,@ids,@line,$b_first,$b_proc_int,$starter); + # use --arm flag when testing arm cpus + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-4-core-pinebook-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv6-single-core-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv7-dual-core-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv7-new-format-model-name-single-core.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-2-die-96-core-rk01.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/16-core-32-mt-ryzen.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/2-16-core-epyc-abucodonosor.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/2-core-probook-antix.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-jean-antix.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-althlon-mjro.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-apu-vc-box.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-a10-5800k-1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-core-ht-atom-bruh.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/core-2-i3.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/8-core-i7-damentz64.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-10-core-xeon-ht.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-core-xeon-fake-dual-die-zyanya.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-core-i5-fake-dual-die-hek.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-1-core-xeon-vm-vs2017.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-1-core-xeon-vps-frodo1.txt"; + # $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-6-core-xeon-no-mt-lathander.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/mips/mips-mainusg-cpuinfo.txt"; + my %speeds = set_cpu_speeds_sys(); + my @cpuinfo = main::reader($file); + my @phys_cpus = (0);# start with 1 always + my ($cache,$core_count,$die_holder,$die_id,$phys_id,$proc_count,$speed) = (0,0,0,0,0,0,0); + my ($phys_holder) = (undef); + # 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 + #$ids[0] = ([(0)]); + $ids[0] = ([]); + $ids[0][0] = ([]); + my %cpu = set_cpu_data(); + # note, there con be a lot of processors, 32 core HT would have 64, for example. + foreach (@cpuinfo){ + next if /^\s*$/; + @line = split /\s*:\s*/, $_; + next if !$line[0]; + $starter = $line[0]; # preserve case for one specific ARM issue + $line[0] = lc($line[0]); + if ($b_arm && !$b_first && $starter eq 'Processor' && $line[1] !~ /^\d+$/){ + #print "l1:$line[1]\n"; + $cpu{'model_name'} = main::cleaner($line[1]); + $cpu{'model_name'} = cpu_cleaner($cpu{'model_name'}); + $cpu{'type'} = 'arm'; + # Processor : AArch64 Processor rev 4 (aarch64) + # Processor : Feroceon 88FR131 rev 1 (v5l) + if ($cpu{'model_name'} && $cpu{'model_name'} =~ /(.*)\srev\s([\S]+)\s(\(([\S]+)\))?/){ + $cpu{'model_name'} = $1; + $cpu{'rev'} = $2; + if ($4){ + $cpu{'arch'} = $4; + $cpu{'model_name'} .= ' ' . $cpu{'arch'} if $cpu{'model_name'} !~ /$cpu{'arch'}/i; + } + $cpu{'processors'}[$proc_count] = 0; + $b_proc_int = 0; + $b_first = 1; + #print "p0:\n"; + } + } + elsif ($line[0] eq 'processor'){ + # this protects against double processor lines, one int, one string + if ($line[1] =~ /^\d+$/){ + $b_proc_int = 1; + $b_first = 1; + $cpu{'processors'}[$proc_count] = 0; + $proc_count++; + #print "p1: $proc_count\n"; + } + else { + if (!$b_proc_int){ + $cpu{'processors'}[$proc_count] = 0; + $proc_count++; + #print "p2a: $proc_count\n"; + } + if (!$b_first ){ + # note: alternate: + # Processor : AArch64 Processor rev 4 (aarch64) + # but no model name type + if ( $b_arm || $line[1] =~ /ARM|AArch/i){ + $b_arm = 1; + $cpu{'type'} = 'arm'; + } + $cpu{'model_name'} = main::cleaner($line[1]); + $cpu{'model_name'} = cpu_cleaner($cpu{'model'}); + #print "p2b:\n"; + } + $b_first = 1; + } + } + elsif (!$cpu{'family'} && + ($line[0] eq 'architecture' || $line[0] eq 'cpu family' || $line[0] eq 'cpu architecture' )){ + if ($line[1] =~ /^\d+$/){ + # translate integers to hex + $cpu{'family'} = uc(sprintf("%x", $line[1])); + } + elsif ($b_arm) { + $cpu{'arch'} = $line[1]; + } + } + elsif (!$cpu{'rev'} && ($line[0] eq 'stepping' || $line[0] eq 'cpu revision' )){ + $cpu{'rev'} = uc(sprintf("%x", $line[1])); + } + # this is hex so uc for cpu arch id + elsif (!$cpu{'model_id'} && $line[0] eq 'model' ){ + $cpu{'model_id'} = uc(sprintf("%x", $line[1])); + } + elsif (!$cpu{'model_id'} && $line[0] eq 'cpu variant' ){ + $cpu{'model_id'} = uc($line[1]); + $cpu{'model_id'} =~ s/^0X//; + } + # cpu can show in arm + elsif (!$cpu{'model_name'} && ( $line[0] eq 'model name' || $line[0] eq 'cpu' || $line[0] eq 'cpu model' )){ + $cpu{'model_name'} = main::cleaner($line[1]); + $cpu{'model_name'} = cpu_cleaner($cpu{'model_name'}); + if ( $b_arm || $line[1] =~ /ARM|AArch/i){ + $b_arm = 1; + $cpu{'type'} = 'arm'; + if ($cpu{'model_name'} && $cpu{'model_name'} =~ /(.*)\srev\s([\S]+)\s(\(([\S]+)\))?/){ + $cpu{'model_name'} = $1; + $cpu{'rev'} = $2; + if ($4){ + $cpu{'arch'} = $4; + $cpu{'model_name'} .= ' ' . $cpu{'arch'} if $cpu{'model_name'} !~ /$cpu{'arch'}/i; + } + #$cpu{'processors'}[$proc_count] = 0; + } + } + elsif ($b_mips || $line[1] =~ /mips/i){ + $b_mips = 1; + $cpu{'type'} = 'mips'; + } + } + elsif ( $line[0] eq 'cpu mhz' ){ + $speed = speed_cleaner($line[1]); + $cpu{'processors'}[$proc_count-1] = $speed; + #$ids[$phys_id][$die_id] = ([($speed)]); + } + elsif (!$cpu{'siblings'} && $line[0] eq 'siblings' ){ + $cpu{'siblings'} = $line[1]; + } + elsif (!$cpu{'cores'} && $line[0] eq 'cpu cores' ){ + $cpu{'cores'} = $line[1]; + } + # 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. + elsif ( $line[0] eq 'physical id' ){ + if ( !defined $phys_holder || $phys_holder != $line[1] ){ + # only increment if not in array counter + push @phys_cpus, $line[1] if ! grep {/$line[1]/} @phys_cpus; + $phys_holder = $line[1]; + $ids[$phys_holder] = ([]) if ! exists $ids[$phys_holder]; + $ids[$phys_holder][$die_id] = ([]) if ! exists $ids[$phys_holder][$die_id]; + #print "pid: $line[1] ph: $phys_holder did: $die_id\n"; + $die_id = 0; + #$die_holder = 0; + } + } + elsif ( $line[0] eq 'core id' ){ + #print "ph: $phys_holder did: $die_id l1: $line[1] s: $speed\n"; + # https://www.pcworld.com/article/3214635/components-processors/ryzen-threadripper-review-we-test-amds-monster-cpu.html + if ($line[1] > 0 ){ + $die_holder = $line[1]; + $core_count++; + } + # NOTE: this logic won't work for die detections, unforutnately. + # ARM uses a different /sys based method, and ryzen relies on math on the cores + # in process_data + elsif ($line[1] == 0 && $die_holder > 0 ){ + $die_holder = $line[1]; + $core_count = 0; + $die_id++ if ($cpu{'type'} ne 'intel' && $cpu{'type'} ne 'amd' ); + } + $phys_holder = 0 if ! defined $phys_holder; + $ids[$phys_holder][$die_id][$line[1]] = $speed; + #print "ph: $phys_holder did: $die_id l1: $line[1] s: $speed\n"; + } + if (!$cpu{'type'} && $line[0] eq 'vendor_id' ){ + $cpu{'type'} = cpu_vendor($line[1]); + } + ## this is only for -C full cpu output + if ( $type eq 'full' ){ + if (!$cpu{'l2-cache'} && $line[0] eq 'cache size'){ + if ($line[1] =~ /(\d+)\sKB$/){ + $cpu{'l2-cache'} = $1; + } + elsif ($line[1] =~ /(\d+)\sMB$/){ + $cpu{'l2-cache'} = ($1*1024); + } + } + if (!$cpu{'flags'} && ($line[0] eq 'flags' || $line[0] eq 'features' )){ + $cpu{'flags'} = $line[1]; + } + } + if ( $extra > 0 && $type eq 'full' ){ + if ($line[0] eq 'bogomips'){ + # new arm shows bad bogomip value, so don't use it + $cpu{'bogomips'} += $line[1] if $line[1] > 50; + } + } + if ($b_admin ){ + if ( !$cpu{'bugs'} && $line[0] eq 'bugs'){ + $cpu{'bugs'} = $line[1]; + } + # unlike family and model id, microcode appears to be hex already + if ( !$cpu{'microcode'} && $line[0] eq 'microcode'){ + if ($line[1] =~ /0x/){ + $cpu{'microcode'} = uc($line[1]); + $cpu{'microcode'} =~ s/^0X//; + } + else { + $cpu{'microcode'} = uc(sprintf("%x", $line[1])); + } + } + } + } + $cpu{'phys'} = scalar @phys_cpus; + $cpu{'dies'} = $die_id++; # count starts at 0, all cpus have 1 die at least + if ($b_arm){ + if ($cpu{'dies'} <= 1){ + my $arm_dies = cpu_dies_sys(); + # case were 4 core arm returned 4 sibling lists, obviously wrong + $cpu{'dies'} = $arm_dies if $arm_dies && $proc_count != $arm_dies; + } + $cpu{'type'} = 'arm' if !$cpu{'type'}; + if (!$bsd_type){ + my %arm_cpus = arm_cpu_name(); + $cpu{'arm-cpus'} = \%arm_cpus if %arm_cpus; + } + } + $cpu{'ids'} = (\@ids); + if ( $extra > 0 && !$cpu{'arch'} && $type ne 'short' ){ + $cpu{'arch'} = cpu_arch($cpu{'type'},$cpu{'family'},$cpu{'model_id'}); + $cpu{'arch'} = $cpu_arch if (!$cpu{'arch'} && $cpu_arch && ($b_mips || $b_arm)) + #print "$cpu{'type'},$cpu{'family'},$cpu{'model_id'},$cpu{'arch'}\n"; + } + if (!$speeds{'cur-freq'}){ + $cpu{'cur-freq'} = $cpu{'processors'}[0]; + $speeds{'min-freq'} = 0; + $speeds{'max-freq'} = 0; + } + else { + $cpu{'cur-freq'} = $speeds{'cur-freq'}; + $cpu{'min-freq'} = $speeds{'min-freq'}; + $cpu{'max-freq'} = $speeds{'max-freq'}; + } + main::log_data('dump','%cpu',\%cpu) if $b_log; + print Data::Dumper::Dumper \%cpu if $test[8]; + eval $end if $b_log; + return %cpu; +} + +sub data_sysctl { + eval $start if $b_log; + my ($type) = @_; + my %cpu = set_cpu_data(); + my (@ids,@line,%speeds,@working); + my ($sep) = (''); + my ($cache,$die_holder,$die_id,$phys_holder,$phys_id,$proc_count,$speed) = (0,0,0,0,0,0,0); + foreach (@sysctl){ + @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) + # freebsd 10: hw.model: AMD Athlon(tm) II X2 245 Processor + $line[1] = main::cleaner($line[1]); + $line[1] = cpu_cleaner($line[1]); + if ( $line[1] =~ /([0-9]+)[\-[:space:]]*([KM]B)\s+L2 cache/) { + my $multiplier = ($2 eq 'MB') ? 1024: 1; + $cpu{'l2-cache'} = $1 * $multiplier; + } + if ( $line[1] =~ /([^0-9\.][0-9\.]+)[\-[:space:]]*[MG]Hz/) { + $cpu{'max-freq'} = $1; + if ($cpu{'max-freq'} =~ /MHz/i) { + $cpu{'max-freq'} =~ s/[\-[:space:]]*MHz//; + $cpu{'max-freq'} = speed_cleaner($cpu{'max-freq'},'mhz'); + } + elsif ($cpu{'max-freq'} =~ /GHz/) { + $cpu{'max-freq'} =~ s/[\-[:space:]]*GHz//i; + $cpu{'max-freq'} = $cpu{'max-freq'} / 1000; + $cpu{'max-freq'} = speed_cleaner($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: + elsif ($line[0] eq 'hw.l1icachesize') { + $cpu{'l1-cache'} = $line[1]/1024; + } + elsif ($line[0] eq 'hw.l2cachesize') { + $cpu{'l2-cache'} = $line[1]/1024; + } + # this is in mghz in samples + elsif ($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; + } + 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]); + } + 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. + elsif ($line[0] eq 'dev.cpu.0.freq_levels') { + $line[1] =~ s/^\s+|\/[0-9]+|\s+$//g; + if ( $line[1] =~ /[0-9]+\s+[0-9]+/ ) { + my @temp = split /\s+/, $line[1]; + $cpu{'max-freq'} = $temp[0]; + $cpu{'min-freq'} = $temp[-1]; + $cpu{'scalings'} = \@temp; + } + } + elsif (!$cpu{'cur-freq'} && $line[0] eq 'dev.cpu.0.freq' ) { + $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; + $cpu{'processors'}[$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. + elsif ( $line[0] eq 'hw.cpu_topology.cpu0.physical_id' ){ + if ($phys_holder != $line[1] ){ + $phys_id++; + $phys_holder = $line[1]; + $ids[$phys_id] = ([(0)]); + $ids[$phys_id][$die_id] = ([(0)]); + } + } + elsif ( $line[0] eq 'hw.cpu_topology.cpu0.core_id' ){ + if ($line[1] > 0 ){ + $die_holder = $line[1]; + } + # this handles multi die cpus like 16 core ryzen + elsif ($line[1] == 0 && $die_holder > 0 ){ + $die_id++ ; + $die_holder = $line[1]; + } + $ids[$phys_id][$die_id][$line[1]] = $speed; + $cpu{'dies'} = $die_id; + } + } + if (!$cpu{'flags'}){ + $cpu{'flags'} = cpu_flags_bsd(); + } + main::log_data('dump','%cpu',\%cpu) if $b_log; + print Data::Dumper::Dumper \%cpu if $test[8]; + eval $end if $b_log; + return %cpu; +} + +sub cpu_properties { + my (%cpu) = @_; + my ($b_amd_zen,$b_epyc,$b_ht,$b_intel,$b_ryzen,$b_xeon); + if ($cpu{'type'} ){ + if ($cpu{'type'} eq 'intel'){ + $b_intel = 1; + $b_xeon = 1 if $cpu{'model_name'} =~ /Xeon/i; + } + elsif ($cpu{'type'} eq 'amd' ){ + if ( $cpu{'family'} && $cpu{'family'} eq '17' ) { + $b_amd_zen = 1; + if ($cpu{'model_name'} ){ + if ($cpu{'model_name'} =~ /Ryzen/i ){ + $b_ryzen = 1; + } + elsif ($cpu{'model_name'} =~ /EPYC/i){ + $b_epyc = 1; + } + } + } + } + } + #my @dies = $phys[0][0]; + my $ref = $cpu{'ids'}; + my @phys = @$ref; + my $phyical_count = 0; + #my $phyical_count = scalar @phys; + my @processors; + my ($speed,$speed_key); + # handle case where cpu reports say, phys id 0, 2, 4, 6 [yes, seen it] + foreach (@phys) { + $phyical_count++ if $_; + } + $phyical_count ||= 1; # assume 1 if no id found, as with ARM + # count unique processors ## + # note, this fails for intel cpus at times + $ref = $cpu{'processors'}; + @processors = @$ref; + #print ref $cpu{'processors'}, "\n"; + my $processors_count = scalar @processors; + #print "p count:$processors_count\n"; + #print Data::Dumper::Dumper \@processors; + # $cpu_cores is per physical cpu + my ($cpu_layout,$cpu_type,$min_max,$min_max_key) = ('','','',''); + my ($cache,$core_count,$cpu_cores,$die_count) = (0,0,0,0); + foreach my $die_ref ( @phys ){ + next if ! $die_ref; + my @dies = @$die_ref; + $core_count = 0; + $die_count = scalar @dies; + #$cpu{'dies'} = $die_count; + foreach my $core_ref (@dies){ + next if ref $core_ref ne 'ARRAY'; + my @cores = @$core_ref; + $core_count = 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 + # arm cpus do not actually show core id so ignore that counter + foreach my $id (@cores){ + $core_count++ if defined $id && !$b_arm; + } + #print 'cores: ' . $core_count, "\n"; + } + } + # this covers potentially cases where ARM cpus have > 1 die + $cpu{'dies'} = ($b_arm && $die_count <= 1 && $cpu{'dies'} > 1) ? $cpu{'dies'}: $die_count; + # 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 ($cpu{'cores'} && ! $core_count || $cpu{'cores'} >= $core_count){ + $cpu_cores = $cpu{'cores'}; + } + elsif ($core_count > $cpu{'cores'}){ + $cpu_cores = $core_count; + } + #print "cpu-c:$cpu_cores\n"; + #$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 ($b_intel){ + if ($cpu{'siblings'} && $cpu{'siblings'} > 1 && $cpu{'cores'} && $cpu{'cores'} > 1 ){ + if ( $cpu{'siblings'}/$cpu{'cores'} == 1 ){ + $b_intel = 0; + $b_ht = 0; + } + else { + $cpu_cores = ($cpu{'siblings'}/2); + $b_ht = 1; + } + } + } + # ryzen is made out of blocks of 8 core dies + elsif ($b_ryzen){ + $cpu_cores = $cpu{'cores'}; + # note: posix ceil isn't present in Perl for some reason, deprecated? + my $working = $cpu_cores / 8; + my @temp = split /\./, $working; + $cpu{'dies'} = ($temp[1] && $temp[1] > 0) ? $temp[0]++ : $temp[0]; + } + # these always have 4 dies + elsif ($b_epyc) { + $cpu_cores = $cpu{'cores'}; + $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 ( $cpu_cores == 0 && ($cpu{'cores'} * $phyical_count > 1)){ + $cpu_cores = ($cpu{'cores'} * $phyical_count); + } + # last check, seeing some intel cpus and vms with intel cpus that do not show any + # core id data at all, or siblings. + if ($cpu_cores == 0 && $processors_count > 0){ + $cpu_cores = $processors_count; + } + # this happens with BSDs which have very little cpu data available + if ( $processors_count == 0 && $cpu_cores > 0 ){ + $processors_count = $cpu_cores; + if ($bsd_type && ($b_ht || $b_amd_zen) && $cpu_cores > 2 ){ + $cpu_cores = $cpu_cores/2;; + } + my $count = $processors_count; + $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; + } + } + # 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: $processors_count phc: $phyical_count coc: $core_count cpc: $cpu_cores\n"; + if (!$b_arm && $processors_count == $phyical_count*$core_count && $cpu_cores > $core_count){ + $b_ht = 0; + #$b_xeon = 0; + $b_intel = 0; + $cpu_cores = 1; + $core_count = 1; + $cpu{'siblings'} = 1; + } + #print "pc: $processors_count s: $cpu{'siblings'} cpuc: $cpu_cores corec: $core_count\n"; + # Algorithm: + # 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) + if ( $processors_count > 1 || ( $b_intel && $cpu{'siblings'} > 0 ) ) { + # non-multicore MT + if ($processors_count == ($phyical_count * $cpu_cores * 2)){ + #print "mt:1\n"; + $cpu_type .= 'MT'; + } +# elsif ($b_xeon && $cpu{'siblings'} > 1){ +# #print "mt:2\n"; +# $cpu_type .= 'MT'; +# } + elsif ($cpu{'siblings'} > 1 && ($cpu{'siblings'} == 2 * $cpu_cores )){ + #print "mt:3\n"; + $cpu_type .= 'MT'; + } + # non-MT multi-core or MT multi-core + if ( ($processors_count == $cpu_cores ) || ($phyical_count < $cpu_cores)){ + my $sep = ($cpu_type) ? ' ' : '' ; + $cpu_type .= $sep . 'MCP'; + } + # only solidly known > 1 die cpus will use this, ryzen and arm for now + if ( $cpu{'dies'} > 1 ){ + my $sep = ($cpu_type) ? ' ' : '' ; + $cpu_type .= $sep . 'MCM'; + } + # >1 cpu sockets active: Symetric Multi Processing + if ($phyical_count > 1){ + my $sep = ($cpu_type) ? ' ' : '' ; + $cpu_type .= $sep . 'SMP'; + } + } + else { + $cpu_type = 'UP'; + } + if ($phyical_count > 1){ + $cpu_layout = $phyical_count . 'x '; + } + $cpu_layout .= count_alpha($cpu_cores) . 'Core'; + $cpu_layout .= ' (' . $cpu{'dies'}. '-Die)' if !$bsd_type && $cpu{'dies'} > 1; + # the only possible change for bsds is if we can get phys counts in the future + if ($bsd_type){ + $cache = $cpu{'l2-cache'} * $phyical_count; + } + # AMD SOS chips appear to report full L2 cache per core + elsif ($cpu{'type'} eq 'amd' && ($cpu{'family'} eq '14' || $cpu{'family'} eq '15' || $cpu{'family'} eq '16')){ + $cache = $cpu{'l2-cache'} * $phyical_count; + } + elsif ($cpu{'type'} ne 'intel'){ + $cache = $cpu{'l2-cache'} * $cpu_cores * $phyical_count; + } + ## note: this handles how intel reports L2, total instead of per core like AMD does + # note that we need to multiply by number of actual cpus here to get true cache size + else { + $cache = $cpu{'l2-cache'} * $phyical_count; + } + if ($cache > 10000){ + $cache = sprintf("%.01f MiB",$cache/1024); # trim to no decimals? + } + elsif ($cache > 0){ + $cache = "$cache KiB"; + } + if ($cpu{'cur-freq'} && $cpu{'min-freq'} && $cpu{'max-freq'} ){ + $min_max = "$cpu{'min-freq'}/$cpu{'max-freq'} MHz"; + $min_max_key = "min/max"; + $speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed'; + $speed = "$cpu{'cur-freq'} MHz"; + } + elsif ($cpu{'cur-freq'} && $cpu{'max-freq'}){ + $min_max = "$cpu{'max-freq'} MHz"; + $min_max_key = "max"; + $speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed'; + $speed = "$cpu{'cur-freq'} MHz"; + } +# elsif ($cpu{'cur-freq'} && $cpu{'max-freq'} && $cpu{'cur-freq'} == $cpu{'max-freq'}){ +# $speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed'; +# $speed = "$cpu{'cur-freq'} MHz (max)"; +# } + elsif ($cpu{'cur-freq'} && $cpu{'min-freq'}){ + $min_max = "$cpu{'min-freq'} MHz"; + $min_max_key = "min"; + $speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed'; + $speed = "$cpu{'cur-freq'} MHz"; + } + elsif ($cpu{'cur-freq'} && !$cpu{'max-freq'}){ + $speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed'; + $speed = "$cpu{'cur-freq'} MHz"; + } + + if ( !$bits_sys && !$b_arm && $cpu{'flags'} ){ + $bits_sys = ($cpu{'flags'} =~ /\blm\b/) ? 64 : 32; + } + my %cpu_properties = ( + 'bits-sys' => $bits_sys, + 'cpu-layout' => $cpu_layout, + 'cpu-type' => $cpu_type, + 'min-max-key' => $min_max_key, + 'min-max' => $min_max, + 'speed-key' => $speed_key, + 'speed' => $speed, + 'l2-cache' => $cache, + ); + main::log_data('dump','%cpu_properties',\%cpu_properties) if $b_log; + #print Data::Dumper::Dumper \%cpu; + #print Data::Dumper::Dumper \%cpu_properties; + #my $dc = scalar @dies; + #print 'phys: ' . $pc . ' dies: ' . $dc, "\n"; + eval $end if $b_log; + return %cpu_properties; +} +sub cpu_speeds { + eval $start if $b_log; + my (@processors) = @_; + my (@speeds); + my @files = main::globber('/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq'); + foreach (@files){ + my $speed = (main::reader($_))[0]; + if ($speed || $speed eq '0'){ + $speed = sprintf "%.0f", $speed/1000; + push @speeds, $speed; + } + } + if (!@speeds){ + foreach (@processors){ + if ($_ || $_ eq '0'){ + $_ = sprintf "%.0f", $_; + push @speeds, $_; + } + } + } + #print join '; ', @speeds, "\n"; + eval $end if $b_log; + return @speeds; +} +sub set_cpu_speeds_sys { + eval $start if $b_log; + my (@arm,%speeds); + my $sys = '/sys/devices/system/cpu/cpufreq/policy0'; + my $sys2 = '/sys/devices/system/cpu/cpu0/cpufreq/'; + my ($cur,$min,$max) = ('scaling_cur_freq','scaling_min_freq','scaling_max_freq'); + if (!-d $sys && -d $sys2){ + $sys = $sys2; + ($cur,$min,$max) = ('scaling_cur_freq','cpuinfo_min_freq','cpuinfo_max_freq'); + } + if (-d $sys){ + $speeds{'cur-freq'} = (main::reader("$sys/$cur"))[0]; + $speeds{'cur-freq'} = speed_cleaner($speeds{'cur-freq'},'khz'); + $speeds{'min-freq'} = (main::reader("$sys/$min"))[0]; + $speeds{'min-freq'} = speed_cleaner($speeds{'min-freq'},'khz'); + $speeds{'max-freq'} = (main::reader("$sys/$max"))[0]; + $speeds{'max-freq'} = speed_cleaner($speeds{'max-freq'},'khz'); + if ($b_arm){ + @arm = main::globber('/sys/devices/system/cpu/cpufreq/policy*/'); + # there are arm chips with two dies, that run at different min max speeds!! + # see: https://github.com/smxi/inxi/issues/128 + # it would be slick to show both die min/max/cur speeds, but this is + # ok for now. + if (scalar @arm > 1){ + my ($current,$max,$min) = (0,0,0); + foreach (@arm){ + $_ =~ s/\/$//; # strip off last slash in case globs have them + my $max_temp = main::reader("$_/cpuinfo_max_freq"); + $max_temp = speed_cleaner($max_temp,'khz'); + if ($max_temp > $max){ + $max = $max_temp; + } + my $min_temp = main::reader("$_/cpuinfo_min_freq"); + $min_temp = speed_cleaner($min_temp,'khz'); + if ($min_temp < $min || $min == 0){ + $max = $min_temp; + } + my $cur_temp = main::reader("$_/cpuinfo_max_freq"); + $cur_temp = speed_cleaner($cur_temp,'khz'); + if ($cur_temp > $current){ + $current = $cur_temp; + } + } + $speeds{'cur-freq'} = $current if $current; + $speeds{'max-freq'} = $max if $max; + $speeds{'min-freq'} = $min if $min; + } + } + # policy4/cpuinfo_max_freq:["2000000"] + # policy4/cpuinfo_min_freq:["200000"] + if ($speeds{'min-freq'} > $speeds{'max-freq'} || $speeds{'min-freq'} == $speeds{'max-freq'}){ + $speeds{'min-freq'} = 0; + } + } + main::log_data('dump','%speeds',\%speeds) if $b_log; + eval $end if $b_log; + return %speeds; +} + +# right now only using this for ARM cpus, this is not the same in intel/amd +sub cpu_dies_sys { + eval $start if $b_log; + my @data = main::globber('/sys/devices/system/cpu/cpu*/topology/core_siblings_list'); + my (@dies); + foreach (@data){ + my $siblings = (main::reader($_))[0]; + if (! grep {/$siblings/} @dies){ + push @dies, $siblings; + } + } + my $die_count = scalar @dies; + eval $end if $b_log; + return $die_count; +} +sub cpu_flags_bsd { + eval $start if $b_log; + my ($flags,$sep) = ('',''); + # this will be null if it was not readable + my $file = main::system_files('dmesg-boot'); + if ( @dmesg_boot){ + foreach (@dmesg_boot){ + 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; + # and replace commas with spaces + $line[1] =~ s/,/ /g; + $flags .= $sep . $line[1]; + $sep = ' '; + } + elsif (/real mem/){ + last; + } + } + if ($flags){ + $flags =~ s/\s+/ /g; + $flags =~ s/^\s+|\s+$//g; + } + } + else { + if ( $file && ! -r $file ){ + $flags = main::row_defaults('dmesg-boot-permissions'); + } + } + eval $end if $b_log; + return $flags; +} + +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 + elsif ($string =~ /centaur/){ + $vendor = "centaur" + } + eval $end if $b_log; + return $vendor; +} +sub get_boost_status { + eval $start if $b_log; + my ($boost); + my $path = '/sys/devices/system/cpu/cpufreq/boost'; + if (-f $path){ + $boost = (main::reader($path))[0]; + if (defined $boost && $boost =~/^[01]$/){ + $boost = ($boost) ? 'enabled' : 'disabled'; + } + } + eval $end if $b_log; + return $boost; +} +sub arm_cpu_name { + eval $start if $b_log; + my (%cpus,$compat); + if ( -e '/sys/firmware/devicetree/base/cpus/cpu@1/compatible' ){ + my @working = main::globber('/sys/firmware/devicetree/base/cpus/cpu@*/compatible'); + foreach my $file (@working){ + $compat = (main::reader($file))[0]; + # 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; + } + } + main::log_data('dump','%cpus',\%cpus) if $b_log; + eval $end if $b_log; + return %cpus; +} + +sub cpu_arch { + eval $start if $b_log; + my ($type,$family,$model) = @_; + my $arch = ''; + # https://en.wikipedia.org/wiki/List_of_AMD_CPU_microarchitectures + # print "$type;$family;$model\n"; + if ( $type eq 'amd'){ + if ($family eq '4'){ + if ( $model =~ /^(3|7|8|9|A)$/ ) {$arch = 'Am486'} + elsif ( $model =~ /^(E|F)$/ ) {$arch = 'Am5x86'} + } + elsif ($family eq '5'){ + if ( $model =~ /^(0|1|2|3)$/ ) {$arch = 'K5'} + elsif ( $model =~ /^(6|7)$/ ) {$arch = 'K6'} + elsif ( $model =~ /^(8)$/ ) {$arch = 'K6-2'} + elsif ( $model =~ /^(9|D)$/ ) {$arch = 'K6-3'} + elsif ( $model =~ /^(A)$/ ) {$arch = 'Geode'} + } + elsif ($family eq '6'){ + if ( $model =~ /^(1|2)$/ ) {$arch = 'K7'} + elsif ( $model =~ /^(3|4)$/ ) {$arch = 'K7 Thunderbird'} + elsif ( $model =~ /^(6|7|8|A)$/ ) {$arch = 'K7 Palomino+'} + else {$arch = 'K7'} + } + elsif ($family eq 'F'){ + if ( $model =~ /^(4|5|7|8|B|C|E|F|14|15|17|18|1B|1C|1F)$/ ) {$arch = 'K8'} + elsif ( $model =~ /^(21|23|24|25|27|28|2C|2F)$/ ) {$arch = 'K8 rev.E'} + elsif ( $model =~ /^(41|43|48|4B|4C|4F|5D|5F|68|6B|6C|6F|7C|7F|C1)$/ ) {$arch = 'K8 rev.F+'} + else {$arch = 'K8'} + } + elsif ($family eq '10'){ + if ( $model =~ /^(2|4|5|6|8|9|A)$/ ) {$arch = 'K10'} + else {$arch = 'K10'} + } + elsif ($family eq '11'){ + if ( $model =~ /^(3)$/ ) {$arch = 'Turion X2 Ultra'} + } + # might also need cache handling like 14/16 + elsif ($family eq '12'){ + if ( $model =~ /^(1)$/ ) {$arch = 'Fusion'} + else {$arch = 'Fusion'} + } + # SOC, apu + elsif ($family eq '14'){ + if ( $model =~ /^(1|2)$/ ) {$arch = 'Bobcat'} + else {$arch = 'Bobcat'} + } + elsif ($family eq '15'){ + if ( $model =~ /^(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)$/ ) {$arch = 'Bulldozer'} + elsif ( $model =~ /^(10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F)$/ ) {$arch = 'Piledriver'} + elsif ( $model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/ ) {$arch = 'Steamroller'} + 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'} + else {$arch = 'Bulldozer'} + } + # SOC, apu + elsif ($family eq '16'){ + if ( $model =~ /^(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)$/ ) {$arch = 'Jaguar'} + elsif ( $model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/ ) {$arch = 'Puma'} + else {$arch = 'Jaguar'} + } + elsif ($family eq '17'){ + if ( $model =~ /^(1)$/ ) {$arch = 'Zen'} + else {$arch = 'Zen'} + } + } + elsif ( $type eq 'arm'){ + if ($family ne ''){$arch="ARMv$family";} + else {$arch='ARM';} + } + # aka VIA + elsif ( $type eq 'centaur'){ + if ($family eq '5'){ + if ( $model =~ /^(4)$/ ) {$arch = 'WinChip C6'} + elsif ( $model =~ /^(8)$/ ) {$arch = 'WinChip 2'} + elsif ( $model =~ /^(9)$/ ) {$arch = 'WinChip 3'} + } + elsif ($family eq '6'){ + if ( $model =~ /^(6)$/ ) {$arch = 'WinChip-based'} + elsif ( $model =~ /^(7|8)$/ ) {$arch = 'C3'} + elsif ( $model =~ /^(9)$/ ) {$arch = 'C3-2'} + elsif ( $model =~ /^(A|D)$/ ) {$arch = 'C7'} + elsif ( $model =~ /^(F)$/ ) {$arch = 'Isaiah'} + } + } + # https://software.intel.com/en-us/articles/intel-architecture-and-processor-identification-with-cpuid-model-and-family-numbers + elsif ( $type eq 'intel'){ + if ($family eq '4'){ + if ( $model =~ /^(0|1|2|3|4|5|6|7|8|9)$/ ) {$arch = '486'} + } + elsif ($family eq '5'){ + if ( $model =~ /^(1|2|3|7)$/ ) {$arch = 'P5'} + elsif ( $model =~ /^(4|8)$/ ) {$arch = 'P5'} # MMX + elsif ( $model =~ /^(9)$/ ) {$arch = 'Quark'} + } + elsif ($family eq '6'){ + if ( $model =~ /^(1)$/ ) {$arch = 'P6 Pro'} + elsif ( $model =~ /^(15)$/ ) {$arch = 'Dothan Tolapai'} # pentium M system on chip + elsif ( $model =~ /^(3)$/ ) {$arch = 'P6 II Klamath'} + elsif ( $model =~ /^(5)$/ ) {$arch = 'P6 II Deschutes'} + elsif ( $model =~ /^(6)$/ ) {$arch = 'P6 II Mendocino'} + elsif ( $model =~ /^(7)$/ ) {$arch = 'P6 III Katmai'} + elsif ( $model =~ /^(8)$/ ) {$arch = 'P6 III Coppermine'} + elsif ( $model =~ /^(9)$/ ) {$arch = 'Banias'} # pentium M + elsif ( $model =~ /^(A)$/ ) {$arch = 'P6 III Xeon'} + elsif ( $model =~ /^(B)$/ ) {$arch = 'P6 III Tualitin'} + elsif ( $model =~ /^(D)$/ ) {$arch = 'Dothan'} # Pentium M + elsif ( $model =~ /^(E)$/ ) {$arch = 'Yonah'} + elsif ( $model =~ /^(F|16)$/ ) {$arch = 'Merom'} + elsif ( $model =~ /^(17|1D)$/ ) {$arch = 'Penryn'} + elsif ( $model =~ /^(1A|1E|1F|2E|25|2C|2F)$/ ) {$arch = 'Nehalem'} + elsif ( $model =~ /^(1C)$/ ) {$arch = 'Bonnell'} # atom Bonnell? 27? + elsif ( $model =~ /^(27|35)$/ ) {$arch = 'Saltwell'} + elsif ( $model =~ /^(25|2C|2F)$/ ) {$arch = 'Westmere'} + elsif ( $model =~ /^(26)$/ ) {$arch = 'Atom Lincroft'} + elsif ( $model =~ /^(2A|2D)$/ ) {$arch = 'Sandy Bridge'} + elsif ( $model =~ /^(36)$/ ) {$arch = 'Atom Cedarview'} + elsif ( $model =~ /^(37|4A|4D|5A)$/ ) {$arch = 'Silvermont'} + elsif ( $model =~ /^(3A|3E)$/ ) {$arch = 'Ivy Bridge'} + elsif ( $model =~ /^(3C|3F|45|46)$/ ) {$arch = 'Haswell'} + elsif ( $model =~ /^(3D|47|4F|56)$/ ) {$arch = 'Broadwell'} + elsif ( $model =~ /^(4E|55|9E)$/ ) {$arch = 'Skylake'} + elsif ( $model =~ /^(5E)$/ ) {$arch = 'Skylake-S'} + elsif ( $model =~ /^(4C|5D)$/ ) {$arch = 'Airmont'} + elsif ( $model =~ /^(8E|9E)$/ ) {$arch = 'Kaby Lake'} + elsif ( $model =~ /^(57)$/ ) {$arch = 'Knights Landing'} + elsif ( $model =~ /^(85)$/ ) {$arch = 'Knights Mill'} + # product codes: https://en.wikipedia.org/wiki/List_of_Intel_microprocessors + # coming: coffee lake; cannonlake; icelake; tigerlake + } + # itanium 1 family 7 all recalled + elsif ($family eq 'B'){ + if ( $model =~ /^(1)$/ ) {$arch = 'Knights Corne'} + } + elsif ($family eq 'F'){ + if ( $model =~ /^(0|1)$/ ) {$arch = 'Netburst Willamette'} + elsif ( $model =~ /^(2)$/ ) {$arch = 'Netburst Northwood'} + elsif ( $model =~ /^(3)$/ ) {$arch = 'Prescott'} # 6? Nocona + elsif ( $model =~ /^(4)$/ ) {$arch = 'Smithfield'} # 6? Nocona + elsif ( $model =~ /^(6)$/ ) {$arch = 'Presler'} + else {$arch = 'Netburst'} + } + } + eval $end if $b_log; + return $arch; +} + +sub count_alpha { + my ($count) = @_; + #print "$count\n"; + my @alpha = qw(Single Dual Triple Quad); + if ($count > 4){ + $count .= '-'; + } + else { + $count = $alpha[$count-1] . ' ' if $count > 0; + } + return $count; +} +sub set_cpu_data { + my %cpu = ( + 'arch' => '', + 'bogomips' => 0, + 'cores' => 0, + 'cur-freq' => 0, + '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, + 'min-freq' => 0, + 'model_id' => '', + 'model_name' => '', + 'processors' => [], + 'rev' => '', + 'scalings' => [], + 'siblings' => 0, + 'type' => '', + ); + return %cpu; +} +# MHZ - cell cpus +sub speed_cleaner { + my ($speed,$opt) = @_; + return if ! $speed || $speed eq '0'; + $speed =~ s/[GMK]HZ$//gi; + $speed = ($speed/1000) if $opt && $opt eq 'khz'; + $speed = sprintf "%.0f", $speed; + return $speed; +} +sub cpu_cleaner { + 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) = @_; + if ($data){ + $data .= ' (' . hex($data) . ')' if hex($data) ne $data; + } + else { + $data = 'N/A'; + } + return $data; +} +} + +## DiskData +{ +package DiskData; +my ($b_hddtemp,$b_nvme); +my ($hddtemp,$nvme) = ('',''); +my (@by_id,@by_path); + +sub get { + eval $start if $b_log; + my (@data,@rows,$key1,$val1); + my ($type) = @_; + $type ||= 'standard'; + my $num = 0; + @data = disk_data($type); + # NOTE: + if (@data){ + if ($type eq 'standard'){ + @data = create_output(@data); + @rows = (@rows,@data); + if ( $bsd_type && !@dm_boot_disk && $type eq 'standard' && $show{'disk'} ){ + $key1 = 'Drive Report'; + my $file = main::system_files('dmesg-boot'); + if ( $file && ! -r $file){ + $val1 = main::row_defaults('dmesg-boot-permissions'); + } + elsif (!$file){ + $val1 = main::row_defaults('dmesg-boot-missing'); + } + else { + $val1 = main::row_defaults('disk-data-bsd'); + } + @data = ({main::key($num++,$key1) => $val1,}); + @rows = (@rows,@data); + } + } + else { + @rows = @data; + # print Data::Dumper::Dumper \@rows; + } + } + else { + $key1 = 'Message'; + $val1 = main::row_defaults('disk-data'); + @rows = ({main::key($num++,$key1) => $val1,}); + } + if (!@rows){ + $key1 = 'Message'; + $val1 = main::row_defaults('disk-data'); + @data = ({main::key($num++,$key1) => $val1,}); + } + #@rows = (@rows,@data); + @data = (); + if ($show{'optical'} || $show{'optical-basic'}){ + @data = OpticalData::get(); + @rows = (@rows,@data); + } + ($b_hddtemp,$b_nvme,$hddtemp,$nvme) = (undef,undef,undef,undef); + (@by_id,@by_path) = (undef,undef); + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my (@disks) = @_; + #print Data::Dumper::Dumper \@disks; + my (@data,@rows); + my ($num,$j) = (0,0); + my ($id,$model,$size,$used,$percent,$size_holder,$used_holder) = ('','','','','','',''); + my @sizing = main::get_size($disks[0]{'size'}) if $disks[0]{'size'}; + #print Data::Dumper::Dumper \@disks; + if (@sizing){ + $size = $sizing[0]; + # note: if a string is returned there will be no Size unit so just use string. + if (defined $sizing[0] && $sizing[1]){ + $size .= ' ' . $sizing[1]; + } + } + $size ||= 'N/A'; + @sizing = main::get_size($disks[0]{'used'}) if $disks[0]{'used'}; + if (@sizing){ + $used = $sizing[0]; + if (defined $sizing[0] && $sizing[1]){ + $used .= ' ' . $sizing[1]; + if (( $disks[0]{'size'} && $disks[0]{'size'} =~ /^[0-9]/ ) && + ( $disks[0]{'used'} =~ /^[0-9]/ ) ){ + $used = $used . ' (' . sprintf("%0.1f", $disks[0]{'used'}/$disks[0]{'size'}*100) . '%)'; + } + } + } + $used ||= 'N/A'; + @data = ({ + main::key($num++,'Local Storage') => '', + main::key($num++,'total') => $size, + main::key($num++,'used') => $used, + }); + @rows = (@rows,@data); + shift @disks; + if ( $show{'disk'} && @disks){ + @disks = sort { $a->{'id'} cmp $b->{'id'} } @disks; + foreach my $ref (@disks){ + ($id,$model,$size) = ('','',''); + my %row = %$ref; + $num = 1; + $model = ($row{'model'}) ? $row{'model'}: 'N/A'; + $id = ($row{'id'}) ? "/dev/$row{'id'}":'N/A'; + my @sizing = main::get_size($row{'size'}); + #print Data::Dumper::Dumper \@disks; + if (@sizing){ + $size = $sizing[0]; + # note: if a string is returned there will be no Size unit so just use string. + if (defined $sizing[0] && $sizing[1]){ + $size .= ' ' . $sizing[1]; + $size_holder = $sizing[0]; + } + $size ||= 'N/A'; + } + else { + $size = 'N/A'; + } + $j = scalar @rows; + @data = ({ + main::key($num++,'ID') => $id, + }); + @rows = (@rows,@data); + if ($row{'type'}){ + $rows[$j]{main::key($num++,'type')} = $row{'type'}, + } + if ($row{'vendor'}){ + $rows[$j]{main::key($num++,'vendor')} = $row{'vendor'}, + } + $rows[$j]{main::key($num++,'model')} = $model; + $rows[$j]{main::key($num++,'size')} = $size; + if ($extra > 1 && $row{'speed'}){ + $rows[$j]{main::key($num++,'speed')} = $row{'speed'}; + $rows[$j]{main::key($num++,'lanes')} = $row{'lanes'} if $row{'lanes'}; + } + if ($extra > 2 && $row{'rotation'}){ + $rows[$j]{main::key($num++,'rotation')} = $row{'rotation'}; + } + if ($extra > 1){ + my $serial = main::apply_filter($row{'serial'}); + $rows[$j]{main::key($num++,'serial')} = $serial; + if ($row{'firmware'}){ + $rows[$j]{main::key($num++,'rev')} = $row{'firmware'}; + } + } + if ($extra > 0 && $row{'temp'}){ + $rows[$j]{main::key($num++,'temp')} = $row{'temp'} . ' C'; + } + # extra level tests already done + if (defined $row{'partition-table'}){ + $rows[$j]{main::key($num++,'scheme')} = $row{'partition-table'}; + } + } + } + + eval $end if $b_log; + return @rows; +} +sub disk_data { + eval $start if $b_log; + my ($type) = @_; + my (@rows,@data,@devs); + my $num = 0; + my ($used) = (0); + PartitionData::partition_data() if !$b_partitions; + foreach my $ref (@partitions){ + my %row = %$ref; + # don't count remote used, also, some cases mount + # panfs is parallel NAS volume manager, need more data + next if ($row{'fs'} && $row{'fs'} =~ /nfs|panfs|sshfs|smbfs|unionfs/); + # 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 && (my $file = main::system_files('partitions'))){ + @data = proc_data($used,$file); + } + elsif ($bsd_type) { + @data = dmesg_boot_data($used); + } + #print Data::Dumper::Dumper \@data; + 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,$file) = @_; + my (@data,@drives); + my ($b_hdx,$size,$drive_size) = (0,0,0); + my @proc_partitions = main::reader($file,'strip'); + shift @proc_partitions; + foreach (@proc_partitions){ + next if (/^\s*$/); + my @row = split /\s+/, $_; + if ( $row[-1] =~ /^([hsv]d[a-z]+|(ada|mmcblk|n[b]?d|nvme[0-9]+n)[0-9]+)$/) { + $drive_size = $row[2]; + $b_hdx = 1 if $row[-1] =~ /^hd[a-z]/; + @data = ({ + 'firmware' => '', + 'id' => $row[-1], + 'model' => '', + 'serial' => '', + 'size' => $drive_size, + 'spec' => '', + 'speed' => '', + 'temp' => '', + 'type' => '', + 'vendor' => '', + }); + @drives = (@drives,@data); + } + # 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 + if ( $row[0] =~ /^(3|8|22|33|43|179|202|252|253|254|259)$/ && + $row[-1] =~ /(mmcblk[0-9]+|n[b]?d[0-9]+|nvme[0-9]+n[0-9]+|[hsv]d[a-z]+)$/ && + ( $row[1] % 16 == 0 || $row[1] % 16 == 8 ) ) { + $size += $row[2]; + } + } + # print Data::Dumper::Dumper \@drives; + main::log_data('data',"size: $size") if $b_log; + @data = ({ + 'size' => $size, + 'used' => $used, + }); + #print Data::Dumper::Dumper \@data; + if ( $show{'disk'} ){ + @drives = (@data,@drives); + # print 'drives:', Data::Dumper::Dumper \@drives; + @data = proc_data_advanced($b_hdx,@drives); + } + main::log_data('dump','@data',\@data) if $b_log; + # print Data::Dumper::Dumper \@data; + eval $end if $b_log; + return @data; +} +sub proc_data_advanced { + eval $start if $b_log; + my ($b_hdx,@drives) = @_; + my ($i) = (0); + my (@data,@disk_data,@rows,@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 = main::system_files('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) = ('','','','','','',''); + if ($extra > 2){ + @data = advanced_disk_data($pt_cmd,$drives[$i]{'id'}); + $pt_cmd = $data[0]; + $drives[$i]{'partition-table'} = uc($data[1]) if $data[1]; + $drives[$i]{'rotation'} = "$data[2] rpm" if $data[2]; + } + #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[^\/]*$//; + } + main::log_data('data',"working path: $working_path") if $b_log; + if ($block_type && @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 $ref (@scsi){ + my %row = %$ref; + 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 ( -e $path){ + $model = (main::reader($path,'strip'))[0]; + if ($model){ + $drives[$i]{'model'} = $model; + } + } + elsif ($block_type eq 'mmc' && -e "${working_path}name"){ + $path = "${working_path}name"; + $model = (main::reader($path,'strip'))[0]; + if ($model){ + $drives[$i]{'model'} = $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::disk_cleaner($drives[$i]{'model'}); + my @device_data = device_vendor($drives[$i]{'model'},''); + $drives[$i]{'model'} = $device_data[1] if $device_data[1]; + $drives[$i]{'vendor'} = $device_data[0] if $device_data[0]; + } + if ($working_path){ + $path = "${working_path}removable"; + $drives[$i]{'type'} = 'Removable' if -e $path && (main::reader($path,'strip'))[0]; # 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("/dev/$drives[$i]{'id'}"); + if ($extra > 1){ + my @speed_data = device_speed($drives[$i]{'id'}); + $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 ( -e $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 ( -e $path){ + $drives[$i]{'firmware'} = (main::reader($path,'strip'))[0]; + last; + } + } + } + } + } + } + # print Data::Dumper::Dumper \@drives; + 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 dmesg_boot_data { + eval $start if $b_log; + my ($used) = @_; + my (@data,@drives,@temp); + my ($id_holder,$i,$size,$working) = ('',0,0,0); + my $file = main::system_files('dmesg-boot'); + if (@dm_boot_disk){ + foreach (@dm_boot_disk){ + my @row = split /:\s*/, $_; + next if ! defined $row[1]; + if ($id_holder ne $row[0]){ + $i++ if $id_holder; + # print "$i $id_holder $row[0]\n"; + $id_holder = $row[0]; + } + # no dots, note: ada2: 2861588MB BUT: ada2: 600.000MB/s + if (! exists $drives[$i]){ + $drives[$i] = ({}); + $drives[$i]{'id'} = $row[0]; + $drives[$i]{'firmware'} = ''; + $drives[$i]{'temp'} = ''; + $drives[$i]{'type'} = ''; + $drives[$i]{'vendor'} = ''; + } + #print "$i\n"; + if ($bsd_type eq 'openbsd'){ + if ($row[1] =~ /,\s*([0-9\.]+[MGTPE][B]?),.*\ssectors$|^]+)>/){ + $drives[$i]{'model'} = $1 if $1; + $drives[$i]{'type'} = 'removable' if $_ =~ /removable$/; + # + my $count = ($drives[$i]{'model'} =~ tr/,//); + if ($count && $count > 1){ + @temp = split /,\s*/, $drives[$i]{'model'}; + $drives[$i]{'model'} = $temp[1]; + } + } + # print "openbsd\n"; + } + else { + if ($row[1] =~ /^([0-9]+[KMGTPE][B]?)\s/){ + $working = main::translate_size($1); + $size += $working if $working; + $drives[$i]{'size'} = $working; + } + if ($row[1] =~ /device$|^]*)>\s(.*)/; + $drives[$i]{'model'} = $1 if $1; + $drives[$i]{'spec'} = $2 if $2; + } + if ($row[1] =~ /^Serial\sNumber\s(.*)/){ + $drives[$i]{'serial'} = $1; + } + if ($row[1] =~ /^([0-9\.]+[MG][B]?\/s)/){ + $drives[$i]{'speed'} = $1; + $drives[$i]{'speed'} =~ s/\.[0-9]+// if $drives[$i]{'speed'}; + } + } + $drives[$i]{'model'} = main::disk_cleaner($drives[$i]{'model'}); + my @device_data = device_vendor($drives[$i]{'model'},''); + $drives[$i]{'vendor'} = $device_data[0] if $device_data[0]; + $drives[$i]{'model'} = $device_data[1] if $device_data[1]; + } + if (!$size){ + $size = main::row_defaults('data-bsd'); + } + } + elsif ( $file && ! -r $file ){ + $size = main::row_defaults('dmesg-boot-permissions'); + } + elsif (!$file ){ + $size = main::row_defaults('dmesg-boot-missing'); + } + @data = ({ + 'size' => $size, + 'used' => $used, + }); + #main::log_data('dump','@data',\@data) if $b_log; + if ( $show{'disk'} ){ + @data = (@data,@drives); + # print 'drives:', Data::Dumper::Dumper \@drives; + } + # print Data::Dumper::Dumper \@data; + eval $end if $b_log; + return @data; +} + +# check for usb/firewire/[and thunderwire 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 advanced_disk_data { + eval $start if $b_log; + my ($set_cmd,$id) = @_; + my ($cmd,$pt,$program,@data,@return); + if ($set_cmd ne 'unset'){ + $return[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')){ + $return[0] = "$program info -q property -n "; + } + elsif ($b_root && -e "/lib/udev/udisks-part-id") { + $return[0] = "/lib/udev/udisks-part-id /dev/"; + } + elsif ($b_root && ($program = main::check_program('fdisk'))) { + $return[0] = "$program -l /dev/"; + } + if (!$return[0]) { + $return[0] = 'na' + } + } + if ($return[0] ne 'na'){ + $cmd = "$return[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/){ + $return[1] = 'gpt'; + last; + } + elsif (/^Disklabel\stype:\s*(.+)/i){ + $return[1] = $1; + last; + } + } + $return[1] = 'dos' if !$return[1]; + } + else { + foreach (@data){ + if ( /^(UDISKS_PARTITION_TABLE_SCHEME|ID_PART_TABLE_TYPE)/ ){ + my @working = split /=/, $_; + $return[1] = $working[1]; + } + elsif (/^ID_ATA_ROTATION_RATE_RPM/){ + my @working = split /=/, $_; + $return[2] = $working[1]; + } + last if $return[1] && $return[2]; + } + } + $return[1] = 'mbr' if $return[1] && lc($return[1]) eq 'dos'; + } + eval $end if $b_log; + return @return; +} +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){ + my @working = ({ + 'vendor' => $vendor, + 'model' => $model, + 'firmware' => $firmware, + }); + @scsi = (@scsi,@working); + } + 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 /_/, $_; + my @device_data = (); + 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::disk_cleaner($model); + @device_data = device_vendor($model,$serial); + $vendor = $device_data[0] if $device_data[0]; + $model = $device_data[1] if $device_data[1]; + # print $device, '::', Cwd::abs_path($_),'::', $model, '::', $vendor, '::', $serial, "\n"; + (@disk_data) = ($model,$vendor,$serial); + last; + } + } + eval $end if $b_log; + return @disk_data; +} +# receives space separated string that may or may not contain vendor data +sub device_vendor { + eval $start if $b_log; + my ($model,$serial) = @_; + my ($vendor) = (''); + my (@data); + return if !$model; + # 0 - match pattern; 1 - replace pattern; 2 - vendor print; 3 - serial pattern + # Data URLs: inxi-resources.txt Section: DiskData device_vendor() + my @vendors = ( + ## These go first because they are the most likely and common ## + ['(Crucial|^CT|-CT|^M4-)','Crucial','Crucial',''], + ['^INTEL','^INTEL','Intel',''], + ['(KINGSTON|DataTraveler|^SMS|^SHS|^SUV)','KINGSTON','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] + ['(SAMSUNG|^MCG[0-9]+GC)','SAMSUNG','Samsung',''], # maybe ^SM + ['(SanDisk|^SDS[S]?[DQ]|^SL([0-9]+)G|^AFGCE|ULTRA\sFIT|Cruzer)','SanDisk','SanDisk',''], + ['(^ST[^T]|[S]?SEAGATE|^X[AFP]|^BUP|Expansion Desk)','[S]?SEAGATE','Seagate',''], # real, SSEAGATE Backup+; XP1600HE30002 + ['^(WD|Western Digital|My (Book|Passport)|00LPCX|Elements)','(^WDC|Western Digital)','Western Digital',''], + ## Then better known ones ## + ['^(A-DATA|ADATA|AXN)','^(A-DATA|ADATA)','A-Data',''], + ['^ADTRON','^(ADTRON)','Adtron',''], + ['^ASUS','^ASUS','ASUS',''], + ['^ATP','^ATP[\s\-]','ATP',''], + ['^Corsair','^Corsair','Corsair',''], + ['^(FUJITSU|MP)','^FUJITSU','Fujitsu',''], + # note: 2012: wdc bought hgst + ['^(HGST)','^HGST','HGST (Hitachi)',''], # HGST HUA + ['^(Hitachi|HDS|IC|HT|HU)','^Hitachi','Hitachi',''], + ['^Hoodisk','^Hoodisk','Hoodisk',''], + ['^(HP\b)','^HP','HP',''], # vb: VB0250EAVER but clashes with vbox; HP_SSD_S700_120G + ['^(LSD|Lexar)','^Lexar','Lexar',''], # mmc-LEXAR_0xb016546c + # OCZSSD2-2VTXE120G is OCZ-VERTEX2_3.5 + ['^(OCZ|APOC|D2|DEN|DEN|DRSAK|EC188|FTNC|GFGC|MANG|MMOC|NIMC|NIMR|PSIR|TALOS2|TMSC|TRSAK)','^OCZ[\s\-]','OCZ',''], + ['^OWC','^OWC[\s\-]','OWC',''], + ['^Philips','^Philips','Philips',''], + ['^PIONEER','^PIONEER','Pioneer',''], + ['^PNY','^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 + ['(^[S]?TOS|^THN|TOSHIBA)','[S]?TOSHIBA','Toshiba',''], # scsi-STOSHIBA_STOR.E_EDITION_ + ## These go last because they are short and could lead to false ID, or are unlikely ## + ['^Android','^Android','Android',''], + # must come before AP|Apacer + ['^APPLE','^APPLE','Apple',''], + ['^(AP|Apacer)','^Apacer','Apacer',''], + ['^BUFFALO','^BUFFALO','Buffalo',''], + ['^CHN\b','','Zheino',''], + ['^Colorful\b','^Colorful','Colorful',''], + ['^DREVO\b','','Drevo',''], + ['^EXCELSTOR','^EXCELSTOR( TECHNOLOGY)?','Excelstor',''], + ['^FASTDISK','^FASTDISK','FASTDISK',''], + ['^FORESEE','^FORESEE','Foresee',''], + ['^GALAX\b','^GALAX','GALAX',''], + ['^Generic','^Generic','Generic',''], + ['^GOODRAM','^GOODRAM','GOODRAM',''], + # supertalent also has FM: |FM + ['^(G[\.]?SKILL)','^G[\.]?SKILL','G.SKILL',''], + ['^HUAWEI','^HUAWEI','Huawei',''], + ['^(IBM|DT)','^IBM','IBM',''], + ['^Imation','^Imation(\sImation)?','Imation',''], # Imation_ImationFlashDrive + ['^(InnoDisk|Innolite)','^InnoDisk( Corp.)?','InnoDisk',''], + ['^Innostor','^Innostor','Innostor',''], + ['^Intenso','^Intenso','Intenso',''], + ['^KingDian','^KingDian','KingDian',''], + ['^(LITE[\-]?ON[\s\-]?IT)','^LITE[\-]?ON[\s\-]?IT','LITE-ON IT',''], # LITEONIT_LSS-24L6G + ['^(LITE[\-]?ON|PH6)','^LITE[\-]?ON','LITE-ON',''], # PH6-CE240-L + ['^M-Systems','^M-Systems','M-Systems',''], + ['^MAXTOR','^MAXTOR','Maxtor',''], + ['^(MT|M5|Micron)','^Micron','Micron',''], + ['^MARVELL','^MARVELL','Marvell',''], + ['^Medion','^Medion','Medion',''], + ['^Motorola','^Motorola','Motorola',''], + ['^(PS[8F]|Patriot)','^Patriot','Patriot',''], + ['^PIX[\s]?JR','^PIX[\s]?JR','Disney',''], + ['^(PLEXTOR|PX-)','^PLEXTOR','Plextor',''], + ['(^Quantum|Fireball)','^Quantum','Quantum',''], + ['^R3','','AMD Radeon',''], # ssd + ['^RENICE','^RENICE','Renice',''], + ['^RIM[\s]','^RIM','RIM',''], + ['^SigmaTel','^SigmaTel','SigmaTel',''], + ['^SPPC','','Silicon Power',''], + ['^(SK\s?HYNIX|HFS)','^SK\s?HYNIX','SK Hynix',''], # HFS128G39TND-N210A + ['^hynix','hynix','Hynix',''],# nvme middle of string, must be after sk hynix + ['^SH','','Smart Modular Tech.',''], + ['^(SMART( Storage Systems)?|TX)','^(SMART( Storage Systems)?)','Smart Storage Systems',''], + ['^(S[FR]-|Sony)','^Sony','Sony',''], + ['^STE[CK]','^STE[CK]','sTec',''], # wd bought this one + ['^STORFLY','^STORFLY','StorFly',''], + # NOTE: F[MNETU] not reliable, g.skill starts with FM too: + # Seagate ST skips STT. + ['^(STT)','','Super Talent',''], + ['^(SF|Swissbit)','^Swissbit','Swissbit',''], + # ['^(SUPERSPEED)','^SUPERSPEED','SuperSpeed',''], # superspeed is a generic term + ['^TANDBERG','^TANDBERG','Tanberg',''], + ['^TEAC','^TEAC','TEAC',''], + ['^(TS|Transcend|JetFlash)','^Transcend','Transcend',''], + ['^TrekStor','^TrekStor','TrekStor',''], + ['^UDinfo','^UDinfo','UDinfo',''], + ['^(UG|Unigen)','^Unigen','Unigen',''], + ['^VBOX','','VirtualBox',''], + ['^(Verbatim|STORE N GO)','^Verbatim','Verbatim',''], + ['^VISIONTEK','^VISIONTEK','VisionTek',''], + ['^VMware','^VMware','VMware',''], + ['^(Vseky|Vaseky)','^Vaseky','Vaseky',''], # ata-Vseky_V880_350G_ + ); + foreach my $ref (@vendors){ + my @row = @$ref; + if ($model =~ /$row[0]/i || ($row[3] && $serial && $serial =~ /$row[3]/)){ + $vendor = $row[2]; + $model =~ s/$row[1]//i if $row[1] && lc($model) ne lc($row[1]); + $model =~ s/^[\s\-_]+|[\s\-_]+$//g; + $model =~ s/\s\s/ /g; + @data = ($vendor,$model); + last; + } + } + eval $end if $b_log; + return @data; +} +# Normally hddtemp requires root, but you can set user rights in /etc/sudoers. +# args: $1 - /dev/ to be tested for +sub hdd_temp { + eval $start if $b_log; + my ($device) = @_; + my ($path) = (''); + my (@data,$hdd_temp); + 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("$sudo$nvme smart-log $device 2>/dev/null"); + foreach (@data){ + my @row = split /\s*:\s*/, $_; + # 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("$sudo$hddtemp -nq -u C $device 2>/dev/null"))[0]; + } + } + eval $end if $b_log; + return $hdd_temp; +} +sub device_speed { + eval $start if $b_log; + my ($device) = @_; + my ($b_nvme,$lanes,$speed,@data); + 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 -f $working; + if ($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 -f $working; + $lanes = 1 if !$lanes; + # 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 -f $working; + $speed = main::disk_cleaner($speed) if $speed; + $speed =~ s/Gbps/Gb\/s/ if $speed; + } + } + } + @data = ($speed,$lanes); + #print "$working $speed\n"; + eval $end if $b_log; + return @data; +} +# gptid/c5e940f1-5ce2-11e6-9eeb-d05099ac4dc2 N/A ada0p1 +sub match_glabel { + eval $start if $b_log; + my ($gptid) = @_; + return if !@glabel || ! $gptid; + #$gptid =~ s/s[0-9]+$//; + 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_glabel { + eval $start if $b_log; + $b_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; +} +} + +## GraphicData +{ +package GraphicData; +my $driver = ''; # we need this as a fallback in case no xorg.0.log +sub get { + eval $start if $b_log; + my (@data,@rows); + my $num = 0; + if (($b_arm || $b_mips) && !$b_soc_gfx && !$b_pci_tool){ + my $key = ($b_arm) ? 'ARM' : 'MIPS'; + @data = ({ + main::key($num++,$key) => main::row_defaults(lc($key) . '-pci',''), + },); + @rows = (@rows,@data); + } + else { + @data = card_data(); + @rows = (@rows,@data); + if (!@rows){ + my $key = 'Message'; + @data = ({ + main::key($num++,$key) => main::row_defaults('pci-card-data',''), + },); + @rows = (@rows,@data); + } + } + @data = display_data(); + @rows = (@rows,@data); + @data = gl_data(); + @rows = (@rows,@data); + eval $end if $b_log; + return @rows; +} +# 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 +# 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 +sub card_data { + eval $start if $b_log; + my (@rows,@data); + my ($j,$num) = (0,1); + foreach (@pci){ + $num = 1; + my @row = @$_; + #print "$row[0] $row[3]\n"; + if ($row[3] == 0 && ( $row[0] =~ /^(vga|disp|display|3d|fb|gpu|hdmi)$/ ) ){ + #print "$row[0] $row[3]\n"; + $j = scalar @rows; + $driver = $row[9]; + $driver ||= 'N/A'; + my $card = main::trimmer($row[4]); + $card = ($card) ? main::pci_cleaner($card,'output') : 'N/A'; + #$card ||= 'N/A'; + # have seen absurdly verbose card descriptions, with non related data etc + if (length($card) > 85 || $size{'max'} < 110){ + $card = main::pci_long_filter($card); + } + @data = ({ + main::key($num++,'Card') => $card, + },); + @rows = (@rows,@data); + if ($extra > 2 && $b_pci_tool && $row[11]){ + my $item = main::get_pci_vendor($row[4],$row[11]); + $rows[$j]{main::key($num++,'vendor')} = $item if $item; + } + $rows[$j]{main::key($num++,'driver')} = $driver; + if ($row[9] && !$bsd_type){ + my $version = main::get_module_version($row[9]); + $version ||= 'N/A'; + $rows[$j]{main::key($num++,'v')} = $version; + } + if ($extra > 0){ + $rows[$j]{main::key($num++,'bus ID')} = (!$row[2] && !$row[3]) ? 'N/A' : "$row[2].$row[3]"; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = ($row[5]) ? "$row[5]:$row[6]" : $row[6]; + } + } + #print "$row[0]\n"; + } + #my $ref = $pci[-1]; + #print $$ref[0],"\n"; + eval $end if $b_log; + return @rows; +} +sub display_data(){ + eval $start if $b_log; + my (%graphics,@row); + my @xdpyinfo; + my $num = 0; + my ($protocol,$server) = ('',''); + # note: these may not always be set, they won't be out of X, for example + $protocol = $ENV{'XDG_SESSION_TYPE'} if $ENV{'XDG_SESSION_TYPE'}; + $protocol = $ENV{'WAYLAND_DISPLAY'} if (!$protocol && $ENV{'WAYLAND_DISPLAY'}); + # need to confirm that there's a point to this test, I believe no, fails out of x + # loginctl also results in the session id + if (!$protocol && $b_display && $b_force_display){ + 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){ + next if /tty[v]?[0-6]$/; # freebsd: ttyv3 + $id = (split /\s+/, $_)[0]; + last; # multiuser? too bad, we'll go for the first one + } + 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 + $protocol = $temp if $temp && $temp ne 'tty'; + } + } + } + if ($extra > 1){ + # initial tests, if wayland, it is certainly a compositor + $protocol = lc($protocol) if $protocol; + $graphics{'compositor'} = display_compositor($protocol); + } + if ( $b_display){ + # 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 + if (my $program = main::check_program('xdpyinfo')){ + my @xdpyinfo = main::grabber("$program $display_opt 2>/dev/null","\n",'strip'); + #@xdpyinfo = map {s/^\s+//;$_} @xdpyinfo if @xdpyinfo; + #print join "\n",@xdpyinfo, "\n"; + foreach (@xdpyinfo){ + my @working = split /:\s+/, $_; + next if ( ($graphics{'dimensions'} && $working[0] ne 'dimensions' ) || !$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{'vendor'} = $working[1]; + } + elsif ($working[0] eq 'version number'){ + $graphics{'version-id'} = $working[1]; + } + elsif ($working[0] eq 'vendor release number'){ + $graphics{'vendor-release'} = $working[1]; + } + elsif ($working[0] eq 'X.Org version'){ + $graphics{'xorg-version'} = $working[1]; + } + elsif ($working[0] eq 'dimensions'){ + $working[1] =~ s/\spixels//; + $working[1] =~ s/\smillimeters/ mm/; + if ($graphics{'dimensions'}){ + $graphics{'dimensions'} = ([@{$graphics{'dimensions'}},$working[1]]); + } + else { + $graphics{'dimensions'} = ([$working[1]]); + } + } + } + #$graphics{'dimensions'} = (\@dimensions); + # we get a bit more info from xrandr than xdpyinfo, but xrandr fails to handle + # multiple screens from different video cards + my $ref = $graphics{'dimensions'}; + if (defined $ref){ + my @screens = @$ref; + if (scalar @screens == 1){ + if (my $program = main::check_program('xrandr')){ + my @xrandr = main::grabber("$program $display_opt 2>/dev/null",'','strip'); + foreach (@xrandr){ + my @working = split /\s+/,$_; + # print join "$_\n"; + if ($working[1] =~ /\*/){ + $working[1] =~ s/\*|\+//g; + $working[1] = sprintf("%.0f",$working[1]); + $working[1] = ($working[1]) ? "$working[1]Hz" : 'N/A'; + my $screen = "$working[0]~$working[1]"; + if ($graphics{'screens'}){ + $graphics{'screens'} = ([@{$graphics{'screens'}},$screen]); + } + else { + $graphics{'screens'} = ([$screen]); + } + } + } + } + } + } + else { + $graphics{'tty'} = tty_data(); + } + } + else { + $graphics{'screens'} = ([main::row_defaults('xdpyinfo-missing')]); + } + } + else { + $graphics{'tty'} = tty_data(); + } + # this gives better output than the failure last case, which would only show: + # for example: X.org: 1.9 instead of: X.org: 1.9.0 + $graphics{'version'} = $graphics{'xorg-version'} if $graphics{'xorg-version'};; + $graphics{'version'} = x_version() if !$graphics{'version'}; + $graphics{'version'} = $graphics{'version-id'} if !$graphics{'version'}; + + undef @xdpyinfo; + #print Data::Dumper::Dumper \%graphics; + if (%graphics){ + my $resolution = ''; + my $server_string = ''; + if ($graphics{'vendor'}){ + my $version = ($graphics{'version'}) ? " $graphics{'version'}" : ''; + $server_string = "$graphics{'vendor'}$version"; + } + elsif ($graphics{'version'}) { + $server_string = "X.org $graphics{'version'}"; + } + if ($graphics{'screens'}){ + my $ref = $graphics{'screens'}; + my @screens = @$ref; + my $sep = ''; + foreach (@screens){ + $resolution .= $sep . $_; + $sep = ', '; + } + } + my @drivers = x_drivers(); + if (!$protocol && !$server_string && !$graphics{'vendor'} && !@drivers){ + $server_string = main::row_defaults('display-server'); + @row = ({ + main::key($num++,'Display') => '', + main::key($num++,'server') => $server_string, + }); + } + else { + $server_string ||= 'N/A'; + # note: if no xorg log, and if wayland, there will be no xorg drivers, + # obviously, so we use the last driver found on the card section in that case. + # those come from lscpi kernel drivers so there should be no xorg/wayland issues. + $driver = ($drivers[0]) ? $drivers[0]: $driver; + @row = ({ + main::key($num++,'Display') => $protocol, + main::key($num++,'server') => $server_string, + main::key($num++,'driver') => $driver, + }); + if ($drivers[2]){ + $row[0]{main::key($num++,'FAILED')} = $drivers[2]; + } + if ($drivers[1]){ + $row[0]{main::key($num++,'unloaded')} = $drivers[1]; + } + if ($extra > 1 && $drivers[3]){ + $row[0]{main::key($num++,'alternate')} = $drivers[3]; + } + if ($graphics{'compositor'}){ + $row[0]{main::key($num++,'compositor')} = $graphics{'compositor'}; + } + } + if ($resolution){ + $row[0]{main::key($num++,'resolution')} = $resolution; + } + else { + $graphics{'tty'} ||= 'N/A'; + $row[0]{main::key($num++,'tty')} = $graphics{'tty'}; + } + } + eval $end if $b_log; + return @row; +} +sub gl_data(){ + eval $start if $b_log; + my $num = 0; + my (@row,$arg); + #print ("$b_display : $b_root\n"); + if ( $b_display){ + if (my $program = main::check_program('glxinfo')){ + # NOTE: glxinfo -B is not always available, unforunately + my @glxinfo = main::grabber("$program $display_opt 2>/dev/null"); + if (!@glxinfo){ + my $type = 'display-console'; + if ($b_root){ + $type = 'display-root-x'; + } + else { + $type = 'display-null'; + } + @row = ({ + main::key($num++,'Message') => main::row_defaults($type), + }); + return @row; + } + #print join "\n",@glxinfo,"\n"; + my $compat_version = ''; + my ($b_compat,@core_profile_version,@direct_render,@renderer,@opengl_version,@working); + foreach (@glxinfo){ + next if /^\s/; + if (/^opengl renderer/i){ + @working = split /:\s*/, $_; + $working[1] = main::cleaner($working[1]); + # Allow all mesas + #if ($working[1] =~ /mesa/i){ + # + #} + push @renderer, $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 + # /opengl version/ && ( f || $2 !~ /mesa/ ) { + elsif (/^opengl version/i){ + # fglrx started appearing with this extra string, does not appear + # to communicate anything of value + @working = split /:\s*/, $_; + $working[1] =~ s/(Compatibility Profile Context|\(Compatibility Profile\))//; + $working[1] =~ s/\s\s/ /g; + $working[1] =~ s/^\s+|\s+$//; + push @opengl_version, $working[1]; + # note: this is going to be off if ever multi opengl versions appear, never seen one + @working = split /\s+/, $working[1]; + $compat_version = $working[0]; + } + elsif (/^opengl core profile version/i){ + # fglrx started appearing with this extra string, does not appear + # to communicate anything of value + @working = split /:\s*/, $_; + $working[1] =~ s/(Compatibility Profile Context|\((Compatibility|Core) Profile\))//; + $working[1] =~ s/\s\s/ /g; + $working[1] =~ s/^\s+|\s+$//; + push @core_profile_version, $working[1]; + } + elsif (/direct rendering/){ + @working = split /:\s*/, $_; + push @direct_render, $working[1]; + } + # if -B was always available, we could skip this, but it is not + elsif (/GLX Visuals/){ + last; + } + } + my ($direct_render,$renderer,$version) = ('N/A','N/A','N/A'); + $direct_render = join ', ', @direct_render if @direct_render; + # non free drivers once filtered and cleaned show the same for core and compat + # but this stopped for some reason at 4.5/4.6 nvidia + if (@core_profile_version && @opengl_version && + join ('', @core_profile_version) ne join( '', @opengl_version) && + !(grep {/nvidia/i} @opengl_version ) ){ + @opengl_version = @core_profile_version; + $b_compat = 1; + } + $version = join ', ', @opengl_version if @opengl_version; + $renderer = join ', ', @renderer if @renderer; + @row = ({ + main::key($num++,'OpenGL') => '', + main::key($num++,'renderer') => $renderer, + main::key($num++,'v') => $version, + }); + + if ($b_compat && $extra > 1 && $compat_version){ + $row[0]{main::key($num++,'compat-v')} = $compat_version; + } + if ($extra > 0){ + $row[0]{main::key($num++,'direct render')} = $direct_render; + } + } + else { + @row = ({ + main::key($num++,'Message') => main::row_defaults('glxinfo-missing'), + }); + } + } + else { + my $type = 'display-console'; + if (!main::check_program('glxinfo')){ + $type = 'glxinfo-missing'; + } + else { + if ($b_root){ + $type = 'display-root'; + } + else { + $type = 'display-try'; + } + } + @row = ({ + main::key($num++,'Message') => main::row_defaults($type), + }); + } + eval $end if $b_log; + return @row; +} +sub tty_data(){ + eval $start if $b_log; + my ($tty); + if ($size{'term-cols'}){ + $tty = "$size{'term-cols'}x$size{'term-lines'}"; + } + elsif ($b_irc && $client{'console-irc'}){ + my $tty_working = main::get_tty_console_irc('tty'); + if (my $program = main::check_program('stty')){ + my $tty_arg = ($bsd_type) ? '-f' : '-F'; + $tty = (main::grabber("$program $tty_arg /dev/pts/$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; +} +sub x_drivers { + eval $start if $b_log; + my ($driver,@driver_data,,%drivers); + my ($alternate,$failed,$loaded,$sep,$unloaded) = ('','','','',''); + if (my $log = main::system_files('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"; + my @xorg = main::reader($log); + # list is from sgfxi plus non-free drivers, plus ARM drivers + my $list = 'amdgpu|apm|ark|armsoc|ati|chips|cirrus|cyrix|fbdev|fbturbo|fglrx|glint|'; + $list .= 'i128|i740|i810|iftv|imstt|intel|ivtv|mach64|mesa|mga|modesetting|'; + $list .= 'neomagic|newport|nouveau|nsc|nvidia|nv|openchrome|radeonhd|radeon|'; + $list .= 'rendition|s3virge|s3|savage|siliconmotion|sisimedia|sisusb|sis|tdfx|'; + $list .= 'tga|trident|tseng|unichrome|v4l|vboxvideo|vesa|vga|via|vmware|voodoo'; + # it's much cheaper to grab the simple pattern match then do the expensive one + # in the main loop. + #@xorg = grep {/Failed|Unload|Loading/} @xorg; + foreach (@xorg){ + next if !/Failed|Unload|Loading/; + # print "$_\n"; + # note that in file names, driver is always lower case + if (/\sLoading.*($list)_drv.so$/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 (/(Unloading\s|UnloadModule).*\"?($list)(_drv.so)?\"?$/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 (/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 ( $_ !~ /\(already loaded/){ + $drivers{$driver}='failed'; + } + # reset the previous line's 'unloaded' to 'loaded' as well + else { + $drivers{$driver}='loaded'; + } + } + elsif ($_ =~ /module does not exist/){ + $drivers{$driver}='alternate'; + } + } + } + my $sep = ''; + foreach (sort keys %drivers){ + if ($drivers{$_} eq 'loaded') { + $sep = ($loaded) ? ',' : ''; + $loaded .= $sep . $_; + } + elsif ($drivers{$_} eq 'unloaded') { + $sep = ($unloaded) ? ',' : ''; + $unloaded .= $sep . $_; + } + elsif ($drivers{$_} eq 'failed') { + $sep = ($failed) ? ',' : ''; + $failed .= $sep . $_; + } + elsif ($drivers{$_} eq 'alternate') { + $sep = ($alternate) ? ',' : ''; + $alternate .= $sep . $_; + } + } + $loaded ||= 'none'; + @driver_data = ($loaded,$unloaded,$failed,$alternate); + } + eval $end if $b_log; + return @driver_data; +} +sub x_version { + eval $start if $b_log; + my ($version,@data,$program); + # IMPORTANT: both commands send version data to stderr! + if ($program = main::check_program('Xorg')){ + @data = main::grabber("$program -version 2>&1"); + } + elsif ($program = main::check_program('X')){ + @data = main::grabber("$program -version 2>&1"); + } + #print Data::Dumper::Dumper \@data; + if (@data){ + foreach (@data){ + if (/^X.org X server/i){ + my @working = split /\s+/, $_; + $version = $working[3]; + last; + } + elsif (/^X Window System Version/i) { + my @working = split /\s+/, $_; + $version = $working[4]; + last; + } + } + } + eval $end if $b_log; + return $version; +} +# $1 - protocol: wayland|x11 +sub display_compositor { + eval $start if $b_log; + my ($protocol) = @_; + my ($compositor) = (''); + main::set_ps_gui() if ! $b_ps_gui; + if (@ps_gui){ + # 1 check program; 2 search; 3 unused version; 4 print + my @compositors = ( + ['budgie-wm','budgie-wm','','budgie-wm'], + ['compton','compton','','compton'], + ['enlightenment','enlightenment','','enlightenment'], + ['gnome-shell','gnome-shell','','gnome-shell'], + ['kwin_wayland','kwin_wayland','','kwin wayland'], + ['kwin_x11','kwin_x11','','kwin x11'], + #['kwin','kwin','','kwin'], + ['marco','marco','','marco'], + ['muffin','muffin','','muffin'], + ['mutter','mutter','','mutter'], + ['weston','weston','','weston'], + # owned by: compiz-core in debian + ['compiz','compiz','','compiz'], + # did not find follwing in debian apt + ['3dwm','3dwm','','3dwm'], + ['dwc','dwc','','dwc'], + ['grefson','grefson','','grefson'], + ['ireplace','ireplace','','ireplace'], + ['kmscon','kmscon','','kmscon'], + ['metisse','metisse','','metisse'], + ['mir','mir','','mir'], + ['moblin','moblin','','moblin'], + ['rustland','rustland','','rustland'], + ['sway','sway','','sway'], + ['swc','swc','','swc'], + ['unagi','unagi','','unagi'], + ['wayhouse','wayhouse','','wayhouse'], + ['westford','westford','','westford'], + ['xcompmgr','xcompmgr','','xcompmgr'], + ); + foreach my $ref (@compositors){ + my @item = @$ref; + # no need to use check program with short list of ps_gui + # if (main::check_program($item[0]) && (grep {/^$item[1]$/} @ps_gui ) ){ + if (grep {/^$item[1]$/} @ps_gui){ + $compositor = $item[3]; + last; + } + } + } + main::log_data('data',"compositor: $compositor") if $b_log; + eval $end if $b_log; + return $compositor; +} +} + +## MachineData +{ +package MachineData; + +sub get { + eval $start if $b_log; + my (%soc_machine,@data,@rows,$key1,$val1,$which); + my $num = 0; + if ($bsd_type && @sysctl_machine && !$b_dmidecode_force ){ + @data = machine_data_sysctl(); + if (!@data && !$key1){ + $key1 = 'Message'; + $val1 = main::row_defaults('machine-data-force-dmidecode',''); + } + } + elsif ($bsd_type || $b_dmidecode_force){ + my $ref = $alerts{'dmidecode'}; + if ( $$ref{'action'} ne 'use'){ + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + $key1 = ucfirst($key1); + } + else { + @data = machine_data_dmi(); + if (!@data && !$key1){ + $key1 = 'Message'; + $val1 = main::row_defaults('machine-data',''); + } + } + } + elsif (-d '/sys/class/dmi/id/') { + @data = machine_data_sys(); + if (!@data){ + $key1 = 'Message'; + $val1 = main::row_defaults('machine-data-dmidecode',''); + } + } + elsif (!$bsd_type) { + # this uses /proc/cpuinfo so only GNU/Linux + if ($b_arm || $b_mips){ + %soc_machine = machine_data_soc(); + @data = create_output_soc(%soc_machine) if %soc_machine; + } + if (!@data){ + $key1 = 'Message'; + $val1 = main::row_defaults('machine-data-force-dmidecode',''); + } + } + # if error case, null data, whatever + if ($key1) { + @data = ({main::key($num++,$key1) => $val1,}); + } + eval $end if $b_log; + return @data; +} +## 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 create_output { + eval $start if $b_log; + my ($ref) = @_; + my (%data,@row,@rows); + %data = %$ref; + 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; + } + # 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{'sys_vendor'} eq $data{'board_vendor'} ) && + ( ($data{'product_version'} && $data{'product_version'} eq $data{'board_version'} ) || + (!$data{'product_version'} && $data{'product_name'} eq $data{'board_name'} ) ) ){ + $b_skip_system = 1; + } + } + $data{'device'} ||= 'N/A'; + $j = scalar @rows; + @row = ({ + main::key($num++,'Type') => ucfirst($data{'device'}), + },); + @rows = (@rows,@row); + if (!$b_skip_system){ + # this has already been tested for above so we know it's not null + $system_vendor = main::cleaner($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::apply_filter($data{'product_serial'}); + $rows[$j]{main::key($num++,'System')} = $system_vendor; + $rows[$j]{main::key($num++,'product')} = $product_name; + $rows[$j]{main::key($num++,'v')} = $product_version; + $rows[$j]{main::key($num++,'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'} 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::apply_filter($data{'chassis_serial'}); + $chassis_vendor ||= ''; + $chassis_type ||= ''; + $rows[$j]{main::key($num++,'Chassis')} = $chassis_vendor; + if ($chassis_type){ + $rows[$j]{main::key($num++,'type')} = $chassis_type; + } + if ($chassis_version){ + $rows[$j]{main::key($num++,'v')} = $chassis_version; + } + $rows[$j]{main::key($num++,'serial')} = $chassis_serial; + } + } + $j++; # start new row + } + if ($data{'firmware'}){ + $firmware = $data{'firmware'}; + } + $mobo_vendor = ($data{'board_vendor'}) ? main::cleaner($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::apply_filter($data{'board_serial'}); + $bios_vendor = ($data{'bios_vendor'}) ? main::cleaner($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++,'Mobo')} = $mobo_vendor; + $rows[$j]{main::key($num++,'model')} = $mobo_model; + if ($mobo_version){ + $rows[$j]{main::key($num++,'v')} = $mobo_version; + } + $rows[$j]{main::key($num++,'serial')} = $mobo_serial; + if ($extra > 2 && $data{'board_uuid'}){ + $rows[$j]{main::key($num++,'uuid')} = $data{'board_uuid'}; + } + $rows[$j]{main::key($num++,$firmware)} = $bios_vendor; + $rows[$j]{main::key($num++,'v')} = $bios_version; + if ($bios_rev){ + $rows[$j]{main::key($num++,'rev')} = $bios_rev; + } + $rows[$j]{main::key($num++,'date')} = $bios_date; + if ($bios_romsize){ + $rows[$j]{main::key($num++,'rom size')} = $bios_romsize; + } + eval $end if $b_log; + return @rows; +} +sub create_output_soc { + my (%data,@row,@rows); + my (%soc_machine) = @_; + my $num = 0; + my $j = 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'}){ + my $key = ($b_arm) ? 'ARM Device': 'MIPS Device'; + $rows[$j]{main::key($num++,'Type')} = $key; + my $system = 'System'; + if (defined $soc_machine{'model'}){ + $rows[$j]{main::key($num++,'System')} = $soc_machine{'model'}; + $system = 'details'; + } + my $device = $soc_machine{'device'}; + $device ||= 'N/A'; + $rows[$j]{main::key($num++,$system)} = $device; + } + # 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++,'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++,'serial')} = main::apply_filter($soc_machine{'serial'}); + } + eval $end if $b_log; + return @rows; +} + +sub machine_data_sys { + eval $start if $b_log; + my (%data,$path,$vm); + 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::dmi_cleaner($data{$_}) : ''; + } + elsif (!$b_root && -e $path && !-r $path ){ + $data{$_} = main::row_defaults('root-required'); + } + else { + $data{$_} = ''; + } + } + if ($data{'chassis_type'}){ + if ( $data{'chassis_type'} == 1){ + $data{'device'} = get_device_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"; +# } + main::log_data('dump','%data',\%data) if $b_log; + my @rows = create_output(\%data); + eval $end if $b_log; + return @rows; +} +# 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 (%soc_machine,@temp); + if (my $file = main::system_files('cpuinfo')){ + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-shevaplug-1.2ghz.txt"; + my @data = main::reader($file); + foreach (@data){ + if (/^(Hardware|machine)\s*:/i){ + @temp = split /\s*:\s*/, $_; + $temp[1] = main::arm_cleaner($temp[1]); + $temp[1] = main::dmi_cleaner($temp[1]); + $soc_machine{'device'} = main::cleaner($temp[1]); + } + elsif (/^(system type)\s*:/i){ + @temp = split /\s*:\s*/, $_; + $temp[1] = main::dmi_cleaner($temp[1]); + $soc_machine{'model'} = main::cleaner($temp[1]); + } + elsif (/^Revision/i){ + @temp = split /\s*:\s*/, $_; + $soc_machine{'firmware'} = $temp[1]; + } + elsif (/^Serial/i){ + @temp = split /\s*:\s*/, $_; + $soc_machine{'serial'} = $temp[1]; + } + } + } + if (!$soc_machine{'model'} && -f '/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::dmi_cleaner($model); + $model = (split /\x01|\x02|\x03|\x00/, $model)[0] if $model; + # idea was to use only first part of string, but now try using all + #my (@result) = (); + #@result = split(/\s+/, $soc_machine{'device'}) if $soc_machine{'device'}; + if ( !$soc_machine{'device'} || ($model && $model !~ /$soc_machine{'device'}/i) ){ + $model = main::arm_cleaner($model); + $soc_machine{'model'} = $model; + } + } + } + if (!$soc_machine{'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; + $soc_machine{'serial'} = $serial if $serial; + } + #print Data::Dumper::Dumper \%soc_machine; + eval $end if $b_log; + return %soc_machine; +} + +# 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; + my (%data,$vm); + return if ! @dmi; + $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 (@dmi){ + my @ref = @$_; + # bios/firmware + if ($ref[0] == 0){ + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + foreach my $item (@ref){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split /:\s+/, $item; + if ($value[0] eq 'Release Date') {$data{'bios_date'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Vendor') {$data{'bios_vendor'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Version') {$data{'bios_version'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'ROM Size') {$data{'bios_romsize'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'BIOS Revision') {$data{'bios_rev'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] =~ /^UEFI is supported/) {$data{'firmware'} = 'UEFI';} + } + } + next; + } + # system information + elsif ($ref[0] == 1){ + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + foreach my $item (@ref){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split /:\s+/, $item; + if ($value[0] eq 'Product Name') {$data{'product_name'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Version') {$data{'product_version'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Serial Number') {$data{'product_serial'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Manufacturer') {$data{'sys_vendor'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'UUID') {$data{'sys_uuid'} = main::dmi_cleaner($value[1]) } + } + } + next; + } + # baseboard information + elsif ($ref[0] == 2){ + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + foreach my $item (@ref){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split /:\s+/, $item; + if ($value[0] eq 'Product Name') {$data{'board_name'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Serial Number') {$data{'board_serial'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Manufacturer') {$data{'board_vendor'} = main::dmi_cleaner($value[1]) } + } + } + next; + } + # chassis information + elsif ($ref[0] == 3){ + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + foreach my $item (@ref){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split /:\s+/, $item; + if ($value[0] eq 'Serial Number') {$data{'chassis_serial'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Type') {$data{'chassis_type'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Manufacturer') {$data{'chassis_vendor'} = main::dmi_cleaner($value[1]) } + elsif ($value[0] eq 'Version') {$data{'chassis_version'} = main::dmi_cleaner($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 ($ref[0] == 4){ + # skip first three row, we don't need that data + splice @ref, 0, 3 if @ref; + if (!$data{'device'}){ + if (grep {/hypervisor/i} @ref){ + $data{'device'} = 'virtual-machine'; + } + } + last; + } + elsif ($ref[0] > 4){ + last; + } + } + if (!$data{'device'}){ + $data{'device'} = get_device_vm($data{'sys_vendor'},$data{'product_name'}); + $data{'device'} ||= 'other-vm?'; + } +# print "dmi:\n"; +# foreach (keys %data){ +# print "$_: $data{$_}\n"; +# } + main::log_data('dump','%data',\%data) if $b_log; + my @rows = create_output(\%data); + eval $end if $b_log; + return @rows; +} +# 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 (%data,$vm); + # ^hw\.(vendor|product|version|serialno|uuid) + foreach (@sysctl_machine){ + next if ! $_; + my @item = split /:/, $_; + next if ! $item[1]; + if ($item[0] eq 'hw.vendor'){ + $data{'board_vendor'} = main::dmi_cleaner($item[1]); + } + elsif ($item[0] eq 'hw.product'){ + $data{'board_name'} = main::dmi_cleaner($item[1]); + } + elsif ($item[0] eq 'hw.version'){ + $data{'board_version'} = $item[1]; + } + elsif ($item[0] eq 'hw.serialno'){ + $data{'board_serial'} = $item[1]; + } + elsif ($item[0] eq 'hw.serial'){ + $data{'board_serial'} = $item[1]; + } + elsif ($item[0] eq 'hw.uuid'){ + $data{'board_uuid'} = $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"?([\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; + } + } + } + my @rows = create_output(\%data); + eval $end if $b_log; + return @rows; +} + +sub get_device_sys { + eval $start if $b_log; + my ($chasis_id) = @_; + my ($device) = (''); + my @chassis; + # https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.8.0.pdf + $chassis[2] = 'unknown'; + # note: 13 is all-in-one which we take as a mac type system + $chassis[3] = 'desktop'; + $chassis[4] = 'desktop'; + $chassis[6] = 'desktop'; + $chassis[7] = 'desktop'; + $chassis[13] = 'desktop'; + $chassis[15] = 'desktop'; + $chassis[24] = 'desktop'; + # 5 - pizza box was a 1 U desktop enclosure, but some old laptops also id this way + $chassis[5] = 'pizza-box'; + $chassis[9] = 'laptop'; + # note: lenovo T420 shows as 10, notebook, but it's not a notebook + $chassis[10] = 'laptop'; + $chassis[16] = 'laptop'; + $chassis[14] = 'notebook'; + $chassis[8] = 'portable'; + $chassis[11] = 'portable'; + $chassis[17] = 'server'; + $chassis[23] = 'server'; + $chassis[25] = 'server'; + $chassis[27] = 'blade'; + $chassis[25] = 'blade'; + $chassis[29] = 'blade'; + $chassis[12] = 'docking-station'; + $chassis[18] = 'expansion-chassis'; + $chassis[19] = 'sub-chassis'; + $chassis[20] = 'bus-expansion'; + $chassis[21] = 'peripheral'; + $chassis[22] = 'RAID'; + $chassis[26] = 'compact-PCI'; + $device = $chassis[$chasis_id] if $chassis[$chasis_id]; + eval $end if $b_log; + return $device; +} + +sub get_device_vm { + eval $start if $b_log; + my ($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/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' ) { + my @vm_data = (@pci,@sysctl,@dmesg_boot); + if (-e '/dev/disk/by-id'){ + my @dev = glob('/dev/disk/by-id/*'); + @vm_data = (@vm_data,@dev); + } + if ( grep {/innotek|vbox|virtualbox/i} @vm_data){ + $vm = 'virtualbox'; + } + elsif (grep {/vmware/i} @vm_data){ + $vm = 'vmware'; + } + elsif (grep {/Virtual HD/i} @vm_data){ + $vm = 'hyper-v'; + } + if (!$vm && (my $file = main::system_files('cpuinfo'))){ + my @info = main::reader($file); + $vm = 'virtual-machine' if grep {/^flags.*hypervisor/} @info; + } + 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'; + } + eval $end if $b_log; + return $vm; +} + +} + +## NetworkData +{ +package NetworkData; +my ($b_ip_run,@ifs_found); +sub get { + eval $start if $b_log; + my (@data,@rows); + my $num = 0; + if (($b_arm || $b_mips) && !$b_soc_net && !$b_pci_tool){ + # do nothing, but keep the test conditions to force + # the non arm case to always run + } + else { + @data = card_data(); + @rows = (@rows,@data) if @data; + } + @data = usb_data(); + @rows = (@rows,@data) if @data; + # note: rasberry pi uses usb networking only + if (!@rows && ($b_arm || $b_mips)){ + my $key = ($b_arm) ? 'ARM' : 'MIPS'; + @data = ({ + main::key($num++,$key) => main::row_defaults(lc($key) . '-pci',''), + },); + @rows = (@rows,@data); + } + if ($show{'network-advanced'}){ + # @ifs_found = (); + # shift @ifs_found; + # pop @ifs_found; + if (!$bsd_type){ + @data = advanced_data_sys('check','',0,'',''); + @rows = (@rows,@data) if @data; + } + else { + @data = advanced_data_bsd('check'); + @rows = (@rows,@data) if @data; + } + } + if ($show{'ip'}){ + @data = wan_ip(); + @rows = (@rows,@data); + } + eval $end if $b_log; + return @rows; +} +# 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 (bsds) +sub card_data { + eval $start if $b_log; + my ($b_wifi,@rows,@data,%holder); + my ($j,$num) = (0,1); + foreach (@pci){ + $num = 1; + my @row = @$_; + #print "$row[0] $row[3]\n"; + # NOTE: class 06 subclass 80 + # https://www-s.acm.illinois.edu/sigops/2007/roll_your_own/7.c.1.html + if (($row[0] && $row[0] =~ /^(eth|ethernet|ethernet-phy|network|wifi|wlan)$/ )|| ($row[1] && $row[1] eq '0680' ) ){ + #print "$row[0] $row[3]\n"; + $j = scalar @rows; + my $driver = $row[9]; + my $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 $card = $row[4]; + $card = ($card) ? main::pci_cleaner($card,'output') : 'N/A'; + #$card ||= 'N/A'; + $driver ||= 'N/A'; + @data = ({ + main::key($num++,'Card') => $card, + },); + @rows = (@rows,@data); + #if ($extra > 2 && $b_pci_tool && $row[11]){ + # my $item = main::get_pci_vendor($row[4],$row[11]); + # $rows[$j]{main::key($num++,'model')} = $item if $item; + #} + if ($row[1] eq '0680'){ + $rows[$j]{main::key($num++,'type')} = 'network bridge'; + } + $rows[$j]{main::key($num++,'driver')} = $driver; + if ($extra > 0){ + if ($row[9] && !$bsd_type){ + my $version = main::get_module_version($row[9]); + $version ||= 'N/A'; + $rows[$j]{main::key($num++,'v')} = $version; + } + $row[8] ||= 'N/A'; + # as far as I know, wifi has no port, but in case it does in future, use it + $rows[$j]{main::key($num++,'port')} = $row[8] if (!$b_wifi || ( $b_wifi && $row[8] ne 'N/A') ); + my $bus_id = 'N/A'; + # note: for arm/mips we want to see the single item bus id, why not? + if ($row[2] && $row[3]){$bus_id = "$row[2].$row[3]"} + elsif ($row[2]){$bus_id = $row[2]} + elsif ($row[3]){$bus_id = $row[3]} + $rows[$j]{main::key($num++,'bus ID')} = $bus_id; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = $chip_id; + } + if ($show{'network-advanced'}){ + if (!$bsd_type){ + @data = advanced_data_sys($row[5],$row[6],$holder{$chip_id},$b_wifi,''); + } + else { + @data = advanced_data_bsd("$row[9]$row[11]",$b_wifi); + } + @rows = (@rows,@data); + } + } + #print "$row[0]\n"; + } + # @rows = (); + # we want to handle ARM errors in main get + if (!@rows && !$b_arm){ + my $key = 'Message'; + @data = ({ + main::key($num++,$key) => main::row_defaults('pci-card-data',''), + },); + @rows = (@rows,@data); + + } + #my $ref = $pci[-1]; + #print $$ref[0],"\n"; + eval $end if $b_log; + return @rows; +} +sub usb_data { + eval $start if $b_log; + my (@data,@rows,@temp2,$b_wifi,$driver,$path,$product,$product2,$test,$vendor,$vendor2); + my ($j,$num) = (0,1); + return if !@usb; + foreach my $ref (@usb){ + my @row = @$ref; + # a device will always be the second or > device on the bus + if ($row[1] > 1){ + $num = 1; + ($product,$product2,$test,$vendor,$vendor2) = ('','','','',''); + if ($usb_level == 1){ + $product = main::cleaner($row[3]); + } + else { + foreach my $line (@row){ + my @working = split /:/, $line; + if ($working[0] eq 'idVendor' && $working[2]){ + $vendor = main::cleaner($working[2]); + } + if ($working[0] eq 'idProduct' && $working[2]){ + $product = main::cleaner($working[2]); + } + if ($working[0] eq 'iVendor' && $working[2]){ + $product2 = main::cleaner($working[2]); + } + if ($working[0] eq 'iProduct' && $working[2]){ + $product2 = main::cleaner($working[2]); + } + if ($working[0] eq 'Descriptor_Configuration'){ + last; + } + } + if ($vendor && $product){ + $product = ($product =~ /$vendor/) ? $product: "$vendor $product"; + } + elsif ($vendor && $product2){ + $product = ($product2 =~ /$vendor/) ? $product2: "$vendor $product2"; + } + elsif ($vendor2 && $product){ + $product = ($product =~ /$vendor2/) ? $product: "$vendor2 $product"; + } + elsif ($vendor2 && $product2){ + $product = ($product2 =~ /$vendor2/) ? $product2: "$vendor2 $product2"; + } + elsif ($vendor){ + $product = $vendor; + } + elsif ($vendor2){ + $product = $vendor2; + } + $test = "$vendor $product $vendor2 $vendor2"; + } + if ($product && network_device($test)){ + @temp2 = main::get_usb_drivers($row[0],$row[2]) if !$bsd_type && -d "/sys/devices"; + if (@temp2){ + $driver = $temp2[0] if $temp2[0]; + $path = $temp2[1] if $temp2[1]; + } + $driver ||= 'usb-network'; + @data = ({ + main::key($num++,'Card') => $product, + main::key($num++,'type') => 'USB', + main::key($num++,'driver') => $driver, + },); + $b_wifi = check_wifi($product); + @rows = (@rows,@data); + if ($extra > 0){ + $rows[$j]{main::key($num++,'bus ID')} = "$row[0]:$row[1]"; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = $row[2]; + } + if ($show{'network-advanced'}){ + if (!$bsd_type){ + my (@temp,$vendor,$chip); + @temp = split (/:/, $row[2]) if $row[2]; + ($vendor,$chip) = ($temp[0],$temp[1]) if @temp; + @data = advanced_data_sys($vendor,$chip,0,$b_wifi,$path); + } + # NOTE: we need the driver.number, like wlp0 to get a match, and + # we can't get that from usb data, so we have to let it fall back down + # to the check function for BSDs. + #else { + # @data = advanced_data_bsd($row[2],$b_wifi); + #} + @rows = (@rows,@data) if @data; + } + $j = scalar @rows; + } + } + } + eval $end if $b_log; + return @rows; +} +sub advanced_data_sys { + eval $start if $b_log; + return if ! -d '/sys/class/net'; + my ($vendor,$chip,$count,$b_wifi,$path_usb) = @_; + my $num = 0; + my $key = 'IF'; + my ($b_check,$b_usb,$if,$path,@paths,@row,@rows); + # 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; + if ( $count > 0 && $count < scalar @paths ){ + @paths = splice @paths, $count, scalar @paths; + } + if ($vendor eq 'check'){ + $b_check = 1; + $key = 'IF-ID'; + } + #print join '; ', @paths, $count, "\n"; + foreach (@paths){ + my ($data1,$data2,$duplex,$mac,$speed,$state); + # for usb, we already know where we are + if (!$b_usb){ + if (!$b_arm || $b_pci_tool ){ + $path = "$_/device/vendor"; + $data1 = (main::reader($path))[0] if -e $path; + $data1 =~ s/^0x// if $data1; + $path = "$_/device/device"; + $data2 = (main::reader($path))[0] if -e $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; + } + elsif ($b_arm) { + $path = Cwd::abs_path($_); + $path =~ /($chip)/; + if ($1){ + $data1 = $vendor; + $data2 = $chip; + } + } + } + #print "d1:$data1 v:$vendor d2:$data2 c:$chip\n"; + if ( $b_usb || $b_check || ( $data1 && $data2 && $data1 eq $vendor && $data2 eq $chip )) { + $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 -e $path; + $duplex ||= 'N/A'; + $path = "$_/address"; + $mac = (main::reader($path))[0] if -e $path; + $mac = main::apply_filter($mac); + $path = "$_/speed"; + $speed = (main::reader($path))[0] if -e $path; + $speed ||= 'N/A'; + $path = "$_/operstate"; + $state = (main::reader($path))[0] if -e $path; + $state ||= 'N/A'; + #print "$speed \n"; + @row = ({ + main::key($num++,$key) => $if, + main::key($num++,'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 : nont 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 = ($speed =~ /^[0-9]+$/) ? "$speed Mbps" : $speed; + $row[0]{main::key($num++,'speed')} = $speed; + $row[0]{main::key($num++,'duplex')} = $duplex; + } + $row[0]{main::key($num++,'mac')} = $mac; + if ($b_check){ + @rows = (@rows,@row); + } + else { + @rows = @row; + } + if ($show{'ip'}){ + @row = if_ip($if); + @rows = (@rows,@row); + } + last if !$b_check; + } + } + eval $end if $b_log; + return @rows; +} +sub advanced_data_bsd { + eval $start if $b_log; + return if ! @ifs_bsd; + my ($if,$b_wifi) = @_; + my (@data,@row,@rows,$working_if); + my ($b_check,$state,$speed,$duplex,$mac); + my $num = 0; + my $key = 'IF'; + my $j = 0; + if ($if eq 'check'){ + $b_check = 1; + $key = 'IF-ID'; + } + foreach my $ref (@ifs_bsd){ + if (ref $ref ne 'ARRAY'){ + $working_if = $ref; + # print "$working_if\n"; + next; + } + else { + @data = @$ref; + } + if ( $b_check || $working_if eq $if){ + $if = $working_if if $b_check; + # print "top: if: $if ifs: @ifs_found\n"; + next if ($b_check && grep {/$if/} @ifs_found); + foreach my $line (@data){ + # ($state,$speed,$duplex,$mac) + $duplex = $data[2]; + $duplex ||= 'N/A'; + $mac = main::apply_filter($data[3]); + $speed = $data[1]; + $speed ||= 'N/A'; + $state = $data[0]; + $state ||= 'N/A'; + #print "$speed \n"; + @row = ({ + main::key($num++,$key) => $if, + main::key($num++,'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'){ + # make sure the value is strictly numeric before appending Mbps + $speed = ($speed =~ /^[0-9]+$/) ? "$speed Mbps" : $speed; + $row[0]{main::key($num++,'speed')} = $speed; + $row[0]{main::key($num++,'duplex')} = $duplex; + } + $row[0]{main::key($num++,'mac')} = $mac; + } + @rows = (@rows,@row); + if ($show{'ip'}){ + @row = if_ip($if) if $if; + @rows = (@rows,@row) if @row; + } + } + } + eval $end if $b_log; + return @rows; +} +## 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 ($if) = @_; + my (@data,@row,@rows,$working_if); + my $num = 0; + my $j = 0; + $b_ip_run = 1; + OUTER: + foreach my $ref (@ifs){ + if (ref $ref ne 'ARRAY'){ + $working_if = $ref; + # print "if:$if wif:$working_if\n"; + next; + } + else { + @data = @$ref; + # print "ref:$ref\n"; + } + if ($working_if eq $if){ + foreach my $ref2 (@data){ + $j = scalar @rows; + $num = 1; + if ($limit > 0 && $j >= $limit){ + @row = ({ + main::key($num++,'Message') => main::row_defaults('output-limit',scalar @data), + },); + @rows = (@rows,@row); + last OUTER; + } + my @data2 = @$ref2; + #print "$data2[0] $data2[1]\n"; + my ($ipv,$ip,$broadcast,$scope,$scope_id); + $ipv = ($data2[0])? $data2[0]: 'N/A'; + $ip = main::apply_filter($data2[1]); + $scope = ($data2[3])? $data2[3]: 'N/A'; + 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' ; + @row = ({ + main::key($num++,"IP v$ipv") => $ip, + main::key($num++,$key) => $data2[4], + main::key($num++,'scope') => $scope, + },); + } + else { + @row = ({ + main::key($num++,"IP v$ipv") => $ip, + main::key($num++,'scope') => $scope, + },); + } + } + else { + @row = ({ + main::key($num++,'IF') => $if, + main::key($num++,"IP v$ipv") => $ip, + main::key($num++,'scope') => $scope, + },); + } + @rows = (@rows,@row); + if ($extra > 1 && $data2[2]){ + $broadcast = main::apply_filter($data2[2]); + $rows[$j]{main::key($num++,'broadcast')} = $broadcast; + } + } + } + } + eval $end if $b_log; + return @rows; +} +# 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 (@data,$ip); + my $num = 0; + # time: 0.06 - 0.07 seconds + if (my $program = main::check_program('dig')){ + $ip = (main::grabber("$program +short +time=1 +tries=1 myip.opendns.com \@resolver1.opendns.com 2>/dev/null"))[0]; + } + else { + # note: tests: akamai: 0.055 - 0.065 icanhazip.com: 0.177 0.164 + # smxi: 0.525, so almost 10x slower. Dig is fast too + # leaving smxi as last test because I know it will always be up. + my @urls = qw( http://whatismyip.akamai.com/ http://icanhazip.com/ https://smxi.org/opt/ip.php); + foreach (@urls){ + $ip = main::download_file('stdout',$_); + if ($ip){ + # print "$_\n"; + chomp $ip; + $ip = (split /\s+/, $ip)[-1]; + last; + } + } + } + if ($ip && $show{'filter'}){ + $ip = $filter_string; + } + $ip ||= main::row_defaults('IP', 'WAN IP'); + @data = ({ + main::key($num++,'WAN IP') => $ip, + },); + eval $end if $b_log; + return @data; +} + +### 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 + +# Wi-Fi.*Adapter Wireless.*Adapter Ethernet.*Adapter WLAN.*Adapter +# Network.*Adapter 802\.11 Atheros Atmel D-Link.*Adapter D-Link.*Wireless Linksys +# Netgea Ralink Realtek.*Network Realtek.*Wireless Realtek.*WLAN Belkin.*Wireless +# Belkin.*WLAN Belkin.*Network Actiontec.*Wireless Actiontec.*Network AirLink.*Wireless +# Asus.*Network Asus.*Wireless Buffalo.*Wireless Davicom DWA-.*RangeBooster DWA-.*Wireless +# ENUWI-.*Wireless LG.*Wi-Fi Rosewill.*Wireless RNX-.*Wireless Samsung.*LinkStick +# Samsung.*Wireless Sony.*Wireless TEW-.*Wireless TP-Link.*Wireless +# WG[0-9][0-9][0-9].*Wireless WNA[0-9][0-9][0-9] WNDA[0-9][0-9][0-9] +# Zonet.*ZEW.*Wireless +sub network_device { + eval $start if $b_log; + my ($device_string) = @_; + my ($b_network); + # belkin=050d; d-link=07d1; netgear=0846; ralink=148f; realtek=0bda; + # Atmel makes other stuff + my @tests = qw(wifi Wi-Fi.*Adapter Ethernet \bLAN\b WLAN Network 802\.11 + Wireless.*Adapter 54\sMbps Network 100\/1000 Mobile\sBroadband Atheros D-Link.*Adapter + Dell.*Wireless D-Link.*Wireless Linksys Netgea Ralink Realtek.*Network Realtek.*Wireless + Belkin.*Wireless Actiontec.*Wireless AirLink.*Wireless Asus.*Wireless + Buffalo.*Wireless Davicom DWA-.*RangeBooster DWA-.*Wireless + ENUWI-.*Wireless LG.*Wi-Fi Rosewill.*Wireless RNX-.*Wireless Samsung.*LinkStick + Samsung.*Wireless Sony.*Wireless TEW-.*Wireless TP-Link.*Wireless + WG[0-9][0-9][0-9].*Wireless WNA[0-9][0-9][0-9] WNDA[0-9][0-9][0-9] + Zonet.*ZEW.*Wireless 050d:935b 0bda:8189 0bda:8197 + ); + foreach (@tests){ + if ($device_string =~ /$_/i ){ + $b_network = 1; + last; + } + } + eval $end if $b_log; + return $b_network; +} +sub check_wifi { + my ($item) = @_; + my $b_wifi = ($item =~ /wireless|wifi|wi-fi|wlan|802\.11|centrino/i) ? 1 : 0; + return $b_wifi; +} +} + +## OpticalData +{ +package OpticalData; + +sub get { + eval $start if $b_log; + my (@data,@rows,$key1,$val1); + my $num = 0; + if ($bsd_type){ + #@data = optical_data_bsd(); + $key1 = 'Optical Report'; + $val1 = main::row_defaults('optical-data-bsd'); + @data = ({main::key($num++,$key1) => $val1,}); + if ( @dm_boot_optical){ + @data = optical_data_bsd(); + } + else{ + my $file = main::system_files('dmesg-boot'); + if ( $file && ! -r $file ){ + $val1 = main::row_defaults('dmesg-boot-permissions'); + } + elsif (!$file){ + $val1 = main::row_defaults('dmesg-boot-missing'); + } + else { + $val1 = main::row_defaults('optical-data-bsd'); + } + $key1 = 'Optical Report'; + @data = ({main::key($num++,$key1) => $val1,}); + } + } + else { + @data = optical_data_linux(); + } + if (!@data){ + $key1 = 'Message'; + $val1 = main::row_defaults('optical-data'); + @data = ({main::key($num++,$key1) => $val1,}); + } + @rows = (@rows,@data); + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my (%devices) = @_; + my (@data,@rows); + my $num = 0; + my $j = 0; + # build floppy if any + foreach my $key (sort keys %devices){ + if ($devices{$key}{'type'} eq 'floppy'){ + @data = ({ main::key($num++,ucfirst($devices{$key}{'type'})) => "/dev/$key"}); + @rows = (@rows,@data); + delete $devices{$key}; + } + } + foreach my $key (sort keys %devices){ + $j = scalar @rows; + $num = 1; + my $vendor = $devices{$key}{'vendor'}; + $vendor ||= 'N/A'; + my $model = $devices{$key}{'model'}; + $model ||= 'N/A'; + @data = ({ + main::key($num++,ucfirst($devices{$key}{'type'})) => "/dev/$key", + main::key($num++,'vendor') => $vendor, + main::key($num++,'model') => $model, + }); + @rows = (@rows,@data); + if ($extra > 0){ + my $rev = $devices{$key}{'rev'}; + $rev ||= 'N/A'; + $rows[$j]{ main::key($num++,'rev')} = $rev; + } + if ($extra > 1 && $devices{$key}{'serial'}){ + $rows[$j]{ main::key($num++,'serial')} = main::apply_filter($devices{$key}{'serial'}); + } + my $ref = $devices{$key}{'links'}; + my $links = (@$ref) ? join ',', sort @$ref: 'N/A' ; + $rows[$j]{ main::key($num++,'dev-links')} = $links; + if ($show{'optical'}){ + $j = scalar @rows; + my $speed = $devices{$key}{'speed'}; + $speed ||= 'N/A'; + my ($audio,$multisession) = ('',''); + if (defined $devices{$key}{'multisession'}){ + $multisession = ( $devices{$key}{'multisession'} == 1 ) ? 'yes' : 'no' ; + } + $multisession ||= 'N/A'; + if (defined $devices{$key}{'audio'}){ + $audio = ( $devices{$key}{'audio'} == 1 ) ? 'yes' : 'no' ; + } + $audio ||= 'N/A'; + my $dvd = 'N/A'; + my (@rw,$rws); + if (defined $devices{$key}{'dvd'}){ + $dvd = ( $devices{$key}{'dvd'} == 1 ) ? 'yes' : 'no' ; + } + if ($devices{$key}{'cdr'}){ + push @rw, 'cd-r'; + } + if ($devices{$key}{'cdrw'}){ + push @rw, 'cd-rw'; + } + if ($devices{$key}{'dvdr'}){ + push @rw, 'dvd-r'; + } + if ($devices{$key}{'dvdram'}){ + push @rw, 'dvd-ram'; + } + $rws = (@rw) ? join ',', @rw: 'none' ; + @data = ({ + main::key($num++,'Features') => '', + main::key($num++,'speed') => $speed, + main::key($num++,'multisession') => $multisession, + main::key($num++,'audio') => $audio, + main::key($num++,'dvd') => $dvd, + main::key($num++,'rw') => $rws, + }); + @rows = (@rows,@data); + + if ($extra > 0 ){ + my $state = $devices{$key}{'state'}; + $state ||= 'N/A'; + $rows[$j]{ main::key($num++,'state')} = $state; + } + } + } + #print Data::Dumper::Dumper \%devices; + eval $end if $b_log; + return @rows; +} +sub optical_data_bsd { + eval $start if $b_log; + my (@data,%devices,@rows,@temp); + my ($count,$i,$working) = (0,0,''); + foreach (@dm_boot_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 $devices{$working}){ + $devices{$working} = ({}); + $devices{$working}{'links'} = ([]); + $devices{$working}{'model'} = ''; + $devices{$working}{'rev'} = ''; + $devices{$working}{'state'} = ''; + $devices{$working}{'vendor'} = ''; + $devices{$working}{'temp'} = ''; + $devices{$working}{'type'} = ($working =~ /^cd/) ? 'optical' : 'unknown'; + } + #print "$_\n"; + if ($bsd_type ne 'openbsd'){ + if ($row[1] && $row[1] =~ /^<([^>]+)>/){ + $devices{$working}{'model'} = $1; + $count = ($devices{$working}{'model'} =~ tr/ //); + if ($count && $count > 1){ + @temp = split /\s+/, $devices{$working}{'model'}; + $devices{$working}{'vendor'} = $temp[0]; + my $index = ($#temp > 2 ) ? ($#temp - 1): $#temp; + $devices{$working}{'model'} = join ' ', @temp[1..$index]; + $devices{$working}{'rev'} = $temp[-1] if $count > 2; + } + if ($show{'optical'}){ + if (/\bDVD\b/){ + $devices{$working}{'dvd'} = 1; + } + if (/\bRW\b/){ + $devices{$working}{'cdrw'} = 1; + $devices{$working}{'dvdr'} = 1 if $devices{$working}{'dvd'}; + } + } + } + if ($row[1] && $row[1] =~ /^Serial/){ + @temp = split /\s+/,$row[1]; + $devices{$working}{'serial'} = $temp[-1]; + } + if ($show{'optical'}){ + if ($row[1] =~ /^([0-9\.]+[MGTP][B]?\/s)/){ + $devices{$working}{'speed'} = $1; + $devices{$working}{'speed'} =~ s/\.[0-9]+//; + } + if (/\bDVD[-]?RAM\b/){ + $devices{$working}{'cdr'} = 1; + $devices{$working}{'dvdram'} = 1; + } + if ($row[2] && $row[2] =~ /,\s(.*)$/){ + $devices{$working}{'state'} = $1; + $devices{$working}{'state'} =~ s/\s+-\s+/, /; + } + } + } + else { + if ($row[2] && $row[2] =~ /<([^>]+)>/){ + $devices{$working}{'model'} = $1; + $count = ($devices{$working}{'model'} =~ tr/,//); + #print "c: $count $row[2]\n"; + if ($count && $count > 1){ + @temp = split /,\s*/, $devices{$working}{'model'}; + $devices{$working}{'vendor'} = $temp[0]; + $devices{$working}{'model'} = $temp[1]; + $devices{$working}{'rev'} = $temp[2]; + } + if ($show{'optical'}){ + if (/\bDVD\b/){ + $devices{$working}{'dvd'} = 1; + } + if (/\bRW\b/){ + $devices{$working}{'cdrw'} = 1; + $devices{$working}{'dvdr'} = 1 if $devices{$working}{'dvd'}; + } + if (/\bDVD[-]?RAM\b/){ + $devices{$working}{'cdr'} = 1; + $devices{$working}{'dvdram'} = 1; + } + } + } + if ($show{'optical'}){ + #print "$row[1]\n"; + if (($row[1] =~ tr/,//) > 1){ + @temp = split /,\s*/, $row[1]; + $devices{$working}{'speed'} = $temp[2]; + } + + } + } + } + + main::log_data('dump','%devices',\%devices) if $b_log; + #print Data::Dumper::Dumper \%devices; + @rows = create_output(%devices) if %devices; + eval $end if $b_log; + return @rows; +} +sub optical_data_linux { + eval $start if $b_log; + my (@data,%devices,@info,@rows); + @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 $devices{$working}){ + my @temp = ($_ ne $working) ? ([$_]) : ([]); + $devices{$working} = ({'links' => @temp}); + $devices{$working}{'type'} = ($working =~ /^fd/) ? 'floppy' : 'optical' ; + } + else { + my $ref = $devices{$working}{'links'}; + push @$ref, $_ 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 %devices){ + next if $devices{$key}{'type'} eq 'floppy'; + my $device = "/sys/block/$key/device"; + if ( -d $device){ + if (-e "$device/vendor"){ + $devices{$key}{'vendor'} = (main::reader("$device/vendor"))[0]; + $devices{$key}{'vendor'} = main::cleaner($devices{$key}{'vendor'}); + $devices{$key}{'state'} = (main::reader("$device/state"))[0]; + $devices{$key}{'model'} = (main::reader("$device/model"))[0]; + $devices{$key}{'model'} = main::cleaner($devices{$key}{'model'}); + $devices{$key}{'rev'} = (main::reader("$device/rev"))[0]; + } + } + elsif ( -e "/proc/ide/$_/model"){ + $devices{$key}{'vendor'} = (main::reader("/proc/ide/$_/model"))[0]; + $devices{$key}{'vendor'} = main::cleaner($devices{$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:/) { + $devices{$key}{'speed'} = $split[$index]; + } + elsif ($item =~/^Can read multisession:/) { + $devices{$key}{'multisession'}=$split[$index+1]; + } + elsif ($item =~/^Can read MCN:/) { + $devices{$key}{'mcn'}=$split[$index+1]; + } + elsif ($item =~/^Can play audio:/) { + $devices{$key}{'audio'}=$split[$index+1]; + } + elsif ($item =~/^Can write CD-R:/) { + $devices{$key}{'cdr'}=$split[$index+1]; + } + elsif ($item =~/^Can write CD-RW:/) { + $devices{$key}{'cdrw'}=$split[$index+1]; + } + elsif ($item =~/^Can read DVD:/) { + $devices{$key}{'dvd'}=$split[$index+1]; + } + elsif ($item =~/^Can write DVD-R:/) { + $devices{$key}{'dvdr'}=$split[$index+1]; + } + elsif ($item =~/^Can write DVD-RAM:/) { + $devices{$key}{'dvdram'}=$split[$index+1]; + } + } + } + } + main::log_data('dump','%devices',\%devices) if $b_log; + #print Data::Dumper::Dumper \%devices; + @rows = create_output(%devices) if %devices; + eval $end if $b_log; + return @rows; +} + +} + +## PartitionData +{ +package PartitionData; + +sub get { + eval $start if $b_log; + my (@rows,$key1,$val1); + my $num = 0; + partition_data() if !$b_partitions; + if (!@partitions) { + $key1 = 'Message'; + #$val1 = ($bsd_type && $bsd_type eq 'darwin') ? + # main::row_defaults('darwin-feature') : main::row_defaults('partition-data'); + $val1 = main::row_defaults('partition-data'); + @rows = ({main::key($num++,$key1) => $val1,}); + } + else { + @rows = create_output(); + } + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my $num = 0; + my $j = 0; + my (@data,@data2,%part,@rows,$dev,$dev_type,$fs); + @partitions = sort { $a->{'id'} cmp $b->{'id'} } @partitions; + foreach my $ref (@partitions){ + my %row = %$ref; + $num = 1; + next if $row{'type'} eq 'secondary' && $show{'partition'}; + @data2 = main::get_size($row{'size'}) if (defined $row{'size'}); + my $size = (@data2) ? $data2[0] . ' ' . $data2[1]: 'N/A'; + @data2 = main::get_size($row{'used'}) if (defined $row{'used'}); + my $used = (@data2) ? $data2[0] . ' ' . $data2[1]: 'N/A'; + my $percent = (defined $row{'percent-used'}) ? ' (' . $row{'percent-used'} . '%)' : ''; + %part = (); + if (defined $row{'dev-base'}){ + if ($row{'dev-base'} =~ /^non-dev-/){ + $row{'dev-base'} =~ s/^non-dev-//; + $dev_type = 'raid'; + $dev = $row{'dev-base'}; + } + # note: I have seen this: beta:data/ for sshfs path + elsif ($row{'dev-base'} =~ /^\/\/|:\//){ + $dev_type = 'remote'; + $dev = $row{'dev-base'}; + } + # an error has occurred almost for sure + elsif (!$row{'dev-base'}){ + $dev_type = 'dev'; + $dev = main::row_defaults('unknown-dev'); + } + else { + $dev_type = 'dev'; + $dev = '/dev/' . $row{'dev-base'}; + } + } + else { + $dev_type = 'dev'; + } + $fs = ($row{'fs'}) ? lc($row{'fs'}): 'N/A'; + $dev ||= 'N/A'; + $j = scalar @rows; + @data = ({ + main::key($num++,'ID') => $row{'id'}, + main::key($num++,'size') => $size, + main::key($num++,'used') => $used . $percent, + main::key($num++,'fs') => $fs, + main::key($num++,$dev_type) => $dev, + }); + @rows = (@rows,@data); + if ($show{'label'}){ + $rows[$j]{main::key($num++,'label')} = ($row{'label'}) ? $row{'label'}: 'N/A'; + } + if ($show{'uuid'}){ + $rows[$j]{main::key($num++,'uuid')} = ($row{'uuid'}) ? $row{'uuid'}: 'N/A'; + } + } + eval $end if $b_log; + return @rows; +} + +sub partition_data { + eval $start if $b_log; + #return if $bsd_type && $bsd_type eq 'darwin'; # darwin has muated output, of course + my (@data,@rows,@mapper,@mount,@partitions_working,%part); + my ($b_fake_map,$b_fs,$b_load,$cols,$roots) = (0,1,0,6,0); + my ($back_size,$back_used) = (4,3); + my ($dev_base,$fs,$id,$label,$percent_used,$size,$type,$uuid,$used); + $b_partitions = 1; + set_lsblk() if !$bsd_type && !$b_lsblk; + # set labels, uuid, gpart + set_label_uuid() if !$b_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 + if (!$bsd_type){ + @partitions_working = main::grabber("df -P -T -k 2>/dev/null"); + if (-d '/dev/mapper'){ + @mapper = main::globber('/dev/mapper/*'); + } + } + else { + # this is missing the file system data + if ($bsd_type ne 'darwin'){ + @partitions_working = main::grabber("df -T -k 2>/dev/null"); + } + #Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on + else { + $cols = 8; + $b_fake_map = 1; + ($back_size,$back_used) = (7,6); + } + } + # busybox only supports -k and -P, openbsd, darwin + if (!@partitions_working){ + @partitions_working = main::grabber("df -k 2>/dev/null"); + $b_fs = 0; + $cols = 5 if !$bsd_type || $bsd_type ne 'darwin'; + if (my $path = main::check_program('mount')){ + @mount = main::grabber("$path 2>/dev/null"); + } + } + # determine positions + my $row1 = shift @partitions_working; + # 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 + foreach (@partitions_working){ + if (/\s\/$/){ + $roots++; + } + } + @partitions_working = grep {!/^rootfs/} @partitions_working if $roots > 1; + my $filters = '^(aufs|cgroup.*|cgmfs|configfs|debugfs|\/dev|dev|\/dev/loop[0-9]*|'; + $filters .= 'devfs|devtmpfs|fdescfs|iso9660|linprocfs|none|procfs|\/run(\/.*)?|'; + $filters .= 'run|shm|squashfs|sys|\/sys\/.*|sysfs|tmpfs|type|udev|unionfs|vartmp)$'; + foreach (@partitions_working){ + # stupid apple bullshit + $_ =~ s/^map\s+([\S]+)/map:\/$1/ if $b_fake_map; + my @row = split /\s+/, $_; + if ($row[0] =~ /$filters/ || $row[0] =~ /^ROOT/i || ($b_fs && $row[1] eq 'tmpfs')){ + next; + } + $dev_base = ''; + $fs = ''; + $id = ''; + $label = ''; + $size = 0; + $used = 0; + %part = (); + $percent_used = 0; + $type = ''; + $uuid = ''; + $b_load = 0; + # NOTE: using -P for linux fixes line wraps, and for bsds, assuming they don't use such long file names + 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){ + $row[0] = get_mapper($row[0],@mapper); + } + $dev_base = $row[0]; + $dev_base =~ s/^\/dev\///; + %part = check_lsblk($dev_base,0) if @lsblk; + } + # 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|zfs)$/ ) ) ){ + $dev_base = "non-dev-$row[0]"; + } + # this handles yet another fredforfaen special case where a mounted drive + # has the search string in its name + if ($row[-1] =~ /^\/$|^\/boot$|^\/var$|^\/var\/tmp$|^\/var\/log$|^\/home$|^\/opt$|^\/tmp$|^\/usr$/){ + $b_load = 1; + # note, older df in bsd do not have file system column + $type = 'main'; + } + elsif ($row[$cols] !~ /^\/$|^\/boot$|^\/var$|^\/var\/tmp$|^\/var\/log$|^\/home$|^\/opt$|^\/tmp$|^\/usr$|^filesystem/){ + $b_load = 1; + $type = 'secondary'; + } + if ($b_load){ + if (!$bsd_type){ + if ($b_fs){ + $fs = (%part && $part{'fs'}) ? $part{'fs'} : $row[1]; + } + else { + $fs = get_mounts_fs($row[0],@mount); + } + if ($show{'label'}) { + if (%part && $part{'label'}) { + $label = $part{'label'}; + } + elsif ( @labels){ + $label = get_label($row[0]); + } + } + if ($show{'uuid'}) { + if (%part && $part{'uuid'}) { + $uuid = $part{'uuid'}; + } + elsif ( @uuids){ + $uuid = get_uuid($row[0]); + } + } + } + else { + $fs = ($b_fs) ? $row[1]: get_mounts_fs($row[0],@mount); + if (@gpart && ($show{'label'} || $show{'uuid'} ) ){ + my @extra = get_bsd_label_uuid("$dev_base"); + if (@extra){ + $label = $extra[0]; + $uuid = $extra[1]; + } + } + } + $id = join ' ', @row[$cols .. $#row]; + $id =~ s/\/home\/[^\/]+\/(.*)/\/home\/$filter_string\/$1/ if $show{'filter'}; + $size = $row[$cols - $back_size]; + $used = $row[$cols - $back_used]; + $percent_used = sprintf( "%.1f", ( $used/$size )*100 ) if ($size); + @data = ({ + 'id' => $id, + 'dev-base' => $dev_base, + 'fs' => $fs, + 'label' => $label, + 'size' => $size, + 'type' => $type, + 'used' => $used, + 'uuid' => $uuid, + 'percent-used' => $percent_used, + }); + @partitions = (@partitions,@data); + } + } + @data = swap_data(); + @partitions = (@partitions,@data); + main::log_data('dump','@partitions',\@partitions) if $b_log; + # print Data::Dumper::Dumper \@partitions; + eval $end if $b_log; +} + +sub swap_data { + eval $start if $b_log; + my (@swap,@working,$path,$label,$uuid); + 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"); + } + } + 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); + } + # 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)/; + my @data = split /\s+/, $_; + my $dev_base = $data[0]; + $dev_base =~ s/^\/dev\///; + my $size = $data[$size_id]; + my $used = $data[$used_id]; + my $percent_used = sprintf( "%.1f", ( $used/$size )*100 ); + if ($show{'label'} && @labels){ + $label = get_label($data[0]); + } + if ($show{'uuid'} && @uuids){ + $uuid = get_uuid($data[0]); + } + if ($bsd_type && @gpart && ($show{'label'} || $show{'uuid'} ) ){ + my @extra = get_bsd_label_uuid("$dev_base"); + if (@extra){ + $label = $extra[0]; + $uuid = $extra[1]; + } + } + @data = ({ + 'id' => "swap-$s", + 'dev-base' => $dev_base, + 'fs' => 'swap', + 'label' => $label, + 'size' => $size, + 'type' => 'main', + 'used' => $used, + 'uuid' => $uuid, + 'percent-used' => $percent_used, + }); + @swap = (@swap,@data); + $s++; + } + eval $end if $b_log; + return @swap; +} +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) + foreach (@mount){ + if ($bsd_type && $_ =~ /^$item\son.*\(([^,\s\)]+)[,\s]*.*\)/){ + $fs = $1; + last; + } + elsif (!$bsd_type && $_ =~ /^$item\son.*\stype\s([\S]+)\s\([^\)]+\)/){ + $fs = $1; + last; + } + } + eval $end if $b_log; + main::log_data('data',"fs: $fs") if $b_log; + return $fs; +} +# 1. Name: ada1p1 +# label: (null) +# label: ssd-root +# rawuuid: b710678b-f196-11e1-98fd-021fc614aca9 +sub get_bsd_label_uuid { + eval $start if $b_log; + my ($item) = @_; + my (@data,$b_found); + foreach (@gpart){ + my @working = split /\s*:\s*/, $_; + if ($_ =~ /^[0-9]+\.\sName:/ && $working[1] eq $item){ + $b_found = 1; + } + elsif ($_ =~ /^[0-9]+\.\sName:/ && $working[1] ne $item){ + $b_found = 0; + } + if ($b_found){ + if ($working[0] eq 'label'){ + $data[0] = $working[1]; + $data[0] =~ s/\(|\)//g; # eg: label:(null) - we want to show null + } + if ($working[0] eq 'rawuuid'){ + $data[1] = $working[1]; + $data[0] =~ s/\(|\)//g; + } + } + } + main::log_data('dump','@data',\@data) if $b_log; + eval $end if $b_log; + return @data; +} +sub set_label_uuid { + eval $start if $b_log; + $b_label_uuid = 1; + if ( $show{'unmounted'} || $show{'label'} || $show{'uuid'} ){ + if (!$bsd_type){ + 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/*'); + } + } + else { + if ( my $path = main::check_program('gpart')){ + @gpart = main::grabber("$path list 2>/dev/null",'strip'); + } + } + } + eval $end if $b_log; +} +sub set_lsblk { + eval $start if $b_log; + $b_lsblk = 1; + my (@temp,@working); + if (my $program = main::check_program('lsblk')){ + @working = main::grabber("$program -bP --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT 2>/dev/null"); + foreach (@working){ + if (/NAME="([^"]*)"\s+TYPE="([^"]*)"\s+RM="([^"]*)"\s+FSTYPE="([^"]*)"\s+SIZE="([^"]*)"\s+LABEL="([^"]*)"\s+UUID="([^"]*)"\s+SERIAL="([^"]*)"\s+MOUNTPOINT="([^"]*)"/){ + my $size = ($5) ? $5/1024: 0; + # some versions of lsblk do not return serial, fs, uuid, or label + my @temp = ({ + 'name' => $1, + 'type' => $2, + 'rm' => $3, + 'fs' => $4, + 'size' => $size, + 'label' => $6, + 'uuid' => $7, + 'serial' => $8, + 'mount' => $9, + }); + @lsblk = (@lsblk,@temp); + } + } + } + # print Data::Dumper::Dumper \@lsblk; + main::log_data('dump','@lsblk',\@lsblk) if $b_log; + eval $end if $b_log; +} +sub check_lsblk { + eval $start if $b_log; + my ($name,$b_size) = @_; + my (%part,@row); + foreach my $ref (@lsblk){ + my %row = %$ref; + next if ! $row{'name'}; + if ($name eq $row{'name'}){ + %part = %row; + last; + } + } + # print Data::Dumper::Dumper \%part; + main::log_data('dump','%part',\%part) if $b_log; + eval $end if $b_log; + return %part; +} +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; +} +# args: $1 - dev item $2 - @mapper +# check for mapper, then get actual dev item if mapped +# /dev/mapper/ will usually be a symbolic link to the real /dev id +sub get_mapper { + eval $start if $b_log; + my ($item,@mapper) = @_; + my $mapped = ''; + foreach (@mapper){ + if ($item eq $_){ + my $temp = Cwd::abs_path($_); + $mapped = $temp if $temp; + last; + } + } + $mapped ||= $item; + eval $end if $b_log; + return $mapped; +} +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; +} +} + +## ProcessData +{ +package ProcessData; + +sub get { + eval $start if $b_log; + my (@processes,@rows); + if ($show{'ps-cpu'}){ + @rows = cpu_processes(); + @processes = (@processes,@rows); + } + if ($show{'ps-mem'}){ + @rows = mem_processes(); + @processes = (@processes,@rows); + } + return @processes; + eval $end if $b_log; +} +sub cpu_processes { + eval $start if $b_log; + my ($j,$num,$cpu,$cpu_mem,$mem) = (0,0,'','',''); + my (@processes); + my $count = ($b_irc)? 5: $ps_count; + my @rows = sort { + my @a = split(/\s+/,$a); + my @b = split(/\s+/,$b); + $b[2] <=> $a[2] } @ps_aux; + # if there's a count limit, for irc, etc, only use that much of the data + @rows = splice @rows,0,$count; + + $j = scalar @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"; + my @data = ({ + main::key($num++,'CPU top') => "$count$throttled", + },); + @processes = (@processes,@data); + my $i = 1; + foreach (@rows){ + $num = 1; + $j = scalar @processes; + my @row = split /\s+/, $_; + my @command = process_starter(scalar @row, $row[10],$row[11]); + @data = ({ + main::key($num++,$i++) => '', + main::key($num++,'cpu') => $row[2] . '%', + main::key($num++,'command') => $command[0], + },); + @processes = (@processes,@data); + if ($command[1]) { + $processes[$j]{main::key($num++,'started by')} = $command[1]; + } + $processes[$j]{main::key($num++,'pid')} = $row[1]; + if ($extra > 0){ + my $decimals = ($row[5]/1024 > 10 ) ? 1 : 2; + $mem = (defined $row[5]) ? sprintf( "%.${decimals}f", $row[5]/1024 ) . ' MiB' : 'N/A'; + $mem .= ' (' . $row[3] . '%)'; + $processes[$j]{main::key($num++,'mem')} = $mem; + } + #print Data::Dumper::Dumper \@processes, "i: $i; j: $j "; + } + eval $end if $b_log; + return @processes; +} +sub mem_processes { + eval $start if $b_log; + my ($j,$num,$cpu,$cpu_mem,$mem) = (0,0,'','',''); + my (@data,@processes,$memory); + my $count = ($b_irc)? 5: $ps_count; + my @rows = sort { + my @a = split(/\s+/,$a); + my @b = split(/\s+/,$b); + $b[5] <=> $a[5] } @ps_aux; + @rows = splice @rows,0,$count; + #print Data::Dumper::Dumper \@rows; + @processes = main::memory_data_full('process') if !$b_mem; + $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"; + @data = ({ + main::key($num++,'Memory top') => "$count$throttled", + },); + @processes = (@processes,@data); + my $i = 1; + foreach (@rows){ + $num = 1; + $j = scalar @processes; + my @row = split /\s+/, $_; + my $decimals = ($row[5]/1024 > 10 ) ? 1 : 2; + $mem = ($row[5]) ? sprintf( "%.${decimals}f", $row[5]/1024 ) . ' MiB' : 'N/A'; + my @command = process_starter(scalar @row, $row[10],$row[11]); + $mem .= " (" . $row[3] . "%)"; + @data = ({ + main::key($num++,$i++) => '', + main::key($num++,'mem') => $mem, + main::key($num++,'command') => $command[0], + },); + @processes = (@processes,@data); + if ($command[1]) { + $processes[$j]{main::key($num++,'started by')} = $command[1]; + } + $processes[$j]{main::key($num++,'pid')} = $row[1]; + if ($extra > 0){ + $cpu = $row[2] . '%'; + $processes[$j]{main::key($num++,'cpu')} = $cpu; + } + #print Data::Dumper::Dumper \@processes, "i: $i; j: $j "; + } + eval $end if $b_log; + return @processes; +} +sub process_starter { + my ($count, $row10, $row11) = @_; + my (@return); + # note: [migration/0] would clear with a simple basename + if ($count > 11 && $row11 =~ /^\//){ + $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)"; + } + elsif ($count < $ps_count){ + $throttled = " (throttled from $ps_count)"; + } + return $throttled; +} +} + +## RaidData +{ +package RaidData; +# debugger switches +my ($b_md,$b_zfs); + +sub get { + eval $start if $b_log; + my (@rows,$key1,$val1); + my $num = 0; + raid_data() if !$b_raid; + #print 'get: ', Data::Dumper::Dumper \@raid; + if (!@raid && !@hardware_raid){ + if ($show{'raid-forced'}){ + $key1 = 'Message'; + $val1 = main::row_defaults('raid-data'); + } + } + else { + @rows = create_output(); + } + if (!@rows && $key1){ + @rows = ({main::key($num++,$key1) => $val1,}); + } + eval $end if $b_log; + ($b_md,$b_zfs,@hardware_raid) = undef; + return @rows; +} +sub create_output { + eval $start if $b_log; + my (@arrays,@arrays_holder,@components,@components_good,@data,@failed,@rows, + @sizes,@spare,@temp); + my ($allocated,$available,$blocks_avail,$chunk_raid,$component_string,$raid, + $ref2,$ref3,$report_size,$size,$status); + my ($b_row_1_sizes); + my ($i,$j,$num,$status_id) = (0,0,0,0); + #print Data::Dumper::Dumper \@raid; + if (@hardware_raid){ + foreach my $ref (@hardware_raid){ + my %row = %$ref; + $num = 1; + my $device = ($row{'device'}) ? $row{'device'}: 'N/A'; + my $driver = ($row{'driver'}) ? $row{'driver'}: 'N/A'; + @data = ({ + main::key($num++,'Hardware') => $device, + }); + @rows = (@rows,@data); + $j = scalar @rows - 1; + $rows[$j]{main::key($num++,'vendor')} = $row{'vendor'} if $row{'vendor'}; + $rows[$j]{main::key($num++,'driver')} = $driver; + if ($extra > 0){ + my $driver_version = ($row{'driver-version'}) ? $row{'driver-version'}: 'N/A' ; + $rows[$j]{main::key($num++,'v')} = $driver_version; + if ($extra > 2){ + my $port= ($row{'port'}) ? $row{'port'}: 'N/A' ; + $rows[$j]{main::key($num++,'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++,'bus ID')} = $bus_id; + } + if ($extra > 1){ + my $chip_id = (defined $row{'vendor-id'} && defined $row{'chip-id'}) ? "$row{'vendor-id'}.$row{'chip-id'}": 'N/A' ; + $rows[$j]{main::key($num++,'chip ID')} = $chip_id; + } + if ($extra > 2){ + my $rev= (defined $row{'rev'} && $row{'rev'}) ? $row{'rev'}: 'N/A' ; + $rows[$j]{main::key($num++,'rev')} = $rev; + } + } + } + if ($extra > 2 && $raid[0]{'system-supported'}){ + @data = ({ + main::key($num++,'Supported md-raid types') => $raid[0]{'system-supported'}, + }); + @rows = (@rows,@data); + } + foreach my $ref (@raid){ + $j = scalar @rows; + my %row = %$ref; + $b_row_1_sizes = 0; + next if !%row; + $num = 1; + @data = ({ + main::key($num++,'Device') => $row{'id'}, + main::key($num++,'type') => $row{'type'}, + main::key($num++,'status') => $row{'status'}, + }); + @rows = (@rows,@data); + if ($row{'type'} eq 'mdraid'){ + $blocks_avail = 'blocks'; + $chunk_raid = 'chunk size'; + $report_size = 'report'; + if ($extra > 0){ + $available = ($row{'blocks'}) ? $row{'blocks'} : 'N/A'; + } + $size = ($row{'report'}) ? $row{'report'}: ''; + $size .= " $row{'u-data'}" if $size; + $size ||= 'N/A'; + $status_id = 2; + } + else { + $blocks_avail = 'free'; + $chunk_raid = 'allocated'; + $report_size = 'size'; + @sizes = ($row{'size'}) ? main::get_size($row{'size'}) : (); + $size = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + @sizes = ($row{'free'}) ? main::get_size($row{'free'}) : (); + $available = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + if ($extra > 2){ + @sizes = ($row{'allocated'}) ? main::get_size($row{'allocated'}) : (); + $allocated = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + } + $status_id = 1; + } + $ref2 = $row{'arrays'}; + @arrays = @$ref2; + @arrays = grep {defined $_} @arrays; + @arrays_holder = @arrays; + if (($row{'type'} eq 'mdraid' && $extra == 0 ) || !defined $arrays[0]{'raid'} ){ + $raid = (defined $arrays[0]{'raid'}) ? $arrays[0]{'raid'}: 'no-raid'; + $rows[$j]{main::key($num++,'raid')} = $raid; + } + if ( ( $row{'type'} eq 'zfs' || ($row{'type'} eq 'mdraid' && $extra == 0 ) ) && $size){ + #print "here 0\n"; + $rows[$j]{main::key($num++,$report_size)} = $size; + $size = ''; + $b_row_1_sizes = 1; + } + if ( $row{'type'} eq 'zfs' && $available){ + $rows[$j]{main::key($num++,$blocks_avail)} = $available; + $available = ''; + $b_row_1_sizes = 1; + } + if ( $row{'type'} eq 'zfs' && $allocated){ + $rows[$j]{main::key($num++,$chunk_raid)} = $allocated; + $allocated = ''; + } + $i = 0; + my $count = scalar @arrays; + foreach $ref3 (@arrays){ + my %row2 = %$ref3; + if ($count > 1){ + $j = scalar @rows; + $num = 1; + @sizes = ($row2{'size'}) ? main::get_size($row2{'size'}) : (); + $size = (@sizes) ? "$sizes[0] $sizes[1]" : 'N/A'; + @sizes = ($row2{'free'}) ? main::get_size($row2{'free'}) : (); + $available = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + $raid = (defined $row2{'raid'}) ? $row2{'raid'}: 'no-raid'; + $status = ($row2{'status'}) ? $row2{'status'}: 'N/A'; + @data = ({ + main::key($num++,'array') => $raid, + main::key($num++,'status') => $status, + main::key($num++,'size') => $size, + main::key($num++,'free') => $available, + }); + @rows = (@rows,@data); + } + # items like cache may have one component, with a size on that component + elsif (!$b_row_1_sizes && $row{'type'} eq 'zfs'){ + #print "here $count\n"; + @sizes = ($row2{'size'}) ? main::get_size($row2{'size'}) : (); + $size = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + @sizes = ($row2{'free'}) ? main::get_size($row2{'free'}) : (); + $available = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + $rows[$j]{main::key($num++,'size')} = $size; + $rows[$j]{main::key($num++,'free')} = $available; + if ($extra > 2){ + @sizes = ($row{'allocated'}) ? main::get_size($row2{'allocated'}) : (); + $allocated = (@sizes) ? "$sizes[0] $sizes[1]" : ''; + if ($allocated){ + $rows[$j]{main::key($num++,$chunk_raid)} = $allocated; + } + } + } + $ref3 = $row2{'components'}; + @components = (ref $ref3 eq 'ARRAY') ? @$ref3 : (); + @failed = (); + @spare = (); + @components_good = (); + # @spare = split(/\s+/, $row{'unused'}) if $row{'unused'}; + foreach my $item (@components){ + @temp = split /~/, $item; + if (defined $temp[$status_id] && $temp[$status_id] =~ /^(F|DEGRADED|FAULTED|UNAVAIL)$/){ + $temp[0] = "$temp[0]~$temp[1]" if $status_id == 2; + push @failed, $temp[0]; + } + elsif (defined $temp[$status_id] && $temp[$status_id] =~ /(S|OFFLINE)$/){ + $temp[0] = "$temp[0]~$temp[1]" if $status_id == 2; + push @spare, $temp[0]; + } + else { + $temp[0] = ($status_id == 2) ? "$temp[0]~$temp[1]" : $temp[0]; + push @components_good, $temp[0]; + } + } + $component_string = (@components_good) ? join ' ', @components_good : 'N/A'; + $rows[$j]{main::key($num++,'Components')} = ''; + $rows[$j]{main::key($num++,'online')} = $component_string; + if (@failed){ + $rows[$j]{main::key($num++,'FAILED')} = join ' ', @failed; + } + if (@spare){ + $rows[$j]{main::key($num++,'spare')} = join ' ', @spare; + } + if ($row{'type'} eq 'mdraid' && $extra > 0 ){ + $j = scalar @rows; + $num = 1; + #print Data::Dumper::Dumper \@arrays_holder; + $rows[$j]{main::key($num++,'Info')} = ''; + $raid = (defined $arrays_holder[0]{'raid'}) ? $arrays_holder[0]{'raid'}: 'no-raid'; + $rows[$j]{main::key($num++,'raid')} = $raid; + $rows[$j]{main::key($num++,$blocks_avail)} = $available; + if ($size){ + $rows[$j]{main::key($num++,$report_size)} = $size; + } + my $chunk = ($row{'chunk-size'}) ? $row{'chunk-size'}: 'N/A'; + $rows[$j]{main::key($num++,$chunk_raid)} = $chunk; + if ($extra > 1){ + if ($row{'bitmap'}){ + $rows[$j]{main::key($num++,'bitmap')} = $row{'bitmap'}; + } + if ($row{'super-block'}){ + $rows[$j]{main::key($num++,'super blocks')} = $row{'super-block'}; + } + if ($row{'algorithm'}){ + $rows[$j]{main::key($num++,'algorithm')} = $row{'algorithm'}; + } + } + } + $i++; + } + 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++,'Recovering')} = $percent; + my $finish = ($row{'recovery-finish'})?$row{'recovery-finish'} : 'N/A'; + $rows[$j]{main::key($num++,'time remaining')} = $finish; + if ($extra > 0){ + if ($row{'sectors-recovered'}){ + $rows[$j]{main::key($num++,'sectors')} = $row{'sectors-recovered'}; + } + } + if ($extra > 1 && $row{'recovery-speed'}){ + $rows[$j]{main::key($num++,'speed')} = $row{'recovery-speed'}; + } + } + } + eval $end if $b_log; + #print Data::Dumper::Dumper \@rows; + return @rows; +} +sub raid_data { + eval $start if $b_log; + my (@data); + $b_raid = 1; + if ($b_hardware_raid){ + hardware_raid(); + } + if ($b_md || (my $file = main::system_files('mdstat') )){ + @data = mdraid_data($file); + @raid = (@raid,@data) if @data; + } + if ($b_zfs || (my $path = main::check_program('zpool') )){ + @data = zfs_data($path); + @raid = (@raid,@data) if @data; + } + main::log_data('dump','@raid',\@raid) if $b_log; + #print Data::Dumper::Dumper \@raid; + 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 hardware_raid { + eval $start if $b_log; + my ($driver,$vendor,@data,@working); + foreach my $ref (@pci){ + @working = @$ref; + next if $working[1] ne '0104'; + $driver = ($working[9]) ? lc($working[9]): ''; + $driver =~ s/-/_/g if $driver; + my $driver_version = ($driver) ? main::get_module_version($driver): ''; + if ($extra > 2 && $b_pci_tool && $working[11]){ + $vendor = main::get_pci_vendor($working[4],$working[11]); + } + @data = ({ + '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, + }); + @hardware_raid = (@hardware_raid,@data); + } + # print Data::Dumper::Dumper \@hardware_raid; + main::log_data('dump','@hardware_raid',\@hardware_raid) if $b_log; + eval $end if $b_log; +} +sub mdraid_data { + eval $start if $b_log; + my ($mdstat) = @_; + my $j = 0; + #$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-4-device-1.txt"; + #$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-rebuild-1.txt"; + #$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-2-mirror-fserver2-1.txt"; + #$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-2-raid10-abucodonosor.txt"; + my @working = main::reader($mdstat,'strip'); + #print Data::Dumper::Dumper \@working; + my (@data,@mdraid,@temp,$b_found,$system,$unused); + # NOTE: a system with empty mdstat will still 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] =~ /^used\sdevices/){ + $unused = ( split /:\s*/, $working[0])[1]; + $unused =~ s/<|>|none//g if $unused; + pop @working; + } + foreach (@working){ + $_ =~ s/\s*:\s*/:/; + # print "$_\n"; + #md126 : active (auto-read-only) raid1 sdq1[0] + if (/^(md[0-9]+)\s*:\s*([^\s]+)(\s\([^)]+\))?\s([^\s]+)\s(.*)/){ + my $id = $1; + my $status = $2; + my $raid = $4; + my $component_string = $5; + @temp = (); + $raid =~ s/^raid1$/mirror/; + $raid =~ s/^raid/raid-/; + $raid = 'mirror' if $raid eq '1'; + # remember, these include the [x] id, so remove that for disk/unmounted + my @components = split /\s+/, $component_string; + foreach my $component (@components){ + $component =~ /([\S]+)\[([0-9]+)\]\(?([SF])?\)?/; + my $string = "$1~"; + $string .= (defined $2) ? "c$2" : ''; + $string .= (defined $3) ? "~$3" : ''; + push @temp, $string; + } + @components = @temp; + #print "$component_string\n"; + $j = scalar @mdraid; + @data = ({ + 'id' => $id, + 'arrays' => ([],), + 'status' => $status, + 'type' => 'mdraid', + }); + @mdraid = (@mdraid,@data); + $mdraid[$j]{'arrays'}[0]{'raid'} = $raid; + $mdraid[$j]{'arrays'}[0]{'components'} = \@components; + } + #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]{'system-supported'} = $system if $system; + $mdraid[0]{'unused'} = $unused if $unused; + } + #print Data::Dumper::Dumper \@mdraid; + eval $end if $b_log; + return @mdraid; +} + +sub zfs_data { + eval $start if $b_log; + my ($zpool) = @_; + my (@components,@data,@zfs); + my ($allocated,$free,$ref,$size,$status); + my $b_v = 1; + my ($i,$j,$k) = (0,0,0); + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-1-mirror-main-solestar.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-2-mirror-main-solestar.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-v-tank-1.txt"; + #my @working = main::reader($file);$zpool = ''; + my @working = main::grabber("$zpool list -v 2>/dev/null"); + DiskData::set_glabel() if $bsd_type && !$b_glabel; + # 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; + } + #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]): ''; + $status = (defined $row[$status_i] && $row[$status_i] ne '') ? $row[$status_i]: 'no-status'; + $j = scalar @zfs; + @data = ({ + 'id' => $row[0], + 'allocated' => $allocated, + 'arrays' => ([],), + 'free' => $free, + 'size' => $size, + 'status' => $status, + 'type' => 'zfs', + }); + @zfs = (@zfs,@data); + } + #print Data::Dumper::Dumper \@zfs; + # raid level is the second item in the output, unless it is not, sometimes it is absent + if ($row[1] =~ /raid|mirror/){ + $row[1] =~ s/^raid1/mirror/; + #$row[1] =~ s/^raid/raid-/; # need to match in zpool status + $ref = $zfs[$j]{'arrays'}; + $k = scalar @$ref; + $zfs[$j]{'arrays'}[$k]{'raid'} = $row[1]; + $i = 0; + $zfs[$j]{'arrays'}[$k]{'size'} = ($row[2] && $row[2] ne '-') ? main::translate_size($row[2]) : ''; + $zfs[$j]{'arrays'}[$k]{'allocated'} = ($row[3] && $row[3] ne '-') ? main::translate_size($row[3]) : ''; + $zfs[$j]{'arrays'}[$k]{'free'} = ($row[4] && $row[4] ne '-') ? main::translate_size($row[4]) : ''; + } + # https://blogs.oracle.com/eschrock/entry/zfs_hot_spares + elsif ($row[1] =~ /spares/){ + next; + } + # the first is a member of a raid array + # ada2 - - - - - - + # this second is a single device not in an array + # ada0s2 25.9G 14.6G 11.3G - 0% 56% + # gptid/3838f796-5c46-11e6-a931-d05099ac4dc2 - - - - - - + elsif ($row[1] =~ /^([a-z0-9]+[0-9]+|([\S]+)\/.*)$/ && + ($row[2] eq '-' || $row[2] =~ /^[0-9\.]+[MGTP]$/ )){ + $row[1] =~ /^([a-z0-9]+[0-9]+|([\S]+)\/.*)\s*(DEGRADED|FAULTED|OFFLINE)?$/; + my $working = $1; + my $state = ($3) ? $3 : ''; + if ($working =~ /[\S]+\// && @glabel){ + $working = DiskData::match_glabel($working); + } + # 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% + if (!$zfs[$j]{'size'} && $row[2] && $row[2] ne '-') { + $size = ($row[2])? main::translate_size($row[2]): ''; + $zfs[$j]{'arrays'}[$k]{'size'} = $size; + } + if (!$zfs[$j]{'allocated'} && $row[3] && $row[3] ne '-') { + $allocated = ($row[3])? main::translate_size($row[3]): ''; + $zfs[$j]{'arrays'}[$k]{'allocated'} = $allocated; + } + if (!$zfs[$j]{'free'} && $row[4] && $row[4] ne '-') { + $free = ($row[4])? main::translate_size($row[4]): ''; + $zfs[$j]{'arrays'}[$k]{'free'} = $free; + } + $zfs[$j]{'arrays'}[$k]{'components'}[$i] = $working . '~' . $state; + $i++; + } + } + # print Data::Dumper::Dumper \@zfs; + # clear out undefined arrrays values + $j = 0; + foreach $ref (@zfs){ + my %row = %$ref; + my $ref2 = $row{'arrays'}; + my @arrays = (ref $ref2 eq 'ARRAY' ) ? @$ref2 : (); + @arrays = grep {defined $_} @arrays; + $zfs[$j]{'arrays'} = \@arrays; + $j++; + } + @zfs = zfs_status($zpool,@zfs); + # print Data::Dumper::Dumper \@zfs; + eval $end if $b_log; + return @zfs; +} +sub zfs_status { + eval $start if $b_log; + my ($zpool,@zfs) = @_; + my ($cmd,$status,$file,$raid,@arrays,@pool_status,@temp); + my ($i,$j,$k,$l) = (0,0,0,0); + foreach my $ref (@zfs){ + my %row = %$ref; + $i = 0; + $k = 0; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-1-mirror-main-solestar.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-2-mirror-main-solestar.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-tank-1.txt"; + #@pool_status = main::reader($file,'strip'); + $cmd = "$zpool status $row{'id'} 2>/dev/null"; + @pool_status = main::grabber($cmd,"\n",'strip'); + main::log_data('cmd',$cmd) if $b_log; + my $ref2 = $row{'arrays'}; + @arrays = (ref $ref2 eq 'ARRAY' ) ? @$ref2 : (); + #print "$row{'id'} rs:$row{'status'}\n"; + $status = ($row{'status'} && $row{'status'} eq 'no-status') ? check_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 (@arrays){ + #print 'ref: ', ref $array, "\n"; + #next if ref $array ne 'HASH'; + my %row2 = %$array; + my $ref3 = $row2{'components'}; + my @components = (ref $ref3 eq 'ARRAY') ? @$ref3 : (); + $l = 0; + # zpool status: mirror-0 ONLINE 2 0 0 + $raid = ($row2{'raid'}) ? "$row2{'raid'}-$i": $row2{'raid'}; + $status = ($raid) ? check_status($raid,@pool_status): ''; + $zfs[$j]{'arrays'}[$k]{'status'} = $status; + #print "$raid i:$i j:$j k:$k $status\n"; + foreach my $component (@components){ + my @temp = split /~/, $component; + $status = ($temp[0]) ? check_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 + $i++ if $row2{'raid'}; # && $row2{'raid'} eq 'mirror'; + } + $j++; + } + eval $end if $b_log; + return @zfs; +} +sub check_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; +} +} + +## RamData +{ +package RamData; + +sub get { + my (@data,@rows,$key1,@ram,$val1); + my $num = 0; + my $ref = $alerts{'dmidecode'}; + @rows = main::memory_data_full('ram') if !$b_mem; + if ( $$ref{'action'} ne 'use'){ + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + @data = ({ + main::key($num++,'RAM Report') => '', + main::key($num++,$key1) => $val1, + }); + @rows = (@rows,@data); + } + else { + @ram = dmidecode_data(); + if (@ram){ + @data = create_output(@ram); + } + else { + $key1 = 'message'; + $val1 = main::row_defaults('ram-data'); + @data = ({ + main::key($num++,'RAM Report') => '', + main::key($num++,$key1) => $val1, + }); + } + @rows = (@rows,@data); + } + eval $end if $b_log; + return @rows; +} + +sub create_output { + eval $start if $b_log; + my (@ram) = @_; + return if !@ram; + my $num = 0; + my $j = 0; + my (@data,@rows); + foreach (@ram){ + $j = scalar @rows; + my %ref = %$_; + $num = 1; + @data = ({ + main::key($num++,'Array') => '', + main::key($num++,'capacity') => process_size($ref{'capacity'}), + }); + @rows = (@rows,@data); + if ($ref{'cap-qualifier'}){ + $rows[$j]{main::key($num++,'note')} = $ref{'cap-qualifier'}; + } + $rows[$j]{main::key($num++,'slots')} = $ref{'slots'}; + $rows[$j]{main::key($num++,'EC')} = $ref{'eec'}; + if ($extra > 0 ){ + $rows[$j]{main::key($num++,'max module size')} = process_size($ref{'max-module-size'}); + if ($ref{'mod-qualifier'}){ + $rows[$j]{main::key($num++,'note')} = $ref{'mod-qualifier'}; + } + } + foreach my $ref2 ($ref{'modules'}){ + my @modules = @$ref2; + # print Data::Dumper::Dumper \@modules; + foreach my $ref3 ( @modules){ + $num = 1; + $j = scalar @rows; + # multi array setups will start index at next from previous array + next if ref $ref3 ne 'HASH'; + my %mod = %$ref3; + $mod{'locator'} ||= 'N/A'; + @data = ({ + main::key($num++,'Device') => $mod{'locator'}, + main::key($num++,'size') => process_size($mod{'size'}), + }); + @rows = (@rows,@data); + next if ($mod{'size'} =~ /\D/); + if ($extra > 1 && $mod{'type'} ){ + $rows[$j]{main::key($num++,'info')} = $mod{'type'}; + } + $rows[$j]{main::key($num++,'speed')} = $mod{'speed'}; + if ($extra > 0 ){ + $mod{'device-type'} ||= 'N/A'; + $rows[$j]{main::key($num++,'type')} = $mod{'device-type'}; + if ($extra > 2 && $mod{'device-type'} ne 'N/A'){ + $mod{'device-type-detail'} ||= 'N/A'; + $rows[$j]{main::key($num++,'detail')} = $mod{'device-type-detail'}; + } + } + if ($extra > 2 ){ + $mod{'data-width'} ||= 'N/A'; + $rows[$j]{main::key($num++,'bus width')} = $mod{'data-width'}; + $mod{'total-width'} ||= 'N/A'; + $rows[$j]{main::key($num++,'total')} = $mod{'total-width'}; + } + if ($extra > 1 ){ + $mod{'manufacturer'} ||= 'N/A'; + $rows[$j]{main::key($num++,'manufacturer')} = $mod{'manufacturer'}; + $mod{'part-number'} ||= 'N/A'; + $rows[$j]{main::key($num++,'part-no')} = $mod{'part-number'}; + } + if ($extra > 2 ){ + $mod{'serial'} = main::apply_filter($mod{'serial'}); + $rows[$j]{main::key($num++,'serial')} = $mod{'serial'}; + } + } + } + } + eval $end if $b_log; + return @rows; +} + +sub dmidecode_data { + eval $start if $b_log; + my ($b_5,$handle,@ram,@temp); + my ($derived_module_size,$max_cap_5,$max_cap_16,$max_module_size) = (0,0,0,0); + my ($i,$j,$k) = (0,0,0); + foreach (@dmi){ + my @ref = @$_; + # Portable Battery + if ($ref[0] == 5){ + $ram[$k] = ({}) if !$ram[$k]; + foreach my $item (@ref){ + @temp = split /:\s*/, $item; + 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.*$//; + $ram[$k]{'voltage'} = $temp[1]; + } + elsif ($temp[0] eq 'Associated Memory Slots'){ + $ram[$k]{'slots-5'} = $temp[1]; + } + } + $ram[$k]{'modules'} = ([],); + #print Data::Dumper::Dumper \@ram; + $b_5 = 1; + } + elsif ($ref[0] == 6){ + my ($size,$speed,$type) = (0,0,0); + foreach my $item (@ref){ + @temp = split /:\s*/, $item; + next if ! $temp[1]; + if ($temp[0] eq 'Installed Size'){ + # get module size + + $size = calculate_size($temp[1],0); + # get data after module size + $temp[1] =~ s/ Connection\)?//; + $temp[1] =~ s/^[0-9]+\s*[MGTP]B\s*\(?//; + $type = lc($temp[1]); + } + elsif ($temp[0] eq 'Current Speed'){ + $speed = $temp[1]; + } + } + $ram[$k]{'modules'}[$j] = ({ + 'size' => $size, + 'speed-ns' => $speed, + 'type' => $type, + }); + #print Data::Dumper::Dumper \@ram; + $j++; + } + elsif ($ref[0] == 16){ + $handle = $ref[1]; + $ram[$handle] = $ram[$k] if $ram[$k]; + $ram[$k] = undef; + $ram[$handle] = ({}) if !$ram[$handle]; + foreach my $item (@ref){ + @temp = split /:\s*/, $item; + 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 set_dmidecode_data, 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 ($ref[0] == 17){ + my ($bank_locator,$configured_clock_speed,$data_width) = ('','',''); + my ($device_type,$device_type_detail,$form_factor,$locator,$main_locator) = ('','','','',''); + my ($manufacturer,$part_number,$serial,$speed,$total_width) = ('','','','',''); + my ($device_size,$i_data,$i_total,$working_size) = (0,0,0,0); + foreach my $item (@ref){ + @temp = split /:\s*/, $item; + next if ! $temp[1]; + if ($temp[0] eq 'Array Handle'){ + $handle = hex($temp[1]); + } + elsif ($temp[0] eq 'Data Width'){ + $data_width = $temp[1]; + } + elsif ($temp[0] eq 'Total Width'){ + $total_width = $temp[1]; + } + # 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*[MTPG]B/ ) { + $derived_module_size = calculate_size($temp[1],$derived_module_size); + $working_size = calculate_size($temp[1],0); + $device_size = $working_size; + } + else { + $device_size = $temp[1]; + } + } + elsif ($temp[0] eq 'Locator'){ + $temp[1] =~ s/RAM slot #/Slot/; + $locator = $temp[1]; + } + elsif ($temp[0] eq 'Bank Locator'){ + $bank_locator = $temp[1]; + } + elsif ($temp[0] eq 'Form Factor'){ + $form_factor = $temp[1]; + } + elsif ($temp[0] eq 'Type'){ + $device_type = $temp[1]; + } + elsif ($temp[0] eq 'Type Detail'){ + $device_type_detail = $temp[1]; + } + elsif ($temp[0] eq 'Speed'){ + $speed = $temp[1]; + } + elsif ($temp[0] eq 'Configured Clock Speed'){ + $configured_clock_speed = $temp[1]; + } + elsif ($temp[0] eq 'Manufacturer'){ + $temp[1] = main::dmi_cleaner($temp[1]); + $manufacturer = $temp[1]; + } + elsif ($temp[0] eq 'Part Number'){ + $temp[1] =~ s/(^[0]+$||.*Module.*|Undefined.*|PartNum.*|\[Empty\]|^To be filled.*)//g; + $part_number = $temp[1]; + } + elsif ($temp[0] eq 'Serial Number'){ + $temp[1] =~ s/(^[0]+$|Undefined.*|SerNum.*|\[Empty\]|^To be filled.*)//g; + $serial = $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; + } + 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; + } + $ram[$handle]{'derived-module-size'} = $derived_module_size; + $ram[$handle]{'modules'}[$i]{'configured-clock-speed'} = $configured_clock_speed; + $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]{'part-number'} = $part_number; + $ram[$handle]{'modules'}[$i]{'serial'} = $serial; + $ram[$handle]{'modules'}[$i]{'speed'} = $speed; + $ram[$handle]{'modules'}[$i]{'total-width'} = $total_width; + $i++ + } + elsif ($ref[0] < 17 ){ + next; + } + elsif ($ref[0] > 17 ){ + last; + } + } + @ram = data_processor(@ram) if @ram; + main::log_data('dump','@ram',\@ram) if $b_log; + # print Data::Dumper::Dumper \@ram; + eval $end if $b_log; + return @ram; +} +sub data_processor { + eval $start if $b_log; + my (@ram) = @_; + my $b_debug = 0; + my (@return,@temp); + my $est = 'est.'; + + foreach (@ram){ + # because we use the actual array handle as the index, + # there will be many undefined keys + next if ! defined $_; + my %ref = %$_; + my ($max_cap,$max_mod_size) = (0,0); + my ($alt_cap,$est_cap,$est_mod,$unit) = (0,'','',''); + $max_cap = $ref{'max-capacity-16'}; + # make sure they are integers not string if empty + $ref{'slots-5'} ||= 0; + $ref{'slots-16'} ||= 0; + $ref{'max-capacity-5'} ||= 0; + $ref{'max-module-size'} ||= 0; + #$ref{'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: $ref{'max-module-size'} :dms: $ref{'derived-module-size'} :mc: $max_cap :uc: $ref{'used-capacity'}\n"; + print "1a: s5: $ref{'slots-5'} s16: $ref{'slots-16'}\n"; + } + if (!$max_cap && $ref{'max-capacity-5'}) { + $max_cap = $ref{'max-capacity-5'}; + } + if ($b_debug){ + print "2: mms: $ref{'max-module-size'} :dms: $ref{'derived-module-size'} :mc: $max_cap :uc: $ref{'used-capacity'}\n"; + } + # 2: now check to see if actually found module sizes are > than listed max module, replace if > + if ( $ref{'max-module-size'} && $ref{'derived-module-size'} && + $ref{'derived-module-size'} > $ref{'max-module-size'} ){ + $ref{'max-module-size'} = $ref{'derived-module-size'}; + $est_mod = $est; + } + if ($b_debug){ + print "3: dcf: $ref{'device-count-found'} :dms: $ref{'derived-module-size'} :mc: $max_cap :uc: $ref{'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 && ($ref{'device-count-found'} || $ref{'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 ($ref{'used-capacity'} && $ref{'max-capacity-16'}){ + if ($ref{'used-capacity'} > $max_cap){ + if ($ref{'max-module-size'} && + $ref{'used-capacity'} < ($ref{'slots-16'} * $ref{'max-module-size'} )){ + $max_cap = $ref{'slots-16'} * $ref{'max-module-size'}; + $est_cap = $est; + print "A\n" if $b_debug; + } + elsif ($ref{'derived-module-size'} && + $ref{'used-capacity'} < ($ref{'slots-16'} * $ref{'derived-module-size'}) ){ + $max_cap = $ref{'slots-16'} * $ref{'derived-module-size'}; + $est_cap = $est; + print "B\n" if $b_debug; + } + else { + $max_cap = $ref{'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 ($ref{'slots-16'} > 1 && $ref{'device-count-found'} > 1 && + $max_cap < ($ref{'derived-module-size'} * $ref{'slots-16'} ) ){ + $max_cap = $ref{'derived-module-size'} * $ref{'slots-16'}; + $est_cap = $est; + print "D\n" if $b_debug; + } + elsif ($ref{'device-count-found'} > 0 && $max_cap < ( $ref{'derived-module-size'} * $ref{'device-count-found'} )){ + $max_cap = $ref{'derived-module-size'} * $ref{'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 cap + # however do not use it if cap / devices equals the derived module size + elsif ($ref{'max-module-size'} > 0 && + ($ref{'max-module-size'} * $ref{'slots-16'}) == $ref{'max-capacity-5'} && + $ref{'max-capacity-5'} != $ref{'max-capacity-16'} && + $ref{'derived-module-size'} != ($ref{'max-capacity-16'}/$ref{'slots-16'}) ){ + $max_cap = $ref{'max-capacity-5'}; + $est_cap = $est; + print "F\n" if $b_debug; + } + + } + if ($b_debug){ + print "4: mms: $ref{'max-module-size'} :dms: $ref{'derived-module-size'} :mc: $max_cap :uc: $ref{'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 ($ref{'max-module-size'} && $max_cap && $ref{'max-module-size'} > $max_cap){ + $ref{'max-module-size'} = 0; + } + if ($b_debug){ + print "5: dms: $ref{'derived-module-size'} :s16: $ref{'slots-16'} :mc: $max_cap\n"; + } + + # now prep for rebuilding the ram array data + if (!$ref{'max-module-size'}){ + # ie: 2x4gB + if (!$est_cap && $ref{'derived-module-size'} > 0 && $max_cap > ($ref{'derived-module-size'} * $ref{'slots-16'} * 4) ){ + $est_cap = 'check'; + print "G\n" if $b_debug; + } + if ($max_cap && ($ref{'slots-16'} || $ref{'slots-5'})){ + my $slots = 0; + if ($ref{'slots-16'} && $ref{'slots-16'} >= $ref{'slots-5'}){ + $slots = $ref{'slots-16'}; + } + elsif ($ref{'slots-5'} && $ref{'slots-5'} > $ref{'slots-16'}){ + $slots = $ref{'slots-5'}; + } + if ($ref{'derived-module-size'} * $slots > $max_cap){ + $ref{'max-module-size'} = $ref{'derived-module-size'}; + } + else { + $ref{'max-module-size'} = sprintf("%.f",$max_cap/$slots); + } + $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 && $ref{'max-module-size'} > 0){ + if ($max_cap > ( $ref{'max-module-size'} * $ref{'slots-16'})){ + $est_cap = 'check'; + print "H\n" if $b_debug; + } + } + } + } + @temp = ({ + 'capacity' => $max_cap, + 'cap-qualifier' => $est_cap, + 'eec' => $ref{'eec'}, + 'location' => $ref{'location'}, + 'max-module-size' => $ref{'max-module-size'}, + 'mod-qualifier' => $est_mod, + 'modules' => $ref{'modules'}, + 'slots' => $ref{'slots-16'}, + 'use' => $ref{'use'}, + 'voltage' => $ref{'voltage'}, + }); + @return = (@return,@temp); + } + eval $end if $b_log; + return @return; +} +sub process_size { + my ($size) = @_; + my ($b_trim,$unit) = (0,''); + return 'N/A' if ( ! $size ); + return $size if $size =~ /\D/; + if ( $size < 1024 ){ + $unit='MiB'; + } + elsif ( $size < 1024000 ){ + $size = $size / 1024; + $unit='GiB'; + $b_trim = 1; + } + elsif ( $size < 1024000000 ){ + $size = $size / 1024000; + $unit='TiB'; + $b_trim = 1; + } + # we only want a max 2 decimal places, and only when it's + # a unit > MB + $size = sprintf("%.2f",$size) if $b_trim; + $size =~ s/\.[0]+$//; + $size = "$size $unit"; + return $size; +} +sub calculate_size { + my ($data, $size) = @_; + if ( $data =~ /^[0-9]+\s*[GMTP]B/) { + if ( $data =~ /([0-9]+)\s*GB/ ) { + $data = $1 * 1024; + } + elsif ( $data =~ /([0-9]+)\s*MB/ ) { + $data = $1; + } + elsif ( $data =~ /([0-9]+)\s*TB/ ) { + $data = $1 * 1024 * 1000; + } + elsif ( $data =~ /([0-9]+)\s*PB/ ) { + $data = $1 * 1024 * 1000 * 1000; + } + if ($data =~ /^[0-9][0-9]+$/ && $data > $size ) { + $size=$data; + } + } + else { + $size = 0; + } + return $size; +} +} + +## RepoData +{ +package RepoData; + +# easier to keep these package global, but undef after done +my (@dbg_files,$debugger_dir); +my $num = 0; +sub get { + eval $start if $b_log; + ($debugger_dir) = @_; + my (@data,@rows); + if ($bsd_type){ + @rows = get_repos_bsd(); + } + else { + @rows = get_repos_linux(); + } + if ($debugger_dir){ + @rows = @dbg_files; + undef @dbg_files; + undef $debugger_dir; + } + else { + if (!@rows){ + my $pm = (!$bsd_type) ? 'package manager': 'OS type'; + @data = ( + {main::key($num++,'Alert') => "No repo data detected. Does $self_name support your $pm?"}, + ); + @rows = (@data); + } + } + eval $end if $b_log; + return @rows; +} +sub get_repos_linux { + eval $start if $b_log; + my (@content,@data,@data2,@data3,@files,$repo,@repos,@rows); + my ($key,$path); + my $apk = '/etc/apk/repositories'; + my $apt = '/etc/apt/sources.list'; + my $eopkg_dir = '/var/lib/eopkg/'; + 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 $slackpkg = '/etc/slackpkg/mirrors'; + my $slackpkg_plus = '/etc/slackpkg/slackpkgplus.conf'; + my $yum_conf = '/etc/yum.conf'; + my $yum_repo_dir = '/etc/yum.repos.d/'; + my $zypp_repo_dir = '/etc/zypp/repos.d/'; + my $b_test = 0; + # apt - debian, buntus, also 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('/etc/apt/sources.list.d/*.list'); + push @files, $apt; + main::log_data('data',"apt repo files:\n" . main::joiner(\@files, "\n", 'unset') ) if $b_log; + foreach ( sort @files){ + # altlinux uses rpms in apt files! + @data = repo_builder($_,'apt','^\s*(deb|rpm)') if -r $_; + @rows = (@rows,@data); + } + #@files = main::globber("$ENV{'HOME'}/bin/scripts/inxi/data/repo/apt/*.sources"); + @files = main::globber('/etc/apt/sources.list.d/*.sources'); + main::log_data('data',"apt deb822 repo files:\n" . main::joiner(\@files, "\n", 'unset') ) if $b_log; + foreach $file (@files){ + @data2 = main::reader($file,'strip'); + my $count = scalar @data2; + push @dbg_files, $file if $debugger_dir; + #print "$file\n"; + @apt_urls = (); + @apt_working = (); + $counter = 0; + $b_apt_enabled = 1; + foreach my $row (@data2){ + $counter++; + next if $row =~ /^\s*$|^\s*#/; + #print "row:$row\n"; + if ($row =~ /^Types:\s*(.*)/){ + my $type_holder= $1; + #print "ath:$type_holder\n"; + if ($apt_types && @apt_working){ + if ($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"; + @apt_urls = (@apt_urls,@data3); + @data3 = (); + @apt_working = (); + $apt_arch = ''; + $apt_comp = ''; + $apt_suites = ''; + $apt_types = ''; + } + $apt_types = $type_holder; + $b_apt_enabled = 1; + } + if ($row =~ /^Enabled:\s*(.*)/){ + my $status = $1; + $b_apt_enabled = ($status =~ /no/i) ? 0: 1; + } + if ($row =~ /:\//){ + my $url = $row; + $url =~ s/^URIs:\s*//; + push @apt_working, $url if $url; + } + if ($row =~ /^Suites:\s*(.*)/){ + $apt_suites = $1; + } + if ($row =~ /^Components:\s*(.*)/){ + $apt_comp = $1; + } + if ($row =~ /^Architectures:\s*(.*)/){ + $apt_arch = $1; + } + # we've hit the last line!! + if ($counter == $count && @apt_working){ + #print "2: url builder\n"; + if ($b_apt_enabled){ + foreach $repo (@apt_working){ + my $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 "s2:$string\n"; + push @data3, $string; + } + } + #print join "\n",@data3,"\n"; + @apt_urls = (@apt_urls,@data3); + @data3 = (); + @apt_working = (); + $apt_arch = ''; + $apt_comp = ''; + $apt_suites = ''; + $apt_types = ''; + } + } + if (@apt_urls){ + $key = repo_builder('active','apt'); + @apt_urls = url_cleaner(@apt_urls); + } + else { + $key = repo_builder('missing','apt'); + } + @data = ( + {main::key($num++,$key) => $file}, + [@apt_urls], + ); + @rows = (@rows,@data); + } + @files = (); + } + # pacman: Arch and derived + 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); + @files = main::uniq(@files); + unshift @files, $pacman if @repos; + foreach (@files){ + if (-f $_){ + @data = repo_builder($_,$repo,'^\s*Server','\s*=\s*',1); + @rows = (@rows,@data); + } + else { + # set it so the debugger knows the file wasn't there + push @dbg_files, $_ if $debugger_dir; + @data = ( + {main::key($num++,'File listed in') => $pacman}, + [("$_ does not seem to exist.")], + ); + @rows = (@rows,@data); + } + } + if (!@rows){ + @data = ( + {main::key($num++,repo_builder('missing','no-files')) => $pacman }, + ); + @rows = (@rows,@data); + } + } + # slackware + if (-f $slackpkg || -f $slackpkg_plus){ + #$slackpkg = "$ENV{HOME}/bin/scripts/inxi/data/repo/slackware/slackpkg-2.conf"; + if (-f $slackpkg){ + @data = repo_builder($slackpkg,'slackpkg','^[[:space:]]*[^#]+'); + @rows = (@rows,@data); + } + if (-f $slackpkg_plus){ + push @dbg_files, $slackpkg_plus if $debugger_dir; + @data = main::reader($slackpkg_plus,'strip'); + my (@repoplus_list,$active_repos); + foreach my $row (@data){ + @data2 = split /\s*=\s*/, $row; + @data2 = map { $_ =~ s/^\s+|\s+$//g ; $_ } @data2; + last if $data2[0] =~ /^SLACKPKGPLUS/ && $data2[1] eq 'off'; + # REPOPLUS=( slackpkgplus restricted alienbob ktown multilib slacky) + if ($data2[0] =~ /^REPOPLUS/){ + @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/){ + $data2[0] =~ s/MIRRORPLUS\[\'|\'\]//g; + if ($data2[0] =~ /$active_repos/){ + push @content,"$data2[0] ~ $data2[1]"; + } + } + } + if (! @content){ + $key = repo_builder('missing','slackpkg+'); + } + else { + @content = url_cleaner(@content); + $key = repo_builder('active','slackpkg+'); + } + @data = ( + {main::key($num++,$key) => $slackpkg_plus}, + [@content], + ); + @data = url_cleaner(@data); + @rows = (@rows,@data); + @content = (); + } + } + # redhat/suse + if (-d $yum_repo_dir || -f $yum_conf || -d $zypp_repo_dir){ + if (-d $yum_repo_dir || -f $yum_conf){ + @files = main::globber("$yum_repo_dir*.repo"); + push @files, $yum_conf if -f $yum_conf; + $repo = 'yum'; + } + elsif (-d $zypp_repo_dir){ + @files = main::globber("$zypp_repo_dir*.repo"); + main::log_data('data',"zypp repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log; + $repo = 'zypp'; + } + #$repo = 'yum'; + #push @files, "$ENV{'HOME'}/bin/scripts/inxi/data/repo/yum/rpmfusion-nonfree-1.repo"; + 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; + } + # Note: it looks like enabled comes before url + elsif ($line =~ /^(metalink|mirrorlist|baseurl)\s*=\s*(.*)/){ + $url = $2; + } + # note: enabled = 1. enabled = 0 means disabled + elsif ($line =~ /^enabled\s*=\s*([01])/){ + $enabled = $1; + } + # 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_builder('missing',$repo); + } + else { + @content = url_cleaner(@content); + $key = repo_builder('active',$repo); + } + @data = ( + {main::key($num++,$key) => $_}, + [@content], + ); + @rows = (@rows,@data); + @content = (); + } + } + # print Data::Dumper::Dumper \@rows; + } + # gentoo + if (-d $portage_dir && main::check_program('emerge')){ + @files = main::globber("$portage_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*(.*)/){ + $url = $2; + } + # note: enabled = 1. enabled = 0 means disabled + elsif ($line =~ /^auto-sync\s*=\s*([01])/){ + $enabled = $1; + } + # 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_builder('missing','portage'); + } + else { + @content = url_cleaner(@content); + $key = repo_builder('active','portage'); + } + @data = ( + {main::key($num++,$key) => $_}, + [@content], + ); + @rows = (@rows,@data); + @content = (); + } + } + } + # Alpine linux + if (-f $apk){ + @data = repo_builder($apk,'apk','^\s*[^#]+'); + @rows = (@rows,@data); + } + # Mandriva/Mageia using: urpmq + 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; + @content = url_cleaner(@content); + # get the repo + $repo = $2; + @data = ( + {main::key($num++,'urpmq repo') => $repo}, + [@content], + ); + @rows = (@rows,@data); + @content = (); + } + } + } + # 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){ + @content = url_cleaner(@content); + $key = repo_builder('active',$which); + @data = ( + {main::key($num++,$key) => $repo}, + [@content], + ); + @rows = (@rows,@data); + $repo = ''; + @content = (); + } + } + # last one if present + if ($repo && @content){ + @content = url_cleaner(@content); + $key = repo_builder('active',$which); + @data = ( + {main::key($num++,$key) => $repo}, + [@content], + ); + @rows = (@rows,@data); + } + } + # print Dumper \@rows; + eval $end if $b_log; + return @rows; +} +sub get_repos_bsd { + eval $start if $b_log; + my (@content,@data,@data2,@data3,@files,@rows); + my ($key); + my $bsd_pkg = '/usr/local/etc/pkg/repos/'; + my $freebsd = '/etc/freebsd-update.conf'; + my $freebsd_pkg = '/etc/pkg/FreeBSD.conf'; + 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){ + if ( -f $portsnap ) { + @data = repo_builder($portsnap,'portsnap','^\s*SERVERNAME','\s*=\s*',1); + @rows = (@rows,@data); + } + if ( -f $freebsd ){ + @data = repo_builder($freebsd,'freebsd','^\s*ServerName','\s+',1); + @rows = (@rows,@data); + } +# if ( -f $freebsd_pkg ){ +# @data = repo_builder($freebsd_pkg,'freebsd-pkg','^\s*url',':\s+',1); +# @rows = (@rows,@data); +# } + if ( -d $bsd_pkg || -f $freebsd_pkg){ + @files = main::globber('/usr/local/etc/pkg/repos/*.conf'); + push @files, $freebsd_pkg if -f $freebsd_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] eq 'yes'){ + push @data3, "$url" + } + $url = ''; + } + } + } + if (! @data3){ + $key = repo_builder('missing','bsd-package'); + } + else { + @data3 = url_cleaner(@data3); + $key = repo_builder('active','bsd-package'); + } + @data = ( + {main::key($num++,$key) => $_}, + [@data3], + ); + @rows = (@rows,@data); + @data3 = (); + } + } + } + } + elsif (-f $openbsd || -f $openbsd2) { + if (-f $openbsd){ + @data = repo_builder($openbsd,'openbsd','^installpath','\s*=\s*',1); + @rows = (@rows,@data); + } + if (-f $openbsd2){ + @data = repo_builder($openbsd2,'openbsd','^(http|ftp)','',1); + @rows = (@rows,@data); + } + } + elsif (-f $netbsd){ + # not an empty row, and not a row starting with # + @data = repo_builder($netbsd,'netbsd','^\s*[^#]+$'); + @rows = (@rows,@data); + } + # 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_builder('missing','freebsd-nf'); + } + elsif ($bsd_type eq 'openbsd'){ + $key = repo_builder('missing','openbsd-nf'); + } + elsif ($bsd_type eq 'netbsd'){ + $key = repo_builder('missing','netbsd-nf'); + } + else { + $key = repo_builder('missing','bsd-nf'); + } + @data = ( + {main::key($num++,'Message') => $key}, + [()], + ); + @rows = (@rows,@data); + } + eval $start if $b_log; + return @rows; +} +sub repo_builder { + eval $start if $b_log; + my ($file,$type,$search,$split,$count) = @_; + my (@content,@data,$missing,$key); + my %unfound = ( + 'apk' => 'No active APK repos in', + 'apt' => 'No active apt repos in', + 'bsd-package' => 'No enabled BSD pkg servers in', + 'bsd-nf' => 'No BSD pkg server files found', + 'eopkg' => 'No active eopkg repos found', + 'pacman' => 'No active pacman repos in', + 'pacman-g2' => 'No active pacman-g2 repos in', + 'pisi' => 'No active pisi repos found', + 'portage' => 'No enabled portage sources in', + 'portsnap' => 'No ports servers in', + 'freebsd' => 'No FreeBSD update servers in', + 'freebsd-nf' => 'No FreeBSD update server files found', + 'freebsd-pkg' => 'No FreeBSD default pkg server in', + 'openbsd' => 'No OpenBSD pkg mirrors in', + 'openbsd-nf' => 'No OpenBSD pkg mirror files found', + 'netbsd' => 'No NetBSD pkg servers in', + 'netbsd-nf' => 'No NetBSD pkg server files found', + 'no-files' => 'No repo files found in', + 'slackpkg' => 'No active slackpkg repos in', + 'slackpkg+' => 'No active slackpkg+ repos in', + 'yum' => 'No active yum repos in', + 'zypp' => 'No active zypp repos in', + ); + $missing = $unfound{$type}; + return $missing if $file eq 'missing'; + my %keys = ( + 'apk' => 'APK repo', + 'apt' => 'Active apt repos in', + 'bsd-package' => 'BSD enabled pkg servers in', + 'eopkg' => 'Active eopkg repo', + 'freebsd' => 'FreeBSD update server', + 'freebsd-pkg' => 'FreeBSD default pkg server', + 'pacman' => 'Active pacman repo servers in', + 'pacman-g2' => 'Active pacman-g2 repo servers in', + 'pisi' => 'Active pisi repo', + 'portage' => 'Enabled portage sources in', + 'portsnap' => 'BSD ports server', + 'openbsd' => 'OpenBSD pkg mirror', + 'netbsd' => 'NetBSD pkg servers', + 'slackpkg' => 'slackpkg repos in', + 'slackpkg+' => 'slackpkg+ repos in', + 'yum' => 'Active yum repos in', + 'zypp' => 'Active zypp repos in', + ); + $key = $keys{$type}; + return $key if $file eq 'active'; + push @dbg_files, $file if $debugger_dir; + @content = main::reader($file); + @content = grep {/$search/i && !/^\s*$/} @content if @content; + @content = data_cleaner(@content); + if ($split){ + @content = map { + my @inner = split (/$split/, $_); + $inner[$count]; + } @content; + } + if (!@content){ + $key = $missing; + } + else { + @content = url_cleaner(@content); + } + @data = ( + {main::key($num++,$key) => $file}, + [@content], + ); + eval $end if $b_log; + return @data; +} +sub data_cleaner { + my (@content) = @_; + # basics: trim white space, get rid of double spaces + @content = map { $_ =~ s/^\s+|\s+$//g; $_ =~ s/\s\s+/ /g; $_} @content; + return @content; +} +# clean if irc +sub url_cleaner { + my (@content) = @_; + @content = map { $_ =~ s/:\//: \//; $_} @content if $b_irc; + return @content; +} +sub file_path { + my ($filename,$dir) = @_; + my ($working); + $working = $filename; + $working =~ s/^\///; + $working =~ s/\//-/g; + $working = "$dir/file-repo-$working.txt"; + return $working; +} +} + +## SensorData +{ +package SensorData; +my (@sensors_data); +my ($b_ipmi) = (0); +sub get { + eval $start if $b_log; + my ($key1,$program,$val1,@data,@rows,%sensors); + my $num = 0; + my $source = 'sensors'; + # we're allowing 1 or 2 ipmi tools, first the gnu one, then the + # almost certain to be present in BSDs + if ( $b_ipmi || + ( main::globber('/dev/ipmi**') && + ( ( $program = main::check_program('ipmi-sensors') ) || + ( $program = main::check_program('ipmitool') ) ) ) ){ + if ($b_ipmi || $b_root){ + %sensors = ipmi_data($program); + @data = create_output('ipmi',%sensors); + if (!@data) { + $key1 = 'Message'; + $val1 = main::row_defaults('sensors-data-ipmi'); + #$val1 = main::row_defaults('dev'); + @data = ({main::key($num++,$key1) => $val1,}); + } + @rows = (@rows,@data); + $source = 'lm-sensors'; # trips per sensor type output + } + else { + $key1 = 'Permissions'; + $val1 = main::row_defaults('sensors-ipmi-root'); + @data = ({main::key($num++,$key1) => $val1,}); + @rows = (@rows,@data); + } + } + my $ref = $alerts{'sensors'}; + if ( $$ref{'action'} ne 'use'){ + #print "here 1\n"; + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + $key1 = ucfirst($key1); + @data = ({main::key($num++,$key1) => $val1,}); + @rows = (@rows,@data); + } + else { + %sensors = lm_sensors_data(); + @data = create_output($source,%sensors); + #print "here 2\n"; + if (!@data) { + $key1 = 'Message'; + $val1 = main::row_defaults('sensors-data-linux'); + @data = ({main::key($num++,$key1) => $val1,}); + } + @rows = (@rows,@data); + } + undef @sensors_data; + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my ($source,%sensors) = @_; + # note: might revisit this, since gpu sensors data might be present + return if ! %sensors; + my (@gpu,@data,@rows,@fan_default,@fan_main); + my ($data_source) = (''); + my $fan_number = 0; + my $num = 0; + my $j = 0; + @gpu = gpu_data() if ( $source eq 'sensors' || $source eq 'lm-sensors' ); + 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' ; + $data_source = $source if ($source eq 'ipmi' || $source eq 'lm-sensors'); + @data = ({ + main::key($num++,'System Temperatures') => $data_source, + main::key($num++,$cpu1_key) => $cpu_temp, + }); + @rows = (@rows,@data); + if ($sensors{'cpu2-temp'}){ + $rows[$j]{main::key($num++,'cpu-2')} = $sensors{'cpu2-temp'} . $temp_unit; + } + if ($sensors{'cpu3-temp'}){ + $rows[$j]{main::key($num++,'cpu-3')} = $sensors{'cpu3-temp'} . $temp_unit; + } + if ($sensors{'cpu4-temp'}){ + $rows[$j]{main::key($num++,'cpu-4')} = $sensors{'cpu4-temp'} . $temp_unit; + } + $rows[$j]{main::key($num++,'mobo')} = $mobo_temp; + if (defined $sensors{'sodimm-temp'}){ + my $sodimm_temp = $sensors{'sodimm-temp'} . $temp_unit; + $rows[$j]{main::key($num++,'sodimm')} = $sodimm_temp; + } + if (defined $sensors{'psu-temp'}){ + my $psu_temp = $sensors{'psu-temp'} . $temp_unit; + $rows[$j]{main::key($num++,'psu')} = $psu_temp; + } + if (defined $sensors{'ambient-temp'}){ + my $ambient_temp = $sensors{'ambient-temp'} . $temp_unit; + $rows[$j]{main::key($num++,'ambient')} = $ambient_temp; + } + if (scalar @gpu == 1){ + my $gpu_temp = $gpu[0]{'temp'}; + my $gpu_type = $gpu[0]{'type'}; + my $gpu_unit = (defined $gpu[0]{'temp-unit'} && $gpu_temp ) ? " $gpu[0]{'temp-unit'}" : ' C'; + $rows[$j]{main::key($num++,'gpu')} = $gpu_type; + $rows[$j]{main::key($num++,'temp')} = $gpu_temp . $gpu_unit; + } + $j = scalar @rows; + my $ref_main = $sensors{'fan-main'}; + my $ref_default = $sensors{'fan-default'}; + @fan_main = @$ref_main if @$ref_main; + @fan_default = @$ref_default if @$ref_default; + my $fan_def = ($data_source) ? $data_source : ''; + if (!@fan_main && !@fan_default){ + $fan_def = ($fan_def) ? "$data_source N/A" : 'N/A'; + } + $rows[$j]{main::key($num++,'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++,'cpu')} = $fan_main[$i]; + $b_cpu = 1; + } + elsif ($i == 2 && $b_cpu){ + $rows[$j]{main::key($num++,'mobo')} = $fan_main[$i]; + } + elsif ($i == 3){ + $rows[$j]{main::key($num++,'psu')} = $fan_main[$i]; + } + elsif ($i == 4){ + $rows[$j]{main::key($num++,'sodimm')} = $fan_main[$i]; + } + elsif ($i > 4){ + $fan_number = $i - 4; + $rows[$j]{main::key($num++,"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++,"fan-$i")} = $fan_default[$i]; + } + } + $rows[$j]{main::key($num++,'psu')} = $sensors{'fan-psu'} if defined $sensors{'fan-psu'}; + $rows[$j]{main::key($num++,'psu-1')} = $sensors{'fan-psu1'} if defined $sensors{'fan-psu1'}; + $rows[$j]{main::key($num++,'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 == 1 && defined $gpu[0]{'fan-speed'}){ + my $gpu_fan = $gpu[0]{'fan-speed'} . $gpu[0]{'speed-unit'}; + my $gpu_type = $gpu[0]{'type'}; + $rows[$j]{main::key($num++,'gpu')} = $gpu_type; + $rows[$j]{main::key($num++,'fan')} = $gpu_fan; + } + if (scalar @gpu > 1){ + $j = scalar @rows; + $rows[$j]{main::key($num++,'GPU')} = ''; + my $gpu_unit = (defined $gpu[0]{'temp-unit'} ) ? " $gpu[0]{'temp-unit'}" : ' C'; + foreach my $ref (@gpu){ + my %info = %$ref; + # 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++,'device')} = $gpu_type; + if (defined $info{'screen'} ){ + $rows[$j]{main::key($num++,'screen')} = $info{'screen'}; + } + $rows[$j]{main::key($num++,'temp')} = $gpu_temp; + if (defined $gpu_fan){ + $rows[$j]{main::key($num++,'fan')} = $gpu_fan; + } + } + } + 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++,'Voltages')} = $data_source; + $rows[$j]{main::key($num++,'12v')} = $sensors{'volts-12'}; + $rows[$j]{main::key($num++,'5v')} = $sensors{'volts-5'}; + $rows[$j]{main::key($num++,'3.3v')} = $sensors{'volts-3.3'}; + $rows[$j]{main::key($num++,'vbat')} = $sensors{'volts-vbat'}; + if ($extra > 1 && $source eq 'ipmi' ){ + $sensors{'volts-dimm-p1'} ||= 'N/A'; + $sensors{'volts-dimm-p2'} ||= 'N/A'; + $rows[$j]{main::key($num++,'dimm-p1')} = $sensors{'volts-dimm-p1'} if $sensors{'volts-dimm-p1'}; + $rows[$j]{main::key($num++,'dimm-p2')} = $sensors{'volts-dimm-p2'} if $sensors{'volts-dimm-p2'}; + $rows[$j]{main::key($num++,'soc-p1')} = $sensors{'volts-soc-p1'} if $sensors{'volts-soc-p1'}; + $rows[$j]{main::key($num++,'soc-p2')} = $sensors{'volts-soc-p2'} if $sensors{'volts-soc-p2'}; + } + } + eval $end if $b_log; + return @rows; +} +sub ipmi_data { + eval $start if $b_log; + my ($program) = @_; + my ($b_cpu_0,$cmd,$file,@data,$fan_working,%sensors,@row,$sys_fan_nu, + $temp_working,$working_unit); + $program ||= 'ipmi-sensors'; # only for debugging, will always exist if reaches here + my ($b_ipmitool,$i_key,$i_value,$i_unit); + if ($program =~ /ipmi-sensors$/){ + $cmd = $program; + ($b_ipmitool,$i_key,$i_value,$i_unit) = (0,1,3,4); + } + else { + $cmd = "$program sensors"; + ($b_ipmitool,$i_key,$i_value,$i_unit) = (1,0,1,2); + } + @data = main::grabber("$cmd 2>/dev/null"); + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-archerseven-1.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-crazy-epyc-1.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-crazy-epyc-1.txt";$program='ipmi-sensors'; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-RK016013.txt";$program='ipmi-sensors'; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-lathander.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-zwerg.txt"; + #@data = main::reader($file); + return if ! @data; + foreach (@data){ + next if /^\s*$/; + # print "$_\n"; + @row = split /\s*\|\s*/, $_; + next if $row[$i_value] !~ /^[0-9\.]+$/i; + # print "$row[$i_key] - $row[$i_value]\n"; + if ($row[$i_key] =~ /^(System[\s_]Temp|System[\s_]?Board)$/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] =~ /^(Ambient)$/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([01])?([\s_]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([1-4])([\s_]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[_]?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-[0-9][A-Z]?)$/i){ + $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) { + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + $sensors{'fan-main'}[1] = int($row[$i_value]); + } + # 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 + elsif ($row[$i_key] =~ /^(SYS[\s_])?FAN[\s_]?([0-9A-F]+)/i) { + $sys_fan_nu = hex($2); + next if $row[$i_value] !~ /^[0-9\.]+$/; + $fan_working = int($row[$i_value]); + $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) { + $sensors{'fan-psu'} = int($row[$i_value]); + } + elsif ($row[$i_key] =~ /^(FAN PSU1|PSU1 FAN)$/i) { + $sensors{'fan-psu-1'} = int($row[$i_value]); + } + elsif ($row[$i_key] =~ /^(FAN PSU2|PSU2 FAN)$/i) { + $sensors{'fan-psu-2'} = int($row[$i_value]); + } + if ($extra > 0){ + if ($row[$i_key] =~ /^(MAIN\s|P[_]?)?12V$/i) { + $sensors{'volts-12'} = $row[$i_value]; + } + elsif ($row[$i_key] =~ /^(MAIN\s5V|P5V|5VCC|5V PG)$/i) { + $sensors{'volts-5'} = $row[$i_value]; + } + elsif ($row[$i_key] =~ /^(MAIN\s3.3V|P3V3|3.3VCC|3.3V PG)$/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)/i) { + $sensors{'volts-dimm-p1'} = $row[$i_value]; + } + elsif (! $sensors{'volts-dimm-p2'} && $row[$i_key] =~ /^(P2_VMEM|VDimmP2|MEM RSR B PG)/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; + %sensors = data_processor(%sensors) if %sensors; + main::log_data('dump','ipmi: %sensors',\%sensors) if $b_log; + eval $end if $b_log; + # print Data::Dumper::Dumper \%sensors; + return %sensors; +} +sub lm_sensors_data { + eval $start if $b_log; + my (%sensors); + my ($b_valid,$sys_fan_nu) = (0,0); + my ($adapter,$fan_working,$temp_working,$working_unit) = ('','','',''); + @sensors_data = main::grabber(main::check_program('sensors') . " 2>/dev/null"); + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/amdgpu-w-fan-speed-stretch-k10.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/peci-tin-geggo.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-w-other-biker.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-asus-chassis-1.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-devnull-1.txt"; + #@sensors_data = main::reader($file); + #print @sensors_data; + @sensors_data = map {$_ =~ s/\s*:\s*\+?/:/;$_} @sensors_data; + foreach (@sensors_data){ + # we get this from gpu_data() + if (/^(amdgpu|intel|nouveau|radeon|.*hwmon)-pci/){ + $b_valid = 0; + $adapter = ''; + next; + } + if (/^(?:(?!amdgpu|intel|nouveau|radeon|.*hwmon).)*-(isa|pci|virtual)-/){ + $b_valid = 1; + $adapter = $1; + next; + } + next if !$b_valid; + my @working = split /:/, $_; + next if !$working[0] || /^Adapter/; + #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 ($_ =~ /^(AMBIENT|M\/B|MB|SIO|SYS).*:([0-9\.]+)[\s°]*(C|F)/i) { + $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 cpu die temp + elsif ($_ =~ /^(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 ($_ =~ /^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 ($_ =~ /^(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 ($_ =~ /^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 ($_ =~ /^(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 (!$sensors{'fan-main'}[1] && $_ =~ /^(CPU|Processor).*:([0-9]+)[\s]RPM/i) { + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + $sensors{'fan-main'}[1] = $2; + } + elsif (!$sensors{'fan-main'}[2] && $_ =~ /^(M\/B|MB|SYS).*:([0-9]+)[\s]RPM/i) { + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + $sensors{'fan-main'}[2] = $2; + } + elsif (!$sensors{'fan-main'}[3] && $_ =~ /(Power|P\/S|POWER).*:([0-9]+)[\s]RPM/i) { + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + $sensors{'fan-main'}[3] = $2; + } + elsif (!$sensors{'fan-main'}[4] && $_ =~ /(SODIMM).*:([0-9]+)[\s]RPM/i) { + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + $sensors{'fan-main'}[4] = $2; + } + # 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 ($_ =~ /^(AUX|CASE|CHASSIS).*:([0-9]+)[\s]RPM/i) { + $temp_working = $2; + $sensors{'fan-main'} = () if !$sensors{'fan-main'}; + 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'} = () if !$sensors{'fan-default'}; + $sensors{'fan-default'}[1] = $2; + } + elsif ($_ =~ /^FAN([2-9]|1[0-9]).*:([0-9]+)[\s]RPM/i) { + $fan_working = $2; + $sys_fan_nu = $1; + $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; + } + } + } + if ($extra > 0){ + if ($_ =~ /^[+]?(12 Volt|12V).*:([0-9\.]+)\sV/i) { + $sensors{'volts-12'} = $2; + } + # note: 5VSB is a field name + elsif ($_ =~ /^[+]?(5 Volt|5V):([0-9\.]+)\sV/i) { + $sensors{'volts-5'} = $2; + } + elsif ($_ =~ /^[+]?(3\.3 Volt|3\.3V).*:([0-9\.]+)\sV/i) { + $sensors{'volts-3.3'} = $2; + } + elsif ($_ =~ /^(Vbat).*:([0-9\.]+)\sV/i) { + $sensors{'volts-vbat'} = $2; + } + } + } + # print Data::Dumper::Dumper \%sensors; + %sensors = data_processor(%sensors) if %sensors; + main::log_data('dump','lm-sensors: %sensors',\%sensors) if $b_log; + # print Data::Dumper::Dumper \%sensors; + eval $end if $b_log; + return %sensors; +} + +# oddly, openbsd sysctl actually has hw.sensors data! +sub sysctl_data { + eval $start if $b_log; + my (@data,%sensors); + foreach (@sysctl_sensors){ + if (/^hw.sensors\.([0-9a-z]+)\.(temp|fan|volt)([0-9])/){ + my $sensor = $1; + my $type = $2; + my $number = $3; + my @working = split /:/, $_; + } + last if /^(hw.cpuspeed|hw.vendor|hw.physmem)/; + } + + %sensors = data_processor(%sensors) if %sensors; + main::log_data('dump','%sensors',\%sensors) if $b_log; + # print Data::Dumper::Dumper \%sensors; + 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 data_processor { + eval $start if $b_log; + my (%sensors) = @_; + my ($cpu_temp,$cpu2_temp,$cpu3_temp,$cpu4_temp,$index_count_fan_default, + $index_count_fan_main,$mobo_temp,$psu_temp) = (0,0,0,0,0,0,0,0); + my ($fan_type,$i,$j) = (0,0,0); + my $temp_diff = 20; # for C, handled for F after that is determined + my (@fan_main,@fan_default); + # 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'}; + #} + } + else { + $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] = ''; + # 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'}; + $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, + '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; + return %sensors; +} +sub gpu_data { + eval $start if $b_log; + return @gpudata if $b_gpudata; + my ($cmd,@data,@data2,$path,@screens,$temp); + my ($j) = (0); + 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; + @data = (@data,@data2); + $j = scalar @gpudata; + $gpudata[$j] = ({}); + foreach my $item (@data){ + if ($item =~ /^\s*Attribute\s\'([^']+)\'\s.*:\s*([\S]+)\.$/){ + my $attribute = $1; + my $value = $2; + $gpudata[$j]{'type'} = 'nvidia'; + $gpudata[$j]{'speed-unit'} = '%'; + $gpudata[$j]{'screen'} = $screen; + if (!$gpudata[$j]{'temp'} && $attribute eq 'GPUCoreTemp'){ + $gpudata[$j]{'temp'} = $value; + } + elsif (!$gpudata[$j]{'ram'} && $attribute eq 'VideoRam'){ + $gpudata[$j]{'ram'} = $value; + } + elsif (!$gpudata[$j]{'clock'} && $attribute eq 'GPUCurrentClockFreqs'){ + $gpudata[$j]{'clock'} = $value; + } + elsif (!$gpudata[$j]{'bus'} && $attribute eq 'PCIBus'){ + $gpudata[$j]{'bus'} = $value; + } + elsif (!$gpudata[$j]{'bus-id'} && $attribute eq 'PCIDevice'){ + $gpudata[$j]{'bus-id'} = $value; + } + elsif (!$gpudata[$j]{'fan-speed'} && $attribute eq 'GPUCurrentFanSpeed'){ + $gpudata[$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 @gpudata; + $gpudata[$j] = ({}); + my $value = $1; + $gpudata[$j]{'type'} = 'amd'; + $gpudata[$j]{'temp'} = $value; + } + } + } + if (@sensors_data){ + my ($b_found,$holder) = (0,''); + foreach (@sensors_data){ + next if (/^Adapter:/ || /^\s*$/); + if (/^(amdgpu|intel|nouveau|radeon)-pci-(.*)/){ + $b_found = 1; + $holder = $1; + $j = scalar @gpudata; + } + if (/^(?:(?!amdgpu|.*hwmon|intel|nouveau|radeon).)*-(pci|virtual|isa)-(.*)/){ + $b_found = 0; + $holder = ''; + } + if ($b_found){ + if (/^temp.*:([0-9]+).*(C|F)/){ + $gpudata[$j]{'temp'} = $1; + $gpudata[$j]{'type'} = $holder; + $gpudata[$j]{'unit'} = $2; + } + if (/^fan.*:([0-9]+).*(RPM)?/){ + $gpudata[$j]{'fan-speed'} = $1; + # NOTE: we test for nvidia %, everything else stays with nothing + $gpudata[$j]{'speed-unit'} = ''; + } + main::log_data('dump','sensors output: video: @gpudata',\@gpudata); + } + } + } + # we'll probably use this data elsewhere so make it a one time call + $b_gpudata = 1; + # print Data::Dumper::Dumper \@gpudata; + eval $end if $b_log; + return @gpudata; +} +} + +## SlotData +{ +package SlotData; + +sub get { + eval $start if $b_log; + my (@data,@rows,$key1,$val1); + my $num = 0; + my $ref = $alerts{'dmidecode'}; + if ( $$ref{'action'} eq 'use' && (!$b_arm || $b_slot_tool )){ + @rows = slot_data(); + } + elsif ($b_arm && !$b_slot_tool){ + $key1 = 'ARM'; + $val1 = main::row_defaults('arm-pci',''); + @rows = ({main::key($num++,$key1) => $val1,}); + } + elsif ( $$ref{'action'} ne 'use'){ + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + $key1 = ucfirst($key1); + @rows = ({main::key($num++,$key1) => $val1,}); + } + eval $end if $b_log; + return @rows; +} +sub slot_data { + eval $start if $b_log; + my (@data,@rows); + my $num = 0; + foreach (@dmi){ + $num = 1; + my @ref = @$_; + if ($ref[0] == 9){ + my ($designation,$id,$length,$type,$usage) = ('','','','',''); + # skip first two row, we don't need that data + splice @ref, 0, 2 if @ref; + my $j = scalar @rows; + foreach my $item (@ref){ + if ($item !~ /^~/){ # skip the indented rows + my @value = split /:\s+/, $item; + if ($value[0] eq 'Type'){ + $type = $value[1]; + } + if ($value[0] eq 'Designation'){ + $designation = $value[1]; + } + if ($value[0] eq 'Current Usage'){ + $usage = $value[1]; + + } + if ($value[0] eq 'ID'){ + $id = $value[1]; + } + if ($extra > 1 && $value[0] eq 'Length'){ + $length = $value[1]; + } + } + } + if ($type){ + $id = 'N/A' if ($id eq '' ); + if ($type eq 'Other' && $designation){ + $type = $designation; + } + elsif ($type && $designation) { + $type = "$type $designation"; + } + @data = ( + { + main::key($num++,'Slot') => $id, + main::key($num++,'type') => $type, + main::key($num++,'status') => $usage, + }, + ); + @rows = (@rows,@data); + if ($extra > 1 ){ + $rows[$j]{main::key($num++,'length')} = $length; + } + } + } + } + if (!@rows){ + my $key = 'Message'; + @data = ({ + main::key($num++,$key) => main::row_defaults('pci-slot-data',''), + },); + @rows = (@rows,@data); + } + eval $end if $b_log; + return @rows; +} +} + +## UnmountedData +{ +package UnmountedData; + +sub get { + eval $start if $b_log; + my (@data,@rows,$key1,$val1); + my $num = 0; + if ($bsd_type){ + $key1 = 'Message'; + $val1 = main::row_defaults('unmounted-data-bsd'); + } + else { + if (my $file = main::system_files('partitions')){ + @data = unmounted_data($file); + if (!@data){ + $key1 = 'Message'; + $val1 = main::row_defaults('unmounted-data'); + } + else { + @rows = create_output(@data); + } + } + else { + $key1 = 'Message'; + $val1 = main::row_defaults('unmounted-file'); + } + } + if (!@rows && $key1){ + @rows = ({main::key($num++,$key1) => $val1,}); + } + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my (@unmounted) = @_; + my (@data,@rows,$fs); + my $num = 0; + @unmounted = sort { $a->{'dev-base'} cmp $b->{'dev-base'} } @unmounted; + foreach my $ref (@unmounted){ + my %row = %$ref; + $num = 1; + my @data2 = main::get_size($row{'size'}) if (defined $row{'size'}); + my $size = (@data2) ? $data2[0] . ' ' . $data2[1]: 'N/A'; + if ($row{'fs'}){ + $fs = lc($row{'fs'}); + } + else { + if (main::check_program('file')){ + $fs = ($b_root) ? 'N/A' : main::row_defaults('root-required'); + } + else { + $fs = 'requires file'; + } + } + @data = ({ + main::key($num++,'ID') => , "/dev/$row{'dev-base'}", + main::key($num++,'size') => , $size, + main::key($num++,'fs') => , $fs, + main::key($num++,'label') => , $row{'label'}, + main::key($num++,'uuid') => , $row{'uuid'}, + }); + @rows = (@rows,@data); + } + eval $end if $b_log; + return @rows; +} +sub unmounted_data { + eval $start if $b_log; + my ($file) = @_; + my ($fs,$label,$size,$uuid,@data,%part,@unmounted); + my @mounted = ('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 @mounts = main::reader($file,'strip'); + my $num = 0; + PartitionData::set_lsblk() if !$bsd_type && !$b_lsblk; + # set labels, uuid, gpart + PartitionData::partition_data() if !$b_partitions; + PartitionData::set_label_uuid() if !$b_label_uuid; + RaidData::raid_data() if !$b_raid; + @mounted = get_mounted(@mounted); + foreach (@mounts){ + my @working = split /\s+/, $_; + ($fs,$label,$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 + if ( $working[-1] !~ /^(nvme[0-9]+n|mmcblk|mtdblk|mtdblock)[0-9]+$/ && + $working[-1] =~ /[a-z][0-9]+$|dm-[0-9]+$/ && $working[-1] !~ /loop/ && + !(grep {$working[-1] =~ /$_/} @mounted)){ + %part = PartitionData::check_lsblk($working[-1],0) if (@lsblk && $working[-1]); + if (%part){ + $fs = $part{'fs'}; + $label = $part{'label'}; + $uuid = $part{'uuid'}; + $size = $part{'size'} if $part{'size'} && !$working[2]; + } + $size ||= $working[2]; + $fs = unmounted_filesystem($working[-1]) if !$fs; + $label = PartitionData::get_label("/dev/$working[-1]") if !$label; + $uuid = PartitionData::get_uuid("/dev/$working[-1]") if !$uuid; + @data = ({ + 'dev-base' => $working[-1], + 'fs' => $fs, + 'label' => $label, + 'size' => $size, + 'uuid' => $uuid, + }); + @unmounted = (@unmounted,@data); + } + } + # print Data::Dumper::Dumper @unmounted; + 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 (@mounted) = @_; + foreach my $ref (@partitions){ + my %row = %$ref; + push @mounted, $row{'dev-base'} if $row{'dev-base'}; + } + foreach my $ref (@raid){ + my %row = %$ref; + my $ref2 = $row{'arrays'}; + # we want to not show md0 etc in unmounted report + push @mounted, $row{'id'} if $row{'id'}; + my @arrays = (ref $ref2 eq 'ARRAY' ) ? @$ref2 : (); + @arrays = grep {defined $_} @arrays; + foreach my $array (@arrays){ + my %row2 = %$array; + my $ref3 = $row2{'components'}; + my @components = (ref $ref3 eq 'ARRAY') ? @$ref3 : (); + foreach my $component (@components){ + my @temp = split /~/, $component; + push @mounted, $temp[0]; + } + } + } + eval $end if $b_log; + return @mounted; +} +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','swap','btrfs', + 'ffs','hammer','hfs\+','hfs\splus','hfs\sextended\sversion\s[1-9]','hfsj', + 'hfs','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("$sudo$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; +} +} + +## UsbData +{ +package UsbData; + +sub get { + eval $start if $b_log; + my (@data,@rows,$key1,$val1); + my $num = 0; + my $ref = $alerts{'lsusb'}; + my $ref2 = $alerts{'usbdevs'}; + if ( $$ref{'action'} ne 'use' && $$ref2{'action'} ne 'use'){ + if ($os eq 'linux' ){ + $key1 = $$ref{'action'}; + $val1 = $$ref{$key1}; + } + else { + $key1 = $$ref2{'action'}; + $val1 = $$ref2{$key1}; + } + $key1 = ucfirst($key1); + @rows = ({main::key($num++,$key1) => $val1,}); + } + else { + @rows = usb_data(); + if (!@rows){ + my $key = 'Message'; + @data = ({ + main::key($num++,$key) => main::row_defaults('usb-data',''), + },); + @rows = (@rows,@data); + } + } + eval $end if $b_log; + return @rows; +} +sub usb_data { + eval $start if $b_log; + return if ! @usb; + my (@data,@row,@rows,$bus_id,$chip_id,$speed,$protocol,$class,$vendor,$product); + my $num = 0; + my $j = 0; + # note: the data has been presorted in set_lsusb_data by: + # bus id then device id, so we don't need to worry about the order + foreach my $ref (@usb){ + my @id = @$ref; + $j = scalar @rows; + $num = 1; + $bus_id = "$id[0]:$id[1]"; + $chip_id = $id[2]; + my $b_hub = 0; + # it's a hub + if ($id[1] == 1){ + foreach my $line (@id){ + #print "$line\n"; + @row = split /:/, $line; + next if ! defined $row[0]; + if ($row[0] eq 'bcdUSB' && defined $row[1]){ + $speed = ($row[1] =~ /^[0-9,\.]+$/) ? sprintf("%1.1f",$row[1]) : $row[1]; + } + elsif ($row[0] eq '~bInterfaceProtocol' && $row[2] ){ + $protocol = $row[2]; + } + } + $protocol ||= 'N/A'; + $speed ||= 'N/A'; + #print "pt0:$protocol\n"; + @data = ({ + main::key($num++,'Hub') => $bus_id, + main::key($num++,'usb') => $speed, + main::key($num++,'type') => $protocol, + },); + @rows = (@rows,@data); + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = $chip_id; + } + } + # it's a device + else { + ($class,$product,$protocol,$vendor,$speed) = ('','','','',''); + foreach my $line (@id){ + @row = split /:/, $line; + next if ! defined $row[0]; + if ($row[0] eq 'bcdUSB' && defined $row[1]){ + $speed = sprintf("%.1f",$row[1]); + } + elsif ($row[0] eq 'bDeviceClass' && defined $row[1] && $row[1] == 9){ + $b_hub = 1; + } + elsif ($row[0] eq 'idVendor' && $row[2]){ + $vendor = main::cleaner($row[2]); + } + elsif ($row[0] eq 'idProduct' && $row[2]){ + $product = main::cleaner($row[2]); + } + # we want hubs to cascade to last item + elsif ($row[0] eq '~bInterfaceClass' && $row[2] && defined $row[1] && $row[1] != 9){ + $class = main::cleaner($row[2]); + } + elsif ($row[0] eq '~bInterfaceProtocol' && defined $row[2]){ + $protocol = $row[2]; + $protocol =~ s/none//i if $protocol; + last if $class; + } + } + if ( $b_hub ){ + if ($vendor && $product){ + $protocol = "$vendor $product"; + } + elsif (!$product && $protocol && $vendor){ + $protocol = "$vendor $protocol"; + } + $speed ||= 'N/A'; + $protocol ||= 'N/A'; + #print "pt2:$protocol\n"; + @data = ({ + main::key($num++,'Hub') => $bus_id, + main::key($num++,'usb') => $speed, + main::key($num++,'type') => $protocol, + },); + @rows = (@rows,@data); + } + else { + if ($vendor && $product){ + if ($product !~ /$vendor/){ + $product = "$vendor $product"; + } + } + elsif (!$product && !$vendor && $protocol){ + $product = $protocol; + } + elsif (!$product){ + $product = $vendor; + } + # bInterfaceProtocol:0 but $row[2] undefined + #print "pt3:$class:$product\n"; + # for we want Mass Storage Device instead of Bulk-Only + # we want to filter out certain protocol values that are less + # informative than the class type. + if ($protocol && $class && $class ne $protocol && protocol_filter($protocol) ){ + $class = $protocol; + } + $class ||= 'N/A'; + #print "pt3:$class:$product\n"; + $product ||= 'N/A'; + $speed ||= 'N/A'; + $rows[$j]{main::key($num++,'Device')} = $product; + $rows[$j]{main::key($num++,'bus ID')} = $bus_id; + if ($extra > 0){ + $rows[$j]{main::key($num++,'usb')} = $speed; + } + $rows[$j]{main::key($num++,'type')} = $class; + } + if ($extra > 1){ + $rows[$j]{main::key($num++,'chip ID')} = $chip_id; + } + } + } + #print Data::Dumper::Dumper \@rows; + eval $end if $b_log; + return @rows; +} +sub protocol_filter { + eval $start if $b_log; + my ($string) = @_; + $string =~ s/Bulk-Only|streaming|Bidirectional|None//i if $string; + eval $end if $b_log; + return $string; +} +} + +## add metric / imperial (us) switch +## WeatherData +{ +package WeatherData; + +sub get { + eval $start if $b_log; + my (@rows,$key1,$val1); + my $num = 0; + @rows = create_output(); + eval $end if $b_log; + return @rows; +} +sub create_output { + eval $start if $b_log; + my $num = 0; + my (@data,@location,@rows,%weather,); + my ($conditions) = ('NA'); + 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::apply_filter($location_string); + @location = ($show{'weather-location'},$location_string,''); + } + else { + @location = get_location(); + if (!$location[0]) { + return @rows = ({ + main::key($num++,'Message') => main::row_defaults('weather-null','current location'), + }); + } + } + %weather = get_weather(@location); + if (!$weather{'weather'}) { + return @rows = ({ + main::key($num++,'Message') => main::row_defaults('weather-null','weather data'), + }); + } + $conditions = "$weather{'weather'}"; + my $temp = unit_output($weather{'temp'},$weather{'temp-c'},'C',$weather{'temp-f'},'F'); + @data = ({ + main::key($num++,'Temperature') => $temp, + main::key($num++,'Conditions') => $conditions, + },); + @rows = (@rows,@data); + if ($extra > 0){ + my $pressure = unit_output($weather{'pressure'},$weather{'pressure-mb'},'mb',$weather{'pressure-in'},'in'); + my $wind = wind_output($weather{'wind'},$weather{'wind-direction'},$weather{'wind-mph'},$weather{'wind-ms'}, + $weather{'wind-gust-mph'},$weather{'wind-gust-ms'}); + $rows[0]{main::key($num++,'Wind')} = $wind; + $rows[0]{main::key($num++,'Humidity')} = $weather{'humidity'}; + $rows[0]{main::key($num++,'Pressure')} = $pressure; + } + if ($extra > 1){ + if ($weather{'heat-index'}){ + my $heat = unit_output($weather{'heat-index'},$weather{'heat-index-c'},'C',$weather{'heat-index-f'},'F'); + $rows[0]{main::key($num++,'Heat Index')} = $heat; + } + if ($weather{'windchill'}){ + my $chill = unit_output($weather{'windchill'},$weather{'windchill-c'},'C',$weather{'windchill-f'},'F'); + $rows[0]{main::key($num++,'Wind Chill')} = $chill ; + } + if ($weather{'dewpoint'}){ + my $dew = unit_output($weather{'dewpoint'},$weather{'dewpoint-c'},'C',$weather{'dewpoint-f'},'F'); + $rows[0]{main::key($num++,'Dew Point')} = $dew; + } + } + if ($extra > 2){ + if (!$show{'filter'}){ + $rows[0]{main::key($num++,'Location')} = $location[1]; + $rows[0]{main::key($num++,'altitude')} = elevation_output($weather{'elevation-m'},$weather{'elevation-ft'}); + } + } + $rows[0]{main::key($num++,'Current Time')} = $weather{'date-time'}; + if ($extra > 2){ + $rows[0]{main::key($num++,'Observation Time')} = $weather{'observation-time-local'}; + } + eval $end if $b_log; + return @rows; +} +sub elevation_output { + 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 unit_output { + eval $start if $b_log; + my ($primary,$metric,$m_unit,$imperial,$i_unit) = @_; + my $result = ''; + if ($metric && $imperial && $weather_unit eq 'mi' ){ + $result = "$metric $m_unit ($imperial $i_unit)"; + } + elsif ($metric && $imperial && $weather_unit eq 'im' ){ + $result = "$imperial $i_unit ($metric $m_unit)"; + } + elsif ($metric && $weather_unit eq 'm' ){ + $result = "$metric $m_unit"; + } + elsif ($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 wind_output { + 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 $mph && !$ms; + $mph = $ms * 2.23694 if $ms && !$mph; + $kmh = sprintf("%.0f", 18 * $ms / 5) if $ms; + $ms = sprintf("%.1f", $ms ) if $ms; # very low mph speeds yield 0, which is wrong + $mph = sprintf("%.0f", $mph) if $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 (!$mph && $primary){ + $result = $primary; + } + elsif ($mph && $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,%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.txt"; + if (-f $file_cached){ + @weather_data = main::reader($file_cached); + $freshness = (split /\^\^/, $weather_data[0])[1]; + #print "$now:$freshness\n"; + } + if (!$freshness || $freshness < ($now - 90) ) { + @weather_data = (); # reset so we don't write the previous data to file!! + my $url = "http://api.wunderground.com/auto/wui/geo/WXCurrentObXML/index.xml?query=$location[0]"; + my $temp; +# { +# #my $file2 = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/weather-1.xml"; +# # my $file2 = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/feed-oslo-1.xml"; +# local $/; +# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/weather-1.xml"; +# open my $fh, '<', $file or die "can't open $file: $!"; +# $temp = <$fh>; +# } + $temp = main::download_file('stdout',$url); + $temp =~ s/\r|\n\n/\n/g; + my @weather_temp = split /\n/, $temp; + foreach (@weather_temp){ + chomp $_; + $_ =~ s/<\/[^>]+>//; + $_ =~ s/.*icon.*|\r//g; + $_ =~ s/\s\s/ /g; + $_ =~ s/^\s+|\s+$//g; + $_ =~ s/>/^^/; + $_ =~ s/^<|NA$//g; + $_ =~ s/^(current|credit|terms|image|title|link|.*_url).*//; + push @weather_data, $_ if $_ !~ /^\s*$/; + } + unshift (@weather_data,("timestamp^^$now")); + main::writer($file_cached,\@weather_data); + #print "$file_cached: download/cleaned\n"; + } + #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 '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]; + } + # 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 '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' ){ + $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 'observation_time_rfc822' ){ + $weather{'observation-time-gmt'} = $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 '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 '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 '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'}){ + $weather{'observation-time-local'} =~ /^(.*)\s([\S]+)$/; + $tz = $2; + # very clever trick, just make the system think it's in the + # remote timezone for this local block only + local $ENV{'TZ'} = $tz; + $date_time = POSIX::strftime "%c", localtime; + $weather{'date-time'} = $date_time; + } + else { + $date_time = POSIX::strftime "%c", localtime; + $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'}){ + $weather{'observation-time-local'} = POSIX::strftime "%c", localtime($weather{'observation-epoch'}); + } + return %weather; + eval $end if $b_log; +} +sub get_location { + eval $start if $b_log; + 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 (-f $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 = "$ENV{'HOME'}/bin/scripts/inxi/data/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/ 2 && @desktop){ + set_info_data(); + } + if ($b_display && !$b_force_display && $extra > 1){ + get_wm(); + } + main::log_data('dump','@desktop', \@desktop) if $b_log; + # ($b_xprop,$kde_session_version,$xdg_desktop,@data,@xprop) = undef; + return @desktop; +} +sub get_kde_data { + eval $start if $b_log; + my ($program,@version_data,@version_data2); + my $kde_full_session = ($ENV{'KDE_FULL_SESSION'}) ? $ENV{'KDE_FULL_SESSION'} : ''; + return 1 if ($xdg_desktop ne 'kde' && !$kde_session_version && $kde_full_session ne 'true' ); + # 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 + if ($xdg_desktop eq 'kde' || $kde_session_version ){ + if ($kde_session_version && $kde_session_version <= 4){ + @data = main::program_values("kded$kde_session_version"); + if (@data){ + $desktop[0] = $data[3]; + $desktop[1] = main::program_version("kded$kde_session_version",$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] && ($program = main::check_program("kded$kde_session_version")) ){ + @version_data = main::grabber("$program --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("kded$kde_session_version"))){ + @version_data = main::grabber("$program --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::row_defaults('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] && ($program = 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. + @version_data = main::grabber("$program --version 2>/dev/null"); + $desktop[3] = main::awk(\@version_data,'^Using Qt version',4) if @version_data; + } + $desktop[2] = 'Qt'; + } + } + # KDE_FULL_SESSION property is only available since KDE 3.5.5. + elsif ($kde_full_session eq 'true'){ + @version_data = 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); + main::set_ps_gui() if ! $b_ps_gui; + if ($desktop_session eq 'trinity' || $xdg_desktop eq 'trinity' || (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; + } + } + elsif ($xdg_desktop eq 'unity'){ + @data = main::program_values('unity'); + $desktop[0] = $data[3]; + $desktop[0] ||= 'Unity'; + $desktop[1] = main::program_version('cinnamon',$data[0],$data[1],$data[2],$data[5],$data[6]); + #set_gtk_data() if $extra > 1; + } + elsif ( $xdg_desktop =~ /budgie/ ){ + @data = main::program_values('budgie'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('budgie-desktop',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # debian package: lxde-core. + # NOTE: some distros fail to set XDG data for root + elsif ( $xdg_desktop =~ /^(lxde|razor|lxqt)$/ || (grep {/^(razor-session|lxsession|lxqt-session)$/} @ps_gui)){ + # note: openbox-lxde --version may be present, but returns openbox data + if ($xdg_desktop eq 'lxde' || (grep {/^lxsession$/} @ps_gui )){ + @data = main::program_values('lxde'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('lxpanel',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # NOTE: lxqt-about opens a gui dialog + elsif ($xdg_desktop eq 'razor' || $xdg_desktop eq 'lxqt' || (grep {/^(razor-desktop|lxqt-session)$/} @ps_gui)) { + if (grep {/^lxqt-session$/} @ps_gui){ + @data = main::program_values('lxqt'); + $desktop[0] = $data[3]; + # BAD: lxqt-about opens dialogue, sigh + $desktop[1] = main::program_version('lxqt-panel',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif (grep {/^razor-session$/} @ps_gui){ + $desktop[0] = 'Razor-Qt'; + } + else { + $desktop[0] = 'LX-Qt-Variant'; + } + set_qt_data() if $extra > 1; + } + } + # note, X-Cinnamon value strikes me as highly likely to change, so just + # search for the last part + elsif ( $xdg_desktop =~ /cinnamon/ ){ + @data = main::program_values('cinnamon'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('cinnamon',$data[0],$data[1],$data[2],$data[5],$data[6]); + #set_gtk_data() if $extra > 1; + } + elsif ($xdg_desktop eq 'pantheon' || $desktop_session eq 'pantheon'){ + @data = main::program_values('pantheon'); + $desktop[0] = $data[3]; + #$desktop[1] = main::program_version('pantheon',$data[0],$data[1],$data[2],$data[5],$data[6]); + #set_gtk_data() if $extra > 1; + } + eval $end if $b_log; +} +sub get_env_xprop_de_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(); + # 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 'muffinr' but that's not part of cinnom + if ( (main::check_program('muffin') || main::check_program('cinnamon-session') ) && + ($b_xprop && main::awk(\@xprop,'_muffin') )){ + @data = main::program_values('cinnamon'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('cinnamon',$data[0],$data[1],$data[2],$data[5],$data[6]); + #set_gtk_data() if $extra > 1; + $desktop[0] ||= 'Cinnamon'; + } + elsif ($xdg_desktop eq 'mate' || ( $b_xprop && main::awk(\@xprop,'_marco') )){ + # NOTE: mate-about reported wrong version, 1.18.0 when actual was 1.18.2 + if ($program = main::check_program('mate-session') ) { + $value = 'mate-session'; + } + if ($value){ + @data = main::program_values($value); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version($program,$data[0],$data[1],$data[2],$data[5],$data[6]); + } + #set_gtk_data() if $extra > 1; + $desktop[0] ||= 'MATE'; + } + # 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... + elsif ($xdg_desktop eq 'gnome' || $ENV{'GNOME_DESKTOP_SESSION_ID'} || + (main::check_program('gnome-shell') && $b_xprop && main::awk(\@xprop,'^_gnome') ) ){ + if ($program = main::check_program('gnome-about') ) { + @data = main::program_values('gnome-about'); + $desktop[1] = main::program_version('gnome-about',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif ($program = main::check_program('gnome-shell') ) { + @data = main::program_values('gnome-shell'); + $desktop[1] = main::program_version('gnome-shell',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # set_gtk_data() if $extra > 1; + $desktop[0] = ( $data[3] ) ? $data[3] : 'Gnome'; + } + eval $end if $b_log; +} +sub get_xprop_de_data { + eval $start if $b_log; + my ($program,@version_data,$version); + #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 to xfce to solve some other + # issues so don't test for that. $xdg_desktop eq 'xfce' + # 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_wm() will handle alternate wm detections. + if ((main::check_program('xfdesktop')) && main::awk(\@xprop,'^(xfdesktop|xfce)' )){ + # this is a very expensive test that doesn't usually result in a find + # talk to xfce to see what id they will be using for xfce 5 +# if (main::awk(\@xprop, 'xfce4')){ +# $version = '4'; +# } + if (main::awk(\@xprop, 'xfce5')){ + $version = '5'; + } + else { + $version = '4'; + } + @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] ){ + @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]; + } + } + elsif (main::check_program('enlightenment') && main::awk(\@xprop,'enlightenment' )){ + $desktop[0] = 'Enlightenment'; + # no -v or --version but version is in xprop -root + # ENLIGHTENMENT_VERSION(STRING) = "Enlightenment 0.16.999.49898" + $desktop[1] = main::awk(\@xprop,'enlightenment_version',2,'\s+=\s+' ); + $desktop[1] = (split /"/, $desktop[1])[1] if $desktop[1]; + $desktop[1] = (split /\s+/, $desktop[1])[1] if $desktop[1]; + } + # must come right after xfce + elsif (main::check_program('icewm') && main::awk(\@xprop,'icewm' )){ + @data = main::program_values('icewm'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('icewm',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # debian package: i3-wm + elsif (main::check_program('i3') && main::awk(\@xprop,'^i3_' )){ + @data = main::program_values('i3'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('i3',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif (main::check_program('mwm') && main::awk(\@xprop,'^_motif' )){ + @data = main::program_values('mwm'); + $desktop[0] = $data[3]; + # $desktop[1] = main::program_version('mwm',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # debian package name: wmaker + elsif (main::check_program('WindowMaker') && main::awk(\@xprop,'^_?windowmaker' )){ + @data = main::program_values('wmaker'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('wmaker',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif (main::check_program('wm2') && main::awk(\@xprop,'^_wm2' )){ + @data = main::program_values('wm2'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('wm2',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif (main::check_program('herbstluftwm') && main::awk(\@xprop,'herbstluftwm' )){ + @data = main::program_values('herbstluftwm'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('herbstluftwm',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif ( (main::check_program('blackbox') || main::check_program('fluxbox')) && main::awk(\@xprop,'blackbox_pid' )){ + if (@ps_gui && (grep {/^fluxbox$/} @ps_gui )){ + @data = main::program_values('fluxbox'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('fluxbox',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + else { + @data = main::program_values('blackbox'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('blackbox',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + } + elsif (main::check_program('openbox') && main::awk(\@xprop,'openbox_pid' )){ + @data = main::program_values('openbox'); + $desktop[0] = $data[3]; + $desktop[1] = main::program_version('openbox',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + elsif (main::check_program('amiwm') && main::awk(\@xprop,'amiwm' )){ + @data = main::program_values('amiwm'); + $desktop[0] = $data[3]; + #$desktop[1] = main::program_version('openbox',$data[0],$data[1],$data[2],$data[5],$data[6]); + } + # 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 !$b_ps_gui; + if (@ps_gui){ + # 1 check program; 2 search; 3 values; 4 version; 5 -optional: print value + my @desktops =( + ['fluxbox','fluxbox','fluxbox','fluxbox'], + ['fvwm-crystal','fvwm-crystal','fvwm-crystal','fvwm'], + ['fvwm2','fvwm2','fvwm2','fvwm2'], + ['fvwm','fvwm','fvwm','fvwm'], + ['pekwm','pekwm','pekwm','pekwm'], + ['awesome','awesome','awesome','awesome'], + ['blackbox','blackbox','blackbox','blackbox'], + ['openbox','openbox','openbox','openbox'], + # not in debian apt + ['scrotwm','scrotwm','scrotwm','scrotwm'], + ['spectrwm','spectrwm','spectrwm','spectrwm'], + ['twm','twm','twm','twm'], + # note: built from source, but I assume it will show: /usr/bin/dwm + ['dwm','dwm','dwm','dwm'], + # not in debian apt, current is wmii, version 3 + ['wmii2','wmii2','wmii2','wmii2'], + ['wmii','wmii','wmii','wmii'], + ['9wm','9wm','9wm','9wm'], + ['amiwm','amiwm','amiwm','amiwm'], + ['flwm','flwm','flwm','flwm'], + ['jwm','jwm','jwm','jwm'], + ['mwm','mwm','mwm','mwm'], + ['notion','notion','notion','notion'], + ['ratpoison','ratpoison','ratpoison','ratpoison'], + ['sawfish','sawfish','sawfish','sawfish'], + ['matchbox-window-manager','matchbox-window-manager', + 'matchbox-window-manager','matchbox-window-manager'], + ['afterstep','afterstep','afterstep','afterstep'], + ['WindowMaker','WindowMaker','wmaker','wmaker'], + ['windowlab','windowlab','windowlab','windowlab'], + ['xmonad','xmonad','xmonad','xmonad'], + ); + foreach my $ref (@desktops){ + my @item = @$ref; + # no need to use check program with short list of ps_gui + # if ( main::check_program($item[0]) && (grep {/^$item[1]$/} @ps_gui)){ + if (grep {/^$item[1]$/} @ps_gui){ + @data = main::program_values($item[2]); + $desktop[0] = $data[3]; + if ($data[1] && $data[2]){ + $desktop[1] = main::program_version($item[3],$data[0],$data[1],$data[2],$data[5],$data[6]); + } + last; + } + } + } + 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 + if (!$desktop[3] && ($program = main::check_program("qmake"))){ + @version_data = main::grabber("$program --version 2>/dev/null"); + $desktop[2] = 'Qt'; + $desktop[3] = main::awk(\@version_data,'^Using Qt version',4) if @version_data; + } + if (!$desktop[3] && ($program = main::check_program("qtdiag") )){ + @data = main::program_values('qtdiag'); + $desktop[3] = main::program_version($program,$data[0],$data[1],$data[2],$data[5],$data[6]); + $desktop[2] = $data[3]; + } + 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 (!$b_wmctrl) { + get_wm_main(); + } + if ( (!$desktop[5] || $b_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 = 'blackbox|compiz|kwin_wayland|kwin_x11|kwin|marco|muffin|'; + $wms .= 'openbox|herbstluftwm|twin|wm2|windowmaker|i3'; + foreach (@xprop){ + if (/\b($wms)\b/){ + $working = $1; + $working = 'wmaker' if $working eq 'windowmaker'; + last; + } + } + } + if (!$desktop[5]){ + main::set_ps_gui() if ! $b_ps_gui; + # order matters, see above logic + $wms = '9wm|afterstep|amiwm|awesome|budgie-wm|compiz|fluxbox|blackbox|dwm|'; + $wms .= 'flwm|fvwm-crystal|fvwm2|fvwm|gala|gnome-shell|i3|jwm|'; + $wms .= 'twin|kwin_wayland|kwin_x11|kwin|matchbox-window-manager|marco|'; + $wms .= 'muffin|mutter|metacity|mwm|notion|openbox|ratpoison|sawfish|scrotwm|spectrwm|'; + $wms .= 'twm|windowlab|WindowMaker|wm2|wmii2|wmii|xfwm4|xfwm5|xmonad'; + foreach (@ps_gui){ + if (/^($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 (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_values($temp); + return if !@data; + # print Data::Dumper::Dumper \@data; + $desktop[5] = $data[3] if $type eq 'manual'; + # note: if values returns 0 for 1 and 2, it doesn't support versioning + if ($extra > 2 && $data[1] && $data[2]){ + my $version = main::program_version($temp,$data[0],$data[1],$data[2],$data[5],$data[6]); + $desktop[6] = $version if $version; + } + } + eval $end if $b_log; +} + +sub set_gtk_data { + eval $start if $b_log; + my ($version,$program,@data); + # this is a hack, and has to be changed with every toolkit version change, and + # only dev systems # have this installed, but it's a cross distro command try it. + if ($program = main::check_program('pkg-config')){ + @data = main::grabber("$program --modversion gtk+-4.0 2>/dev/null"); + $version = main::awk(\@data,'\S'); + # note: opensuse gets null output here, we need the command to get version and output sample + if ( !$version ){ + @data = main::grabber("$program --modversion gtk+-3.0 2>/dev/null"); + $version = main::awk(\@data,'\S'); + } + if ( !$version ){ + @data = main::grabber("$program --modversion gtk+-2.0 2>/dev/null"); + $version = main::awk(\@data,'\S'); + } + } + # now let's go to more specific version tests, this will never cover everything and that's fine. + if (!$version){ + # we'll try some known package managers next. dpkg will handle a lot of distros + # this is the most likely order as of: 2014-01-13. Not going to try to support all + # package managers too much work, just the very biggest ones. + if ($program = main::check_program('dpkg')){ + @data = main::grabber("$program -s libgtk-3-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s+'); + # just guessing on gkt 4 package name + if (!$version){ + @data = main::grabber("$program -s libgtk-4-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s+'); + } + if (!$version){ + @data = main::grabber("$program -s libgtk2.0-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s+'); + } + } + elsif ($program = main::check_program('pacman')){ + @data = main::grabber("$program -Qi gtk3 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + # just guessing on gkt 4 package name + if (!$version){ + @data = main::grabber("$program -Qi gtk4 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + } + if (!$version){ + @data = main::grabber("$program -Qi gtk2 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + } + } + elsif ($program = main::check_program('rpm')){ + @data = main::grabber("$program -qi libgtk-3-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + # just guessing on gkt 4 package name + if (!$version){ + @data = main::grabber("$program -qi libgtk-4-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + } + if (!$version){ + @data = main::grabber("$program -qi libgtk-3-0 2>/dev/null"); + $version = main::awk(\@data,'^\s*Version',2,'\s*:\s*'); + } + } + } + $desktop[2] = 'Gtk'; + eval $end if $b_log; +} +sub set_info_data { + eval $start if $b_log; + my (@data,@info,$item); + my $pattern = 'gnome-panel|kicker|lxpanel|lxqt-panel|matchbox-panel|'; + $pattern .= 'mate-panel|plasma-desktop|plasma-netbook|razor-panel|razorqt-panel|'; + $pattern .= 'wingpanel|xfce4-panel|xfce5-panel'; + 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]; + } + } + } + $desktop[4] = join (',', @info) if @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|compiz|enlightenment|^_gnome|herbstluftwm|'; + $pattern .= '^kwin_|^i3_|icewm|_marco|^_motif|_muffin|openbox_pid|'; + $pattern .= '^_?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; +} + +} + +sub get_display_manager { + eval $start if $b_log; + my (@data,@found,$temp,$working,$b_run,$b_vrun,$b_vrunrc); + # ldm - LTSP display manager. Note that sddm does not appear to have a .pid + # extension in Arch note: to avoid positives with directories, test for -f + # explicitly, not -e + my @dms = qw(entranced.pid gdm.pid gdm3.pid kdm.pid ldm.pid + lightdm.pid lxdm.pid mdm.pid nodm.pid pcdm.pid sddm.pid slim.lock + tint2.pid wdm.pid xdm.pid xenodm.pid); + # this is the only one I know of so far that has --version + # lightdm outputs to stderr, so it has to be redirected + my @dms_version = qw(lightdm); + $b_run = 1 if -d "/run"; + # in most linux, /var/run is a sym link to /run, so no need to check it twice + if ( -d "/var/run" ){ + my $rdlink = readlink('/var/run'); + $b_vrun = 1 if !$rdlink || ($rdlink && $rdlink ne '/run'); + $b_vrunrc = 1 if -d "/var/run/rc.d"; + } + foreach my $id (@dms){ + # note: $working will create a dir name out of the dm $id, then + # test if pid is in that note: sddm, in an effort to be unique and special, + # do not use a pid/lock file, but rather a random string inside a directory + # called /run/sddm/ so assuming the existence of the pid inside a directory named + # from the dm. Hopefully this change will not have negative results. + $working = $id; + $working =~ s/\.\S+$//; + # note: there were issues with duplicated dm's in inxi, checking @found corrects it + if ( ( ( $b_run && ( -f "/run/$id" || -d "/run/$working" ) ) || + ( $b_vrun && ( -f "/var/run/$id" || -d "/var/run/$working" ) ) || + ( $b_vrunrc && ( -f "/var/run/rc.d/$working" || -d "/var/run/rc.d/$id" ) ) ) && + ! grep {/$working/} @found ){ + if ($extra > 2 && awk( \@dms_version, $working) && (my $path = main::check_program($working)) ){ + @data = main::grabber("$path --version 2>&1"); + $temp = awk(\@data,'\S',2,'\s+'); + $working .= ' ' . $temp if $temp; + } + push @found, $working; + } + } + if (!@found && grep {/\/usr.*\/x/ && !/\/xprt/} @ps_cmd){ + if (awk (\@ps_cmd, 'startx') ){ + $found[0] = 'startx'; + } + elsif (awk (\@ps_cmd, 'xinit') ){ + $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 + log_data('dump','display manager: @found',\@found) if $b_log; + eval $end if $b_log; + return join ', ', @found if @found; +} + +## Get DistroData +{ +package DistroData; +my (@distro_data,@osr); +sub get { + eval $start if $b_log; + if ($bsd_type){ + get_bsd_os(); + } + else { + get_linux_distro(); + } + eval $end if $b_log; + return @distro_data; +} + +sub get_bsd_os { + eval $start if $b_log; + my ($distro) = (''); + if ($bsd_type eq 'darwin'){ + my $file = '/System/Library/CoreServices/SystemVersion.plist'; + if (-f $file){ + my @data = main::reader($file); + @data = grep {/(ProductName|ProductVersion)/} @data if @data; + @data = grep {//} @data if @data; + @data = map {s/<[\/]?string>//g; } @data if @data; + $distro = join (' ', @data); + } + } + else { + $distro = "$uname[0] $uname[2]"; + } + @distro_data = ($distro,''); + eval $end if $b_log; +} + +sub get_linux_distro { + eval $start if $b_log; + my ($distro,$distro_id,$distro_file,$system_base) = ('','','',''); + my ($b_issue,$b_osr,$b_use_issue,@working); + # order matters! + my @derived = qw(antix-version aptosid-version kanotix-version knoppix-version + pclinuxos-release mandrake-release manjaro-release mx-version pardus-release + porteus-version sabayon-release siduction-version sidux-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|manjaro-release'; + my $os_release_good_s = 'altlinux-release|arch-release|pclinuxos-release|rpi-issue|SuSE-release'; + # 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 + my @distro_files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*'); + my $lsb_release = '/etc/lsb-release'; + my $b_lsb = 1 if -f $lsb_release; + my ($etc_issue,$issue,$lc_issue) = ('','/etc/issue',''); + $b_issue = 1 if -f $issue; + # 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))[0] if -r $issue; + $etc_issue = main::clean_characters($etc_issue); + my $os_release = '/etc/os-release'; + @osr = main::reader($os_release) if -r $os_release; + # debian issue can end with weird escapes like \n \l + # antergos: Antergos Linux \r (\l) + if ($etc_issue){ + $lc_issue = lc($etc_issue) if $etc_issue; + if ($lc_issue =~ /(antergos|grml|linux lite)/){ + $distro_id = $1; + $b_use_issue = 1; + } + elsif ($lc_issue =~ /(raspbian|peppermint)/){ + $distro_id = $1; + $distro_file = $os_release if @osr; + } + } + # 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 && (grep {/manjaro|antergos|chakra|pclinuxos/i} @osr ) ){ + $distro_file = $os_release; + #$system_base = 'Arch Linux'; + } + $distro_id = 'armbian' if grep {/armbian/} @distro_files; + 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; + #$system_base = 'Arch Linux'; + } + my $distro_files_s = join "|", @distro_files; + @working = (@derived,@primary); + foreach my $file (@working){ + if ( "/etc/$file" =~ /($distro_files_s)$/){ + # 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 + # this is for only those distro's with self named release/version files + # because Mint does not use such, it must be done as below + if (@osr && $file =~ /($os_release_good_s)$/){ + $distro_file = $os_release; + } + 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 ( -f '/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) ){ + $distro = get_lsb_release(); + } + elsif ($distro_file && $distro_file eq $os_release){ + $distro = get_os_release(); + $b_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'); + } + else { + $distro = (main::reader($distro_file))[0]; + } + $distro = main::clean_characters($distro) if $distro; + } + # otherwise try the default debian/ubuntu /etc/issue file + elsif ($b_issue){ + if ( !$distro_id && $etc_issue && $lc_issue =~ /(mint|lmde)/ ){ + $distro_id = $1; + $b_use_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_use_issue && @osr){ + $distro = get_os_release(); + $b_osr = 1; + } + elsif (!$b_use_issue && $b_lsb){ + $distro = get_lsb_release(); + } + elsif ($etc_issue) { + $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_osr && @osr){ + $distro = get_os_release(); + $b_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){ + if (!$b_osr && @osr){ + $distro = get_os_release(); + $b_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; + } + } + if ($extra > 0){ + my $base_default = 'antix-version|mx-version'; # osr has base ids + my $base_issue = 'bunsen'; # base only found in issue + my $base_manual = 'kali'; # synthesize, no direct data available + my $base_osr = 'aptosid|grml|siduction'; # osr base, distro id in list of distro files + my $base_osr_issue = 'grml|linux lite'; # osr base, distro id in issue + my $base_upstream_lsb = '/etc/upstream-release/lsb-release'; + my $base_upstream_osr = '/etc/upstream-release/os-release'; + # 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) = (undef,undef); + } + } + elsif ( -r $base_upstream_lsb){ + $system_base = get_lsb_release($base_upstream_lsb); + } + 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'; + } + elsif ($distro_id && $distro_id =~ /(mint)/){ + $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 && $lc_issue && $lc_issue =~ /($base_manual)/){ + my $id = $1; + my %manual = ( + 'kali' => 'Debian testing', + ); + $system_base = $manual{$id}; + } + } + $distro =~ s/Debian/Armbian/ if ($distro && $distro_id eq 'armbian'); + ## finally, if all else has failed, give up + $distro ||= 'unknown'; + @distro_data = ($distro,$system_base); + eval $end if $b_log; +} + +sub get_lsb_release { + eval $start if $b_log; + my ($lsb_file) = @_; + $lsb_file ||= '/etc/lsb-release'; + my ($distro,$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 'DISTRIB_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 'DISTRIB_RELEASE' && $working[1]){ + $release = $working[1]; + } + elsif ($working[0] eq 'DISTRIB_CODENAME' && $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 'DISTRIB_DESCRIPTION' && $working[1]){ + $description = $working[1]; + } + } + if (!$id && !$release && !$codename && $description){ + $distro = $description; + } + else { + $distro = "$id $release $codename"; + $distro =~ s/^\s+|\s\s+|\s+$//g; # get rid of double and trailing spaces + } + eval $end if $b_log; + return $distro; +} +sub get_os_release { + eval $start if $b_log; + my ($base_type) = @_; + my ($base_id,$base_name,$base_version,$distro,$distro_name,$pretty_name, + $lc_name,$name,$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]){ + $pretty_name = $working[1]; + } + elsif ($working[0] eq 'NAME' && $working[1]){ + $name = $working[1]; + $lc_name = lc($name); + } + 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 system base + if ($base_type ){ + if ($working[0] eq 'ID_LIKE' && $working[1]){ + if ($base_type eq 'ubuntu'){ + $working[1] =~ s/ubuntu\sdebian/ubuntu/; + $working[1] = 'ubuntu' if $working[1] eq 'debian'; + } + $base_name = ucfirst($working[1]); + } + elsif ($base_type eq 'ubuntu' && $working[0] eq 'UBUNTU_CODENAME' && $working[1]){ + $base_version = ucfirst($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 ($name && $version_name){ + $distro = $name; + $distro = 'Arco Linux' if $lc_name =~ /^arco/; + if ($version_id && $version_name !~ /$version_id/){ + $distro .= ' ' . $version_id; + } + $distro .= " $version_name"; + } + elsif ($pretty_name && ($pretty_name !~ /tumbleweed/i && $lc_name ne 'arcolinux') ){ + $distro = $pretty_name; + } + elsif ($name){ + $distro = $name; + if ($version_id){ + $distro .= ' ' . $version_id; + } + } + } + # note: mint has varying formats here, some have ubuntu as name, 17 and earlier + else { + # mint 17 used ubuntu os-release, so won't have $base_version + if ($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 = "$base_name $base_id$base_version"; + } + elsif ($base_type eq 'default' && ($pretty_name || ($name && $version_name) ) ){ + $distro = ($name && $version_name) ? "$name $version_name" : $pretty_name; + } + # LMDE has only limited data in os-release, no _LIKE values + elsif ( $base_type eq 'ubuntu' && $lc_name =~ /^(debian|ubuntu)/ && ($pretty_name || ($name && $version_name))){ + $distro = ($name && $version_name) ? "$name $version_name": $pretty_name; + } + } + eval $end if $b_log; + return $distro; +} +# note, these are only for matching derived names, no need to go +# all the way back here, update as new names are known. This is because +# Mint is using UBUNTU_CODENAME without ID data. +sub ubuntu_id { + eval $start if $b_log; + my ($codename) = @_; + $codename = lc($codename); + my ($id) = (''); + my %codenames = ( + '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 ', + ); + $id = $codenames{$codename} if defined $codenames{$codename}; + eval $end if $b_log; + return $id; +} +} +sub get_gcc_data { + eval $start if $b_log; + my ($gcc,@data,@gccs,@temp); + # 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-*'); + foreach (@temp){ + if (/\/gcc-([0-9.]+)$/){ + push @gccs, $1; + } + } + } + unshift @gccs, $gcc; + log_data('dump','@gccs',\@gccs) if $b_log; + eval $end if $b_log; + return @gccs; +} +# rasberry pi only +sub get_gpu_ram_arm { + eval $start if $b_log; + my ($gpu_ram) = (0); + if (my $program = check_program('vcgencmd')){ + # gpu=128M + # "VCHI initialization failed" - you need to add video group to your user + my $working = (grabber("$program get_mem gpu 2>/dev/null"))[0]; + $working = (split /\s*=\s*/, $working)[1] if $working; + $gpu_ram = translate_size($working) if $working; + } + log_data('data',"gpu ram: $gpu_ram") if $b_log; + eval $end if $b_log; + return $gpu_ram; +} +# standard systems +sub get_gpu_ram { + eval $start if $b_log; + my ($gpu_ram) = (0); + eval $end if $b_log; + return $gpu_ram; +} + +sub get_hostname { + eval $start if $b_log; + my $hostname = ''; + if ( $ENV{'HOSTNAME'} ){ + $hostname = $ENV{'HOSTNAME'}; + } + elsif ( !$bsd_type && -f "/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_module('Sys::Hostname')){ + import Sys::Hostname; + $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; +} + +sub get_init_data { + eval $start if $b_log; + my $runlevel = get_runlevel_data(); + my $default = ($extra > 1) ? get_runlevel_default() : ''; + my ($init,$init_version,$rc,$rc_version,$program) = ('','','','',''); + my $comm = ( -e '/proc/1/comm' ) ? (reader('/proc/1/comm'))[0] : ''; + my (@data); + # 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. + if ( $comm ){ + if ( $comm =~ /systemd/ ){ + $init = 'systemd'; + if ( $program = check_program('systemd')){ + $init_version = program_version($program,'^systemd','2','--version'); + } + if (!$init_version && ($program = check_program('systemctl') ) ){ + $init_version = program_version($program,'^systemd','2','--version'); + } + } + # epoch version == Epoch Init System 1.0.1 "Sage" + elsif ($comm =~ /epoch/){ + $init = 'Epoch'; + $init_version = program_version('epoch', '^Epoch', '4','version'); + } + # 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'; + } + } + if (!$init){ + # output: /sbin/init --version: init (upstart 1.1) + # init (upstart 0.6.3) + if ($init_version = program_version('init', 'upstart', '3','--version')){ + $init = 'Upstart'; + } + elsif (check_program('launchctl')){ + $init = 'launchd'; + } + elsif ( -f '/etc/inittab' ){ + $init = 'SysVinit'; + if (check_program('strings')){ + @data = grabber('strings /sbin/init'); + $init_version = awk(\@data,'version\s+[0-9]'); + $init_version = get_piece($init_version,2) if $init_version; + } + } + elsif ( -f '/etc/ttys' ){ + $init = 'init (BSD)'; + } + } + if ( grep { /openrc/ } globber('/run/*openrc*') ){ + $rc = 'OpenRC'; + # /sbin/openrc --version == openrc (OpenRC) 0.13 + if ($program = check_program('openrc')){ + $rc_version = program_version($program, '^openrc', '3','--version'); + } + # /sbin/rc --version == rc (OpenRC) 0.11.8 (Gentoo Linux) + elsif ($program = check_program('rc')){ + $rc_version = program_version($program, '^rc', '3','--version'); + } + if ( -e '/run/openrc/softlevel' ){ + $runlevel = (reader('/run/openrc/softlevel'))[0]; + } + elsif ( -e '/var/run/openrc/softlevel'){ + $runlevel = (reader('/var/run/openrc/softlevel'))[0]; + } + elsif ( $program = check_program('rc-status')){ + $runlevel = (grabber("$program -r 2>/dev/null"))[0]; + } + } + my %init = ( + 'init-type' => $init, + 'init-version' => $init_version, + 'rc-type' => $rc, + 'rc-version' => $rc_version, + 'runlevel' => $runlevel, + 'default' => $default, + ); + eval $end if $b_log; + return %init; +} + +sub get_kernel_data { + eval $start if $b_log; + my ($kernel,$ksplice) = ('',''); + # 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 = $uname[2]; + if (check_program('uptrack-uname') && $kernel){ + $ksplice = qx(uptrack-uname -rm); + $ksplice = trimmer($ksplice); + $kernel = ($ksplice) ? $ksplice . ' (ksplice)' : $kernel; + } + $kernel .= ' ' . $uname[-1]; + $kernel = ($bsd_type) ? $uname[0] . ' ' . $kernel : $kernel; + } + $kernel ||= 'N/A'; + log_data('data',"kernel: $kernel ksplice: $ksplice") if $b_log; + eval $end if $b_log; + return $kernel; +} + +sub get_kernel_bits { + eval $start if $b_log; + my $bits = ''; + if (@uname){ + $bits = $uname[-1]; + $bits = ($bits =~ /64/ ) ? 64 : 32; + } + $bits ||= 'N/A'; + eval $end if $b_log; + return $bits; +} + +sub get_memory_data { + eval $start if $b_log; + my ($type) = @_; + my ($memory); + if (my $file = system_files('meminfo') ) { + $memory = get_memory_data_linux($type,$file); + } + else { + $memory = get_memory_data_bsd($type); + } + eval $end if $b_log; + return $memory; +} + +sub get_memory_data_linux { + eval $start if $b_log; + my ($type,$file) = @_; + my ($gpu,$memory,$not_used,$total) = (0,'',0,0); + my @data = reader($file); + foreach (@data){ + if ($_ =~ /^MemTotal:/){ + $total = get_piece($_,2); + } + elsif ($_ =~ /^(MemFree|Buffers|Cached):/){ + $not_used += get_piece($_,2); + } + } + $gpu = get_gpu_ram_arm() if $b_arm; + #$gpu = translate_size('128M'); + $total += $gpu; + my $used = $total - ($not_used); + my $percent = ($used && $total) ? sprintf("%.1f", ($used/$total)*100) : ''; + if ($type eq 'string'){ + $percent = " ($percent%)" if $percent; + $memory = sprintf("%.1f/%.1f MiB", $used/1024, $total/1024) . $percent; + } + else { + $memory = "$total:$used:$percent:$gpu"; + } + log_data('data',"memory: $memory") if $b_log; + eval $end if $b_log; + return $memory; +} + +# 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 +# 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 +# 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 .... +sub get_memory_data_bsd { + eval $start if $b_log; + my ($type) = @_; + my $memory = ''; + my ($avail,$total,$free_mem,$real_mem) = (0,0,0,0); + my (@data,$message); + my $arg = ($bsd_type ne 'openbsd') ? '-H' : ''; + if (my $program = 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 + my $row = (grabber("vmstat $arg 2>/dev/null",'\n','strip'))[-1]; + if ( $row ){ + @data = split /\s+/, $row; + # dragonfly can have 0 avg, but they may fix that so make test dynamic + if ($data[3] != 0){ + $avail = ($bsd_type ne 'openbsd') ? sprintf ('%.1f',$data[3]/1024) : $data[3]; + } + elsif ($data[4] != 0){ + $free_mem = sprintf ('%.1f',$data[4]); + } + } + } + ## code to get total goes here: + my $ref = $alerts{'sysctl'}; + if ($$ref{'action'} eq 'use'){ + # for dragonfly, we will use free mem, not used because free is 0 + my @working; + foreach (@sysctl){ + # 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 $$ref{'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 (($avail || $free_mem) && !$real_mem){ + my $error = ($message) ? $message: 'total N/A'; + my $used = (!$free_mem) ? $avail : $real_mem - $free_mem; + if ($type eq 'string'){ + $used = sprintf("%.1f",$used/1024); + $memory = "$used/($error) MB"; + } + else { + $memory = "$error:$used:"; + } + } + # use openbsd/dragonfly avail mem data if available + elsif (($avail || $free_mem) && $real_mem) { + my $used = (!$free_mem) ? $avail : $real_mem - $free_mem; + my $percent = ($used && $real_mem) ? sprintf("%.1f", ($used/$real_mem)*100) : ''; + if ($type eq 'string'){ + $used = sprintf("%.1f",$used/1024); + $real_mem = sprintf("%.1f",$real_mem/1024); + $percent = " ($percent)" if $percent; + $memory = "$used/$real_mem MB" . $percent; + } + else { + $memory = "$real_mem:$used:$percent:0"; + } + } + eval $end if $b_log; + return $memory; +} + +sub get_module_version { + eval $start if $b_log; + my ($module) = @_; + return if ! $module; + my ($version); + my $path = "/sys/module/$module/version"; + if (-f $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; +} + +# args: 1 - pci device string; 2 - pci cleaned subsystem string +sub get_pci_vendor { + eval $start if $b_log; + my ($device, $subsystem) = @_; + return if !$subsystem; + my ($vendor,$sep) = ('',''); + my @data = split /\s+/, $subsystem; + foreach (@data){ + if ($device !~ !/\b$_\b/){ + $vendor .= $sep . $_; + $sep = ' '; + } + else { + last; + } + } + eval $end if $b_log; + return $vendor; +} + +# # check? /var/run/nologin for bsds? +sub get_runlevel_data { + eval $start if $b_log; + my $runlevel = ''; + if ( my $program = check_program('runlevel')){ + $runlevel = (grabber("$program 2>/dev/null"))[0]; + $runlevel =~ s/[^\d]//g 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 = ''; + my $b_systemd = 0; + my $inittab = '/etc/inittab'; + my $systemd = '/etc/systemd/system/default.target'; + my $upstart = '/etc/init/rc-sysinit.conf'; + # note: systemd systems do not necessarily have this link created + if ( -e $systemd){ + $default = readlink($systemd); + $default =~ s/.*\/// if $default; + $b_systemd = 1; + } + # 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 + elsif ( -e $upstart){ + # env DEFAULT_RUNLEVEL=2 + @data = reader($upstart); + $default = awk(\@data,'^env\s+DEFAULT_RUNLEVEL',2,'='); + } + # handle weird cases where null but inittab exists + if (!$default && -e $inittab ){ + @data = reader($inittab); + $default = awk(\@data,'^id.*initdefault',2,':'); + } + eval $end if $b_log; + return $default; +} + +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; +} + +sub get_shell_data { + eval $start if $b_log; + my ($ppid) = @_; + my $cmd = "ps -p $ppid -o comm= 2>/dev/null"; + my $shell = qx($cmd); + 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/^.*\///; + my $working = $ENV{'SHELL'}; + $working =~ s/^.*\///; + # NOTE: su -c "inxi -F" results in shell being su + if (($shell eq 'sh' || $shell eq 'sudo' || $shell eq 'su' ) && $shell ne $working){ + $client{'su-start'} = $shell if ($shell eq 'sudo' || $shell eq 'su'); + $shell = $working; + } + #print "shell post: $shell\n"; + # sh because -v/--version doesn't work on it + if ( $shell ne 'sh' ) { + @app = main::program_values(lc($shell)); + if ($app[0]){ + $client{'version'} = main::program_version($shell,$app[0],$app[1],$app[2],$app[5],$app[6]); + } + # guess that it's two and --version + else { + # we're just guessing at the search phrase and position + if ($shell){ + $client{'version'} = main::program_version($shell,$shell,2,''); + } + else { + $client{'version'} = row_defaults('unknown-shell'); + } + } + $client{'version'} =~ s/(\(.*|-release|-version)//; + } + $client{'name'} = lc($shell); + $client{'name-print'} = $shell; + } + else { + $client{'name'} = 'shell'; + $client{'name-print'} = 'Unknown Shell'; + } + $client{'su-start'} = 'sudo' if (!$client{'su-start'} && $ENV{'SUDO_USER'}); + eval $end if $b_log; +} + +sub get_shell_source { + eval $start if $b_log; + my (@data); + my ($msg,$self_parent,$shell_parent) = ('','',''); + my $ppid = getppid(); + $self_parent = get_start_parent($ppid); + if ($b_log){ + $msg = ($ppid) ? "self parent: $self_parent ppid: $ppid": "self parent: undefined"; + log_data('data',$msg); + } + #print "self parent: $self_parent ppid: $ppid\n"; + if ($self_parent){ + $shell_parent = get_shell_parent($self_parent); + $client{'su-start'} = $shell_parent if ($shell_parent eq 'su' && !$client{'su-start'}); + #print "shell parent 1: $shell_parent\n"; + if ($b_log){ + $msg = ($shell_parent) ? "shell parent 1: $shell_parent": "shell parent 1: undefined"; + 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 + for my $i (2..4){ + if ( $shell_parent && + $shell_parent =~ /^(bash|csh|dash|ksh|lksh|loksh|mksh|pdksh|perl|sh|su|sudo|tcsh|zsh)$/ ){ + # no idea why have to do script_parent action twice in su case, but you do. + $self_parent = get_start_parent($self_parent); + $shell_parent = get_shell_parent($self_parent); + #print "shell parent 2: $shell_parent\n"; + if ($b_log){ + $msg = ($shell_parent) ? "shell parent $i: $shell_parent": "shell parent $i: undefined"; + log_data('data',$msg); + } + } + else { + last; + } + } + # to work around a ps -p or gnome-terminal bug, which returns + # gnome-terminal- trim - off end + $shell_parent =~ s/-$// if $shell_parent; + } + if ($b_log){ + $self_parent ||= ''; + $shell_parent ||= ''; + log_data('data',"parents: self: $self_parent shell: $shell_parent"); + } + eval $end if $b_log; + return $shell_parent; +} + +# utilities for get_shell_source +# arg: 1 - parent id +sub get_start_parent { + eval $start if $b_log; + my ($parent) = @_; + # ps -j -fp : bsds ps do not have -f for PPID, so we can't get the ppid + my $cmd = "ps -j -fp $parent"; + log_data('cmd',$cmd) if $b_log; + my @data = grabber($cmd); + #shift @data if @data; + my $self_parent = awk(\@data,"$parent",3,'\s+'); + eval $end if $b_log; + return $self_parent; +} + +# arg: 1 - parent id +sub get_shell_parent { + eval $start if $b_log; + my ($parent) = @_; + my $cmd = "ps -j -p $parent"; + log_data('cmd',$cmd) if $b_log; + my @data = grabber($cmd,'strip'); + #shift @data if @data; + my $shell_parent = awk(\@data, "$parent",-1,'\s+'); + eval $end if $b_log; + return $shell_parent; +} + +# this will test against default IP like: (:0) vs full IP to determine +# ssh status. Surprisingly easy test? Cross platform +sub get_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 = check_program('who')){ + $ssh = (grabber("$program am i 2>/dev/null"))[0]; + # crude IP validation + if ($ssh && $ssh =~ /\(([:0-9a-f]{8,}|[1-9][\.0-9]{6,})\)$/){ + $b_ssh = 1; + } + } + eval $end if $b_log; + return $b_ssh; +} + +sub get_tty_console_irc { + eval $start if $b_log; + my ($type) = @_; + return $tty_session if defined $tty_session; + if ( $type eq 'vtrn' && defined $ENV{'XDG_VTNR'} ){ + $tty_session = $ENV{'XDG_VTNR'}; + } + else { + my $ppid = getppid(); + $tty_session = awk(\@ps_aux,".*$ppid.*$client{'name'}",7,'\s+'); + $tty_session =~ s/^[^[0-9]+// if $tty_session; + } + $tty_session = '' if ! defined $tty_session; + log_data('data',"conole-irc-tty:$tty_session") if $b_log; + eval $end if $b_log; + return $tty_session; +} + +sub get_tty_number { + eval $start if $b_log; + my ($tty); + if ( defined $ENV{'XDG_VTNR'} ){ + $tty = $ENV{'XDG_VTNR'}; + } + else { + $tty = POSIX::ttyname(1); + #variants: /dev/pts/1 /dev/tty1 /dev/ttyp2 /dev/ttyra [hex number a] + $tty =~ s/.*\/[^0-9]*//g if defined $tty; + } + $tty = '' if ! defined $tty; + log_data('data',"tty:$tty") if $b_log; + eval $end if $b_log; + return $tty; +} + +# 2:58PM up 437 days, 8:18, 3 users, load averages: 2.03, 1.72, 1.77 +# 04:29:08 up 3:18, 3 users, load average: 0,00, 0,00, 0,00 +# 10:23PM up 5 days, 16:17, 1 user, load averages: 0.85, 0.90, 1.00 +# 05:36:47 up 1 day, 3:28, 4 users, load average: 1,88, 0,98, 0,62 +# 05:36:47 up 1 day, 3 min, 4 users, load average: 1,88, 0,98, 0,62 +sub get_uptime { + eval $start if $b_log; + my ($days,$hours,$minutes,$uptime) = ('','','',''); + if (check_program('uptime')){ + $uptime = qx(uptime); + $uptime = trimmer($uptime); + #$uptime = '05:36:47 up 3 min, 4 users, load average: 1,88, 0,98, 0,62'; + if ($uptime && + $uptime =~ /[\S]+\s+up\s+(([0-9]+)\s+day[s]?,\s+)?(([0-9]{1,2}):([0-9]{1,2})|([0-9]+)\smin[s]?),\s+[0-9]+\s+user/){ + $days = $2 . 'd' if $2; + $days .= ' ' if ($days && ($4 || $6)); + if ($4 && $5){ + $hours = $4 . 'h '; + $minutes = $5 . 'm'; + } + elsif ($6){ + $minutes = $6 . 'm'; + + } + $uptime = $days . $hours . $minutes; + } + } + $uptime ||= 'N/A'; + eval $end if $b_log; + return $uptime; +} + +# NOTE: annoyingly, /sys does NOT actually use the id, it uses +# the count of physical devices, starting at 0 for hub, on the bus. +# args: $1 - $bus number; $2 - vendor:chip id +sub get_usb_drivers { + eval $start if $b_log; + my ($bus,$id) = @_; + return if !$bus || !$id;# these will be > 0 + my ($chip,$driver,$file,$path,$vendor,$working,$working2,@drivers,@temp); + @temp = split /:/, $id; + $vendor = $temp[0]; + $chip = $temp[1]; + # some have it one level deeper than others + my @globs = ("/sys/bus/usb/devices/usb$bus/$bus-*/","/sys/bus/usb/devices/usb$bus/$bus-*/$bus-*/"); + foreach (@globs){ + $path = get_usb_path($vendor,$chip,$_); + last if $path; + } + if ($path){ + if ( -e "${path}driver"){ + if ($working = Cwd::abs_path("${path}driver")){ + $working =~ s/^.*\///; + if ($working && $working ne 'usb' && $working ne 'usbhid'){ + push @drivers, $working; + } + } + } + # test 2 + @temp = main::globber("$path$bus-*/"); + #print "@temp\n"; + foreach (@temp){ + #print "p2:". $_ . "driver\n"; + $file = $_ . 'driver'; + #print "f:$file\n"; + if (-e $file){ + #print "here\n"; + #print (Cwd::abs_path($file), "\n"); + if ($working = Cwd::abs_path($file)){ + #print "w:$working\n"; + $working =~ s/^.*\///; + if ($working && $working ne 'usb' && $working ne 'usbhid' && ! grep {/$working/} @drivers){ + push @drivers, $working; + } + } + } + } + #print join "\n", @drivers, "\n"; + $driver = join ',', @drivers if @drivers; + } + @temp = ($driver,$path); + eval $end if $b_log; + return @temp; +} + +sub get_usb_path { + eval $start if $b_log; + my ($vendor,$chip,$glob) = @_; + my ($path,$working,$working2); + #print "$vendor,$chip,$glob\n"; + my @temp = main::globber($glob); + #print join "\n", @temp, "\n"; + # first we need to get the device path, since it's not predictable + foreach (@temp){ + #print "$_\n"; + $working = $_ . 'idVendor'; + $working2 = $_ . 'idProduct'; + if (-e $working && (main::reader($working))[0] eq $vendor && + -e $working2 && (main::reader($working2))[0] eq $chip){ + $path = $_; + #print "$_\n"; + last; + } + } + eval $end if $b_log; + return $path +} + + +#### ------------------------------------------------------------------- +#### SET DATA VALUES +#### ------------------------------------------------------------------- + +sub set_dmesg_boot_data { + eval $start if $b_log; + my ($file,@temp); + my ($counter) = (0); + $b_dmesg_boot_check = 1; + if (!$b_fake_dboot){ + $file = system_files('dmesg-boot'); + } + else { + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/bsd-disks-diabolus.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/freebsd-disks-solestar.txt"; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/freebsd-enceladus-1.txt"; + ## matches: toshiba: openbsd-5.6-sysctl-2.txt + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/openbsd-5.6-dmesg.boot-1.txt"; + ## matches: compaq: openbsd-5.6-sysctl-1.txt" + $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/openbsd-dmesg.boot-1.txt"; + } + if ($file){ + return if ! -r $file; + @dmesg_boot = reader($file); + # 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 (@dmesg_boot){ + $counter++ if /^(OpenBSD|DragonFly|FreeBSD is a registered trademark)/; + last if $counter > 1; + $_ =~ s/\s*=\s*|:\s*/:/; + $_ =~ s/\"//g; + $_ =~ s/^\s+/~/; + $_ =~ s/\s\s/ /g; + $_ =~ s/^(\S+)\sat\s/$1:at /; # ada0 at ahcich0 + push @temp, $_; + if (/^bios[0-9]:(at|vendor)/){ + push @sysctl_machine, $_; + } + } + @dmesg_boot = @temp; + # FreeBSD: 'da*' is a USB device 'ada*' is a SATA device 'mmcsd*' is an SD card + if ($b_dm_boot_disk && @dmesg_boot){ + @dm_boot_disk = grep {/^(ad|ada|da|mmcblk|mmcsd|nvme[0-9]+n|sd|wd)[0-9]+(:|\sat\s)/} @dmesg_boot; + log_data('dump','@dm_boot_disk',\@dm_boot_disk) if $b_log; + print Dumper \@dm_boot_disk if $test[9]; + } + if ($b_dm_boot_optical && @dmesg_boot){ + @dm_boot_optical = grep {/^(cd)[0-9]+(\([^)]+\))?(:|\sat\s)/} @dmesg_boot; + log_data('dump','@dm_boot_optical',\@dm_boot_optical) if $b_log; + print Dumper \@dm_boot_optical if $test[9]; + } + } + log_data('dump','@dmesg_boot',\@dmesg_boot) if $b_log; + #print Dumper \@dmesg_boot if $test[9]; + eval $end if $b_log; +} + +# note, all actual tests have already been run in check_tools so if we +# got here, we're good. +sub set_dmi_data { + eval $start if $b_log; + if ($alerts{'dmidecode'}{'action'} eq 'use' ){ + set_dmidecode_data(); + } + eval $end if $b_log; +} + +sub set_dmidecode_data { + eval $start if $b_log; + my ($content,@data,@working,$type,$handle); + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/pci-freebsd-8.2-2"; + #open my $fh, '<', $file or die "can't open $file: $!"; + #chomp(@data = <$fh>); + my $path = check_program('dmidecode'); + $content = qx($path 2>/dev/null) if $path; + @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; + $b_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 Dumper \@dmi if $test[2]; + eval $end if $b_log; +} + +sub set_ip_data { + eval $start if $b_log; + if ($alerts{'ip'}{'action'} eq 'use' ){ + set_ip_addr(); + } + elsif ($alerts{'ifconfig'}{'action'} eq 'use'){ + set_ifconfig(); + } + eval $end if $b_log; +} + +sub set_ip_addr { + eval $start if $b_log; + my $program = check_program('ip'); + my @data = grabber("$program addr 2>/dev/null",'\n','strip') if $program; + #my @data = reader("$ENV{'HOME'}/bin/scripts/inxi/data/if/scope-ipaddr-1.txt",'strip') or die $!; + my ($b_skip,$broadcast,$if,$ip,@ips,$scope,$if_id,$type,@temp,@temp2); + foreach (@data){ + if (/^[0-9]/){ + #print "$_\n"; + if (@ips){ + #print "$if\n"; + @temp = ($if,[@ips]); + @ifs = (@ifs,@temp); + @ips = (); + } + @temp = split /:\s+/,$_; + $if = $temp[1]; + if ($if eq 'lo'){ + $b_skip = 1; + $if = ''; + next; + } + $b_skip = 0; + @temp = (); + } + elsif (!$b_skip && /^inet/){ + #print "$_\n"; + @temp = split /\s+/, $_; + ($broadcast,$ip,$scope,$if_id,$type) = ('','','','',''); + $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; + } + @temp = ($type,$ip,$broadcast,$scope,$if_id); + @ips = (@ips,[@temp]); + #print Dumper \@ips; + } + } + #print Dumper \@ips if $test[4]; + if (@ips){ + @temp = ($if,[@ips]); + @ifs = (@ifs,@temp); + } + main::log_data('dump','@ifs',\@ifs) if $b_log; + print Dumper \@ifs if $test[3]; + eval $end if $b_log; +} + +sub set_ifconfig { + eval $start if $b_log; + my $program = check_program('ifconfig'); # not in user path, sbin + my @data = grabber("$program 2>/dev/null",'\n','') if $program; + #my @data = reader("$ENV{'HOME'}/bin/scripts/inxi/data/if/vps-ifconfig-1.txt",'') 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"; + @temp = ($if,[@ips]); + @ifs = (@ifs,@temp); + @ips = (); + } + if ($mac){ + @temp = ($if,[($state,$speed,$duplex,$mac)]); + @ifs_bsd = (@ifs_bsd,@temp); + ($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; + } + # lladdr openbsd + elsif (!$b_skip && $bsd_type && /^\s+(ether|media|status|lladdr)/){ + $_ =~ s/^\s+//; + # media: Ethernet 100baseTX freebsd 7.3 + # media: Ethernet autoselect (1000baseT ) Freebsd 8.2 + # + if (/^media/){ + # openbsd: media: Ethernet autoselect (1000baseT full-duplex) + if ($bsd_type && $bsd_type eq 'openbsd'){ + $_ =~ /\s\([\S]+\s([\S]+)\)/; + $duplex = $1; + } + else { + $_ =~ /<([^>]+)>/; + $duplex = $1; + } + $_ =~ /\s\(([1-9][\S]+\s)/; + $speed = $1; + $speed =~ s/\s+$// if $speed; + } + elsif (!$mac && /^ether|lladdr/){ + $mac = (split /\s+/, $_)[1]; + } + elsif (/^status/){ + $state = (split /\s+/, $_)[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/; + @temp = ($type,$ip,$broadcast,$scope,$if_id); + @ips = (@ips,[@temp]); + #print Dumper \@ips; + } + } + if (@ips){ + @temp = ($if,[@ips]); + @ifs = (@ifs,@temp); + } + if ($mac){ + @temp = ($if,[($state,$speed,$duplex,$mac)]); + @ifs_bsd = (@ifs_bsd,@temp); + ($state,$speed,$duplex,$mac) = ('','','',''); + } + print Dumper \@ifs if $test[3]; + print Dumper \@ifs_bsd if $test[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 set_pci_data { + eval $start if $b_log; + if ( $b_pci ){ + if (!$bsd_type){ + if ($alerts{'lspci'}{'action'} eq 'use' ){ + set_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 ($b_arm || $b_mips){ + set_soc_data(); + } + } + else { + #if (1 == 1){ + if ($alerts{'pciconf'}{'action'} eq 'use'){ + set_pciconf_data(); + } + } + } + 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 +# 11 driver_nu [bsd, like: em0 - driver em; nu 0. Used to match IF in -n +# 12 subsystem/vendor +# 13 subsystem vendor:chip id +sub set_lspci_data { + eval $start if $b_log; + my ($busid,$busid_nu,$content,$port,$driver,$modules,$device,$vendor_id,$chip_id,$rev, + $subsystem,$subsystem_id,$type,$type_id,@pcis,@temp,@working); + # my @pcis = grabber('lspci -nnv','\n','strip'); + my $path = check_program('lspci'); + $content = qx($path -nnv 2>/dev/null) if $path; + @pcis = split /\n/, $content if $content; + #my $file = "$ENV{HOME}/bin/scripts/inxi/data/lspci/racermach-1-knnv.txt"; + #my $file = "$ENV{HOME}/bin/scripts/inxi/data/lspci/rk016013-knnv.txt"; + #@pcis = reader($file); + #print scalar @pcis; + @pcis = map {$_ =~ s/^\s+//; $_} @pcis if @pcis; + $b_pci_tool = 1 if @pcis && scalar @pcis > 10; + foreach (@pcis){ + #print "$_\n"; + if ($device){ + if ($_ =~ /^\s*$/) { + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$subsystem,$subsystem_id); + @pci = (@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 = cleaner($subsystem); + $subsystem = pci_cleaner($subsystem,'pci'); + $subsystem = pci_cleaner_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) + elsif ($_ =~ /^(([0-9a-f]{2,4}:)?[0-9a-f]{2}:[0-9a-f]{2})[.:]([0-9a-f]+)\s(.*)\s\[([0-9a-f]{4}):([0-9a-f]{4})\](\s\(rev\s([^\)]+)\))?/){ + $busid = $1; + $busid_nu = hex($3); + @working = split /:\s+/, $4; + $device = $working[1]; + $type = $working[0]; + $vendor_id = $5; + $chip_id = $6; + $rev = ($8)? $8 : ''; + $device = cleaner($device); + $working[0] =~ /\[([^\]]+)\]$/; + $type_id = $1; + $b_hardware_raid = 1 if $type_id eq '0104'; + $type = lc($type); + $type = pci_cleaner($type,'pci'); + $type =~ s/\s+$//; + $port = ''; + $driver = ''; + $modules = ''; + $subsystem = ''; + $subsystem_id = ''; + } + } + if ($device && $busid){ + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id, + $rev,$port,$driver,$modules,$subsystem,$subsystem_id); + @pci = (@pci,[@temp]); + $device = ''; + } + print Dumper \@pci if $test[4]; + main::log_data('dump','@pci',\@pci) if $b_log; + eval $end if $b_log; +} + +# 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 set_pciconf_data { + eval $start if $b_log; + my ($busid,$busid_nu,$content,$port,$driver,$driver_nu,$modules,$device,$vendor, + $vendor_id,$chip_id,$rev,$type,$type_id,@data,@temp,@working); +# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/pciconf/pci-freebsd-8.2-2"; +# open my $fh, '<', $file or die "can't open $file: $!"; +# chomp(@data = <$fh>); + my $path = check_program('pciconf'); + $content = qx($path -lv 2>/dev/null) if $path; + @data = split /\n/, $content if $content; + $b_pci_tool = 1 if @data && scalar @data > 10; + foreach (@data){ + if ($_ =~ /^[^@]+\@pci/){ + push @working, ''; + } + $_ =~ s/^\s+//; + push @working, $_; + } + foreach (@working){ + if ($driver){ + if ($_ =~ /^\s*$/) { + $vendor = cleaner($vendor); + $device = cleaner($device); + if ($vendor && $device){ + if ($vendor !~ /$device/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); + @pci = (@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]; + } + } + elsif (/^([^@]+)\@pci([0-9]{1,3}:[0-9]{1,3}:[0-9]{1,3}):([0-9]{1,3}).*class=([^\s]+)\scard=([^\s]+)\schip=([^\s]+)\srev=([^\s]+)/){ + $driver = $1; + $busid = $2; + $busid_nu = $3; + $type_id = $4; + #$vendor_id = $5; + $vendor_id = substr($6,6,4); + $chip_id = substr($6,2,4); + $rev = $7; + $vendor = ''; + $device = ''; + $type = ''; + $driver =~ /(^[a-z]+)([0-9]+$)/; + $driver = $1; + $driver_nu = $2; + } + } + if ($driver && $busid){ + $vendor = cleaner($vendor); + $device = cleaner($device); + $device = ( $vendor && $device !~ /$vendor/) ? "$vendor $device" : $device; + @temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,$rev,$port,$driver,$modules,$driver_nu); + @pci = (@pci,[@temp]); + $device = ''; + } + print Dumper \@pci if $test[4]; + main::log_data('dump','@pci',\@pci) if $b_log; + eval $end if $b_log; +} + +## 1 +# /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 +# /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 +# /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 +# /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] +sub set_soc_data { + eval $start if $b_log; + my ($content,@files,@temp2,@temp3,@working); + if (-d '/sys/devices/platform/'){ + @files = globber('/sys/devices/platform/soc*/*/uevent'); + @temp2 = globber('/sys/devices/platform/soc*/*/*/uevent'); + @files = (@files,@temp2) if @temp2; + } + if (globber('/sys/devices/soc*')){ + @temp2 = globber('/sys/devices/soc*/*/uevent'); + @files = (@files,@temp2) if @temp2; + @temp2 = globber('/sys/devices/soc*/*/*/uevent'); + @files = (@files,@temp2) if @temp2; + } + @temp2 = globber('/sys/devices/*/uevent'); # see case 8 + @files = (@files,@temp2) if @temp2; + @temp2 = undef; + # not sure why, but even as root/sudo, /subsystem/uevent is unreadable with -r test true + @files = grep {!/subsystem/} @files if @files; + foreach my $file (@files){ + next if -z $file; + my ($busid,$busid_nu,$chip_id,$device,$driver,$modules,$port,$rev, + $temp,$type,$type_id,$vendor_id,@working); + $chip_id = $file; + # variants: /soc/20100000.ethernet /soc/soc:audio /soc:/ /soc@0/ + # mips: /sys/devices/soc.0/1180000001800.mdio/8001180000001800:07/ + $chip_id =~ /\/sys\/devices\/(platform\/)?(soc[^\/]*\/)?([^\/]+\/)?([^\/]+\/)?([^\/]+)[\.:]([^\/]+)\/uevent$/; + $chip_id = $5; + $temp = $6; + @working = reader($file, 'strip') if -r $file; + 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]; + } + 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; + $driver = '' if ! defined $driver; + $busid = (defined $temp && $temp =~ /^[0-9]+$/) ? $temp: 0; + $busid_nu = 0; + $type_id = ''; + $port = ''; + $rev = ''; + $modules = ''; + # note: use these for main Card match for -AGN + $b_soc_audio = 1 if $type =~ /^(audio|daudio|hdmi|multimedia)$/; + $b_soc_gfx = 1 if $type =~ /^(vga|disp|display|3d|fb|gpu|hdmi)$/; + $b_soc_net = 1 if $type =~ /^(eth|ethernet|ethernet-phy|network|wifi|wlan)$/; + @temp3 = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,$rev,$port,$driver,$modules); + @pci = (@pci,[@temp3]); + } + print Dumper \@pci if $test[4]; + main::log_data('dump','@pci',\@pci) if $b_log; + eval $end if $b_log; +} +sub set_ps_aux { + eval $start if $b_log; + @ps_aux = split "\n",qx(ps aux);; + shift @ps_aux; # get rid of header row + $_=lc for @ps_aux; # this is a super fast way to set to lower + # note: regular perl /.../inxi but sudo /.../inxi is added for sudo start + # for pinxi, we want to see the useage data for cpu/ram + @ps_aux = grep {!/\/$self_name\b/} @ps_aux if $self_name eq 'inxi'; + # this is for testing for the presence of the command + @ps_cmd = grep {!/^\[/} map { + 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 > 12) ? 12 : $final; + @split = @split[10 .. $final ]; + join " ", @split; + } @ps_aux; + #@ps_cmd = grep {!/^\[/} @ps_cmd; + # never, because ps loaded before option handler + print Dumper \@ps_cmd if $test[5]; + eval $end if $b_log; +} +sub set_ps_gui { + eval $start if $b_log; + $b_ps_gui = 1; + my ($working,@match,@temp); + # desktops + if ($show{'system'}){ + @temp=qw(razor-desktop razor-session lxsession lxqt-session tdelauncher tdeinit_phase1); + @match = (@match,@temp); + @temp=qw(afterstep awesome blackbox 3dwm dwm fluxbox flwm + fvwm-crystal fvwm2 fvwm i3 jwm matchbox-panel openbox sawfish + scrotwm spectrwm twm WindowMaker wm2 wmii2 wmii); + @match = (@match,@temp); + } + # wm: + if ($show{'system'} && $extra > 1){ + @temp=qw(9wm 3dwm afterstep amiwm awesome blackbox budgie-wm compiz + dwm fluxbox flwm fvwm-crystal fvwm2 fvwm gala gnome-shell i3 jwm + twin kwin_wayland kwin_x11 kwin marco matchbox-window-manager metacity + metisse mir muffin mutter mwm notion openbox ratpoison sawfish + scrotwm spectrwm twm windowlab WindowMaker wm2 wmii2 wmii xfwm4 + xfwm5 xmonad); + @match = (@match,@temp); + } + # info: + if ($show{'system'} && $extra > 2){ + @temp=qw(budgie-panel gnome-panel kicker lxpanel lxqt-panel + matchbox-panel mate-panel plasma-desktop plasma-netbook razor-panel + razorqt-panel wingpanel xfce4-panel xfce5-panel); + @match = (@match,@temp); + } + # compositors (for wayland these are also the server, note + if ($show{'graphic'} && $extra > 1){ + @temp=qw(budgie-wm compiz compton dwc dcompmgr enlightenment + grefson ireplace kmscon kwin_wayland kwin_x11 metisse mir moblin + rustland sway swc unagi wayhouse westford weston xcompmgr); + @match = (@match,@temp); + } + @match = uniq(@match); + my $matches = join '|', @match; + foreach (@ps_cmd){ + if (/^[\S]*\b($matches)(\s|$)/){ + $working = $1; + push @ps_gui, $working; # deal with duplicates with uniq + } + } + @ps_gui = uniq(@ps_gui) if @ps_gui; + print Dumper \@ps_gui if $test[5]; + log_data('dump','@ps_gui',\@ps_gui) if $b_log; + eval $end if $b_log; +} + +sub set_sysctl_data { + eval $start if $b_log; + return if $alerts{'sysctl'}{'action'} ne 'use'; + my (@temp); + # darwin sysctl has BOTH = and : separators, and repeats data. Why? + if (!$b_fake_sysctl){ + my $program = check_program('sysctl'); + @temp = grabber("$program -a 2>/dev/null"); + } + else { + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/obsd_6.1_sysctl_soekris6501_root.txt"; + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/obsd_6.1sysctl_lenovot500_user.txt"; + ## matches: compaq: openbsd-dmesg.boot-1.txt + my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/openbsd-5.6-sysctl-1.txt"; + ## matches: toshiba: openbsd-5.6-dmesg.boot-1.txt + #my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/openbsd-5.6-sysctl-2.txt"; + @temp = reader($file); + } + foreach (@temp){ + $_ =~ s/\s*=\s*|:\s+/:/; + $_ =~ s/\"//g; + push @sysctl, $_; + # we're building these here so we can use these arrays to test + # in each feature if we will try to build the feature for bsds + if (/^hw\.sensors/ && !/^hw\.sensors\.acpi(bat|cmb)/ && !/^hw.sensors.softraid/){ + push @sysctl_sensors, $_; + } + elsif (/^hw\.(vendor|product|version|serialno|uuid)/){ + push @sysctl_machine, $_; + } + elsif (/^hw\.sensors\.acpi(bat|cmb)/){ + push @sysctl_battery, $_; + } + } + print Dumper \@sysctl if $test[7]; + # this thing can get really long. + if ($b_log){ + #main::log_data('dump','@sysctl',\@sysctl); + } + eval $end if $b_log; +} + +# http://www.usb.org/developers/defined_class +sub set_usb_data { + eval $start if $b_log; + if ($alerts{'lsusb'}{'action'} eq 'use' ){ + #$usb_level = 2; + # NOTE: we can't get reliable usb network device with short + if ($usb_level == 2){ + set_lsusb_data_long(); + } + else { + set_lsusb_data_short(); + } + } + elsif ( $alerts{'usbdevs'}{'action'} eq 'use'){ + set_usbdevs_data(); + } + eval $end if $b_log; +} + +sub set_lsusb_data_short { + eval $start if $b_log; + my ($content,@data); + my $b_live = 1; + if ($b_live){ + my $path = check_program('lsusb'); + $content = qx($path 2>/dev/null) if $path; + @data = split /\n/, $content if $content; + } + else { + open my $fh, '<', "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/mdmarmer-lsusb.txt" or die $!; + chomp(@data = <$fh>); + } + foreach (@data){ + next if /^\s*$|^Couldn't/; # expensive second call: || /UNAVAIL/ + my @working = split /\s+/, $_; + $working[3] =~ s/:$//; + my $id = int($working[3]); + if ($id > 1){ + my $bus = int($working[1]); + my $chip = $working[5]; + my @temp = @working[6..$#working]; + my $name = join ' ', @temp; + if ($name !~ /hub/i){ + @usb = (@usb,[$bus,$id,$chip,$name]); + } + } + } + print Dumper \@usb if $test[6]; + main::log_data('dump','@usb: short',\@usb) if $b_log; + eval $end if $b_log; +} + +sub set_lsusb_data_long { + eval $start if $b_log; + my ($content,@data,@working,$bus_id,$device_id,$id,$b_skip); + my $j = 0; + my $b_live = 1; + if ($b_live){ + my $path = check_program('lsusb'); + $content = qx($path -v 2>/dev/null) if $path; + @data = split /\n/, $content if $content; + } + else { + my $file; + #$file = "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/mdmarmer-lsusb-v.txt"; + $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/lsusb-v-dz64.txt"; + open my $fh, '<', $file or die $!; + chomp(@data = <$fh>); + } + foreach (@data){ + # we won't need all the lsusb data, so set it to skip + # after the last item we might want + # Couldn't open device, some information will be missing + next if /^\s*$|^Couldn't/; # expensive second call: || /UNAVAIL/ + if (!$b_skip && $bus_id && /^\s\s/){ + #if ($_ =~ /\bDescriptor\b:/){ + if ($_ =~ /^\s+([\S]+)\sDescriptor:/){ + #$_ =~ /^\s+([\S]+)\sDescriptor:/; + $_ = "Descriptor_$1"; + } + else { + $_ =~ s/^\s\s|[\s]+$//g; + $_ =~ s/^[\s]+/~/g; + #$_ =~ s/[\s]+$//g; + $_ =~ s/\sType/_Type/g; + $_ =~ s/^([\S]+)[\s]+(.*)//; + my $one = ($1) ? $1: ''; + my $two = ($2) ? $2: ''; + $_ = "$one:$two"; + $b_skip = 1 if $one eq '~bInterfaceProtocol'; + #$_ = cleaner($_); + if (/([\S]+):([0-9]+|0x[0-9a-f]+)\s(.*)/){ + $_ = "$1:$2:$3"; + #$b_skip = 1 if $1 eq '~bInterfaceProtocol'; + } + #print "$1\n"; + } + push @working, $_; + } + elsif (/^Bus\s([0-9]+)\sDevice\s([0-9]+):\sID\s(([0-9a-f]{4}):([0-9a-f]{4})).*/){ + #elsif (/^Bus\s/){ + #if (/^Bus\s([0-9]+)\sDevice\s([0-9]+):\sID\s(([0-9a-f]{4}):([0-9a-f]{4})).*/){ + $j = scalar @usb; + $bus_id = int($1); + $device_id = int($2); + $id = $3; + $b_skip = 0; + # we don't need 32, system boot, or 127, end of table + if (@working){ + if ($working[0] != 32 && $working[0] != 127){ + $usb[$j] = ( + [@working], + ); + } + } + @working = ($bus_id,$device_id,$id); + #} + } + } + if (@working){ + $j = scalar @usb; + $usb[$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 + @usb = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @usb; + print Dumper \@usb if $test[6]; + main::log_data('dump','@usb: long',\@usb) if $b_log; + 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 set_usbdevs_data { + eval $start if $b_log; + my (@data,@working,$class,$bus_id,$addr_id,$id,$speed,$protocol); + my $j = 0; + my $ports = 0; + if (!$b_fake_usbdevs){ + my $program = check_program('usbdevs'); + my $content = qx($program -v 2>/dev/null); + @data = split /\n/, $content; + } + else { + open my $fh, '<', "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/bsd-usbdevs-v-1.txt" or die $!; + chomp(@data = <$fh>); + } + foreach (@data){ + if (/^Controller\s\/dev\/usb([0-9]+)/){ + $j = scalar @usb; + $ports = 0; + $bus_id = $1; + @working = (); + } + elsif (/^addr\s([0-9]+):\s([^,]+),[^,]+,[^,]+,\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){ + $j = scalar @usb; + $addr_id = $1; + $speed = "bcdUSB:$2"; + $id = "$4:$6"; + $protocol="~bInterfaceProtocol:0:$5 $3"; + #print "p1:$protocol\n"; + $class='bDeviceClass:9:Hub'; + @working = ($bus_id,$addr_id,$id,$speed,$class,$protocol); + if (@working){ + $usb[$j] = ( + [@working], + ); + } + @working = (); + } + elsif (/^\s+port\s([0-9]+)\saddr\s([0-9]+):\s([^,]+),[^,]+,[^,]+,\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){ + $j = scalar @usb; + $addr_id = "$2"; + $speed = "bcdUSB:$3"; + $id = "$5:$7"; + $protocol="~bInterfaceProtocol:0:$6 $4"; + #print "p2:$protocol\n"; + $ports++; + @working = ($bus_id,$addr_id,$id,$speed,$protocol); + if (@working){ + $usb[$j] = ( + [@working], + ); + } + @working = (); + } + elsif (/^\s+port\s([0-9]+)\spowered/){ + $ports++; + } + } + if (@working){ + $j = scalar @usb; + $usb[$j] = ( + [@working], + ); + } + main::log_data('dump','@usb: usbdevs',\@usb) if $b_log; + print Dumper \@usb if $test[6]; + eval $end if $b_log; +} + +######################################################################## +#### GENERATE LINES +######################################################################## + +#### ------------------------------------------------------------------- +#### LINE CONTROLLERS +#### ------------------------------------------------------------------- + +sub assign_data { + my (%row) = @_; + return if ! %row; + if ($output_type eq 'screen'){ + print_data(%row); + } + else { + %rows = (%rows,%row); + } +} + +sub generate_lines { + eval $start if $b_log; + my (%row,$b_pci_check,$b_dmi_check); + set_ps_aux() if ! @ps_aux; + set_sysctl_data() if $b_sysctl; + # 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'} ){ + set_dmesg_boot_data() if ($bsd_type && !$b_dmesg_boot_check); + %row = generate_short_data(); + assign_data(%row); + } + else { + if ( $show{'system'} ){ + %row = generate_system_data(); + assign_data(%row); + } + if ( $show{'machine'} ){ + if ($b_dmi && !$b_dmi_check ){ + set_dmi_data() ; + $b_dmi_check = 1; + } + set_dmesg_boot_data() if ($bsd_type && !$b_dmesg_boot_check); + %row = line_handler('Machine','machine'); + assign_data(%row); + } + if ( $show{'battery'} ){ + set_dmi_data() if $b_dmi && !$b_dmi_check; + $b_dmi_check = 1; + %row = line_handler('Battery','battery'); + if (%row || $show{'battery-forced'}){ + assign_data(%row); + } + } + if ( $show{'ram'} ){ + set_dmi_data() if $b_dmi && !$b_dmi_check; + $b_dmi_check = 1; + %row = line_handler('Memory','ram'); + assign_data(%row); + } + if ( $show{'slot'} ){ + set_dmi_data() if $b_dmi && !$b_dmi_check; + $b_dmi_check = 1; + %row = line_handler('PCI Slots','slot'); + assign_data(%row); + } + if ( $show{'cpu'} || $show{'cpu-basic'} ){ + set_dmesg_boot_data() if ($bsd_type && !$b_dmesg_boot_check); + my $arg = ($show{'cpu-basic'}) ? 'basic' : 'full' ; + %row = line_handler('CPU','cpu',$arg); + assign_data(%row); + } + if ( $show{'graphic'} ){ + set_pci_data() if !$b_pci_check; + $b_pci_check = 1; + %row = line_handler('Graphics','graphic'); + assign_data(%row); + } + if ( $show{'audio'} ){ + set_pci_data() if !$b_pci_check; + $b_pci_check = 1; + %row = line_handler('Audio','audio'); + assign_data(%row); + } + if ( $show{'network'} ){ + set_usb_data() if !$b_usb_check; + set_pci_data() if !$b_pci_check; + set_ip_data() if ($show{'ip'} || ($bsd_type && $show{'network-advanced'})); + $b_pci_check = 1; + $b_usb_check = 1; + %row = line_handler('Network','network'); + assign_data(%row); + } + if ( $show{'disk'} || $show{'disk-basic'} || $show{'disk-total'} || $show{'optical'} ){ + set_dmesg_boot_data() if ($bsd_type && !$b_dmesg_boot_check); + %row = line_handler('Drives','disk'); + assign_data(%row); + } + if ( $show{'raid'} ){ + set_pci_data() if !$b_pci_check; + %row = line_handler('RAID','raid'); + assign_data(%row); + } + if ( $show{'partition'} || $show{'partition-full'}){ + %row = line_handler('Partition','partition'); + assign_data(%row); + } + if ( $show{'unmounted'} ){ + %row = line_handler('Unmounted','unmounted'); + assign_data(%row); + } + if ( $show{'usb'} ){ + set_usb_data() if !$b_usb_check; + %row = line_handler('USB','usb'); + assign_data(%row); + $b_usb_check = 1; + } + if ( $show{'sensor'} ){ + %row = line_handler('Sensors','sensor'); + assign_data(%row); + } + if ( $show{'repo'} ){ + %row = line_handler('Repos','repo'); + assign_data(%row); + } + if ( $show{'process'} ){ + %row = line_handler('Processes','process'); + assign_data(%row); + } + if ( $show{'weather'} ){ + %row = line_handler('Weather','weather'); + assign_data(%row); + } + if ( $show{'info'} ){ + %row = generate_info_data(); + assign_data(%row); + } + } + if ( $output_type ne 'screen' ){ + output_handler(%rows); + } + eval $end if $b_log; +} + +sub line_handler { + eval $start if $b_log; + my ($key,$sub,$arg) = @_; + my %subs = ( + 'audio' => \&AudioData::get, + 'battery' => \&BatteryData::get, + 'cpu' => \&CpuData::get, + 'disk' => \&DiskData::get, + 'graphic' => \&GraphicData::get, + 'machine' => \&MachineData::get, + 'network' => \&NetworkData::get, + 'partition' => \&PartitionData::get, + 'raid' => \&RaidData::get, + 'ram' => \&RamData::get, + 'repo' => \&RepoData::get, + 'process' => \&ProcessData::get, + 'sensor' => \&SensorData::get, + 'slot' => \&SlotData::get, + 'unmounted' => \&UnmountedData::get, + 'usb' => \&UsbData::get, + 'weather' => \&WeatherData::get, + ); + my (%data); + my $data_name = main::key($prefix++,$key); + my @rows = $subs{$sub}->($arg); + if (@rows){ + %data = ($data_name => \@rows,); + } + eval $end if $b_log; + return %data; +} + +#### ------------------------------------------------------------------- +#### SHORT, DEBUG +#### ------------------------------------------------------------------- + +sub generate_short_data { + eval $start if $b_log; + my $num = 0; + my $kernel_os = ($bsd_type) ? 'OS' : 'Kernel'; + my $client = $client{'name-print'}; + my $client_shell = ($b_irc) ? 'Client' : 'Shell'; + if ($client{'version'}){ + $client .= ' ' . $client{'version'}; + } + my ($cpu_string,$speed,$speed_key,$type) = ('','','speed',''); + my $memory = get_memory_data('string'); + my @cpu = CpuData::get('short'); + if (scalar @cpu > 1){ + $type = ($cpu[2]) ? " (-$cpu[2]-)" : ''; + ($speed,$speed_key) = ('',''); + if ($cpu[6]){ + $speed_key = "$cpu[3]/$cpu[5]"; + $cpu[4] =~ s/ MHz//; + $speed = "$cpu[4]/$cpu[6]"; + } + else { + $speed_key = $cpu[3]; + $speed = $cpu[4]; + } + $cpu[1] ||= row_defaults('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'; + } + } + } + my @disk = DiskData::get('short'); + # print Dumper \@disk; + my $disk_string = 'N/A'; + my ($size,$used,$size_type,$used_type) = ('','','',''); + my (@temp,$size_holder,$used_holder); + if (@disk){ + $size = $disk[0]{'size'}; + if ($disk[0]{'size'} && $disk[0]{'size'} =~ /^[0-9\.]+$/){ + $size_holder = $disk[0]{'size'}; + @temp = get_size($size); + $size = $temp[0]; + $size_type = " $temp[1]"; + } + $used = $disk[0]{'used'}; + if (defined $disk[0]{'used'} && $disk[0]{'used'} =~ /^[0-9\.]+$/){ + $used_holder = $disk[0]{'used'}; + @temp = get_size($used); + $used = $temp[0]; + $used_type = " $temp[1]"; + } + # 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$size_type$percent"; + } + else { + $size ||= row_defaults('disk-size-0'); + $disk_string = "$used$used_type/$size$size_type"; + } + } + #print join '; ', @cpu, " sleep: $cpu_sleep\n"; + $memory ||= 'N/A'; + my @data = ({ + main::key($num++,'CPU') => $cpu_string, + main::key($num++,$speed_key) => $speed, + main::key($num++,$kernel_os) => &get_kernel_data(), + main::key($num++,'Up') => &get_uptime(), + main::key($num++,'Mem') => $memory, + main::key($num++,'Storage') => $disk_string, + # could make -1 for ps aux itself, -2 for ps aux and self + main::key($num++,'Procs') => scalar @ps_aux, + main::key($num++,$client_shell) => $client, + main::key($num++,$self_name) => &get_self_version(), + },); + my %row = ( + main::key($prefix,'SHORT') => [(@data),], + ); + eval $end if $b_log; + return %row; +} + +#### ------------------------------------------------------------------- +#### CONSTRUCTED LINES +#### ------------------------------------------------------------------- + +sub generate_info_data { + eval $start if $b_log; + my $num = 0; + my $gcc_alt = ''; + my $running_in = ''; + my $data_name = main::key($prefix++,'Info'); + my ($b_gcc,%row,$gcc,$index,$ref); + my ($gpu_ram,$parent,$percent,$total,$used) = (0,'','','',''); + my $client_shell = ($b_irc) ? 'Client' : 'Shell'; + my $client = $client{'name-print'}; + my @gccs = get_gcc_data(); + if (@gccs){ + $gcc = shift @gccs; + if ($extra > 1 && @gccs){ + $gcc_alt = join '/', @gccs; + } + $b_gcc = 1; + } + $gcc ||= 'N/A'; + if (!$b_irc && $extra > 1 ){ + # bsds don't support -f option to get PPID + if (($b_display && !$b_force_display) && !$bsd_type){ + $parent = get_shell_source(); + } + else { + $parent = get_tty_number(); + $parent = "tty $parent" if $parent ne ''; + } + if ($parent eq 'login'){ + $client{'su-start'} = $parent if !$client{'su-start'}; + $parent = undef; + } + # can be tty 0 so test for defined + $running_in = $parent if defined $parent; + if ($extra > 2 && $running_in && get_ssh_status() ){ + $running_in .= ' (SSH)'; + } + } + my $memory = get_memory_data('splits'); + if ($memory){ + my @temp = split /:/, $memory; + my @temp2 = get_size($temp[0]); + $gpu_ram = $temp[3] if $temp[3]; + $total = ($temp2[1]) ? $temp2[0] . ' ' . $temp2[1] : $temp2[0]; + @temp2 = get_size($temp[1]); + $used = ($temp2[1]) ? $temp2[0] . ' ' . $temp2[1] : $temp2[0]; + $used .= " ($temp[2]%)" if $temp[2]; + if ($gpu_ram){ + @temp2 = get_size($gpu_ram); + $gpu_ram = $temp2[0] . ' ' . $temp2[1] if $temp2[1]; + } + } + $memory ||= 'N/A'; + my %data = ( + $data_name => [{ + main::key($num++,'Processes') => scalar @ps_aux, + main::key($num++,'Uptime') => &get_uptime(), + main::key($num++,'Memory') => $total, + },], + ); + $index = scalar(@{ $data{$data_name} } ) - 1; + $data{$data_name}[$index]{main::key($num++,'used')} = $used; + if ($gpu_ram){ + $data{$data_name}[$index]{main::key($num++,'gpu')} = $gpu_ram; + } + if ( (!$b_display || $b_force_display) || $extra > 0 ){ + my %init = get_init_data(); + my $init_type = ($init{'init-type'}) ? $init{'init-type'}: 'N/A'; + $data{$data_name}[$index]{main::key($num++,'Init')} = $init_type; + if ($extra > 1 ){ + my $init_version = ($init{'init-version'}) ? $init{'init-version'}: 'N/A'; + $data{$data_name}[$index]{main::key($num++,'v')} = $init_version; + } + if ($init{'rc-type'}){ + $data{$data_name}[$index]{main::key($num++,'rc')} = $init{'rc-type'}; + if ($init{'rc-version'}){ + $data{$data_name}[$index]{main::key($num++,'v')} = $init{'rc-version'}; + } + } + if ($init{'runlevel'}){ + $data{$data_name}[$index]{main::key($num++,'runlevel')} = $init{'runlevel'}; + } + if ($extra > 1 ){ + if ($init{'default'}){ + my $default = ($init{'init-type'} eq 'systemd' && $init{'default'} =~ /[^0-9]$/ ) ? 'target' : 'default'; + $data{$data_name}[$index]{main::key($num++,$default)} = $init{'default'}; + } + } + } + if ($extra > 0 ){ + my $b_clang; + my $clang_version = ''; + if (my $path = check_program('clang')){ + $clang_version = 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++,'Compilers')} = $compiler; + if ($b_gcc){ + $data{$data_name}[$index]{main::key($num++,'gcc')} = $gcc; + if ( $extra > 1 && $gcc_alt){ + $data{$data_name}[$index]{main::key($num++,'alt')} = $gcc_alt; + } + } + if ($b_clang){ + $data{$data_name}[$index]{main::key($num++,'clang')} = $clang_version; + } + } + if ($extra > 2 && $client{'su-start'}){ + $client .= " ($client{'su-start'})"; + } + $data{$data_name}[$index]{main::key($num++,$client_shell)} = $client; + if ($extra > 0 && $client{'version'}){ + $data{$data_name}[$index]{main::key($num++,'v')} = $client{'version'}; + } + if ( $running_in ){ + $data{$data_name}[$index]{main::key($num++,'running in')} = $running_in; + } + $data{$data_name}[$index]{main::key($num++,$self_name)} = &get_self_version(); + + eval $end if $b_log; + return %data; +} + +sub generate_system_data { + eval $start if $b_log; + my $num = 0; + my (%row,$ref,$index,$val1); + my $data_name = main::key($prefix++,'System'); + my ($desktop,$desktop_info,$desktop_key,$toolkit,$wm) = ('','','Desktop','',''); + my (@desktop_data,$desktop_version); + + my %data = ( + $data_name => [{}], + ); + $index = scalar(@{ $data{$data_name} } ) - 1; + if ($show{'host'}){ + $data{$data_name}[$index]{main::key($num++,'Host')} = &get_hostname(); + } + $data{$data_name}[$index]{main::key($num++,'Kernel')} = &get_kernel_data(); + $data{$data_name}[$index]{main::key($num++,'bits')} = &get_kernel_bits; + if ($extra > 0){ + my @compiler = get_compiler_version(); # get compiler data + if (scalar @compiler != 2){ + @compiler = ('N/A', ''); + } + $data{$data_name}[$index]{main::key($num++,'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++,'v')} = $compiler[1]; + } + } + # 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]; + $desktop .= ' ' . $desktop_version if $desktop_version; + if ($extra > 0 && $desktop_data[3]){ + #$desktop .= ' (' . $desktop_data[2]; + #$desktop .= ( $desktop_data[3] ) ? ' ' . $desktop_data[3] . ')' : ')'; + $toolkit = "$desktop_data[2] $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] =~ /^(gnome[\s\-_]shell|budgie-wm)$/i || + index(lc($desktop_data[5]),lc($desktop_data[0])) == -1 )){ + $wm = $desktop_data[5]; + $wm .= ' ' . $desktop_data[6] if $extra > 2 && $desktop_data[6]; + } + } + if (!$b_display || ( !$desktop && $b_root)) { + my $tty = get_tty_number(); + if (!$desktop){ + $desktop_info = ''; + } + # it is defined, as '' + if ( $tty eq '' && $client{'console-irc'}){ + $tty = get_tty_console_irc('vtnr'); + } + $desktop = "tty $tty" if $tty ne ''; + $desktop_key = 'Console'; + } + $desktop ||= 'N/A'; + $data{$data_name}[$index]{main::key($num++,$desktop_key)} = $desktop; + if ($toolkit){ + $data{$data_name}[$index]{main::key($num++,'tk')} = $toolkit; + } + if ($extra > 2){ + if ($desktop_info){ + $data{$data_name}[$index]{main::key($num++,'info')} = $desktop_info; + } + } + if ($extra > 1){ + $data{$data_name}[$index]{main::key($num++,'wm')} = $wm if $wm; + my $dms = get_display_manager(); + $dms ||= 'N/A'; + $data{$data_name}[$index]{main::key($num++,'dm')} = $dms; + } + #if ($extra > 2 && $desktop_key ne 'Console'){ + # my $tty = get_tty_number(); + # $data{$data_name}[$index]{main::key($num++,'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++,$distro_key)} = $distro; + if ($extra > 0 && $distro_data[1]){ + $data{$data_name}[$index]{main::key($num++,'base')} = $distro_data[1]; + } + eval $end if $b_log; + return %data; +} + +####################################################################### +#### LAUNCH ######################################################################## -main $@ ## From the End comes the Beginning +main(); ## From the End comes the Beginning ## note: this EOF is needed for smxi handling, this is what triggers the full download ok ###**EOF**###