1#!/usr/local/bin/perl
2## infobash: Copyright (C) 2005-2007  Michiel de Boer aka locsmif
3## inxi: Copyright (C) 2008-2021 Harald Hope
4##       Additional features (C) Scott Rogers - kde, cpu info
5## Further fixes (listed as known): Horst Tritremmel <hjt at sidux.com>
6## Steven Barrett (aka: damentz) - usb audio patch; swap percent used patch
7## Jarett.Stevens - dmidecode -M patch for older systems without /sys machine
8##
9## License: GNU GPL v3 or greater
10##
11## You should have received a copy of the GNU General Public License
12## along with this program.  If not, see <http://www.gnu.org/licenses/>.
13##
14## If you don't understand what Free Software is, please read (or reread)
15## this page: http://www.gnu.org/philosophy/free-sw.html
16##
17## DEVS: NOTE: geany/scite folding is picky. Leave 1 space after # or it breaks!
18
19use strict;
20use warnings;
21# use diagnostics;
22use 5.008;
23
24## Perl 7 things for testing: depend on Perl 5.032
25# use 5.032;
26# use compat::perl5;  # act like Perl 5's defaults
27# no feature qw(indirect);
28# no multidimensional;
29# no bareword::filehandles;
30
31use Cwd qw(abs_path); # #abs_path realpath getcwd
32use Data::Dumper qw(Dumper); # print_r
33# NOTE: load in SystemDebugger unless encounter issues with require/import
34# use File::Find;
35use File::stat; # needed for Xorg.0.log file mtime comparisons
36use Getopt::Long qw(GetOptions);
37# Note: default auto_abbrev is enabled
38Getopt::Long::Configure ('bundling', 'no_ignore_case',
39'no_getopt_compat', 'no_auto_abbrev','pass_through');
40use POSIX qw(ceil uname strftime ttyname);
41# use Benchmark qw(:all);_
42# use Devel::Size qw(size total_size);
43# use feature qw(say state); # 5.10 or newer Perl
44
45### INITIALIZE VARIABLES ###
46
47## INXI INFO ##
48my $self_name='inxi';
49my $self_version='3.3.08';
50my $self_date='2021-10-21';
51my $self_patch='00';
52## END INXI INFO ##
53
54my ($b_pledge,@pledges);
55if (eval {require OpenBSD::Pledge}){
56	OpenBSD::Pledge->import();
57	$b_pledge = 1;
58	# cpath/wpath: dir/files .inxi, --debug > 9, -c 9x, -w/W;
59	# dns/inet: ftp upload --debug > 20; exec/proc/rpath: critical;
60	# prot_exec: Perl import; getpw: perl getpwuid() -c 9x, Net::FTP --debug > 20;
61	# stdio: default; error: debugging pledge/perl
62	# tested. not required: mcast pf ps recvfd sendfd tmppath tty unix vminfo;
63	# Pledge removal: OptionsHandler::post_process() [dns,inet,cpath,getpw,wpath];
64	# SelectColors::set_selection() [getpw]
65	@pledges = qw(cpath dns exec getpw inet proc prot_exec rpath wpath);
66	pledge(@pledges);
67}
68
69## Self data
70my ($self_path,$user_config_dir,$user_config_file,$user_data_dir);
71
72## Hashes
73my (%alerts,%build_prop,%client,%colors,%disks_bsd,%dboot,%devices,%dl,
74%dmmapper,%force,%loaded,%mapper,%program_values,%rows,%sensors_raw,
75%service_tool,%show,%sysctl,%system_files,%usb);
76
77## System Arrays
78my (@app,@dmi,@gpudata,@ifs,@ifs_bsd,@paths,@ps_aux,@ps_cmd,@ps_gui,
79@sensors_exclude,@sensors_use,@uname);
80
81## Disk/Logical/Partition/RAID arrays
82my (@btrfs_raid,@glabel,@labels,@lsblk,@lvm,@lvm_raid,@md_raid,@partitions,
83@proc_partitions,@raw_logical,@soft_raid,@swaps,@uuids,@zfs_raid);
84
85## Debuggers
86my %debugger = ('level' => 0);
87my (@dbg,%fake,@t0);
88my ($b_hires,$b_log,$b_log_colors,$b_log_full);
89my ($end,$start,$fh_l,$log_file); # log file handle, file
90my ($t1,$t2,$t3) = (0,0,0); # timers
91## debug / temp tools
92$debugger{'sys'} = 1;
93$client{'test-konvi'} = 0;
94
95# NOTE: redhat removed HiRes from Perl Core Modules.
96if (eval {require Time::HiRes}){
97	Time::HiRes->import('gettimeofday','tv_interval','usleep');
98	$b_hires = 1;
99}
100@t0 = eval 'Time::HiRes::gettimeofday()' if $b_hires; # let's start it right away
101
102## Booleans [busybox_ps not used actively]
103my ($b_admin,$b_android,$b_arm,$b_busybox_ps,$b_cygwin,$b_display,$b_irc,
104$b_mips,$b_ppc,$b_root,$b_running_in_display,$b_sparc);
105
106## System
107my ($bsd_type,$device_vm,$language,$os,$pci_tool,$wan_url) = ('','','','','','');
108my ($bits_sys,$cpu_arch,$ppid);
109my ($cpu_sleep,$dl_timeout,$limit,$ps_cols,$ps_count) = (0.35,4,10,0,5);
110my $sensors_cpu_nu = 0;
111my ($dl_ua,$weather_source,$weather_unit) = ('s-tools/' . $self_name  . '-',100,'mi');
112
113## Tools
114my ($bt_tool,$display,$ftp_alt);
115my ($display_opt,$sudoas) = ('','');
116
117## Output
118my $extra = 0;# supported values: 0-3
119my $filter_string = '<filter>';
120my $line1 = "----------------------------------------------------------------------\n";
121my $line2 = "======================================================================\n";
122my $line3 = "----------------------------------------\n";
123my ($output_file,$output_type) = ('','screen');
124my $prefix = 0; # for the primary row hash key prefix
125
126## Initialize internal hashes
127# these assign a separator to non irc states. Important! Using ':' can
128# trigger stupid emoticon. Note: SEP1/SEP2 from short form not used anymore.
129# behaviors in output on IRC, so do not use those.
130my %sep = (
131's1-irc' => ':',
132's1-console' => ':',
133's2-irc' => '',
134's2-console' => ':',
135);
136#$show{'host'} = 1;
137my %size = (
138'console' => 115,
139# Default indentation level. NOTE: actual indent is 1 greater to allow for
140# spacing
141'indent' => 11,
142'wrap-max' => 90,
143'irc' => 100, # shorter because IRC clients have nick  lists etc
144'max' => 0,
145'no-display' => 130,
146# these will be set dynamically in set_display_width()
147'term' => 80,
148'term-lines' => 100,
149);
150my %use = (
151'update' => 1, # switched off/on with maintainer config ALLOW_UPDATE
152'weather' => 1, # switched off/on with maintainer config ALLOW_WEATHER
153);
154
155########################################################################
156#### STARTUP
157########################################################################
158
159#### -------------------------------------------------------------------
160#### MAIN
161#### -------------------------------------------------------------------
162
163sub main {
164	#	print Dumper \@ARGV;
165	eval $start if $b_log;
166	initialize();
167	## Uncomment these two values for start client debugging
168	# $debugger{'level'} = 3; # 3 prints timers / 10 prints to log file
169	# set_debugger(); # for debugging of konvi and other start client issues
170	## legacy method
171	# my $ob_start = StartClient->new();
172	#$ob_start->get_client_data();
173	StartClient::set();
174	# print_line(Dumper \%client);
175	OptionsHandler::get();
176	set_debugger(); # right after so it's set
177	CheckTools::set();
178	set_colors();
179	set_sep();
180	# print download_file('stdout','https://') . "\n";
181	OutputGenerator::generate();
182	eval $end if $b_log;
183	cleanup();
184	# weechat's executor plugin forced me to do this, and rightfully so,
185	# because else the exit code from the last command is taken..
186	exit 0;
187}
188
189#### -------------------------------------------------------------------
190#### INITIALIZE
191#### -------------------------------------------------------------------
192
193sub initialize {
194	set_os();
195	set_path();
196	set_user_paths();
197	set_basics();
198	set_system_files();
199	Configs::set();
200	# set_downloader();
201	set_display_width('live');
202}
203
204## CheckTools
205{
206package CheckTools;
207my (%commands);
208sub set {
209	eval $start if $b_log;
210	set_commands();
211	my ($action,$program,$message,@data);
212	foreach my $test (keys %commands){
213		($action,$program) = ('use','');
214		$message = main::row_defaults('tool-present');
215		if ($commands{$test}->[1] && (
216			($commands{$test}->[1] eq 'linux' && $os ne 'linux') ||
217			($commands{$test}->[1] eq 'bsd' && $os eq 'linux'))){
218			$action = 'platform';
219		}
220		elsif ($program = main::check_program($test)){
221			# > 0 means error in shell
222			# my $cmd = "$program $commands{$test} >/dev/null";
223			# print "$cmd\n";
224			$pci_tool = $test if $test =~ /pci/;
225			if ($commands{$test}->[0] eq 'exec-sys'){
226				$action = 'permissions' if system("$program $commands{$test}->[2] >/dev/null 2>&1");
227			}
228			elsif ($commands{$test}->[0] eq 'exec-string'){
229				@data = main::grabber("$program $commands{$test}->[2] 2>&1");
230				# dmidecode errors are so specific it gets its own section
231				# also sets custom dmidecode error messages
232				if ($test eq 'dmidecode'){
233					$action = set_dmidecode(\@data) if scalar @data < 15;
234				}
235				elsif (grep { $_ =~ /$commands{$test}->[3]/i } @data){
236					$action = 'permissions';
237				}
238			}
239		}
240		else {
241			$action = 'missing';
242		}
243		$alerts{$test}->{'action'} = $action;
244		$alerts{$test}->{'path'} = $program;
245		if ($action eq 'missing'){
246			$alerts{$test}->{'message'} = main::row_defaults('tool-missing-recommends',"$test");
247		}
248		elsif ($action eq 'permissions'){
249			$alerts{$test}->{'message'} = main::row_defaults('tool-permissions',"$test");
250		}
251		elsif ($action eq 'platform'){
252			$alerts{$test}->{'message'} = main::row_defaults('tool-missing-os', $uname[0] . " $test");
253		}
254	}
255	print Data::Dumper::Dumper \%alerts if $dbg[25];
256	set_fake_bsd_tools() if $fake{'bsd'};
257	set_forced_tools();
258	eval $end if $b_log;
259}
260sub set_dmidecode {
261	my ($data) = @_;
262	my $action = 'use';
263	if ($b_root){
264		foreach (@$data){
265			# don't need first line or scanning /dev/mem lines
266			if (/^(# dmi|Scanning)/){
267				next;
268			}
269			elsif ($_ =~ /No SMBIOS/i){
270				$action = 'smbios';
271				last;
272			}
273			elsif ($_ =~ /^\/dev\/mem: Operation/i){
274				$action = 'no-data';
275				last;
276			}
277			else {
278				$action = 'unknown-error';
279				last;
280			}
281		}
282	}
283	else {
284		if (grep { $_ =~ /^\/dev\/mem: Permission/i } @$data){
285			$action = 'permissions';
286		}
287		else {
288			$action = 'unknown-error';
289		}
290	}
291	if ($action ne 'use' && $action ne 'permissions'){
292		if ($action eq 'smbios'){
293			$alerts{'dmidecode'}->{'message'} = main::row_defaults('dmidecode-smbios');
294		}
295		elsif ($action eq 'no-data'){
296			$alerts{'dmidecode'}->{'message'} = main::row_defaults('dmidecode-dev-mem');
297		}
298		elsif ($action eq 'unknown-error'){
299			$alerts{'dmidecode'}->{'message'} = main::row_defaults('tool-unknown-error','dmidecode');
300		}
301	}
302	return $action;
303}
304sub set_commands {
305	# note: gnu/linux has sysctl so it may be used that for something if present
306	# there is lspci for bsds so doesn't hurt to check it
307	if (!$bsd_type){
308		if ($use{'pci'}){
309			$commands{'lspci'} = ['exec-sys','','-n'];
310		}
311		if ($use{'logical'}){
312			$commands{'lvs'} = ['exec-sys','',''];
313		}
314	}
315	else {
316		if ($use{'pci'}){
317			$commands{'pciconf'} = ['exec-sys','','-l'];
318			$commands{'pcictl'} = ['exec-sys','',' pci0 list'];
319			$commands{'pcidump'} = ['exec-sys','',''];
320		}
321		if ($use{'sysctl'}){
322			# note: there is a case of kernel.osrelease but it's a linux distro
323			$commands{'sysctl'} = ['exec-sys','','kern.osrelease'];
324		}
325		if ($use{'bsd-partition'}){
326			$commands{'bioctl'} = ['missing','',''];
327			$commands{'disklabel'} = ['missing','',''];
328			$commands{'fdisk'} = ['missing','',''];
329			$commands{'gpart'} = ['missing','',''];
330		}
331	}
332	if ($use{'dmidecode'}){
333		$commands{'dmidecode'} = ['exec-string','','-t chassis -t baseboard -t processor',''];
334	}
335	if ($use{'usb'}){
336		# note: lsusb ships in FreeBSD ports sysutils/usbutils
337		$commands{'lsusb'} = ['missing','','',''];
338		# we want these set for various null bsd data tests
339		$commands{'usbconfig'} = ['exec-string','bsd','list','permissions'];
340		$commands{'usbdevs'} = ['missing','bsd','',''];
341	}
342	if ($show{'bluetooth'}){
343		$commands{'bluetoothctl'} = ['missing','linux','',''];
344		# bt-adapter hangs when bluetooth service is disabled
345		$commands{'bt-adapter'} = ['missing','linux','',''];
346		$commands{'hciconfig'} = ['missing','linux','',''];
347	}
348	if ($show{'sensor'}){
349		$commands{'sensors'} = ['missing','linux','',''];
350	}
351	if ($show{'ip'} || ($bsd_type && $show{'network-advanced'})){
352		$commands{'ip'} = ['missing','linux','',''];
353		$commands{'ifconfig'} = ['missing','','',''];
354	}
355	# can't check permissions since we need to know the partition/disc
356	if ($use{'block-tool'}){
357		$commands{'blockdev'} = ['missing','linux','',''];
358		$commands{'lsblk'} = ['missing','linux','',''];
359	}
360	if ($use{'btrfs'}){
361		$commands{'btrfs'} = ['missing','linux','',''];
362	}
363	if ($use{'mdadm'}){
364		$commands{'mdadm'} = ['missing','linux','',''];
365	}
366	if ($use{'smartctl'}){
367		$commands{'smartctl'} = ['missing','','',''];
368	}
369	if ($show{'unmounted'}){
370		$commands{'disklabel'} = ['missing','bsd','xx'];
371	}
372}
373sub set_forced_tools {
374	if ($bt_tool){
375		if ($bt_tool ne 'bluetootctl' && $alerts{'bluetoothctl'}->{'action'} eq 'use'){
376			$alerts{'bluetoothctl'}->{'action'} = 'missing';
377		}
378		if ($bt_tool ne 'bt-adapter' && $alerts{'bt-adapter'}->{'action'} eq 'use'){
379			$alerts{'bt-adapter'}->{'action'} = 'missing';
380		}
381		if ($bt_tool ne 'hciconfig' && $alerts{'hciconfig'}->{'action'} eq 'use'){
382			$alerts{'hciconfig'}->{'action'} = 'missing';
383		}
384	}
385}
386# only for dev/debugging BSD
387sub set_fake_bsd_tools {
388	$system_files{'dmesg-boot'} = '/var/run/dmesg.boot' if $fake{'dboot'};
389	$alerts{'sysctl'}->{'action'} = 'use' if $fake{'sysctl'};
390	if ($fake{'pciconf'} || $fake{'pcictl'} || $fake{'pcidump'}){
391		$alerts{'pciconf'}->{'action'} = 'use' if $fake{'pciconf'};
392		$alerts{'pcictl'}->{'action'} = 'use' if $fake{'pcictl'};
393		$alerts{'pcidump'}->{'action'} = 'use' if $fake{'pcidump'};
394		$alerts{'lspci'} = {
395		'action' => 'missing',
396		'message' => 'Required program lspci not available',
397		};
398	}
399	if ($fake{'usbconfig'} || $fake{'usbdevs'}){
400		$alerts{'usbconfig'}->{'action'} = 'use' if $fake{'usbconfig'};
401		$alerts{'usbdevs'}->{'action'} = 'use' if $fake{'usbdevs'};
402		$alerts{'lsusb'} = {
403		'action' => 'missing',
404		'message' => 'Required program lsusb not available',
405		};
406	}
407	if ($fake{'disklabel'}){
408		$alerts{'disklabel'}->{'action'} = 'use';
409	}
410}
411}
412
413# args: 1 - desktop/app command for --version; 2 - search string;
414# 3 - space print number; 4 - [optional] version arg: -v, version, etc
415# 5 - [optional] exit first find 0/1; 6 - [optional] 0/1 stderr output
416sub set_basics {
417	### LOCALIZATION - DO NOT CHANGE! ###
418	# set to default LANG to avoid locales errors with , or .
419	# Make sure every program speaks English.
420	$ENV{'LANG'}='C';
421	$ENV{'LC_ALL'}='C';
422	# remember, perl uses the opposite t/f return as shell!!!
423	# some versions of busybox do not have tty, like openwrt
424	$b_irc = (check_program('tty') && system('tty >/dev/null')) ? 1 : 0;
425	# print "birc: $b_irc\n";
426	$b_display = ($ENV{'DISPLAY'}) ? 1 : 0;
427	$b_root = $< == 0; # root UID 0, all others > 0
428	$dl{'dl'} = 'curl';
429	$dl{'curl'} = 1;
430	$dl{'tiny'} = 1; # note: two modules needed, tested for in set_downloader
431	$dl{'wget'} = 1;
432	$dl{'fetch'} = 1;
433	$client{'console-irc'} = 0;
434	$client{'dcop'} = (check_program('dcop')) ? 1 : 0;
435	$client{'qdbus'} = (check_program('qdbus')) ? 1 : 0;
436	$client{'konvi'} = 0;
437	$client{'name'} = '';
438	$client{'name-print'} = '';
439	$client{'su-start'} = ''; # shows sudo/su
440	$client{'version'} = '';
441	$colors{'default'} = 2;
442	$show{'partition-sort'} = 'id'; # sort order for partitions
443	@raw_logical = (0,0,0);
444	$ppid = getppid();
445}
446
447# args: $1 - default OR override default cols max integer count. $_[0]
448# is the display width override.
449sub set_display_width {
450	my ($width) = @_;
451	if ($width eq 'live'){
452		## sometimes tput will trigger an error (mageia) if irc client
453		if (!$b_irc){
454			if (my $program = check_program('tput')){
455				# Arch urxvt: 'tput: unknown terminal "rxvt-unicode-256color"'
456				# trips error if use qx(); in FreeBSD, if you use 2>/dev/null
457				# it makes default value 80x24, who knows why?
458				chomp($size{'term'} = qx{$program cols});
459				chomp($size{'term-lines'} = qx{$program lines});
460				$size{'term-cols'} = $size{'term'};
461			}
462			# print "tc: $size{'term'} cmc: $size{'console'}\n";
463			# double check, just in case it's missing functionality or whatever
464			if (!is_int($size{'term'} || $size{'term'} == 0)){
465				$size{'term'}=80;
466				# we'll be using this for terminal dimensions later so don't set default.
467				# $size{'term-lines'}=100;
468			}
469		}
470		# this lets you set different size for in or out of display server
471		if (!$b_running_in_display && $size{'no-display'}){
472			$size{'console'} = $size{'no-display'};
473		}
474		# term_cols is set in top globals, using tput cols
475		# print "tc: $size{'term'} cmc: $size{'console'}\n";
476		if ($size{'term'} < $size{'console'}){
477			$size{'console'} = $size{'term'};
478		}
479		# adjust, some terminals will wrap if output cols == term cols
480		$size{'console'} = ($size{'console'} - 2);
481		# echo cmc: $size{'console'}
482		# comes after source for user set stuff
483		if (!$b_irc){
484			$size{'max'} = $size{'console'};
485		}
486		else {
487			$size{'max'} = $size{'irc'};
488		}
489	}
490	else {
491		$size{'max'} = $width;
492	}
493	# print "tc: $size{'term'} cmc: $size{'console'} cm: $size{'max'}\n";
494}
495
496sub set_os {
497	@uname = uname();
498	$os = lc($uname[0]);
499	$cpu_arch = lc($uname[-1]);
500	if ($cpu_arch =~ /arm|aarch/){$b_arm = 1;}
501	elsif ($cpu_arch =~ /mips/){$b_mips = 1}
502	elsif ($cpu_arch =~ /power|ppc/){$b_ppc = 1}
503	elsif ($cpu_arch =~ /sparc/){$b_sparc = 1}
504	# aarch32 mips32 intel/amd handled in cpu
505	if ($cpu_arch =~ /(armv[1-7]|32|sparc_v9)/){
506		$bits_sys = 32;
507	}
508	elsif ($cpu_arch =~ /(alpha|64|e2k)/){
509		$bits_sys = 64;
510	}
511	$b_cygwin = 1 if $os =~ /cygwin/;
512	$b_android = 1 if -e '/system/build.prop';
513	if ($os =~ /(aix|bsd|cosix|dragonfly|darwin|hp-?ux|indiana|irix|sunos|solaris|ultrix|unix)/){
514		if ($os =~ /openbsd/){
515			$os = 'openbsd';
516		}
517		elsif ($os =~ /darwin/){
518			$os = 'darwin';
519		}
520		# NOTE: most tests internally are against !$bsd_type
521		if ($os =~ /kfreebsd/){
522			$bsd_type = 'debian-bsd';
523		}
524		else {
525			$bsd_type = $os;
526		}
527	}
528}
529
530# Sometimes users will have more PATHs local to their setup, so we want those
531# too.
532sub set_path {
533	# Extra path variable to make execute failures less likely, merged below
534	my (@path);
535	# NOTE: recent Xorg's show error if you try /usr/bin/Xorg -version but work
536	# if you use the /usr/lib/xorg-server/Xorg path.
537	@paths = qw(/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin);
538	@path = split(':', $ENV{'PATH'}) if $ENV{'PATH'};
539	# print "paths: @paths\nPATH: $ENV{'PATH'}\n";
540	# Create a difference of $PATH and $extra_paths and add that to $PATH:
541	foreach my $id (@path){
542		if (!(grep { /^$id$/ } @paths) && $id !~ /(game)/){
543			push(@paths, $id);
544		}
545	}
546	# print "paths: @paths\n";
547}
548
549sub set_sep {
550	if ($b_irc){
551		# too hard to read if no colors, so force that for users on irc
552		if ($colors{'scheme'} == 0){
553			$sep{'s1'} = $sep{'s1-console'};
554			$sep{'s2'} = $sep{'s2-console'};
555		}
556		else {
557			$sep{'s1'} = $sep{'s1-irc'};
558			$sep{'s2'} = $sep{'s2-irc'};
559		}
560	}
561	else {
562		$sep{'s1'} = $sep{'s1-console'};
563		$sep{'s2'} = $sep{'s2-console'};
564	}
565}
566
567# Important: -n makes it non interactive, no prompt for password
568# only use doas/sudo if not root, -n option requires sudo -V 1.7 or greater.
569# for some reason sudo -n with < 1.7 in Perl does not print to stderr
570# sudo will just error out which is the safest course here for now,
571# otherwise that interactive sudo password thing is too annoying
572sub set_sudo {
573	if (!$b_root){
574		my ($path);
575		if (!$force{'no-doas'} && ($path = check_program('doas'))){
576			$sudoas = "$path -n ";
577		}
578		elsif (!$force{'no-sudo'} && ($path = check_program('sudo'))){
579			my @data = program_data('sudo');
580			$data[1] =~ s/^([0-9]+\.[0-9]+).*/$1/;
581			# print "sudo v: $data[1]\n";
582			$sudoas = "$path -n " if is_numeric($data[1]) && $data[1] >= 1.7;
583		}
584	}
585}
586
587sub set_system_files {
588	my %files = (
589	'asound-cards' => '/proc/asound/cards',
590	'asound-modules' => '/proc/asound/modules',
591	'asound-version' => '/proc/asound/version',
592	'dmesg-boot' => '/var/run/dmesg.boot',
593	'proc-cmdline' => '/proc/cmdline',
594	'proc-cpuinfo' => '/proc/cpuinfo',
595	'proc-mdstat' => '/proc/mdstat',
596	'proc-meminfo' => '/proc/meminfo',
597	'proc-modules' => '/proc/modules', # not used
598	'proc-mounts' => '/proc/mounts',# not used
599	'proc-partitions' => '/proc/partitions',
600	'proc-scsi' => '/proc/scsi/scsi',
601	'proc-version' => '/proc/version',
602	# note: 'xorg-log' is set in set_xorg_log() only if -G is triggered
603	);
604	foreach (keys %files){
605		$system_files{$_} = (-e $files{$_}) ? $files{$_} : '';
606	}
607}
608
609sub set_user_paths {
610	my ($b_conf,$b_data);
611	# this needs to be set here because various options call the parent
612	# initialize function directly.
613	$self_path = $0;
614	$self_path =~ s/[^\/]+$//;
615	# print "0: $0 sp: $self_path\n";
616	if (defined $ENV{'XDG_CONFIG_HOME'} && $ENV{'XDG_CONFIG_HOME'}){
617		$user_config_dir=$ENV{'XDG_CONFIG_HOME'};
618		$b_conf=1;
619	}
620	elsif (-d "$ENV{'HOME'}/.config"){
621		$user_config_dir="$ENV{'HOME'}/.config";
622		$b_conf=1;
623	}
624	else {
625		$user_config_dir="$ENV{'HOME'}/.$self_name";
626	}
627	if (defined $ENV{'XDG_DATA_HOME'} && $ENV{'XDG_DATA_HOME'}){
628		$user_data_dir="$ENV{'XDG_DATA_HOME'}/$self_name";
629		$b_data=1;
630	}
631	elsif (-d "$ENV{'HOME'}/.local/share"){
632		$user_data_dir="$ENV{'HOME'}/.local/share/$self_name";
633		$b_data=1;
634	}
635	else {
636		$user_data_dir="$ENV{'HOME'}/.$self_name";
637	}
638	# note, this used to be created/checked in specific instance, but we'll just
639	# do it universally so it's done at script start.
640	if (! -d $user_data_dir){
641		mkdir $user_data_dir;
642		# system "echo", "Made: $user_data_dir";
643	}
644	if ($b_conf && -f "$ENV{'HOME'}/.$self_name/$self_name.conf"){
645		# system 'mv', "-f $ENV{'HOME'}/.$self_name/$self_name.conf", $user_config_dir;
646		# print "WOULD: Moved $self_name.conf from $ENV{'HOME'}/.$self_name to $user_config_dir\n";
647	}
648	if ($b_data && -d "$ENV{'HOME'}/.$self_name"){
649		# system 'mv', '-f', "$ENV{'HOME'}/.$self_name/*", $user_data_dir;
650		# system 'rm', '-Rf', "$ENV{'HOME'}/.$self_name";
651		# print "WOULD: Moved data dir $ENV{'HOME'}/.$self_name to $user_data_dir\n";
652	}
653	$log_file="$user_data_dir/$self_name.log";
654	# system 'echo', "$ENV{'HOME'}/.$self_name/* $user_data_dir";
655	# print "scd: $user_config_dir sdd: $user_data_dir \n";
656}
657
658sub set_xorg_log {
659	eval $start if $b_log;
660	my (@temp,@x_logs);
661	my ($file_holder,$time_holder,$x_mtime) = ('',0,0);
662	# NOTE: other variations may be /var/run/gdm3/... but not confirmed
663	# worry about we are just going to get all the Xorg logs we can find,
664	# and not which is 'right'.
665	@temp = globber('/var/log/Xorg.*.log');
666	push(@x_logs, @temp) if @temp;
667	@temp = globber('/var/lib/gdm/.local/share/xorg/Xorg.*.log');
668	push(@x_logs, @temp) if @temp;
669	@temp = globber($ENV{'HOME'} . '/.local/share/xorg/Xorg.*.log',);
670	push(@x_logs, @temp) if @temp;
671	# root will not have a /root/.local/share/xorg directory so need to use a
672	# user one if we can find one.
673	if ($b_root){
674		@temp = globber('/home/*/.local/share/xorg/Xorg.*.log');
675		push(@x_logs, @temp) if @temp;
676	}
677	foreach (@x_logs){
678		if (-r $_){
679			my $src_info = File::stat::stat("$_");
680			# print "$_\n";
681			if ($src_info){
682				$x_mtime = $src_info->mtime;
683				# print $_ . ": $x_time" . "\n";
684				if ($x_mtime > $time_holder){
685					$time_holder = $x_mtime;
686					$file_holder = $_;
687				}
688			}
689		}
690	}
691	if (!$file_holder && check_program('xset')){
692		my $data = qx(xset q 2>/dev/null);
693		foreach (split('\n', $data)){
694			if ($_ =~ /Log file/i){
695				$file_holder = get_piece($_,3);
696				last;
697			}
698		}
699	}
700	print "Xorg log file: $file_holder\nLast modified: $time_holder\n" if $dbg[14];
701	log_data('data',"Xorg log file: $file_holder") if $b_log;
702	$system_files{'xorg-log'} = $file_holder;
703	eval $end if $b_log;
704}
705
706########################################################################
707#### UTILITIES
708########################################################################
709
710#### -------------------------------------------------------------------
711#### COLORS
712#### -------------------------------------------------------------------
713
714## arg: 1 - the type of action, either integer, count, or full
715sub get_color_scheme {
716	my ($type) = @_;
717	eval $start if $b_log;
718	my @color_schemes = (
719	[qw(EMPTY EMPTY EMPTY)],
720	[qw(NORMAL NORMAL NORMAL)],
721	# for dark OR light backgrounds
722	[qw(BLUE NORMAL NORMAL)],
723	[qw(BLUE RED NORMAL)],
724	[qw(CYAN BLUE NORMAL)],
725	[qw(DCYAN NORMAL NORMAL)],
726	[qw(DCYAN BLUE NORMAL)],
727	[qw(DGREEN NORMAL NORMAL)],
728	[qw(DYELLOW NORMAL NORMAL)],
729	[qw(GREEN DGREEN NORMAL)],
730	[qw(GREEN NORMAL NORMAL)],
731	[qw(MAGENTA NORMAL NORMAL)],
732	[qw(RED NORMAL NORMAL)],
733	# for light backgrounds
734	[qw(BLACK DGREY NORMAL)],
735	[qw(DBLUE DGREY NORMAL)],
736	[qw(DBLUE DMAGENTA NORMAL)],
737	[qw(DBLUE DRED NORMAL)],
738	[qw(DBLUE BLACK NORMAL)],
739	[qw(DGREEN DYELLOW NORMAL)],
740	[qw(DYELLOW BLACK NORMAL)],
741	[qw(DMAGENTA BLACK NORMAL)],
742	[qw(DCYAN DBLUE NORMAL)],
743	# for dark backgrounds
744	[qw(WHITE GREY NORMAL)],
745	[qw(GREY WHITE NORMAL)],
746	[qw(CYAN GREY NORMAL)],
747	[qw(GREEN WHITE NORMAL)],
748	[qw(GREEN YELLOW NORMAL)],
749	[qw(YELLOW WHITE NORMAL)],
750	[qw(MAGENTA CYAN NORMAL)],
751	[qw(MAGENTA YELLOW NORMAL)],
752	[qw(RED CYAN NORMAL)],
753	[qw(RED WHITE NORMAL)],
754	[qw(BLUE WHITE NORMAL)],
755	# miscellaneous
756	[qw(RED BLUE NORMAL)],
757	[qw(RED DBLUE NORMAL)],
758	[qw(BLACK BLUE NORMAL)],
759	[qw(BLACK DBLUE NORMAL)],
760	[qw(NORMAL BLUE NORMAL)],
761	[qw(BLUE MAGENTA NORMAL)],
762	[qw(DBLUE MAGENTA NORMAL)],
763	[qw(BLACK MAGENTA NORMAL)],
764	[qw(MAGENTA BLUE NORMAL)],
765	[qw(MAGENTA DBLUE NORMAL)],
766	);
767	eval $end if $b_log;
768	if ($type eq 'count'){
769		return scalar @color_schemes;
770	}
771	if ($type eq 'full'){
772		return @color_schemes;
773	}
774	else {
775		return @{$color_schemes[$type]};
776		# print Dumper $color_schemes[$scheme_nu];
777	}
778}
779
780sub set_color_scheme {
781	eval $start if $b_log;
782	my ($scheme) = @_;
783	$colors{'scheme'} = $scheme;
784	my $index = ($b_irc) ? 1 : 0; # defaults to non irc
785
786	# NOTE: qw(...) kills the escape, it is NOT the same as using
787	# Literal "..", ".." despite docs saying it is.
788	my %color_palette = (
789	'EMPTY' => [ '', '' ],
790	'DGREY' => [ "\e[1;30m", "\x0314" ],
791	'BLACK' => [ "\e[0;30m", "\x0301" ],
792	'RED' => [ "\e[1;31m", "\x0304" ],
793	'DRED' => [ "\e[0;31m", "\x0305" ],
794	'GREEN' => [ "\e[1;32m", "\x0309" ],
795	'DGREEN' => [ "\e[0;32m", "\x0303" ],
796	'YELLOW' => [ "\e[1;33m", "\x0308" ],
797	'DYELLOW' => [ "\e[0;33m", "\x0307" ],
798	'BLUE' => [ "\e[1;34m", "\x0312" ],
799	'DBLUE' => [ "\e[0;34m", "\x0302" ],
800	'MAGENTA' => [ "\e[1;35m", "\x0313" ],
801	'DMAGENTA' => [ "\e[0;35m", "\x0306" ],
802	'CYAN' => [ "\e[1;36m", "\x0311" ],
803	'DCYAN' => [ "\e[0;36m", "\x0310" ],
804	'WHITE' => [ "\e[1;37m", "\x0300" ],
805	'GREY' => [ "\e[0;37m", "\x0315" ],
806	'NORMAL' => [ "\e[0m", "\x03" ],
807	);
808	my @scheme = get_color_scheme($colors{'scheme'});
809	$colors{'c1'} = $color_palette{$scheme[0]}->[$index];
810	$colors{'c2'} = $color_palette{$scheme[1]}->[$index];
811	$colors{'cn'} = $color_palette{$scheme[2]}->[$index];
812	# print Dumper \@scheme;
813	# print "$colors{'c1'}here$colors{'c2'} we are!$colors{'cn'}\n";
814	eval $end if $b_log;
815}
816
817sub set_colors {
818	eval $start if $b_log;
819	# it's already been set with -c 0-43
820	if (exists $colors{'c1'}){
821		return 1;
822	}
823	# This let's user pick their color scheme. For IRC, only shows the color
824	# schemes, no interactive. The override value only will be placed in user
825	# config files. /etc/inxi.conf can also override
826	if (exists $colors{'selector'}){
827		my $ob_selector = SelectColors->new($colors{'selector'});
828		$ob_selector->select_schema();
829		return 1;
830	}
831	# set the default, then override as required
832	my $color_scheme = $colors{'default'};
833	# these are set in user configs
834	if (defined $colors{'global'}){
835		$color_scheme = $colors{'global'};
836	}
837	else {
838		if ($b_irc){
839			if (defined $colors{'irc-virt-term'} && $b_display && $client{'console-irc'}){
840				$color_scheme = $colors{'irc-virt-term'};
841			}
842			elsif (defined $colors{'irc-console'} && !$b_display){
843				$color_scheme = $colors{'irc-console'};
844			}
845			elsif (defined $colors{'irc-gui'}){
846				$color_scheme = $colors{'irc-gui'};
847			}
848		}
849		else {
850			if (defined $colors{'console'} && !$b_display){
851				$color_scheme = $colors{'console'};
852			}
853			elsif (defined $colors{'virt-term'}){
854				$color_scheme = $colors{'virt-term'};
855			}
856		}
857	}
858	# force 0 for | or > output, all others prints to irc or screen
859	if (!$b_irc && ! -t STDOUT){
860		$color_scheme = 0;
861	}
862	set_color_scheme($color_scheme);
863	eval $end if $b_log;
864}
865
866## SelectColors
867{
868package SelectColors;
869my (@data,%configs,%status);
870my ($type,$w_fh);
871my $safe_color_count = 12; # null/normal + default color group
872my $count = 0;
873# args: 1 - type
874sub new {
875	my $class = shift;
876	($type) = @_;
877	my $self = {};
878	return bless $self, $class;
879}
880sub select_schema {
881	eval $start if $b_log;
882	assign_selectors();
883	main::set_color_scheme(0);
884	set_status();
885	start_selector();
886	create_color_selections();
887	if (!$b_irc){
888		Configs::check_file();
889		get_selection();
890	}
891	else {
892		print_irc_message();
893	}
894	eval $end if $b_log;
895}
896
897sub set_status {
898	$status{'console'} = (defined $colors{'console'}) ? "Set: $colors{'console'}" : 'Not Set';
899	$status{'virt-term'} = (defined $colors{'virt-term'}) ? "Set: $colors{'virt-term'}" : 'Not Set';
900	$status{'irc-console'} = (defined $colors{'irc-console'}) ? "Set: $colors{'irc-console'}" : 'Not Set';
901	$status{'irc-gui'} = (defined $colors{'irc-gui'}) ? "Set: $colors{'irc-gui'}" : 'Not Set';
902	$status{'irc-virt-term'} = (defined $colors{'irc-virt-term'}) ? "Set: $colors{'irc-virt-term'}" : 'Not Set';
903	$status{'global'} = (defined $colors{'global'}) ? "Set: $colors{'global'}" : 'Not Set';
904}
905
906sub assign_selectors {
907	if ($type == 94){
908		$configs{'variable'} = 'CONSOLE_COLOR_SCHEME';
909		$configs{'selection'} = 'console';
910	}
911	elsif ($type == 95){
912		$configs{'variable'} = 'VIRT_TERM_COLOR_SCHEME';
913		$configs{'selection'} = 'virt-term';
914	}
915	elsif ($type == 96){
916		$configs{'variable'} = 'IRC_COLOR_SCHEME';
917		$configs{'selection'} = 'irc-gui';
918	}
919	elsif ($type == 97){
920		$configs{'variable'} = 'IRC_X_TERM_COLOR_SCHEME';
921		$configs{'selection'} = 'irc-virt-term';
922	}
923	elsif ($type == 98){
924		$configs{'variable'} = 'IRC_CONS_COLOR_SCHEME';
925		$configs{'selection'} = 'irc-console';
926	}
927	elsif ($type == 99){
928		$configs{'variable'} = 'GLOBAL_COLOR_SCHEME';
929		$configs{'selection'} = 'global';
930	}
931}
932sub start_selector {
933	my $whoami = getpwuid($<) || "unknown???";
934	if (!$b_irc){
935		@data = (
936		[ 0, '', '', "Welcome to $self_name! Please select the default
937		$configs{'selection'} color scheme."],
938		);
939	}
940	push(@data,
941	[ 0, '', '', "Because there is no way to know your $configs{'selection'}
942	foreground/background colors, you can set your color preferences from
943	color scheme option list below:"],
944	[ 0, '', '', "0 is no colors; 1 is neutral."],
945	[ 0, '', '', "After these, there are 4 sets:"],
946	[ 0, '', '', "1-dark^or^light^backgrounds; 2-light^backgrounds;
947	3-dark^backgrounds; 4-miscellaneous"],
948	[ 0, '', '', ""],
949	);
950	if (!$b_irc){
951		push(@data,
952		[ 0, '', '', "Please note that this will set the $configs{'selection'}
953		preferences only for user: $whoami"],
954		);
955	}
956	push(@data,
957	[ 0, '', '', "$line1"],
958	);
959	main::print_basic(\@data);
960	@data = ();
961}
962sub create_color_selections {
963	my $spacer = '^^'; # printer removes double spaces, but replaces ^ with ' '
964	$count = (main::get_color_scheme('count') - 1);
965	foreach my $i (0 .. $count){
966		if ($i > 9){
967			$spacer = '^';
968		}
969		if ($configs{'selection'} =~ /^(global|irc-gui|irc-console|irc-virt-term)$/ && $i > $safe_color_count){
970			last;
971		}
972		main::set_color_scheme($i);
973		push(@data,
974		[0, '', '', "$i)$spacer$colors{'c1'}Card:$colors{'c2'}^nVidia^GT218
975		$colors{'c1'}Display^Server$colors{'c2'}^x11^(X.Org^1.7.7)$colors{'cn'}"],
976		);
977	}
978	main::print_basic(\@data);
979	@data = ();
980	main::set_color_scheme(0);
981}
982sub get_selection {
983	my $number = $count + 1;
984	@data = (
985	[0, '', '', ($number++) . ")^Remove all color settings. Restore $self_name default."],
986	[0, '', '', ($number++) . ")^Continue, no changes or config file setting."],
987	[0, '', '', ($number++) . ")^Exit, use another terminal, or set manually."],
988	[0, '', '', "$line1"],
989	[0, '', '', "Simply type the number for the color scheme that looks best to your
990	eyes for your $configs{'selection'} settings and hit <ENTER>. NOTE: You can bring this
991	option list up by starting $self_name with option: -c plus one of these numbers:"],
992	[0, '', '', "94^-^console,^not^in^desktop^-^$status{'console'}"],
993	[0, '', '', "95^-^terminal,^desktop^-^$status{'virt-term'}"],
994	[0, '', '', "96^-^irc,^gui,^desktop^-^$status{'irc-gui'}"],
995	[0, '', '', "97^-^irc,^desktop,^in^terminal^-^$status{'irc-virt-term'}"],
996	[0, '', '', "98^-^irc,^not^in^desktop^-^$status{'irc-console'}"],
997	[0, '', '', "99^-^global^-^$status{'global'}"],
998	[0, '', '',  ""],
999	[0, '', '', "Your selection(s) will be stored here: $user_config_file"],
1000	[0, '', '', "Global overrides all individual color schemes. Individual
1001	schemes remove the global setting."],
1002	[0, '', '', "$line1"],
1003	);
1004	main::print_basic(\@data);
1005	@data = ();
1006	my $response = <STDIN>;
1007	chomp($response);
1008	if (!main::is_int($response) || $response > ($count + 3)){
1009		@data = (
1010		[0, '', '', "Error - Invalid Selection. You entered this: $response. Hit <ENTER> to continue."],
1011		[0, '', '',  "$line1"],
1012		);
1013		main::print_basic(\@data);
1014		my $response = <STDIN>;
1015		start_selector();
1016		create_color_selections();
1017		get_selection();
1018	}
1019	else {
1020		process_selection($response);
1021	}
1022	if ($b_pledge){
1023		@pledges = grep {$_ ne 'getpw'} @pledges;
1024		OpenBSD::Pledge::pledge(@pledges);
1025	}
1026}
1027sub process_selection {
1028	my $response = shift;
1029	if ($response == ($count + 3)){
1030		@data = (
1031		[0, '', '', "Ok, exiting $self_name now. You can set the colors later."],
1032		);
1033		main::print_basic(\@data);
1034		exit 0;
1035	}
1036	elsif ($response == ($count + 2)){
1037		@data = (
1038		[0, '', '', "Ok, continuing $self_name unchanged."],
1039		[0, '', '',  "$line1"],
1040		);
1041		main::print_basic(\@data);
1042		if (defined $colors{'console'} && !$b_display){
1043			main::set_color_scheme($colors{'console'});
1044		}
1045		if (defined $colors{'virt-term'}){
1046			main::set_color_scheme($colors{'virt-term'});
1047		}
1048		else {
1049			main::set_color_scheme($colors{'default'});
1050		}
1051	}
1052	elsif ($response == ($count + 1)){
1053		@data = (
1054		[0, '', '', "Removing all color settings from config file now..."],
1055		[0, '', '',  "$line1"],
1056		);
1057		main::print_basic(\@data);
1058		delete_all_config_colors();
1059		main::set_color_scheme($colors{'default'});
1060	}
1061	else {
1062		main::set_color_scheme($response);
1063		@data = (
1064		[0, '', '', "Updating config file for $configs{'selection'} color scheme now..."],
1065		[0, '', '',  "$line1"],
1066		);
1067		main::print_basic(\@data);
1068		if ($configs{'selection'} eq 'global'){
1069			delete_all_colors();
1070		}
1071		else {
1072			delete_global_color();
1073		}
1074		set_config_color_scheme($response);
1075	}
1076}
1077sub delete_all_colors {
1078	my @file_lines = main::reader($user_config_file);
1079	open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!);
1080	foreach (@file_lines){
1081		if ($_ !~ /^(CONSOLE_COLOR_SCHEME|GLOBAL_COLOR_SCHEME|IRC_COLOR_SCHEME|IRC_CONS_COLOR_SCHEME|IRC_X_TERM_COLOR_SCHEME|VIRT_TERM_COLOR_SCHEME)/){
1082			print {$w_fh} "$_";
1083		}
1084	}
1085	close $w_fh;
1086}
1087sub delete_global_color {
1088	my @file_lines = main::reader($user_config_file);
1089	open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!);
1090	foreach (@file_lines){
1091		if ($_ !~ /^GLOBAL_COLOR_SCHEME/){
1092			print {$w_fh} "$_";
1093		}
1094	}
1095	close $w_fh;
1096}
1097sub set_config_color_scheme {
1098	my $value = shift;
1099	my @file_lines = main::reader($user_config_file);
1100	my $b_found = 0;
1101	open($w_fh, '>', $user_config_file) or main::error_handler('open', $user_config_file, $!);
1102	foreach (@file_lines){
1103		if ($_ =~ /^$configs{'variable'}/){
1104			$_ = "$configs{'variable'}=$value";
1105			$b_found = 1;
1106		}
1107		print $w_fh "$_\n";
1108	}
1109	if (!$b_found){
1110		print $w_fh "$configs{'variable'}=$value\n";
1111	}
1112	close $w_fh;
1113}
1114
1115sub print_irc_message {
1116	@data = (
1117	[ 0, '', '', "$line1"],
1118	[ 0, '', '', "After finding the scheme number you like, simply run this again
1119	in a terminal to set the configuration data file for your irc client. You can
1120	set color schemes for the following: start inxi with -c plus:"],
1121	[ 0, '', '', "94 (console,^not^in^desktop^-^$status{'console'})"],
1122	[ 0, '', '', "95 (terminal, desktop^-^$status{'virt-term'})"],
1123	[ 0, '', '', "96 (irc,^gui,^desktop^-^$status{'irc-gui'})"],
1124	[ 0, '', '', "97 (irc,^desktop,^in terminal^-^$status{'irc-virt-term'})"],
1125	[ 0, '', '', "98 (irc,^not^in^desktop^-^$status{'irc-console'})"],
1126	[ 0, '', '', "99 (global^-^$status{'global'})"]
1127	);
1128	main::print_basic(\@data);
1129	exit 0;
1130}
1131
1132}
1133
1134#### -------------------------------------------------------------------
1135#### CONFIGS
1136#### -------------------------------------------------------------------
1137
1138## Configs
1139# public: set() check_file()
1140{
1141package Configs;
1142sub set {
1143	my ($configs) = @_;
1144	my ($key, $val,@config_files);
1145	if (!$configs){
1146		@config_files = (
1147		qq(/etc/$self_name.conf),
1148		qq(/etc/$self_name.d/$self_name.conf),
1149		qq($user_config_dir/$self_name.conf)
1150		);
1151	}
1152	else {
1153		@config_files = @$configs;
1154	}
1155	# Config files should be passed in an array as a param to this function.
1156	# Default intended use: global @CONFIGS;
1157	foreach (@config_files){
1158		next unless open(my $fh, '<', "$_");
1159		while (<$fh>){
1160			chomp;
1161			s/#.*//;
1162			s/^\s+//;
1163			s/\s+$//;
1164			s/'|"//g;
1165			s/true/1/i; # switch to 1/0 perl boolean
1166			s/false/0/i; # switch to 1/0 perl boolean
1167			next unless length;
1168			($key, $val) = split(/\s*=\s*/, $_, 2);
1169			next unless length($val);
1170			process_item($key,$val);
1171			# print "f: $file key: $key val: $val\n";
1172		}
1173		close $fh;
1174	}
1175}
1176# note: someone managed to make a config file with corrupted values, so check
1177# int explicitly, don't assume it was done correctly.
1178# args: 0: key; 1: value
1179sub process_item {
1180	my ($key,$val) = @_;
1181	if ($key eq 'ALLOW_UPDATE' || $key eq 'B_ALLOW_UPDATE'){
1182		$use{'update'} = $val if main::is_int($val)}
1183	elsif ($key eq 'ALLOW_WEATHER' || $key eq 'B_ALLOW_WEATHER'){
1184		$use{'weather'} = $val if main::is_int($val)}
1185	elsif ($key eq 'CPU_SLEEP'){
1186		$cpu_sleep = $val if main::is_numeric($val)}
1187	elsif ($key eq 'DL_TIMEOUT'){
1188		$dl_timeout = $val if main::is_int($val)}
1189	elsif ($key eq 'DOWNLOADER'){
1190		if ($val =~ /^(curl|fetch|ftp|perl|wget)$/){
1191			# this dumps all the other data and resets %dl for only the
1192			# desired downloader.
1193			$val = main::set_perl_downloader($val);
1194			%dl = ('dl' => $val, $val => 1);
1195		}}
1196	elsif ($key eq 'FILTER_STRING'){
1197		$filter_string = $val}
1198	elsif ($key eq 'LANGUAGE'){
1199		$language = $val if $val =~ /^(en)$/}
1200	elsif ($key eq 'LIMIT'){
1201		$limit = $val if main::is_int($val)}
1202	elsif ($key eq 'OUTPUT_TYPE'){
1203		$output_type = $val if $val =~ /^(json|screen|xml)$/}
1204	elsif ($key eq 'NO_DIG'){
1205		$force{'no-dig'} = $val if main::is_int($val)}
1206	elsif ($key eq 'NO_DOAS'){
1207		$force{'no-doas'} = $val if main::is_int($val)}
1208	elsif ($key eq 'NO_HTML_WAN'){
1209		$force{'no-html-wan'} = $val if main::is_int($val)}
1210	elsif ($key eq 'NO_SUDO'){
1211		$force{'no-sudo'} = $val if main::is_int($val)}
1212	elsif ($key eq 'PARTITION_SORT'){
1213		if ($val =~ /^(dev-base|fs|id|label|percent-used|size|uuid|used)$/){
1214			$show{'partition-sort'} = $val;
1215		}}
1216	elsif ($key eq 'PS_COUNT'){
1217		$ps_count = $val if main::is_int($val) }
1218	elsif ($key eq 'SENSORS_CPU_NO'){
1219		$sensors_cpu_nu = $val if main::is_int($val)}
1220	elsif ($key eq 'SENSORS_EXCLUDE'){
1221		@sensors_exclude = split(/\s*,\s*/, $val) if $val}
1222	elsif ($key eq 'SENSORS_USE'){
1223		@sensors_use = split(/\s*,\s*/, $val) if $val}
1224	elsif ($key eq 'SHOW_HOST' || $key eq 'B_SHOW_HOST'){
1225		if (main::is_int($val)){
1226			$show{'host'} = $val;
1227			$show{'no-host'} = 1 if !$show{'host'};
1228		}
1229	}
1230	elsif ($key eq 'USB_SYS'){
1231		$force{'usb-sys'} = $val if main::is_int($val)}
1232	elsif ($key eq 'WAN_IP_URL'){
1233		if ($val =~ /^(ht|f)tp[s]?:\//i){
1234			$wan_url = $val;
1235			$force{'no-dig'} = 1;
1236		}
1237	}
1238	elsif ($key eq 'WEATHER_SOURCE'){
1239		$weather_source = $val if main::is_int($val)}
1240	elsif ($key eq 'WEATHER_UNIT'){
1241		$val = lc($val) if $val;
1242		if ($val && $val =~ /^(c|f|cf|fc|i|m|im|mi)$/){
1243			my %units = ('c'=>'m','f'=>'i','cf'=>'mi','fc'=>'im');
1244			$val = $units{$val} if defined $units{$val};
1245			$weather_unit = $val;
1246		}
1247	}
1248	# layout
1249	elsif ($key eq 'CONSOLE_COLOR_SCHEME'){
1250		$colors{'console'} = $val if main::is_int($val)}
1251	elsif ($key eq 'GLOBAL_COLOR_SCHEME'){
1252		$colors{'global'} = $val if main::is_int($val)}
1253	elsif ($key eq 'IRC_COLOR_SCHEME'){
1254		$colors{'irc-gui'} = $val if main::is_int($val)}
1255	elsif ($key eq 'IRC_CONS_COLOR_SCHEME'){
1256		$colors{'irc-console'} = $val if main::is_int($val)}
1257	elsif ($key eq 'IRC_X_TERM_COLOR_SCHEME'){
1258		$colors{'irc-virt-term'} = $val if main::is_int($val)}
1259	elsif ($key eq 'VIRT_TERM_COLOR_SCHEME'){
1260		$colors{'virt-term'} = $val if main::is_int($val)}
1261	# note: not using the old short SEP1/SEP2
1262	elsif ($key eq 'SEP1_IRC'){
1263		$sep{'s1-irc'} = $val}
1264	elsif ($key eq 'SEP1_CONSOLE'){
1265		$sep{'s1-console'} = $val}
1266	elsif ($key eq 'SEP2_IRC'){
1267		$sep{'s2-irc'} = $val}
1268	elsif ($key eq 'SEP2_CONSOLE'){
1269		$sep{'s2-console'} = $val}
1270	# size
1271	elsif ($key eq 'COLS_MAX_CONSOLE'){
1272		$size{'console'} = $val if main::is_int($val)}
1273	elsif ($key eq 'COLS_MAX_IRC'){
1274		$size{'irc'} = $val if main::is_int($val)}
1275	elsif ($key eq 'COLS_MAX_NO_DISPLAY'){
1276		$size{'no-display'} = $val if main::is_int($val)}
1277	elsif ($key eq 'INDENT'){
1278		$size{'indent'} = $val if main::is_int($val)}
1279	elsif ($key eq 'WRAP_MAX' || $key eq 'INDENT_MIN'){
1280		$size{'wrap-max'} = $val if main::is_int($val)}
1281	#  print "mc: key: $key val: $val\n";
1282	# print Dumper (keys %size) . "\n";
1283}
1284sub check_file {
1285	$user_config_file = "$user_config_dir/$self_name.conf";
1286	if (! -f $user_config_file){
1287		open(my $fh, '>', $user_config_file) or
1288		 main::error_handler('create', $user_config_file, $!);
1289	}
1290}
1291}
1292
1293#### -------------------------------------------------------------------
1294#### DEBUGGERS
1295#### -------------------------------------------------------------------
1296
1297# called in the initial -@ 10 program args setting so we can get logging
1298# as soon as possible # will have max 3 files, inxi.log, inxi.1.log,
1299# inxi.2.log
1300sub begin_logging {
1301	return 1 if $fh_l; # if we want to start logging for testing before options
1302	my $log_file_2="$user_data_dir/$self_name.1.log";
1303	my $log_file_3="$user_data_dir/$self_name.2.log";
1304	my $data = '';
1305	$end='main::log_data("fe", (caller(1))[3], "");';
1306	$start='main::log_data("fs", (caller(1))[3], \@_);';
1307	#$t3 = tv_interval ($t0, [gettimeofday]);
1308	$t3 = eval 'Time::HiRes::tv_interval (\@t0, [Time::HiRes::gettimeofday()]);' if $b_hires;
1309	# print Dumper $@;
1310	my $now = strftime "%Y-%m-%d %H:%M:%S", localtime;
1311	return if $debugger{'timers'};
1312	# do the rotation if logfile exists
1313	if (-f $log_file){
1314		# copy if present second to third
1315		if (-f $log_file_2){
1316			rename $log_file_2, $log_file_3 or error_handler('rename', "$log_file_2 -> $log_file_3", "$!");
1317		}
1318		# then copy initial to second
1319		rename $log_file, $log_file_2 or error_handler('rename', "$log_file -> $log_file_2", "$!");
1320	}
1321	# now create the logfile
1322	# print "Opening log file for reading: $log_file\n";
1323	open($fh_l, '>', $log_file) or error_handler(4, $log_file, "$!");
1324	# and echo the start data
1325	$data = $line2;
1326	$data .= "START $self_name LOGGING:\n";
1327	$data .= "NOTE: HiRes timer not available.\n" if !$b_hires;
1328	$data .= "$now\n";
1329	$data .= "Elapsed since start: $t3\n";
1330	$data .= "n: $self_name v: $self_version p: $self_patch d: $self_date\n";
1331	$data .= '@paths:' . joiner(\@paths, '::', 'unset') . "\n";
1332	$data .= $line2;
1333
1334	print $fh_l $data;
1335}
1336
1337# NOTE: no logging available until get_parameters is run, since that's what
1338# sets logging # in order to trigger earlier logging manually set $b_log
1339# to true in top variables.
1340# args: $1 - type [fs|fe|cat|dump|raw] OR data to log
1341# arg: $2 -
1342# arg: $one type (fs/fe/cat/dump/raw) or logged data;
1343# [$two is function name; [$three - function args]]
1344sub log_data {
1345	return if !$b_log;
1346	my ($one, $two, $three) = @_;
1347	my ($args,$data,$timer) = ('','','');
1348	my $spacer = '   ';
1349	# print "1: $one 2: $two 3: $three\n";
1350	if ($one eq 'fs'){
1351		if (ref $three eq 'ARRAY'){
1352			# print Data::Dumper::Dumper $three;
1353			$args = "\n${spacer}Args: " . joiner($three, '; ', 'unset');
1354		}
1355		else {
1356			$args = "\n${spacer}Args: None";
1357		}
1358		# $t1 = [gettimeofday];
1359		#$t3 = tv_interval ($t0, [gettimeofday]);
1360		$t3 = eval 'Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()])' if $b_hires;
1361		# print Dumper $@;
1362		$data = "Start: Function: $two$args\n${spacer}Elapsed: $t3\n";
1363		$spacer='';
1364		$timer = $data if $debugger{'timers'};
1365	}
1366	elsif ($one eq 'fe'){
1367		# print 'timer:', Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()]),"\n";
1368		#$t3 = tv_interval ($t0, [gettimeofday]);
1369		eval '$t3 = Time::HiRes::tv_interval(\@t0, [Time::HiRes::gettimeofday()])' if $b_hires;
1370		# print Dumper $t3;
1371		$data = "${spacer}Elapsed: $t3\nEnd: Function: $two\n";
1372		$spacer='';
1373		$timer = $data if $debugger{'timers'};
1374	}
1375	elsif ($one eq 'cat'){
1376		if ($b_log_full){
1377			foreach my $file ($two){
1378				my $contents = do { local(@ARGV, $/) = $file; <> }; # or: qx(cat $file)
1379				$data = "$data${line3}Full file data: $file\n\n$contents\n$line3\n";
1380			}
1381			$spacer='';
1382		}
1383	}
1384	elsif ($one eq 'cmd'){
1385		$data = "Command: $two\n";
1386		$data .= qx($two);
1387	}
1388	elsif ($one eq 'data'){
1389		$data = "$two\n";
1390	}
1391	elsif ($one eq 'dump'){
1392		$data = "$two:\n";
1393		if (ref $three eq 'HASH'){
1394			$data .= Data::Dumper::Dumper $three;
1395		}
1396		elsif (ref $three eq 'ARRAY'){
1397			# print Data::Dumper::Dumper $three;
1398			$data .= Data::Dumper::Dumper $three;
1399		}
1400		else {
1401			$data .= Data::Dumper::Dumper $three;
1402		}
1403		$data .= "\n";
1404		# print $data;
1405	}
1406	elsif ($one eq 'raw'){
1407		if ($b_log_full){
1408			$data = "\n${line3}Raw System Data:\n\n$two\n$line3";
1409			$spacer='';
1410		}
1411	}
1412	else {
1413		$data = "$two\n";
1414	}
1415	if ($debugger{'timers'}){
1416		print $timer if $timer;
1417	}
1418	# print "d: $data";
1419	elsif ($data){
1420		print $fh_l "$spacer$data";
1421	}
1422}
1423
1424sub set_debugger {
1425	user_debug_test_1() if $debugger{'test-1'};
1426	if ($debugger{'level'} >= 20){
1427		error_handler('not-in-irc', 'debug data generator') if $b_irc;
1428		my $option = ($debugger{'level'} > 22) ? 'main-full' : 'main';
1429		$debugger{'gz'} = 1 if ($debugger{'level'} == 22 || $debugger{'level'} == 24);
1430		my $ob_sys = SystemDebugger->new($option);
1431		$ob_sys->run_debugger();
1432		$ob_sys->upload_file($ftp_alt) if $debugger{'level'} > 20;
1433		exit 0;
1434	}
1435	elsif ($debugger{'level'} >= 10 && $debugger{'level'} <= 12){
1436		$b_log = 1;
1437		if ($debugger{'level'} == 11){
1438			$b_log_full = 1;
1439		}
1440		elsif ($debugger{'level'} == 12){
1441			$b_log_colors = 1;
1442		}
1443		begin_logging();
1444	}
1445	elsif ($debugger{'level'} <= 3){
1446		if ($debugger{'level'} == 3){
1447			$b_log = 1;
1448			$debugger{'timers'} = 1;
1449			begin_logging();
1450		}
1451		else {
1452			$end = '';
1453			$start = '';
1454		}
1455	}
1456}
1457
1458## SystemDebugger
1459{
1460package SystemDebugger;
1461my $option = 'main';
1462my ($data_dir,$debug_dir,$debug_gz,$parse_src,$upload) = ('','','','','');
1463my @content;
1464my $b_debug = 0;
1465my $b_delete_dir = 1;
1466# args: 1 - type
1467# args: 2 - upload
1468sub new {
1469	my $class = shift;
1470	($option) = @_;
1471	my $self = {};
1472	# print "$f\n";
1473	# print "$option\n";
1474	return bless $self, $class;
1475}
1476
1477sub run_debugger {
1478	print "Starting $self_name debugging data collector...\n";
1479	print "Loading required debugger Perl File:: modules... \n";
1480	# Fedora/Redhat doesn't include File::Find File::Copy in
1481	# core modules. why? Or rather, they deliberately removed them.
1482	if (main::check_perl_module('File::Find')){
1483		File::Find->import;
1484	}
1485	else {
1486		main::error_handler('required-module', 'File', 'File::Find');
1487	}
1488	if (main::check_perl_module('File::Copy')){
1489		File::Copy->import;
1490	}
1491	else {
1492		main::error_handler('required-module', 'File', 'File::Copy');
1493	}
1494	if (main::check_perl_module('File::Spec::Functions')){
1495		File::Spec::Functions->import;
1496	}
1497	else {
1498		main::error_handler('required-module', 'File', 'File::Spec::Functions');
1499	}
1500	if ($debugger{'level'} > 20){
1501		if (main::check_perl_module('Net::FTP')){
1502			Net::FTP->import;
1503		}
1504		else {
1505			main::error_handler('required-module', 'Net', 'Net::FTP');
1506		}
1507	}
1508	create_debug_directory();
1509	print "Note: for dmidecode, smartctl, lvm data you must be root.\n" if !$b_root;
1510	print $line3;
1511	if (!$b_debug){
1512		audio_data();
1513		bluetooth_data();
1514		disk_data();
1515		display_data();
1516		network_data();
1517		perl_modules();
1518		system_data();
1519	}
1520	system_files();
1521	print $line3;
1522	if (!$b_debug){
1523		# note: android has unreadable /sys, but -x and -r tests pass
1524		# main::globber('/sys/*') &&
1525		if ($debugger{'sys'} && main::count_dir_files('/sys')){
1526			build_tree('sys');
1527			# kernel crash, not sure what creates it, for ppc, as root
1528			sys_traverse_data() if ($debugger{'sys'} && ($debugger{'sys-force'} || !$b_root || !$b_ppc)) ;
1529		}
1530		else {
1531			print "Skipping /sys data collection.\n";
1532		}
1533		print $line3;
1534		# note: proc has some files that are apparently kernel processes, I've tried
1535		# filtering them out but more keep appearing, so only run proc debugger if not root
1536		if (!$debugger{'no-proc'} && (!$b_root || $debugger{'proc'}) && -d '/proc' && main::count_dir_files('/proc')){
1537			build_tree('proc');
1538			proc_traverse_data();
1539		}
1540		else {
1541			print "Skipping /proc data collection.\n";
1542		}
1543		print $line3;
1544	}
1545	run_self();
1546	print $line3;
1547	compress_dir();
1548}
1549
1550sub create_debug_directory {
1551	my $host = main::get_hostname();
1552	$host =~ s/ /-/g;
1553	$host = 'no-host' if !$host || $host eq 'N/A';
1554	my ($alt_string,$root_string) = ('','');
1555	# note: Time::Piece was introduced in perl 5.9.5
1556	my ($sec,$min,$hour,$mday,$mon,$year) = localtime;
1557	$year = $year+1900;
1558	$mon += 1;
1559	if (length($sec)  == 1){$sec = "0$sec";}
1560	if (length($min)  == 1){$min = "0$min";}
1561	if (length($hour) == 1){$hour = "0$hour";}
1562	if (length($mon)  == 1){$mon = "0$mon";}
1563	if (length($mday) == 1){$mday = "0$mday";}
1564	my $today = "$year-$mon-${mday}_$hour$min$sec";
1565	# my $date = strftime "-%Y-%m-%d_", localtime;
1566	if ($b_root){
1567		$root_string = '-root';
1568	}
1569	my $id = ($debugger{'id'}) ? '-' . $debugger{'id'}: '';
1570	if ($b_arm){$alt_string = '-ARM'}
1571	elsif ($b_mips){$alt_string = '-MIPS'}
1572	elsif ($b_ppc){$alt_string = '-PPC'}
1573	elsif ($b_sparc){$alt_string = '-SPARC'}
1574	$alt_string .= "-BSD-$bsd_type" if $bsd_type;
1575	$alt_string .= '-ANDROID' if $b_android;
1576	$alt_string .= '-CYGWIN' if $b_cygwin; # could be windows arm?
1577	$debug_dir = "$self_name$alt_string-$host$id-$today$root_string-$self_version-$self_patch";
1578	$debug_gz = "$debug_dir.tar.gz";
1579	$data_dir = "$user_data_dir/$debug_dir";
1580	if (-d $data_dir){
1581		unlink $data_dir or main::error_handler('remove', "$data_dir", "$!");
1582	}
1583	mkdir $data_dir or main::error_handler('mkdir', "$data_dir", "$!");
1584	if (-e "$user_data_dir/$debug_gz"){
1585		#rmdir "$user_data_dir$debug_gz" or main::error_handler('remove', "$user_data_dir/$debug_gz", "$!");
1586		print "Failed removing leftover directory:\n$user_data_dir$debug_gz error: $?" if system('rm','-rf',"$user_data_dir$debug_gz");
1587	}
1588	print "Debugger data going into:\n$data_dir\n";
1589}
1590sub compress_dir {
1591	print "Creating tar.gz compressed file of this material...\n";
1592	print "File: $debug_gz\n";
1593	system("cd $user_data_dir; tar -czf $debug_gz $debug_dir");
1594	print "Removing $data_dir...\n";
1595	#rmdir $data_dir or print "failed removing: $data_dir error: $!\n";
1596	return 1 if !$b_delete_dir;
1597	if (system('rm','-rf',$data_dir)){
1598		print "Failed removing: $data_dir\nError: $?\n";
1599	}
1600	else {
1601		print "Directory removed.\n";
1602	}
1603}
1604# NOTE: incomplete, don't know how to ever find out
1605# what sound server is actually running, and is in control
1606sub audio_data {
1607	my (%data,@files,@files2);
1608	print "Collecting audio data...\n";
1609	my @cmds = (
1610	['aplay', '--version'], # alsa
1611	['aplay', '-l'], # alsa
1612	['pactl', '--version'], # pulseaudio
1613	['pactl', 'list'], # pulseaudio
1614	);
1615	run_commands(\@cmds,'audio');
1616	@files = main::globber('/proc/asound/card*/codec*');
1617	if (@files){
1618		my $asound = qx(head -n 1 /proc/asound/card*/codec* 2>&1);
1619		$data{'proc-asound-codecs'} = $asound;
1620	}
1621	else {
1622		$data{'proc-asound-codecs'} = undef;
1623	}
1624	write_data(\%data,'audio');
1625	@files = (
1626	'/proc/asound/cards',
1627	'/proc/asound/version',
1628	);
1629	@files2 = main::globber('/proc/asound/*/usbid');
1630	push(@files,@files2) if @files2;
1631	copy_files(\@files,'audio');
1632}
1633sub bluetooth_data {
1634	print "Collecting bluetooth data...\n";
1635# 	no warnings 'uninitialized';
1636	my @cmds = (
1637	['hciconfig','-a'], # no version
1638	#['hcidump',''], # hangs sometimes
1639	['hcitool','dev'],
1640	['rfkill','--output-all'],
1641	);
1642	# these hang if bluetoothd not enabled
1643	if (@ps_cmd && (grep {m|/bluetoothd|} @ps_cmd)){
1644		push(@cmds,
1645		['bt-adapter','--list'], # no version
1646		['bt-adapter','--info'],
1647		['bluetoothctl','--version'],
1648		['bluetoothctl','-- list'],
1649		['bluetoothctl','-- show']
1650		);
1651	}
1652	run_commands(\@cmds,'bluetooth');
1653}
1654
1655## NOTE: >/dev/null 2>&1 is sh, and &>/dev/null is bash, fix this
1656# ls -w 1 /sysrs > tester 2>&1
1657sub disk_data {
1658	my (%data,@files,@files2);
1659	print "Collecting dev, label, disk, uuid data, df...\n";
1660	@files = (
1661	'/etc/fstab',
1662	'/etc/mtab',
1663	'/proc/devices',
1664	'/proc/mdstat',
1665	'/proc/mounts',
1666	'/proc/partitions',
1667	'/proc/scsi/scsi',
1668	'/proc/sys/dev/cdrom/info',
1669	);
1670	# very old systems
1671	if (-d '/proc/ide/'){
1672		my @ides = main::globber('/proc/ide/*/*');
1673		push(@files, @ides) if @ides;
1674	}
1675	else {
1676		push(@files, '/proc-ide-directory');
1677	}
1678	copy_files(\@files, 'disk');
1679	my @cmds = (
1680	['blockdev', '--version'],
1681	['blockdev', '--report'],
1682	['btrfs', 'fi show'], # no version
1683	['btrfs', 'filesystem show'],
1684	['btrfs', 'filesystem show --mounted'],
1685	# ['btrfs', 'filesystem show --all-devices'],
1686	['df', '-h -T'], # no need for version, and bsd doesn't have its
1687	['df', '-h'],
1688	['df', '-k'],
1689	['df', '-k -T'],
1690	['df', '-k -T -P'],
1691	['df', '-k -T -P -a'],
1692	['df', '-P'],
1693	['dmsetup', 'ls --tree'],
1694	['findmnt', ''],
1695	['findmnt', '--df --no-truncate'],
1696	['findmnt', '--list --no-truncate'],
1697	['gpart', 'list'], # no version
1698	['gpart', 'show'],
1699	['gpart', 'status'],
1700	['ls', '-l /dev'],# core util, don't need version
1701	# block is for mmcblk / arm devices
1702	['ls', '-l /dev/block'],
1703	['ls', '-l /dev/block/bootdevice'],
1704	['ls', '-l /dev/block/bootdevice/by-name'],
1705	['ls', '-l /dev/disk'],
1706	['ls', '-l /dev/disk/by-id'],
1707	['ls', '-l /dev/disk/by-label'],
1708	['ls', '-l /dev/disk/by-partlabel'],
1709	['ls', '-l /dev/disk/by-partuuid'],
1710	['ls', '-l /dev/disk/by-path'],
1711	['ls', '-l /dev/disk/by-uuid'],
1712	# http://comments.gmane.org/gmane.linux.file-systems.zfs.user/2032
1713	['ls', '-l /dev/disk/by-wwn'],
1714	['ls', '-l /dev/mapper'],
1715	['lsblk', '--version'], # important since lsblk has been changing output
1716	['lsblk', '-fs'],
1717	['lsblk', '-fsr'],
1718	['lsblk', '-fsP'],
1719	['lsblk', '-a'],
1720	['lsblk', '-aP'],
1721	['lsblk', '-ar'],
1722	['lsblk', '-p'],
1723	['lsblk', '-pr'],
1724	['lsblk', '-pP'],
1725	['lsblk', '-r'],
1726	['lsblk', '-r --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'],
1727	['lsblk', '-rb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'],
1728	['lsblk', '-rb --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,MAJ:MIN,PKNAME'],
1729	['lsblk', '-Pb --output NAME,PKNAME,TYPE,RM,FSTYPE,SIZE'],
1730	['lsblk', '-Pb --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS'],
1731	# this should always be the live command used internally:
1732	['lsblk', '-bP --output NAME,TYPE,RM,FSTYPE,SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,MAJ:MIN,PKNAME'],
1733	['lvdisplay', '--version'],
1734	['lvdisplay', '-c'],
1735	['lvdisplay', '-cv'],
1736	['lvdisplay', '-cv --segments'],
1737	['lvdisplay', '-m --segments'],
1738	['lvdisplay', '-ma --segments'],
1739	['lvs', '--version'],
1740	['lvs', '--separator :'],
1741	['lvs', '--separator : --segments'],
1742	['lvs', '-o +devices --separator : --segments'],
1743	['lvs', '-o +devices -v --separator : --segments'],
1744	['lvs', '-o +devices -av --separator : --segments'],
1745	['lvs', '-o +devices -aPv --separator : --segments'],
1746	# LSI raid https://hwraid.le-vert.net/wiki/LSIMegaRAIDSAS
1747	['megacli', '-AdpAllInfo -aAll'], # no version
1748	['megacli', '-LDInfo -L0 -a0'],
1749	['megacli', '-PDList -a0'],
1750	['megaclisas-status', ''], # no version
1751	['megaraidsas-status', ''],
1752	['megasasctl', ''],
1753	['mount', ''],
1754	['nvme', 'present'], # no version
1755	['pvdisplay', '--version'],
1756	['pvdisplay', '-c'],
1757	['pvdisplay', '-cv'],
1758	['pvdisplay', '-m'],
1759	['pvdisplay', '-ma'],
1760	['pvs', '--version'],
1761	['pvs', '--separator :'],
1762	['pvs', '--separator : --segments'],
1763	['pvs', '-a --separator : --segments'],
1764	['pvs', '-av --separator : --segments'],
1765	['pvs', '-aPv --separator : --segments -o +pv_major,pv_minor'],
1766	['pvs', '-v --separator : --segments'],
1767	['pvs', '-Pv --separator : --segments'],
1768	['pvs', '--segments  -o pv_name,pv_size,seg_size,vg_name,lv_name,lv_size,seg_pe_ranges'],
1769	['readlink', '/dev/root'], # coreutils, don't need version
1770	['swapon', '-s'], # coreutils, don't need version
1771	# 3ware-raid
1772	['tw-cli', 'info'],
1773	['vgdisplay', ''],
1774	['vgdisplay', '-v'],
1775	['vgdisplay', '-c'],
1776	['vgdisplay', '-vc'],
1777	['vgs', '--separator :'], # part of lvm, don't need version
1778	['vgs', '-av --separator :'],
1779	['vgs', '-aPv --separator :'],
1780	['vgs', '-v --separator :'],
1781	['vgs', '-o +pv_name --separator :'],
1782	['zfs', 'list'],
1783	['zpool', 'list'], # don't use version, might not be supported in linux
1784	['zpool', 'list -v'],
1785	);
1786	run_commands(\@cmds,'disk');
1787	@cmds = (
1788	['atacontrol', 'list'],
1789	['camcontrol', 'devlist'],
1790	['camcontrol', 'devlist -v'],
1791	['geom', 'part list'],
1792	['glabel', 'status'],
1793	['gpart', 'list'], # gpart in linux/bsd but do it here again
1794	['gpart', 'show'],
1795	['gpart', 'status'],
1796	['swapctl', '-l -k'],
1797	['swapctl', '-l -k'],
1798	['vmstat', ''],
1799	['vmstat', '-H'],
1800	);
1801	run_commands(\@cmds,'disk-bsd');
1802}
1803sub display_data {
1804	my (%data,@files,@files2);
1805	my $working = '';
1806	if (!$b_display){
1807		print "Warning: only some of the data collection can occur if you are not in X\n";
1808		main::toucher("$data_dir/display-data-warning-user-not-in-x");
1809	}
1810	if ($b_root){
1811		print "Warning: only some of the data collection can occur if you are running as Root user\n";
1812		main::toucher("$data_dir/display-data-warning-root-user");
1813	}
1814	print "Collecting Xorg log and xorg.conf files...\n";
1815	if (-d "/etc/X11/xorg.conf.d/"){
1816		@files = main::globber("/etc/X11/xorg.conf.d/*");
1817	}
1818	else {
1819		@files = ('/xorg-conf-d');
1820	}
1821	# keep this updated to handle all possible locations we know about for Xorg.0.log
1822	# not using $system_files{'xorg-log'} for now though it would be best to know what file is used
1823	main::set_xorg_log();
1824	push(@files, '/var/log/Xorg.0.log');
1825	push(@files, '/var/lib/gdm/.local/share/xorg/Xorg.0.log');
1826	push(@files, $ENV{'HOME'} . '/.local/share/xorg/Xorg.0.log');
1827	push(@files, $system_files{'xorg-log'}) if $system_files{'xorg-log'};
1828	push(@files, '/etc/X11/xorg.conf');
1829	copy_files(\@files,'display-xorg');
1830	print "Collecting X, xprop, glxinfo, xrandr, xdpyinfo data, wayland, weston...\n";
1831	%data = (
1832	'desktop-session' => $ENV{'DESKTOP_SESSION'},
1833	'gdmsession' => $ENV{'GDMSESSION'},
1834	'gnome-desktop-session-id' => $ENV{'GNOME_DESKTOP_SESSION_ID'},
1835	'kde-full-session' => $ENV{'KDE_FULL_SESSION'},
1836	'kde-session-version' => $ENV{'KDE_SESSION_VERSION'},
1837	'vdpau-driver' => $ENV{'VDPAU_DRIVER'},
1838	'xdg-current-desktop' => $ENV{'XDG_CURRENT_DESKTOP'},
1839	'xdg-session-desktop' => $ENV{'XDG_SESSION_DESKTOP'},
1840	'xdg-vtnr' => $ENV{'XDG_VTNR'},
1841	# wayland data collectors:
1842	'xdg-session-type' => $ENV{'XDG_SESSION_TYPE'},
1843	'wayland-display' =>  $ENV{'WAYLAND_DISPLAY'},
1844	'gdk-backend' => $ENV{'GDK_BACKEND'},
1845	'qt-qpa-platform' => $ENV{'QT_QPA_PLATFORM'},
1846	'clutter-backend' => $ENV{'CLUTTER_BACKEND'},
1847	'sdl-videodriver' => $ENV{'SDL_VIDEODRIVER'},
1848	# program display values
1849	'size-cols-max' => $size{'max'},
1850	'size-indent' => $size{'indent'},
1851	'size-wrap-width' => $size{'wrap-max'},
1852	);
1853	write_data(\%data,'display');
1854	my @cmds = (
1855	# kde 5/plasma desktop 5, this is maybe an extra package and won't be used
1856	['about-distro',''],
1857	['aticonfig','--adapter=all --od-gettemperature'],
1858	['glxinfo',''],
1859	['glxinfo','-B'],
1860	['kded','--version'],
1861	['kded1','--version'],
1862	['kded2','--version'],
1863	['kded3','--version'],
1864	['kded4','--version'],
1865	['kded5','--version'],
1866	['kded6','--version'],
1867	['kded7','--version'],
1868	['kf-config','--version'],
1869	['kf4-config','--version'],
1870	['kf5-config','--version'],
1871	['kf6-config','--version'],
1872	['kf7-config','--version'],
1873	['kwin_x11','--version'],
1874	# ['locate','/Xorg'], # for Xorg.wrap problem
1875	['loginctl','--no-pager list-sessions'],
1876	['nvidia-settings','-q screens'],
1877	['nvidia-settings','-c :0.0 -q all'],
1878	['nvidia-smi','-q'],
1879	['nvidia-smi','-q -x'],
1880	['plasmashell','--version'],
1881	['vainfo',''],
1882	['vdpauinfo',''],
1883	['vulkaninfo',''],
1884	['weston-info',''],
1885	['wmctrl','-m'],
1886	['weston','--version'],
1887	['xdpyinfo',''],
1888	['Xorg','-version'],
1889	['xprop','-root'],
1890	['xrandr',''],
1891	);
1892	run_commands(\@cmds,'display');
1893}
1894sub network_data {
1895	print "Collecting networking data...\n";
1896# 	no warnings 'uninitialized';
1897	my @cmds = (
1898	['ifconfig',''], # no version maybe in bsd, --version in linux
1899	['ip','-Version'],
1900	['ip','addr'],
1901	['ip','-s link'],
1902	);
1903	run_commands(\@cmds,'network');
1904}
1905sub perl_modules {
1906	print "Collecting Perl module data (this can take a while)...\n";
1907	my @modules;
1908	my ($dirname,$holder,$mods,$value) = ('','','','');
1909	my $filename = 'perl-modules.txt';
1910	my @inc;
1911	foreach (sort @INC){
1912		# some BSD installs have '.' n @INC path
1913		if (-d $_ && $_ ne '.'){
1914			$_ =~ s/\/$//; # just in case, trim off trailing slash
1915			$value .= "EXISTS: $_\n";
1916			push(@inc, $_);
1917		}
1918		else {
1919			$value .= "ABSENT: $_\n";
1920		}
1921	}
1922	main::writer("$data_dir/perl-inc-data.txt",$value);
1923	File::Find::find({ wanted => sub {
1924		push(@modules, File::Spec->canonpath($_)) if /\.pm\z/
1925	}, no_chdir => 1 }, @inc);
1926	@modules = sort @modules;
1927	foreach (@modules){
1928		my $dir = $_;
1929		$dir =~ s/[^\/]+$//;
1930		if (!$holder || $holder ne $dir){
1931			$holder = $dir;
1932			$value = "DIR: $dir\n";
1933			$_ =~ s/^$dir//;
1934			$value .= " $_\n";
1935		}
1936		else {
1937			$value = $_;
1938			$value =~ s/^$dir//;
1939			$value = " $value\n";
1940		}
1941		$mods .= $value;
1942	}
1943	open(my $fh, '>', "$data_dir/$filename");
1944	print $fh $mods;
1945	close $fh;
1946}
1947sub system_data {
1948	print "Collecting system data...\n";
1949	# has to run here because if null, error, list constructor throws fatal error
1950	my $ksh = qx(ksh -c 'printf \%s "\$KSH_VERSION"' 2>/dev/null);
1951	my %data = (
1952	'cc' => $ENV{'CC'},
1953	# @(#)MIRBSD KSH R56 2018/03/09: ksh and mksh
1954	'ksh-version' => $ksh, # shell, not env, variable
1955	'manpath' => $ENV{'MANPATH'},
1956	'path' => $ENV{'PATH'},
1957	'shell' => $ENV{'SHELL'},
1958	'xdg-config-home' => $ENV{'XDG_CONFIG_HOME'},
1959	'xdg-config-dirs' => $ENV{'XDG_CONFIG_DIRS'},
1960	'xdg-data-home' => $ENV{'XDG_DATA_HOME'},
1961	'xdg-data-dirs' => $ENV{'XDG_DATA_DIRS'},
1962	);
1963	my @files = main::globber('/usr/bin/gcc*');
1964	if (@files){
1965		$data{'gcc-versions'} = join("\n", @files);
1966	}
1967	else {
1968		$data{'gcc-versions'} = undef;
1969	}
1970	@files = main::globber('/sys/*');
1971	if (@files){
1972		$data{'sys-tree-ls-1-basic'} = join("\n", @files);
1973	}
1974	else {
1975		$data{'sys-tree-ls-1-basic'} = undef;
1976	}
1977	write_data(\%data,'system');
1978	# bsd tools http://cb.vu/unixtoolbox.xhtml
1979	my @cmds = (
1980	# general
1981	['sysctl', '-a'],
1982	['sysctl', '-b kern.geom.conftxt'],
1983	['sysctl', '-b kern.geom.confxml'],
1984	['usbdevs','-v'],
1985	# freebsd
1986	['ofwdump','-a'], # arm / soc
1987	['ofwdump','-ar'], # arm / soc
1988	['pciconf','-l -cv'],
1989	['pciconf','-vl'],
1990	['pciconf','-l'],
1991	['usbconfig','dump_device_desc'],
1992	['usbconfig','list'], # needs root, sigh... why?
1993	# openbsd
1994	['ofctl',''], # arm / soc, need to see data sample of this
1995	['pcidump',''],
1996	['pcidump','-v'],
1997	# netbsd
1998	['kldstat',''],
1999	['pcictl','pci0 list'],
2000	['pcictl','pci0 list -N'],
2001	['pcictl','pci0 list -n'],
2002	);
2003	run_commands(\@cmds,'system-bsd');
2004	# diskinfo -v <disk>
2005	# fdisk <disk>
2006	@cmds = (
2007	['clang','--version'],
2008	# only for prospective ram feature data collection: requires i2c-tools and module eeprom loaded
2009	['decode-dimms',''],
2010	['dmidecode','--version'],
2011	['dmidecode',''],
2012	['dmesg',''],
2013	['gcc','--version'],
2014	['initctl','list'],
2015	['ipmi-sensors','-V'], # version
2016	['ipmi-sensors',''],
2017	['ipmi-sensors','--output-sensor-thresholds'],
2018	['ipmitool','-V'],# version
2019	['ipmitool','sensor'],
2020	['lscpu',''],# part of util-linux
2021	['lspci','--version'],
2022	['lspci',''],
2023	['lspci','-k'],
2024	['lspci','-n'],
2025	['lspci','-nn'],
2026	['lspci','-nnk'],
2027	['lspci','-nnkv'],# returns ports
2028	['lspci','-nnv'],
2029	['lspci','-mm'],
2030	['lspci','-mmk'],
2031	['lspci','-mmkv'],
2032	['lspci','-mmv'],
2033	['lspci','-mmnn'],
2034	['lspci','-v'],
2035	['lsusb','--version'],
2036	['lsusb',''],
2037	['lsusb','-t'],
2038	['lsusb','-v'],
2039	['ps','aux'],
2040	['ps','-e'],
2041	['ps','-p 1'],
2042	['runlevel',''],
2043	['rc-status','-a'],
2044	['rc-status','-l'],
2045	['rc-status','-r'],
2046	['sensors','--version'],
2047	['sensors',''],
2048	['sensors','-j'],
2049	['sensors','-u'],
2050	# leaving this commented out to remind that some systems do not
2051	# support strings --version, but will just simply hang at that command
2052	# which you can duplicate by simply typing: strings then hitting enter.
2053	# ['strings','--version'],
2054	['strings','present'],
2055	['sysctl','-a'],
2056	['systemctl','--version'],
2057	['systemctl','list-units'],
2058	['systemctl','list-units --type=target'],
2059	['systemd-detect-virt',''],
2060	['uname','-a'],
2061	['upower','-e'],
2062	['uptime',''],
2063	['vcgencmd','get_mem arm'],
2064	['vcgencmd','get_mem gpu'],
2065	);
2066	run_commands(\@cmds,'system');
2067	@files = main::globber('/dev/bus/usb/*/*');
2068	copy_files(\@files, 'system');
2069}
2070sub system_files {
2071	print "Collecting system files data...\n";
2072	my (%data,@files,@files2);
2073	@files = RepoItem::get($data_dir);
2074	copy_files(\@files, 'repo');
2075	# chdir "/etc";
2076	@files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*');
2077	push(@files, '/etc/issue');
2078	push(@files, '/etc/lsb-release');
2079	push(@files, '/etc/os-release');
2080	push(@files, '/system/build.prop');# android data file, requires rooted
2081	push(@files, '/var/log/installer/oem-id'); # ubuntu only for oem installs?
2082	copy_files(\@files,'system-distro');
2083	@files = main::globber('/etc/upstream[-_]{[rR]elease,[vV]ersion}/*');
2084	copy_files(\@files,'system-distro');
2085	@files = main::globber('/etc/calamares/branding/*/branding.desc');
2086	copy_files(\@files,'system-distro');
2087	@files = (
2088	'/proc/1/comm',
2089	'/proc/cmdline',
2090	'/proc/cpuinfo',
2091	'/proc/meminfo',
2092	'/proc/modules',
2093	'/proc/net/arp',
2094	'/proc/version',
2095	);
2096	@files2=main::globber('/sys/class/power_supply/*/uevent');
2097	if (@files2){
2098		push(@files,@files2);
2099	}
2100	else {
2101		push(@files, '/sys-class-power-supply-empty');
2102	}
2103	copy_files(\@files, 'system');
2104	@files = (
2105	'/etc/make.conf',
2106	'/etc/src.conf',
2107	'/var/run/dmesg.boot',
2108	);
2109	copy_files(\@files,'system-bsd');
2110	@files = main::globber('/sys/devices/system/cpu/vulnerabilities/*');
2111	copy_files(\@files,'security');
2112}
2113## SELF EXECUTE FOR LOG/OUTPUT
2114sub run_self {
2115	print "Creating $self_name output file now. This can take a few seconds...\n";
2116	print "Starting $self_name from: $self_path\n";
2117	my $i = ($option eq 'main-full')? ' -i' : '';
2118	my $z = ($debugger{'filter'}) ? ' -z' : '';
2119	my $w = ($debugger{'width'}) ? $debugger{'width'} : 120;
2120	my $iz = "$i$z";
2121	$iz =~ s/[\s-]//g;
2122	my $self_file = "$data_dir/$self_name-FERfJLrploudma$iz-slots-y$w.txt";
2123	my $cmd = "$self_path/$self_name -FERfJLrploudma$i$z --slots --debug 10 -y $w > $self_file 2>&1";
2124	system($cmd);
2125	copy($log_file, "$data_dir") or main::error_handler('copy-failed', "$log_file", "$!");
2126	system("$self_path/$self_name --recommends -y 120 > $data_dir/$self_name-recommends-120.txt 2>&1");
2127}
2128
2129## UTILITIES COPY/CMD/WRITE
2130sub copy_files {
2131	my ($files_ref,$type,$alt_dir) = @_;
2132	my ($absent,$error,$good,$name,$unreadable);
2133	my $directory = ($alt_dir) ? $alt_dir : $data_dir;
2134	my $working = ($type ne 'proc') ? "$type-file-": '';
2135	foreach (@$files_ref){
2136		$name = $_;
2137		$name =~ s/^\///;
2138		$name =~ s/\//~/g;
2139		# print "$name\n" if $type eq 'proc';
2140		$name = "$directory/$working$name";
2141		$good = $name . '.txt';
2142		$absent = $name . '-absent';
2143		$error = $name . '-error';
2144		$unreadable = $name . '-unreadable';
2145		# proc have already been tested for readable/exists
2146		if ($type eq 'proc' || -e $_){
2147			print "F:$_\n" if $type eq 'proc' && $debugger{'proc-print'};
2148			if ($type eq 'proc' || -r $_){
2149				copy($_,"$good") or main::toucher($error);
2150			}
2151			else {
2152				main::toucher($unreadable);
2153			}
2154		}
2155		else {
2156			main::toucher($absent);
2157		}
2158	}
2159}
2160sub run_commands {
2161	my ($cmds,$type) = @_;
2162	my $holder = '';
2163	my ($name,$cmd,$args);
2164	foreach my $rows (@$cmds){
2165		if (my $program = main::check_program($rows->[0])){
2166			if ($rows->[1] eq 'present'){
2167				$name = "$data_dir/$type-cmd-$rows->[0]-present";
2168				main::toucher($name);
2169			}
2170			else {
2171				$args = $rows->[1];
2172				$args =~ s/\s|--|\/|=/-/g; # for:
2173				$args =~ s/--/-/g;# strip out -- that result from the above
2174				$args =~ s/^-//g;
2175				$args = "-$args" if $args;
2176				$name = "$data_dir/$type-cmd-$rows->[0]$args.txt";
2177				$cmd = "$program $rows->[1] >$name 2>&1";
2178				system($cmd);
2179			}
2180		}
2181		else {
2182			if ($holder ne $rows->[0]){
2183				$name = "$data_dir/$type-cmd-$rows->[0]-absent";
2184				main::toucher($name);
2185				$holder = $rows->[0];
2186			}
2187		}
2188	}
2189}
2190sub write_data {
2191	my ($data_ref, $type) = @_;
2192	my ($empty,$error,$fh,$good,$name,$undefined,$value);
2193	foreach (keys %$data_ref){
2194		$value = $data_ref->{$_};
2195		$name = "$data_dir/$type-data-$_";
2196		$good = $name . '.txt';
2197		$empty = $name . '-empty';
2198		$error = $name . '-error';
2199		$undefined = $name . '-undefined';
2200		if (defined $value){
2201			if ($value || $value eq '0'){
2202				open($fh, '>', $good) or main::toucher($error);
2203				print $fh "$value";
2204			}
2205			else {
2206				main::toucher($empty);
2207			}
2208		}
2209		else {
2210			main::toucher($undefined);
2211		}
2212	}
2213}
2214## TOOLS FOR DIRECTORY TREE/LS/TRAVERSE; UPLOADER
2215sub build_tree {
2216	my ($which) = @_;
2217	if ($which eq 'sys' && main::check_program('tree')){
2218		print "Constructing /$which tree data...\n";
2219		my $dirname = '/sys';
2220		my $cmd;
2221		system("tree -a -L 10 /sys > $data_dir/sys-data-tree-full-10.txt");
2222		opendir(my $dh, $dirname) or main::error_handler('open-dir',"$dirname", "$!");
2223		my @files = readdir($dh);
2224		closedir $dh;
2225		foreach (@files){
2226			next if /^\./;
2227			$cmd = "tree -a -L 10 $dirname/$_ > $data_dir/sys-data-tree-$_-10.txt";
2228			# print "$cmd\n";
2229			system($cmd);
2230		}
2231	}
2232	print "Constructing /$which ls data...\n";
2233	if ($which eq 'sys'){
2234		directory_ls($which,1);
2235		directory_ls($which,2);
2236		directory_ls($which,3);
2237		directory_ls($which,4);
2238	}
2239	elsif ($which eq 'proc'){
2240		directory_ls('proc',1);
2241		directory_ls('proc',2,'[a-z]');
2242		# don't want the /proc/self or /proc/thread-self directories, those are
2243		# too invasive
2244		#directory_ls('proc',3,'[a-z]');
2245		#directory_ls('proc',4,'[a-z]');
2246	}
2247}
2248
2249# include is basic regex for ls path syntax, like [a-z]
2250sub directory_ls {
2251	my ($dir,$depth,$include) = @_;
2252	$include ||= '';
2253	my ($exclude) = ('');
2254	# we do NOT want to see anything in self or thread-self!!
2255	# $exclude = 'I self -I thread-self' if $dir eq 'proc';
2256	my $cmd = do {
2257		if ($depth == 1){ "ls -l $exclude /$dir/$include 2>/dev/null" }
2258		elsif ($depth == 2){ "ls -l $exclude /$dir/$include*/ 2>/dev/null" }
2259		elsif ($depth == 3){ "ls -l $exclude /$dir/$include*/*/ 2>/dev/null" }
2260		elsif ($depth == 4){ "ls -l $exclude /$dir/$include*/*/*/ 2>/dev/null" }
2261		elsif ($depth == 5){ "ls -l $exclude /$dir/$include*/*/*/*/ 2>/dev/null" }
2262		elsif ($depth == 6){ "ls -l $exclude /$dir/$include*/*/*/*/*/ 2>/dev/null" }
2263	};
2264	my @working;
2265	my $output = '';
2266	my ($type);
2267	my $result = qx($cmd);
2268	open(my $ch, '<', \$result) or main::error_handler('open-data',"$cmd", "$!");
2269	while (my $line = <$ch>){
2270		chomp($line);
2271		$line =~ s/^\s+|\s+$//g;
2272		@working = split(/\s+/, $line);
2273		$working[0] ||= '';
2274		if (scalar @working > 7){
2275			if ($working[0] =~ /^d/){
2276				$type = "d - ";
2277			}
2278			elsif ($working[0] =~ /^l/){
2279				$type = "l - ";
2280			}
2281			elsif ($working[0] =~ /^c/){
2282				$type = "c - ";
2283			}
2284			else {
2285				$type = "f - ";
2286			}
2287			$working[9] ||= '';
2288			$working[10] ||= '';
2289			$output = $output . "  $type$working[8] $working[9] $working[10]\n";
2290		}
2291		elsif ($working[0] !~ /^total/){
2292			$output = $output . $line . "\n";
2293		}
2294	}
2295	close $ch;
2296	my $file = "$data_dir/$dir-data-ls-$depth.txt";
2297	open(my $fh, '>', $file) or main::error_handler('create',"$file", "$!");
2298	print $fh $output;
2299	close $fh;
2300	# print "$output\n";
2301}
2302sub proc_traverse_data {
2303	print "Building /proc file list...\n";
2304	# get rid pointless error:Can't cd to (/sys/kernel/) debug: Permission denied
2305	#no warnings 'File::Find';
2306	no warnings;
2307	$parse_src = 'proc';
2308	File::Find::find(\&wanted, "/proc");
2309	process_proc_traverse();
2310	@content = ();
2311}
2312sub process_proc_traverse {
2313	my ($data,$fh,$result,$row,$sep);
2314	my $proc_dir = "$data_dir/proc";
2315	print "Adding /proc files...\n";
2316	mkdir $proc_dir or main::error_handler('mkdir', "$proc_dir", "$!");
2317	# @content = sort @content;
2318	copy_files(\@content,'proc',$proc_dir);
2319# 	foreach (@content){print "$_\n";}
2320}
2321
2322sub sys_traverse_data {
2323	print "Building /sys file list...\n";
2324	# get rid pointless error:Can't cd to (/sys/kernel/) debug: Permission denied
2325	#no warnings 'File::Find';
2326	no warnings;
2327	$parse_src = 'sys';
2328	File::Find::find(\&wanted, "/sys");
2329	process_sys_traverse();
2330	@content = ();
2331}
2332sub process_sys_traverse {
2333	my ($data,$fh,$result,$row,$sep);
2334	my $filename = "sys-data-parse.txt";
2335	print "Parsing /sys files...\n";
2336	# no sorts, we want the order it comes in
2337	# @content = sort @content;
2338	foreach (@content){
2339		$data='';
2340		$sep='';
2341		my $b_fh = 1;
2342		print "F:$_\n" if $debugger{'sys-print'};
2343		open($fh, '<', $_) or $b_fh = 0;
2344		# needed for removing -T test and root
2345		if ($b_fh){
2346			while ($row = <$fh>){
2347				chomp($row);
2348				$data .= $sep . '"' . $row . '"';
2349				$sep=', ';
2350			}
2351		}
2352		else {
2353			$data = '<unreadable>';
2354		}
2355		$result .= "$_:[$data]\n";
2356		# print "$_:[$data]\n"
2357	}
2358	# print scalar @content . "\n";
2359	open($fh, '>', "$data_dir/$filename");
2360	print $fh $result;
2361	close $fh;
2362	# print $fh "$result";
2363}
2364# perl compiler complains on start if prune = 1 used only once, so either
2365# do $File::Find::prune = 1 if !$File::Find::prune; OR use no warnings 'once'
2366sub wanted {
2367	# note: we want these directories pruned before the -d test so find
2368	# doesn't try to read files inside of the directories
2369	if ($parse_src eq 'proc'){
2370		if ($File::Find::name =~ m!^/proc/[0-9]+! ||
2371		 # /proc/registry is from cygwin, we never want to see that
2372		 $File::Find::name =~ m!^/proc/(irq|spl|sys|reg)! ||
2373		 # these choke on sudo/root: kmsg kcore kpage and we don't want keys or kallsyms
2374		 $File::Find::name =~ m!^/proc/k! ||
2375		 $File::Find::name =~ m!^/proc/bus/pci!){
2376			$File::Find::prune = 1;
2377			return;
2378		}
2379	}
2380	elsif ($parse_src eq 'sys'){
2381		# note: a new file in 4.11 /sys can hang this, it is /parameter/ then
2382		# a few variables. Since inxi does not need to see that file, we will
2383		# not use it.
2384		if ($File::Find::name =~ m!/(kernel/|trace/|parameters|debug)!){
2385			$File::Find::prune = 1;
2386		}
2387	}
2388	return if -d; # not directory
2389	return unless -e; # Must exist
2390	return unless -f; # Must be file
2391	return unless -r; # Must be readable
2392	if ($parse_src eq 'sys'){
2393		# print $File::Find::name . "\n";
2394		# block maybe: cfgroup\/
2395		# picdec\/|, wait_for_fb_sleep/wake is an odroid thing caused hang
2396		# wakeup_count also fails for android, but works fine on regular systems
2397		return if $b_arm && $File::Find::name =~ m!^/sys/power/(wait_for_fb_|wakeup_count$)!;
2398		# do not need . files or __ starting files
2399		return if $File::Find::name =~ m!/\.[a-z]!;
2400		# pp_num_states: amdgpu driver bug; android: wakeup_count
2401		return if $File::Find::name =~ m!/pp_num_states$!;
2402		# comment this one out if you experience hangs or if
2403		# we discover syntax of foreign language characters
2404		# Must be ascii like. This is questionable and might require further
2405		# investigation, it is removing some characters that we might want
2406		# NOTE: this made a bunch of files on arm systems unreadable so we handle
2407		# the readable tests in copy_files()
2408		# return unless -T;
2409	}
2410	elsif ($parse_src eq 'proc'){
2411		return if $File::Find::name =~ m!(/mb_groups|debug)$!;
2412	}
2413	# print $File::Find::name . "\n";
2414	push(@content, $File::Find::name);
2415	return;
2416}
2417# args: 1 - path to file to be uploaded
2418# args: 2 - optional: alternate ftp upload url
2419# NOTE: must be in format: ftp.site.com/incoming
2420sub upload_file {
2421	my ($self, $ftp_url) = @_;
2422	my ($ftp, $domain, $host, $user, $pass, $dir, $error);
2423	$ftp_url ||= main::get_defaults('ftp-upload');
2424	$ftp_url =~ s/\/$//g; # trim off trailing slash if present
2425	my @url = split('/', $ftp_url);
2426	my $file_path = "$user_data_dir/$debug_gz";
2427	$host = $url[0];
2428	$dir = $url[1];
2429	$domain = $host;
2430	$domain =~ s/^ftp\.//;
2431	$user = "anonymous";
2432	$pass = "anonymous\@$domain";
2433
2434	print $line3;
2435	print "Uploading to: $ftp_url\n";
2436	# print "$host $domain $dir $user $pass\n";
2437	print "File to be uploaded:\n$file_path\n";
2438
2439	if ($host && ($file_path && -e $file_path)){
2440		# NOTE: important: must explicitly set to passive true/1
2441		$ftp = Net::FTP->new($host, Debug => 0, Passive => 1) || main::error_handler('ftp-connect', $ftp->message);
2442		$ftp->login($user, $pass) || main::error_handler('ftp-login', $ftp->message);
2443		$ftp->binary();
2444		$ftp->cwd($dir);
2445		print "Connected to FTP server.\n";
2446		$ftp->put($file_path) || main::error_handler('ftp-upload', $ftp->message);
2447		$ftp->quit;
2448		print "Uploaded file successfully!\n";
2449		print $ftp->message;
2450		if ($debugger{'gz'}){
2451			print "Removing debugger gz file:\n$file_path\n";
2452			unlink $file_path or main::error_handler('remove',"$file_path", "$!");
2453			print "File removed.\n";
2454		}
2455		print "Debugger data generation and upload completed. Thank you for your help.\n";
2456	}
2457	else {
2458		main::error_handler('ftp-bad-path', "$file_path");
2459	}
2460}
2461}
2462
2463# random tests for various issues
2464sub user_debug_test_1 {
2465# 	open(my $duped, '>&', STDOUT);
2466# 	local *STDOUT = $duped;
2467# 	my $item = POSIX::strftime("%c", localtime);
2468# 	print "Testing character encoding handling. Perl IO data:\n";
2469# 	print(join(', ', PerlIO::get_layers(STDOUT)), "\n");
2470# 	print "Without binmode: ", $item,"\n";
2471# 	binmode STDOUT,":utf8";
2472# 	print "With binmode: ", $item,"\n";
2473# 	print "Perl IO data:\n";
2474# 	print(join(', ', PerlIO::get_layers(STDOUT)), "\n");
2475# 	close $duped;
2476}
2477
2478# see docs/optimization.txt
2479sub ram_use {
2480    my ($name, $ref) = @_;
2481    printf "%-25s %5d %5d\n", $name, size($ref), total_size($ref);
2482}
2483
2484#### -------------------------------------------------------------------
2485#### DOWNLOADER
2486#### -------------------------------------------------------------------
2487
2488sub download_file {
2489	my ($type, $url, $file,$ua) = @_;
2490	my ($cmd,$args,$timeout) = ('','','');
2491	my $debug_data = '';
2492	my $result = 1;
2493	$ua = ($ua && $dl{'ua'}) ? $dl{'ua'} . $ua : '';
2494	$dl{'no-ssl-opt'} ||= '';
2495	$dl{'spider'} ||= '';
2496	$file ||= 'N/A'; # to avoid debug error
2497	if (!$dl{'dl'}){
2498		return 0;
2499	}
2500	if ($dl{'timeout'}){
2501		$timeout = "$dl{'timeout'}$dl_timeout";
2502	}
2503	# print "$dl{'no-ssl-opt'}\n";
2504	# print "$dl{'dl'}\n";
2505	# tiny supports spider sort of
2506	## NOTE: 1 is success, 0 false for Perl
2507	if ($dl{'dl'} eq 'tiny'){
2508		$cmd = "Using tiny: type: $type \nurl: $url \nfile: $file";
2509		$result = get_file($type, $url, $file);
2510		$debug_data = ($type ne 'stdout') ? $result : 'Success: stdout data not null.';
2511	}
2512	# But: 0 is success, and 1 is false for these
2513	# when strings are returned, they will be taken as true
2514	# urls must be " quoted in case special characters present
2515	else {
2516		if ($type eq 'stdout'){
2517			$args = $dl{'stdout'};
2518			$cmd = "$dl{'dl'} $dl{'no-ssl-opt'} $ua $timeout $args \"$url\" $dl{'null'}";
2519			$result = qx($cmd);
2520			$debug_data = ($result) ? 'Success: stdout data not null.' : 'Download resulted in null data!';
2521		}
2522		elsif ($type eq 'file'){
2523			$args = $dl{'file'};
2524			$cmd = "$dl{'dl'} $dl{'no-ssl-opt'} $ua $timeout $args $file \"$url\" $dl{'null'}";
2525			system($cmd);
2526			$result = ($?) ? 0 : 1; # reverse these into Perl t/f
2527			$debug_data = $result;
2528		}
2529		elsif ($dl{'dl'} eq 'wget' && $type eq 'spider'){
2530			$cmd = "$dl{'dl'} $dl{'no-ssl-opt'} $ua $timeout $dl{'spider'} \"$url\"";
2531			system($cmd);
2532			$result = ($?) ? 0 : 1; # reverse these into Perl t/f
2533			$debug_data = $result;
2534		}
2535	}
2536	print "-------\nDownloader Data:\n$cmd\nResult: $debug_data\n" if $dbg[1];
2537	log_data('data',"$cmd\nResult: $result") if $b_log;
2538	return $result;
2539}
2540
2541sub get_file {
2542	my ($type, $url, $file) = @_;
2543	my $tiny = HTTP::Tiny->new;
2544	# note: default is no verify, so default here actually is to verify unless overridden
2545	$tiny->verify_SSL => 1 if !$dl{'no-ssl-opt'};
2546	my $response = $tiny->get($url);
2547	my $return = 1;
2548	my $debug = 0;
2549	my $fh;
2550	$file ||= 'N/A';
2551	log_data('dump','%{$response}',$response) if $b_log;
2552	# print Dumper $response;
2553	if (!$response->{'success'}){
2554		my $content = $response->{'content'};
2555		$content ||= "N/A\n";
2556		my $msg = "Failed to connect to server/file!\n";
2557		$msg .= "Response: ${content}Downloader: HTTP::Tiny URL: $url\nFile: $file";
2558		log_data('data',$msg) if $b_log;
2559		print error_defaults('download-error',$msg) if $dbg[1];
2560		$return = 0;
2561	}
2562	else {
2563		if ($debug){
2564			print "$response->{success}\n";
2565			print "$response->{status} $response->{reason}\n";
2566			while (my ($key, $value) = each %{$response->{'headers'}}){
2567				for (ref $value eq "ARRAY" ? @$value : $value){
2568					print "$key: $_\n";
2569				}
2570			}
2571		}
2572		if ($type eq "stdout" || $type eq "ua-stdout"){
2573			$return = $response->{'content'};
2574		}
2575		elsif ($type eq "spider"){
2576			# do nothing, just use the return value
2577		}
2578		elsif ($type eq "file"){
2579			open($fh, ">", $file);
2580			print $fh $response->{'content'}; # or die "can't write to file!\n";
2581			close $fh;
2582		}
2583	}
2584	return $return;
2585}
2586
2587sub set_downloader {
2588	eval $start if $b_log;
2589	my $quiet = '';
2590	$dl{'no-ssl'} = '';
2591	$dl{'null'} = '';
2592	$dl{'spider'} = '';
2593	# we only want to use HTTP::Tiny if it's present in user system.
2594	# It is NOT part of core modules. IO::Socket::SSL is also required
2595	# For some https connections so only use tiny as option if both present
2596	if ($dl{'tiny'}){
2597		if (check_perl_module('HTTP::Tiny') && check_perl_module('IO::Socket::SSL')){
2598			HTTP::Tiny->import;
2599			IO::Socket::SSL->import;
2600			$dl{'tiny'} = 1;
2601		}
2602		else {
2603			$dl{'tiny'} = 0;
2604		}
2605	}
2606	# print $dl{'tiny'} . "\n";
2607	if ($dl{'tiny'}){
2608		$dl{'dl'} = 'tiny';
2609		$dl{'file'} = '';
2610		$dl{'stdout'} = '';
2611		$dl{'timeout'} = '';
2612	}
2613	elsif ($dl{'curl'} && check_program('curl')){
2614		$quiet = '-s ' if !$dbg[1];
2615		$dl{'dl'} = 'curl';
2616		$dl{'file'} = "  -L ${quiet}-o ";
2617		$dl{'no-ssl'} = ' --insecure';
2618		$dl{'stdout'} = " -L ${quiet}";
2619		$dl{'timeout'} = ' -y ';
2620		$dl{'ua'} = ' -A ' . $dl_ua;
2621	}
2622	elsif ($dl{'wget'} && check_program('wget')){
2623		$quiet = '-q ' if !$dbg[1];
2624		$dl{'dl'} = 'wget';
2625		$dl{'file'} = " ${quiet}-O ";
2626		$dl{'no-ssl'} = ' --no-check-certificate';
2627		$dl{'spider'} = " ${quiet}--spider";
2628		$dl{'stdout'} = " $quiet -O -";
2629		$dl{'timeout'} = ' -T ';
2630		$dl{'ua'} = ' -U ' . $dl_ua;
2631	}
2632	elsif ($dl{'fetch'} && check_program('fetch')){
2633		$quiet = '-q ' if !$dbg[1];
2634		$dl{'dl'} = 'fetch';
2635		$dl{'file'} = " ${quiet}-o ";
2636		$dl{'no-ssl'} = ' --no-verify-peer';
2637		$dl{'stdout'} = " ${quiet}-o -";
2638		$dl{'timeout'} = ' -T ';
2639	}
2640	# at least openbsd/netbsd
2641	elsif ($bsd_type && check_program('ftp')){
2642		$dl{'dl'} = 'ftp';
2643		$dl{'file'} = ' -o ';
2644		$dl{'null'} = ' 2>/dev/null';
2645		$dl{'stdout'} = ' -o - ';
2646		$dl{'timeout'} = '';
2647	}
2648	else {
2649		$dl{'dl'} = '';
2650	}
2651	# no-ssl-opt is set to 1 with --no-ssl, so it is true, then assign
2652	$dl{'no-ssl-opt'} = $dl{'no-ssl'} if $dl{'no-ssl-opt'};
2653	eval $end if $b_log;
2654}
2655
2656sub set_perl_downloader {
2657	my ($downloader) = @_;
2658	$downloader =~ s/perl/tiny/;
2659	return $downloader;
2660}
2661
2662#### -------------------------------------------------------------------
2663#### ERROR HANDLER
2664#### -------------------------------------------------------------------
2665
2666sub error_handler {
2667	eval $start if $b_log;
2668	my ($err,$one,$two) = @_;
2669	my ($b_help,$b_recommends);
2670	my ($b_exit,$errno) = (1,0);
2671	my $message = do {
2672		if ($err eq 'empty'){ 'empty value' }
2673		## Basic rules
2674		elsif ($err eq 'not-in-irc'){
2675			$errno=1; "You can't run option $one in an IRC client!" }
2676		## Internal/external options
2677		elsif ($err eq 'bad-arg'){
2678			$errno=10; $b_help=1; "Unsupported value: $two for option: $one" }
2679		elsif ($err eq 'bad-arg-int'){
2680			$errno=11; "Bad internal argument: $one" }
2681		elsif ($err eq 'distro-block'){
2682			$errno=20; "Option: $one has been disabled by the $self_name distribution maintainer." }
2683		elsif ($err eq 'option-feature-incomplete'){
2684			$errno=21; "Option: '$one' feature: '$two' has not been implemented yet." }
2685		elsif ($err eq 'unknown-option'){
2686			$errno=22; $b_help=1; "Unsupported option: $one" }
2687		## Data
2688		elsif ($err eq 'open-data'){
2689			$errno=32; "Error opening data for reading: $one \nError: $two" }
2690		elsif ($err eq 'download-error'){
2691			$errno=33; "Error downloading file with $dl{'dl'}: $one \nError: $two" }
2692		## Files:
2693		elsif ($err eq 'copy-failed'){
2694			$errno=40; "Error copying file: $one \nError: $two" }
2695		elsif ($err eq 'create'){
2696			$errno=41; "Error creating file: $one \nError: $two" }
2697		elsif ($err eq 'downloader-error'){
2698			$errno=42; "Error downloading file: $one \nfor download source: $two" }
2699		elsif ($err eq 'file-corrupt'){
2700			$errno=43; "Downloaded file is corrupted: $one" }
2701		elsif ($err eq 'mkdir'){
2702			$errno=44; "Error creating directory: $one \nError: $two" }
2703		elsif ($err eq 'open'){
2704			$errno=45; $b_exit=0; "Error opening file: $one \nError: $two" }
2705		elsif ($err eq 'open-dir'){
2706			$errno=46; "Error opening directory: $one \nError: $two" }
2707		elsif ($err eq 'output-file-bad'){
2708			$errno=47; "Value for --output-file must be full path, a writable directory, \nand include file name. Path: $two" }
2709		elsif ($err eq 'not-writable'){
2710			$errno=48; "The file: $one is not writable!" }
2711		elsif ($err eq 'open-dir-failed'){
2712			$errno=49; "The directory: $one failed to open with error: $two" }
2713		elsif ($err eq 'remove'){
2714			$errno=50; "Failed to remove file: $one Error: $two" }
2715		elsif ($err eq 'rename'){
2716			$errno=51; "There was an error moving files: $one\nError: $two" }
2717		elsif ($err eq 'write'){
2718			$errno=52; "Failed writing file: $one - Error: $two!" }
2719		## Downloaders
2720		elsif ($err eq 'missing-downloader'){
2721			$errno=60; "Downloader program $two could not be located on your system." }
2722		elsif ($err eq 'missing-perl-downloader'){
2723			$errno=61; $b_recommends=1; "Perl downloader missing required module." }
2724		## FTP
2725		elsif ($err eq 'ftp-bad-path'){
2726			$errno=70; "Unable to locate for FTP upload file:\n$one" }
2727		elsif ($err eq 'ftp-connect'){
2728			$errno=71; "There was an error with connection to ftp server: $one" }
2729		elsif ($err eq 'ftp-login'){
2730			$errno=72; "There was an error with login to ftp server: $one" }
2731		elsif ($err eq 'ftp-upload'){
2732			$errno=73; "There was an error with upload to ftp server: $one" }
2733		## Modules
2734		elsif ($err eq 'required-module'){
2735			$errno=80; $b_recommends=1; "The required $one Perl module is not installed:\n$two" }
2736		## DEFAULT
2737		else {
2738			$errno=255; "Error handler ERROR!! Unsupported options: $err!"}
2739	};
2740	print_line("Error $errno: $message\n");
2741	if ($b_help){
2742		print_line("Check -h for correct parameters.\n");
2743	}
2744	if ($b_recommends){
2745		print_line("See --recommends for more information.\n");
2746	}
2747	eval $end if $b_log;
2748	exit $errno if $b_exit && !$debugger{'no-exit'};
2749}
2750
2751sub error_defaults {
2752	my ($type,$one) = @_;
2753	$one ||= '';
2754	my %errors = (
2755	'download-error' => "Download Failure:\n$one\n",
2756	);
2757	return $errors{$type};
2758}
2759
2760#### -------------------------------------------------------------------
2761#### RECOMMENDS
2762#### -------------------------------------------------------------------
2763
2764## CheckRecommends
2765{
2766package CheckRecommends;
2767my (@modules);
2768sub run {
2769	main::error_handler('not-in-irc', 'recommends') if $b_irc;
2770	my (@data,@rows);
2771	my $line = make_line();
2772	my $pm = get_pm();
2773	@data = basic_data($line,$pm);
2774	push(@rows, @data);
2775	if (!$bsd_type){
2776		@data = check_items('required system directories',$line,$pm);
2777		push(@rows, @data);
2778	}
2779	@data = check_items('recommended system programs',$line,$pm);
2780	push(@rows, @data);
2781	@data = check_items('recommended display information programs',$line,$pm);
2782	push(@rows, @data);
2783	@data = check_items('recommended downloader programs',$line,$pm);
2784	push(@rows, @data);
2785	if (!$bsd_type){
2786		@data = check_items('recommended kernel modules',$line,$pm);
2787		push(@rows, @data);
2788	}
2789	@data = check_items('recommended Perl modules',$line,$pm);
2790	push(@rows, @data);
2791	@data = check_items('recommended directories',$line,'');
2792	push(@rows, @data);
2793	@data = check_items('recommended files',$line,'');
2794	push(@rows, @data);
2795	@data = (
2796	['0', '', '', "$line"],
2797	['0', '', '', "Ok, all done with the checks. Have a nice day."],
2798	['0', '', '', " "],
2799	);
2800	push(@rows, @data);
2801	# print Data::Dumper::Dumper \@rows;
2802	main::print_basic(\@rows);
2803	exit 0; # shell true
2804}
2805
2806sub basic_data {
2807	my ($line,$pm_local) = @_;
2808	my (@data,@rows);
2809	my $client = $client{'name-print'};
2810	$pm_local ||= 'N/A';
2811	$client .= ' ' . $client{'version'} if $client{'version'};
2812	my $default_shell = 'N/A';
2813	if ($ENV{'SHELL'}){
2814		$default_shell = $ENV{'SHELL'};
2815		$default_shell =~ s/.*\///;
2816	}
2817	my $sh = main::check_program('sh');
2818	my $sh_real = Cwd::abs_path($sh);
2819	@rows = (
2820	['0', '', '', "$self_name will now begin checking for the programs it needs
2821	to operate."],
2822	['0', '', '', "" ],
2823	['0', '', '', "Check $self_name --help or the man page (man $self_name)
2824	to see what options are available." ],
2825	['0', '', '', "$line" ],
2826	['0', '', '', "Test: core tools:" ],
2827	['0', '', '', "" ],
2828	['0', '', '', "Perl version: ^$]" ],
2829	['0', '', '', "Current shell: " . $client ],
2830	['0', '', '', "Default shell: " . $default_shell ],
2831	['0', '', '', "sh links to: $sh_real" ],
2832	['0', '', '', "Package manager: $pm_local" ],
2833	);
2834	return @rows;
2835}
2836sub check_items {
2837	my ($type,$line,$pm) = @_;
2838	my (@data,%info,@missing,$row,@rows,$result,@unreadable);
2839	my ($b_dir,$b_file,$b_kernel_module,$b_perl_module,$b_program,$item);
2840	my ($about,$extra,$extra2,$extra3,$extra4,$info_os,$install) = ('','','','','','info','');
2841	if ($type eq 'required system directories'){
2842		@data = qw(/proc /sys);
2843		$b_dir = 1;
2844		$item = 'Directory';
2845	}
2846	elsif ($type eq 'recommended system programs'){
2847		if ($bsd_type){
2848			@data = qw(camcontrol dig disklabel dmidecode doas fdisk file glabel gpart
2849			ifconfig ipmi-sensors ipmitool pciconfig pcidump pcictl smartctl sudo
2850			sysctl tree upower uptime usbconfig usbdevs);
2851			$info_os = 'info-bsd';
2852		}
2853		else {
2854			@data = qw(blockdev bt-adapter dig dmidecode doas fdisk file hciconfig
2855			hddtemp ifconfig ip ipmitool ipmi-sensors lsblk lsusb lvs mdadm modinfo
2856			runlevel sensors smartctl strings sudo tree upower uptime);
2857		}
2858		$b_program = 1;
2859		$item = 'Program';
2860		$extra2 = "Note: IPMI sensors are generally only found on servers. To access
2861		that data, you only need one of the ipmi items.";
2862	}
2863	elsif ($type eq 'recommended display information programs'){
2864		if ($bsd_type){
2865			@data = qw(glxinfo wmctrl xdpyinfo xprop xrandr);
2866			$info_os = 'info-bsd';
2867		}
2868		else {
2869			@data = qw(glxinfo wmctrl xdpyinfo xprop xrandr);
2870		}
2871		$b_program = 1;
2872		$item = 'Program';
2873	}
2874	elsif ($type eq 'recommended downloader programs'){
2875		if ($bsd_type){
2876			@data = qw(curl dig fetch ftp wget);
2877			$info_os = 'info-bsd';
2878		}
2879		else {
2880			@data = qw(curl dig wget);
2881		}
2882		$b_program = 1;
2883		$extra = ' (You only need one of these)';
2884		$extra2 = "Perl HTTP::Tiny is the default downloader tool if IO::Socket::SSL is present.
2885		See --help --alt 40-44 options for how to override default downloader(s) in case of issues. ";
2886		$extra3 = "If dig is installed, it is the default for WAN IP data.
2887		Strongly recommended. Dig is fast and accurate.";
2888		$extra4 = ". However, you really only need dig in most cases. All systems should have ";
2889		$extra4 .= "at least one of the downloader options present.";
2890		$item = 'Program';
2891	}
2892	elsif ($type eq 'recommended Perl modules'){
2893		@data = qw(File::Copy File::Find File::Spec::Functions HTTP::Tiny IO::Socket::SSL
2894		Time::HiRes Cpanel::JSON::XS JSON::XS XML::Dumper Net::FTP);
2895		push(@data, qw(OpenBSD::Pledge OpenBSD::Unveil)) if $bsd_type && $bsd_type eq 'openbsd';
2896		$b_perl_module = 1;
2897		$item = 'Perl Module';
2898		$extra = ' (Optional)';
2899		$extra2 = "None of these are strictly required, but if you have them all, you can
2900		eliminate some recommended non Perl programs from the install. ";
2901		$extra3 = "HTTP::Tiny and IO::Socket::SSL must both be present to use as a downloader option.
2902		For json export Cpanel::JSON::XS is preferred over JSON::XS. To run --debug 20-22 File::Copy,
2903		File::Find, and File::Spec::Functions must be present (most distros have these in Core Modules).
2904		";
2905	}
2906	elsif ($type eq 'recommended kernel modules'){
2907		@data = qw(amdgpu drivetemp nouveau);
2908		@modules = main::lister('/sys/module/');
2909		$b_kernel_module = 1;
2910		$extra2 = "GPU modules are only needed if applicable. NVMe drives do not need drivetemp
2911		but other types do.";
2912		$extra3 = "To load a module: modprobe <module-name> - To  permanently load
2913		add to /etc/modules or /etc/modules-load.d/modules.conf (check your system
2914		paths for exact file/directory names).";
2915		$item = 'Kernel Module';
2916	}
2917	elsif ($type eq 'recommended directories'){
2918		if ($bsd_type){
2919			@data = qw(/dev);
2920		}
2921		else {
2922			@data = qw(/dev /dev/disk/by-id /dev/disk/by-label /dev/disk/by-path
2923			/dev/disk/by-uuid /sys/class/dmi/id);
2924		}
2925		$b_dir = 1;
2926		$item = 'Directory';
2927	}
2928	elsif ($type eq 'recommended files'){
2929		if ($bsd_type){
2930			@data = qw(/var/run/dmesg.boot /var/log/Xorg.0.log);
2931		}
2932		else {
2933			@data = qw(/etc/lsb-release /etc/os-release /proc/asound/cards
2934			/proc/asound/version /proc/cpuinfo /proc/mdstat /proc/meminfo /proc/modules
2935			/proc/mounts /proc/scsi/scsi /var/log/Xorg.0.log);
2936		}
2937		$b_file = 1;
2938		$item = 'File';
2939		$extra2 = "Note that not all of these are used by every system,
2940		so if one is missing it's usually not a big deal.";
2941	}
2942	@rows = (
2943	['0', '', '', "$line" ],
2944	['0', '', '', "Test: $type$extra:" ],
2945	['0', '', '', " " ],
2946	);
2947	if ($extra2){
2948		$rows[scalar @rows] = ['0', '', '', $extra2];
2949		$rows[scalar @rows] = ['0', '', '', ' '];
2950	}
2951	if ($extra3){
2952		$rows[scalar @rows] = ['0', '', '', $extra3];
2953		$rows[scalar @rows] = ['0', '', '', ' '];
2954	}
2955	foreach my $item (@data){
2956		$install = '';
2957		$about = '';
2958		%info = item_data($item);
2959		$about = $info{$info_os};
2960		if (($b_dir && -d $item) || ($b_file && -r $item) ||
2961		     ($b_program && main::check_program($item)) ||
2962		     ($b_perl_module && main::check_perl_module($item)) ||
2963		     ($b_kernel_module && @modules && (grep {/^$item$/} @modules))){
2964			$result = 'Present';
2965		}
2966		elsif ($b_file && -f $item){
2967			$result = 'Unreadable';
2968			push(@unreadable, "$item");
2969		}
2970		else {
2971			$result = 'Missing';
2972			if (($b_program || $b_perl_module) && $pm){
2973				$info{$pm} ||= 'N/A';
2974				$install = " ~ Install package: $info{$pm}";
2975			}
2976			push(@missing, "$item$install");
2977		}
2978		$row = make_row($item,$about,$result);
2979		$rows[scalar @rows] = ['0', '', '', $row];
2980	}
2981	$rows[scalar @rows] = ['0', '', '', " "];
2982	if (@missing){
2983		$rows[scalar @rows] = ['0', '', '', "The following $type are missing$extra4:"];
2984		foreach (@missing){
2985			$rows[scalar @rows] = ['0', '', '', "$item: $_"];
2986		}
2987	}
2988	if (@unreadable){
2989		$rows[scalar @rows] = ['0', '', '', "The following $type are not readable: "];
2990		foreach (@unreadable){
2991			$rows[scalar @rows] = ['0', '', '', "$item: $_"];
2992		}
2993	}
2994	if (!@missing && !@unreadable){
2995		$rows[scalar @rows] = ['0', '', '', "All $type are present"];
2996	}
2997	return @rows;
2998}
2999
3000sub item_data {
3001	my ($type) = @_;
3002	my %data = (
3003	# Directory Data
3004	'/sys/class/dmi/id' => {
3005	'info' => '-M system, motherboard, bios',
3006	},
3007	'/dev' => {
3008	'info' => '-l,-u,-o,-p,-P,-D disk partition data',
3009	},
3010	'/dev/disk/by-id' => {
3011	'info' => '-D serial numbers',
3012	},
3013	'/dev/disk/by-path' => {
3014	'info' => '-D extra data',
3015	},
3016	'/dev/disk/by-label' => {
3017	'info' => '-l,-o,-p,-P partition labels',
3018	},
3019	'/dev/disk/by-uuid' => {
3020	'info' => '-u,-o,-p,-P partition uuid',
3021	},
3022	'/proc' => {
3023	'info' => '',
3024	},
3025	'/sys' => {
3026	'info' => '',
3027	},
3028	# File Data
3029	'/etc/lsb-release' => {
3030	'info' => '-S distro version data (older version)',
3031	},
3032	'/etc/os-release' => {
3033	'info' => '-S distro version data (newer version)',
3034	},
3035	'/proc/asound/cards' => {
3036	'info' => '-A sound card data',
3037	},
3038	'/proc/asound/version' => {
3039	'info' => '-A ALSA data',
3040	},
3041	'/proc/cpuinfo' => {
3042	'info' => '-C cpu data',
3043	},
3044	'/proc/mdstat' => {
3045	'info' => '-R mdraid data (if you use dm-raid)',
3046	},
3047	'/proc/meminfo' => {
3048	'info' => '-I,-tm, -m memory data',
3049	},
3050	'/proc/modules' => {
3051	'info' => '-G module data (sometimes)',
3052	},
3053	'/proc/mounts' => {
3054	'info' => '-P,-p partition advanced data',
3055	},
3056	'/proc/scsi/scsi' => {
3057	'info' => '-D Advanced hard disk data (used rarely)',
3058	},
3059	'/var/log/Xorg.0.log' => {
3060	'info' => '-G graphics driver load status',
3061	},
3062	'/var/run/dmesg.boot' => {
3063	'info' => '-D,-d disk data',
3064	},
3065	## Kernel Module Data
3066	'amdgpu' => {
3067	'info' => '-s AMD GPU sensor data (newer AMD GPUs)',
3068	'info-bsd' => '',
3069	},
3070	'drivetemp' => {
3071	'info' => '-Dx drive temperature (kernel >= 5.6)',
3072	'info-bsd' => '',
3073	},
3074	'nouveau' => {
3075	'info' => '-s Nvidia GPU sensor data (if using free driver)',
3076	'info-bsd' => '',
3077	},
3078	## START PACKAGE MANAGER BLOCK ##
3079	# Note: see inxi-perl branch for details: docs/recommends-package-manager.txt
3080	# System Tools
3081	'blockdev' => {
3082	'info' => '--admin -p/-P (filesystem blocksize)',
3083	'info-bsd' => '',
3084	'apt' => 'util-linux',
3085	'pacman' => 'util-linux',
3086	'rpm' => 'util-linux',
3087	},
3088	'bt-adapter' => {
3089	'info' => '-E bluetooth data (if no hciconfig)',
3090	'info-bsd' => '',
3091	'apt' => 'bluez-tools',
3092	'pacman' => 'bluez-tools',
3093	'rpm' => 'bluez-tools',
3094	},
3095	'curl' => {
3096	'info' => '-i (if no dig); -w,-W; -U',
3097	'info-bsd' => '-i (if no dig); -w,-W; -U',
3098	'apt' => 'curl',
3099	'pacman' => 'curl',
3100	'rpm' => 'curl',
3101	},
3102	'camcontrol' => {
3103	'info' => '',
3104	'info-bsd' => '-R; -D; -P. Get actual gptid /dev path',
3105	'apt' => '',
3106	'pacman' => '',
3107	'rpm' => '',
3108	},
3109	'dig' => {
3110	'info' => '-i wlan IP',
3111	'info-bsd' => '-i wlan IP',
3112	'apt' => 'dnsutils',
3113	'pacman' => 'dnsutils',
3114	'rpm' => 'bind-utils',
3115	},
3116	'disklabel' => {
3117	'info' => '',
3118	'info-bsd' => '-j, -p, -P; -R; -o (Open/NetBSD+derived)',
3119	'apt' => '',
3120	'pacman' => '',
3121	'rpm' => '',
3122	},
3123	'dmidecode' => {
3124	'info' => '-M if no sys machine data; -m',
3125	'info-bsd' => '-M if null sysctl; -m; -B if null sysctl',
3126	'apt' => 'dmidecode',
3127	'pacman' => 'dmidecode',
3128	'rpm' => 'dmidecode',
3129	},
3130	'doas' => {
3131	'info' => '-Dx hddtemp-user; -o file-user (alt for sudo)',
3132	'info-bsd' => '-Dx hddtemp-user; -o file-user',
3133	'apt' => 'doas',
3134	'pacman' => 'doas',
3135	'rpm' => 'doas',
3136	},
3137	'fdisk' => {
3138	'info' => '-D partition scheme (fallback)',
3139	'info-bsd' => '-D partition scheme',
3140	'apt' => 'fdisk',
3141	'pacman' => 'util-linux',
3142	'rpm' => 'util-linux',
3143	},
3144	'fetch' => {
3145	'info' => '',
3146	'info-bsd' => '-i (if no dig); -w,-W; -U',
3147	'apt' => '',
3148	'pacman' => '',
3149	'rpm' => '',
3150	},
3151	'file' => {
3152	'info' => '-o unmounted file system (if no lsblk)',
3153	'info-bsd' => '-o unmounted file system',
3154	'apt' => 'file',
3155	'pacman' => 'file',
3156	'rpm' => 'file',
3157	},
3158	'ftp' => {
3159	'info' => '',
3160	'info-bsd' => '-i (if no dig); -w,-W; -U',
3161	'apt' => '',
3162	'pacman' => '',
3163	'rpm' => '',
3164	},
3165	'glabel' => {
3166	'info' => '',
3167	'info-bsd' => '-R; -D; -P. Get actual gptid /dev path',
3168	'apt' => '',
3169	'pacman' => '',
3170	'rpm' => '',
3171	},
3172	'gpart' => {
3173	'info' => '',
3174	'info-bsd' => '-p,-P; -R; -o (FreeBSD+derived)',
3175	'apt' => '',
3176	'pacman' => '',
3177	'rpm' => '',
3178	},
3179	'hciconfig' => {
3180	'info' => '-E bluetooth data (deprecated, good report)',
3181	'info-bsd' => '',
3182	'apt' => 'bluez',
3183	'pacman' => 'bluez-utils-compat (frugalware: bluez-utils)',
3184	'rpm' => 'bluez-utils',
3185	},
3186	'hddtemp' => {
3187	'info' => '-Dx show hdd temp, if no drivetemp module',
3188	'info-bsd' => '-Dx show hdd temp',
3189	'apt' => 'hddtemp',
3190	'pacman' => 'hddtemp',
3191	'rpm' => 'hddtemp',
3192	},
3193	'ifconfig' => {
3194	'info' => '-i ip LAN (deprecated)',
3195	'info-bsd' => '-i ip LAN',
3196	'apt' => 'net-tools',
3197	'pacman' => 'net-tools',
3198	'rpm' => 'net-tools',
3199	},
3200	'ip' => {
3201	'info' => '-i ip LAN',
3202	'info-bsd' => '',
3203	'apt' => 'iproute',
3204	'pacman' => 'iproute2',
3205	'rpm' => 'iproute',
3206	},
3207	'ipmi-sensors' => {
3208	'info' => '-s IPMI sensors (servers)',
3209	'info-bsd' => '',
3210	'apt' => 'freeipmi-tools',
3211	'pacman' => 'freeipmi',
3212	'rpm' => 'freeipmi',
3213	},
3214	'ipmitool' => {
3215	'info' => '-s IPMI sensors (servers)',
3216	'info-bsd' => '-s IPMI sensors (servers)',
3217	'apt' => 'ipmitool',
3218	'pacman' => 'ipmitool',
3219	'rpm' => 'ipmitool',
3220	},
3221	'lsblk' => {
3222	'info' => '-L LUKS/bcache; -o unmounted file system (best option)',
3223	'info-bsd' => '-o unmounted file system',
3224	'apt' => 'util-linux',
3225	'pacman' => 'util-linux',
3226	'rpm' => 'util-linux-ng',
3227	},
3228	'lvs' => {
3229	'info' => '-L LVM data',
3230	'info-bsd' => '',
3231	'apt' => 'lvm2',
3232	'pacman' => 'lvm2',
3233	'rpm' => 'lvm2',
3234	},
3235	'lsusb' => {
3236	'info' => '-A usb audio; -J (optional); -N usb networking',
3237	'info-bsd' => '',
3238	'apt' => 'usbutils',
3239	'pacman' => 'usbutils',
3240	'rpm' => 'usbutils',
3241	},
3242	'mdadm' => {
3243	'info' => '-Ra advanced mdraid data',
3244	'info-bsd' => '',
3245	'apt' => 'mdadm',
3246	'pacman' => 'mdadm',
3247	'rpm' => 'mdadm',
3248	},
3249	'modinfo' => {
3250	'info' => 'Ax; -Nx module version',
3251	'info-bsd' => '',
3252	'apt' => 'module-init-tools',
3253	'pacman' => 'module-init-tools',
3254	'rpm' => 'module-init-tools',
3255	},
3256	'pciconfig' => {
3257	'info' => '',
3258	'info-bsd' => '-A,-E,-G,-N pci devices (FreeBSD+derived)',
3259	'apt' => '',
3260	'pacman' => '',
3261	'rpm' => '',
3262	},
3263	'pcictl' => {
3264	'info' => '',
3265	'info-bsd' => '-A,-E,-G,-N pci devices (NetBSD+derived)',
3266	'apt' => '',
3267	'pacman' => '',
3268	'rpm' => '',
3269	},
3270	'pcidump' => {
3271	'info' => '',
3272	'info-bsd' => '-A,-E,-G,-N pci devices (OpenBSD+derived, doas/su)',
3273	'apt' => '',
3274	'pacman' => '',
3275	'rpm' => '',
3276	},
3277	'runlevel' => {
3278	'info' => '-I fallback to Perl',
3279	'info-bsd' => '',
3280	'apt' => 'systemd or sysvinit',
3281	'pacman' => 'systemd',
3282	'rpm' => 'systemd or sysvinit',
3283	},
3284	'sensors' => {
3285	'info' => '-s sensors output',
3286	'info-bsd' => '',
3287	'apt' => 'lm-sensors',
3288	'pacman' => 'lm-sensors',
3289	'rpm' => 'lm-sensors',
3290	},
3291	'smartctl' => {
3292	'info' => '-Da advanced data',
3293	'info-bsd' => '-Da advanced data',
3294	'apt' => 'smartmontools',
3295	'pacman' => 'smartmontools',
3296	'rpm' => 'smartmontools',
3297	},
3298	'strings' => {
3299	'info' => '-I sysvinit version',
3300	'info-bsd' => '',
3301	'apt' => 'binutils',
3302	'pacman' => '?',
3303	'rpm' => '?',
3304	},
3305	'sudo' => {
3306	'info' => '-Dx hddtemp-user; -o file-user',
3307	'info-bsd' => '-Dx hddtemp-user; -o file-user (alt for doas)',
3308	'apt' => 'sudo',
3309	'pacman' => 'sudo',
3310	'rpm' => 'sudo',
3311	},
3312	'sysctl' => {
3313	'info' => '',
3314	'info-bsd' => '-C; -I; -m; -tm',
3315	'apt' => '?',
3316	'pacman' => '?',
3317	'rpm' => '?',
3318	},
3319	'tree' => {
3320	'info' => '--debugger 20,21 /sys tree',
3321	'info-bsd' => '--debugger 20,21 /sys tree',
3322	'apt' => 'tree',
3323	'pacman' => 'tree',
3324	'rpm' => 'tree',
3325	},
3326	'upower' => {
3327	'info' => '-sx attached device battery info',
3328	'info-bsd' => '-sx attached device battery info',
3329	'apt' => 'upower',
3330	'pacman' => 'upower',
3331	'rpm' => 'upower',
3332	},
3333	'uptime' => {
3334	'info' => '-I uptime',
3335	'info-bsd' => '-I uptime',
3336	'apt' => 'procps',
3337	'pacman' => 'procps',
3338	'rpm' => 'procps',
3339	},
3340	'usbconfig' => {
3341	'info' => '',
3342	'info-bsd' => '-A; -E; -G; -J; -N; (FreeBSD+derived, doas/su)',
3343	'apt' => 'usbutils',
3344	'pacman' => 'usbutils',
3345	'rpm' => 'usbutils',
3346	},
3347	'usbdevs' => {
3348	'info' => '',
3349	'info-bsd' => '-A; -E; -G; -J; -N; (Open/NetBSD+derived)',
3350	'apt' => 'usbutils',
3351	'pacman' => 'usbutils',
3352	'rpm' => 'usbutils',
3353	},
3354	'wget' => {
3355	'info' => '-i (if no dig); -w,-W; -U',
3356	'info-bsd' => '-i (if no dig); -w,-W; -U',
3357	'apt' => 'wget',
3358	'pacman' => 'wget',
3359	'rpm' => 'wget',
3360	},
3361	# Display Tools
3362	'glxinfo' => {
3363	'info' => '-G glx info',
3364	'info-bsd' => '-G glx info',
3365	'apt' => 'mesa-utils',
3366	'pacman' => 'mesa-demos',
3367	'rpm' => 'glx-utils (SUSE: Mesa-demo-x)',
3368	},
3369	'wmctrl' => {
3370	'info' => '-S active window manager (fallback)',
3371	'info-bsd' => '-S active window managerr (fallback)',
3372	'apt' => 'wmctrl',
3373	'pacman' => 'wmctrl',
3374	'rpm' => 'wmctrl',
3375	},
3376	'xdpyinfo' => {
3377	'info' => '-G multi screen resolution',
3378	'info-bsd' => '-G multi screen resolution',
3379	'apt' => 'X11-utils',
3380	'pacman' => 'xorg-xdpyinfo',
3381	'rpm' => 'xorg-x11-utils (SUSE/Fedora?: xdpyinfo)',
3382	},
3383	'xprop' => {
3384	'info' => '-S desktop data',
3385	'info-bsd' => '-S desktop data',
3386	'apt' => 'X11-utils',
3387	'pacman' => 'xorg-xprop',
3388	'rpm' => 'x11-utils',
3389	},
3390	'xrandr' => {
3391	'info' => '-G single screen resolution',
3392	'info-bsd' => '-G single screen resolution',
3393	'apt' => 'x11-xserver-utils',
3394	'pacman' => 'xrandr',
3395	'rpm' => 'x11-server-utils (Fedora: xrandr)',
3396	},
3397	# Perl Modules
3398	'Cpanel::JSON::XS' => {
3399	'info' => '--output json - required for export.',
3400	'info-bsd' => '--output json - required for export.',
3401	'apt' => 'libcpanel-json-xs-perl',
3402	'pacman' => 'perl-cpanel-json-xs',
3403	'rpm' => 'perl-Cpanel-JSON-XS',
3404	},
3405	'File::Copy' => {
3406	'info' => '--debug 20-22 - required to run debugger.',
3407	'info-bsd' => '--debug 20-22 - required to run debugger.',
3408	'apt' => 'Core Modules',
3409	'pacman' => 'Core Modules',
3410	'rpm' => 'perl-File-Copy',
3411	},
3412	'File::Find' => {
3413	'info' => '--debug 20-22 - required to run debugger.',
3414	'info-bsd' => '--debug 20-22 - required to run debugger.',
3415	'apt' => 'Core Modules',
3416	'pacman' => 'Core Modules',
3417	'rpm' => 'perl-File-Find',
3418	},
3419	'File::Spec::Functions' => {
3420	'info' => '--debug 20-22 - required to run debugger.',
3421	'info-bsd' => '--debug 20-22 - required to run debugger.',
3422	'apt' => 'Core Modules',
3423	'pacman' => 'Core Modules',
3424	'rpm' => 'Core Modules',
3425	},
3426	'HTTP::Tiny' => {
3427	'info' => '-U; -w,-W; -i (if dig not installed).',
3428	'info-bsd' => '-U; -w,-W; -i (if dig not installed)',
3429	'apt' => 'libhttp-tiny-perl',
3430	'pacman' => 'Core Modules',
3431	'rpm' => 'Perl-http-tiny',
3432	},
3433	'IO::Socket::SSL' => {
3434	'info' => '-U; -w,-W; -i (if dig not installed).',
3435	'info-bsd' => '-U; -w,-W; -i (if dig not installed)',
3436	'apt' => 'libio-socket-ssl-perl',
3437	'pacman' => 'perl-io-socket-ssl',
3438	'rpm' => 'perl-IO-Socket-SSL',
3439	},
3440	'JSON::XS' => {
3441	'info' => '--output json - required for export (legacy).',
3442	'info-bsd' => '--output json - required for export (legacy).',
3443	'apt' => 'libjson-xs-perl',
3444	'pacman' => 'perl-json-xs',
3445	'rpm' => 'perl-JSON-XS',
3446	},
3447	'Net::FTP' => {
3448	'info' => '--debug 21,22',
3449	'info-bsd' => '--debug 21,22',
3450	'apt' => 'Core Modules',
3451	'pacman' => 'Core Modules',
3452	'rpm' => 'Core Modules',
3453	},
3454	'OpenBSD::Pledge' => {
3455	'info' => "$self_name Perl pledge support.",
3456	'info-bsd' => "$self_name Perl pledge support.",
3457	'apt' => '',
3458	'pacman' => '',
3459	'rpm' => '',
3460	},
3461	'OpenBSD::Unveil' => {
3462	'info' => "Experimental: $self_name Perl unveil support.",
3463	'info-bsd' => "Experimental: $self_name Perl unveil support.",
3464	'apt' => '',
3465	'pacman' => '',
3466	'rpm' => '',
3467	},
3468	'Time::HiRes' => {
3469	'info' => '-C cpu sleep (not required); --debug timers',
3470	'info-bsd' => '-C cpu sleep (not required); --debug timers',
3471	'apt' => 'Core Modules',
3472	'pacman' => 'Core Modules',
3473	'rpm' => 'perl-Time-HiRes',
3474	},
3475	'XML::Dumper' => {
3476	'info' => '--output xml - Crude and raw.',
3477	'info-bsd' => '--output xml - Crude and raw.',
3478	'apt' => 'libxml-dumper-perl',
3479	'pacman' => 'perl-xml-dumper',
3480	'rpm' => 'perl-XML-Dumper',
3481	},
3482	## END PACKAGE MANAGER BLOCK ##
3483	);
3484	return %{$data{$type}};
3485}
3486sub get_pm {
3487	my ($pm) = ('');
3488	# support maintainers of other pm types using custom lists
3489	if (main::check_program('dpkg')){
3490		$pm = 'apt';
3491	}
3492	elsif (main::check_program('pacman')){
3493		$pm = 'pacman';
3494	}
3495	elsif (main::check_program('rpm')){
3496		$pm = 'rpm';
3497	}
3498	return $pm;
3499}
3500# note: end will vary, but should always be treated as longest value possible.
3501# expected values: Present/Missing
3502sub make_row {
3503	my ($start,$middle,$end) = @_;
3504	my ($dots,$line,$sep) = ('','',': ');
3505	foreach (0 .. ($size{'max'} - 16 - length("$start$middle"))){
3506		$dots .= '.';
3507	}
3508	$line = "$start$sep$middle$dots $end";
3509	return $line;
3510}
3511sub make_line {
3512	my $line = '';
3513	foreach (0 .. $size{'max'} - 2){
3514		$line .= '-';
3515	}
3516	return $line;
3517}
3518}
3519
3520#### -------------------------------------------------------------------
3521#### TOOLS
3522#### -------------------------------------------------------------------
3523
3524# Duplicates the functionality of awk to allow for one liner
3525# type data parsing. note: -1 corresponds to awk NF
3526# args 1: array of data; 2: search term; 3: field result; 4: separator
3527# correpsonds to: awk -F='separator' '/search/ {print $2}' <<< @data
3528# array is sent by reference so it must be dereferenced
3529# NOTE: if you just want the first row, pass it \S as search string
3530# NOTE: if $num is undefined, it will skip the second step
3531sub awk {
3532	eval $start if $b_log;
3533	my ($ref,$search,$num,$sep) = @_;
3534	my ($result);
3535	# print "search: $search\n";
3536	return if !@$ref || !$search;
3537	foreach (@$ref){
3538		next if !defined $_;
3539		if (/$search/i){
3540			$result = $_;
3541			$result =~ s/^\s+|\s+$//g;
3542			last;
3543		}
3544	}
3545	if ($result && defined $num){
3546		$sep ||= '\s+';
3547		$num-- if $num > 0; # retain the negative values as is
3548		$result = (split(/$sep/, $result))[$num];
3549		$result =~ s/^\s+|,|\s+$//g if $result;
3550	}
3551	eval $end if $b_log;
3552	return $result;
3553}
3554
3555# $1 - Perl module to check
3556sub check_perl_module {
3557	my ($module) = @_;
3558	my $b_present = 0;
3559	eval "require $module";
3560	$b_present = 1 if !$@;
3561	return $b_present;
3562}
3563
3564# arg: 1 - string or path to search gneerated @paths data for.
3565# note: a few nano seconds are saved by using raw $_[0] for program
3566sub check_program {
3567	(grep { return "$_/$_[0]" if -e "$_/$_[0]"} @paths)[0];
3568}
3569
3570sub cleanup {
3571	# maybe add in future: , $fh_c, $fh_j, $fh_x
3572	foreach my $fh ($fh_l){
3573		if ($fh){
3574			close $fh;
3575		}
3576	}
3577}
3578
3579# args: $1, $2, version numbers to compare by turning them to strings
3580# note that the structure of the two numbers is expected to be fairly
3581# similar, otherwise it may not work perfectly.
3582sub compare_versions {
3583	my ($one,$two) = @_;
3584	if ($one && !$two){return $one;}
3585	elsif ($two && !$one){return $two;}
3586	elsif (!$one && !$two){return}
3587	my ($pad1,$pad2) = ('','');
3588	$pad1 = join('', map {$_ = sprintf("%04s", $_);$_ } split(/[._-]/, $one));
3589	$pad2 = join('', map {$_ = sprintf("%04s", $_);$_ } split(/[._-]/, $two));
3590	# print "p1:$pad1 p2:$pad2\n";
3591	if ($pad1 ge $pad2){return $one}
3592	elsif ($pad2 gt $pad1){return $two}
3593}
3594
3595# some things randomly use hex with 0x starter, return always integer
3596# warning: perl will generate a 32 bit too big number warning if you pass it
3597# random values that exceed 2^32 in hex, even if the base system is 64 bit.
3598# sample: convert_hex(0x000b0000000b);
3599sub convert_hex {
3600	return (defined $_[0] && $_[0] =~ /^0x/) ? hex($_[0]) : $_[0];
3601}
3602
3603# returns count of files in directory, if 0, dir is empty
3604sub count_dir_files {
3605	return unless -d $_[0];
3606	opendir(my $dh, $_[0]) or error_handler('open-dir-failed', "$_[0]", $!);
3607	my $count = grep { ! /^\.{1,2}/ } readdir($dh); # strips out . and ..
3608	closedir $dh;
3609	return $count;
3610}
3611
3612# args: 1 - the string to get piece of
3613# 2 - the position in string, starting at 1 for 0 index.
3614# 3 - the separator, default is ' '
3615sub get_piece {
3616	eval $start if $b_log;
3617	my ($string, $num, $sep) = @_;
3618	$num--;
3619	$sep ||= '\s+';
3620	$string =~ s/^\s+|\s+$//g;
3621	my @temp = split(/$sep/, $string);
3622	eval $end if $b_log;
3623	if (exists $temp[$num]){
3624		$temp[$num] =~ s/,//g;
3625		return $temp[$num];
3626	}
3627}
3628
3629# arg: 1 - command to turn into an array; 2 - optional: splitter
3630# 3 - optionsl, strip and clean data
3631# similar to reader() except this creates an array of data
3632# by lines from the command arg
3633sub grabber {
3634	eval $start if $b_log;
3635	my ($cmd,$split,$strip) = @_;
3636	$split ||= "\n";
3637	my @rows;
3638	if ($strip){
3639		for (split(/$split/, qx($cmd))){
3640			next if /^\s*(#|$)/;
3641			$_ =~ s/^\s+|\s+$//g;
3642			push(@rows,$_);
3643		}
3644	}
3645	else {
3646		@rows = split(/$split/, qx($cmd));
3647	}
3648	eval $end if $b_log;
3649	return @rows;
3650}
3651
3652# args: 1 - string value to glob
3653sub globber {
3654	eval $start if $b_log;
3655	my @files = <$_[0]>;
3656	eval $end if $b_log;
3657	return @files;
3658}
3659
3660# arg MUST be quoted when inserted, otherwise perl takes it for a hex number
3661sub is_hex {
3662	return (defined $_[0] && $_[0] =~ /^0x/) ? 1 : 0;
3663}
3664
3665## NOTE: for perl pre 5.012 length(undef) returns warning
3666# receives string, returns boolean 1 if integer
3667sub is_int {
3668	return 1 if (defined $_[0] && length($_[0]) && length($_[0]) == ($_[0] =~ tr/0123456789//));
3669}
3670
3671# receives string, returns boolean 1 if numeric. tr/// is 4x faster than regex
3672sub is_numeric {
3673	return 1 if (defined $_[0] && ($_[0] =~ tr/0123456789//) >= 1 &&
3674	length($_[0]) == ($_[0] =~ tr/0123456789.//) && ($_[0] =~ tr/.//) <= 1);
3675}
3676
3677# gets array ref, which may be undefined, plus join string
3678# this helps avoid debugger print errors when we are printing arrays
3679# which we don't know are defined or not null.
3680# args: 1 - array ref; 2 - join string; 3 - default value, optional
3681sub joiner {
3682	my ($arr,$join,$default) = @_;
3683	$default ||= '';
3684	my $string = '';
3685	foreach (@$arr){
3686		if (defined $_){
3687			$string .= $_ . $join;
3688		}
3689		else {
3690			$string .= $default . $join;
3691		}
3692	}
3693	return $string;
3694}
3695
3696# gets directory file list
3697sub lister {
3698	return if ! -d $_[0];
3699	opendir my $dir, $_[0] or return;
3700	my @list = readdir $dir;
3701	@list = grep {!/^(\.|\.\.)$/} @list if @list;
3702	closedir $dir;
3703	return @list;
3704}
3705
3706# returns array of: 0: program print name 1: program version
3707# args: 1: program values id  2: program version string
3708# 3: $extra level. Note that StartClient runs BEFORE -x levels are set!
3709# Only use this function when you only need the name/version data returned
3710sub program_data {
3711	eval $start if $b_log;
3712	my ($values_id,$version_id,$level) = @_;
3713	my (@data,$path,@program_data);
3714	$level = 0 if !$level;
3715	# print "val_id: $values_id ver_id:$version_id lev:$level ex:$extra\n";
3716	$version_id = $values_id if !$version_id;
3717	@data = program_values($values_id);
3718	if ($data[3]){
3719		$program_data[0] = $data[3];
3720		# programs that have no version method return 0 0 for index 1 and 2
3721		if ($extra >= $level && $data[1] && $data[2]){
3722			$program_data[1] = program_version($version_id,$data[0],
3723			$data[1],$data[2],$data[5],$data[6],$data[7],$data[8]);
3724		}
3725	}
3726	$program_data[0] ||= '';
3727	$program_data[1] ||= '';
3728	eval $end if $b_log;
3729	return @program_data;
3730}
3731
3732# It's almost 1000 times slower to load these each time program_values is called!!
3733sub set_program_values {
3734	%program_values = (
3735	## Clients ##
3736	'bitchx' => ['bitchx',2,'','BitchX',1,0,0,'',''],# special
3737	'finch' => ['finch',2,'-v','Finch',1,1,0,'',''],
3738	'gaim' => ['[0-9.]+',2,'-v','Gaim',0,1,0,'',''],
3739	'ircii' => ['[0-9.]+',3,'-v','ircII',1,1,0,'',''],
3740	'irssi' => ['irssi',2,'-v','Irssi',1,1,0,'',''],
3741	'irssi-text' => ['irssi',2,'-v','Irssi',1,1,0,'',''],
3742	'konversation' => ['konversation',2,'-v','Konversation',0,0,0,'',''],
3743	'kopete' => ['Kopete',2,'-v','Kopete',0,0,0,'',''],
3744	'kvirc' => ['[0-9.]+',2,'-v','KVIrc',0,0,1,'',''], # special
3745	'pidgin' => ['[0-9.]+',2,'-v','Pidgin',0,1,0,'',''],
3746	'quassel' => ['',1,'-v','Quassel [M]',0,0,0,'',''], # special
3747	'quasselclient' => ['',1,'-v','Quassel',0,0,0,'',''],# special
3748	'quasselcore' => ['',1,'-v','Quassel (core)',0,0,0,'',''],# special
3749	'gribble' => ['^Supybot',2,'--version','Gribble',1,0,0,'',''],# special
3750	'limnoria' => ['^Supybot',2,'--version','Limnoria',1,0,0,'',''],# special
3751	'supybot' => ['^Supybot',2,'--version','Supybot',1,0,0,'',''],# special
3752	'weechat' => ['[0-9.]+',1,'-v','WeeChat',1,0,0,'',''],
3753	'weechat-curses' => ['[0-9.]+',1,'-v','WeeChat',1,0,0,'',''],
3754	'xchat-gnome' => ['[0-9.]+',2,'-v','X-Chat-Gnome',1,1,0,'',''],
3755	'xchat' => ['[0-9.]+',2,'-v','X-Chat',1,1,0,'',''],
3756	## Desktops / wm / compositors ##
3757	'2bwm' => ['^2bwm',0,'0','2bWM',0,1,0,'',''], # unverified/based on mcwm
3758	'3dwm' => ['^3dwm',0,'0','3Dwm',0,1,0,'',''], # unverified
3759	'5dwm' => ['^5dwm',0,'0','5Dwm',0,1,0,'',''], # unverified
3760	'9wm' => ['^9wm',3,'-version','9wm',0,1,0,'',''],
3761	'aewm' => ['^aewm',3,'--version','aewm',0,1,0,'',''],
3762	'aewm++' => ['^Version:',2,'-version','aewm++',0,1,0,'',''],
3763	'afterstep' => ['^afterstep',3,'--version','AfterStep',0,1,0,'',''],
3764	'amiwm' => ['^amiwm',0,'0','AmiWM',0,1,0,'',''], # no version
3765	'antiwm' => ['^antiwm',0,'0','AntiWM',0,1,0,'',''], # no version known
3766	'asc' => ['^asc',0,'0','asc',0,1,0,'',''],
3767	'awesome' => ['^awesome',2,'--version','awesome',0,1,0,'',''],
3768	'beryl' => ['^beryl',0,'0','Beryl',0,1,0,'',''], # unverified; legacy
3769	'blackbox' => ['^Blackbox',2,'--version','Blackbox',0,1,0,'',''],
3770	'bspwm' => ['^\S',1,'-v','bspwm',0,1,0,'',''],
3771	'budgie-desktop' => ['^budgie-desktop',2,'--version','Budgie',0,1,0,'',''],
3772	'budgie-wm' => ['^budgie',0,'0','budgie-wm',0,1,0,'',''],
3773	'cagebreak' => ['^Cagebreak',3,'-v','Cagebreak',0,1,0,'',''],
3774	'calmwm' => ['^calmwm',0,'0','CalmWM',0,1,0,'',''], # unverified
3775	'catwm' => ['^catwm',0,'0','catwm',0,1,0,'',''], # unverified
3776	'cinnamon' => ['^cinnamon',2,'--version','Cinnamon',0,1,0,'',''],
3777	'clfswm' => ['^clsfwm',0,'0','clfswm',0,1,0,'',''], # no version
3778	'compiz' => ['^compiz',2,'--version','Compiz',0,1,0,'',''],
3779	'compton' => ['^\d',1,'--version','Compton',0,1,0,'',''],
3780	'ctwm' => ['^\S',1,'-version','ctwm',0,1,0,'',''],
3781	'cwm' => ['^cwm',0,'0','CWM',0,1,0,'',''], # no version
3782	'dcompmgr' => ['^dcompmgr',0,'0','dcompmgr',0,1,0,'',''], # unverified
3783	'deepin' => ['^Version',2,'file','Deepin',0,100,'=','','/etc/deepin-version'], # special
3784	'deepin-metacity' => ['^metacity',2,'--version','Deepin-Metacity',0,1,0,'',''],
3785	'deepin-mutter' => ['^mutter',2,'--version','Deepin-Mutter',0,1,0,'',''],
3786	'deepin-wm' => ['^gala',0,'0','DeepinWM',0,1,0,'',''], # no version
3787	'dwc' => ['^dwc',0,'0','dwc',0,1,0,'',''], # unverified
3788	'dwm' => ['^dwm',1,'-v','dwm',0,1,1,'^dwm-',''],
3789	'echinus' => ['^echinus',1,'-v','echinus',0,1,1,'',''], # echinus-0.4.9 (c)...
3790	# only listed here for compositor values, version data comes from xprop
3791	'enlightenment' => ['^enlightenment',0,'0','enlightenment',0,1,0,'',''], # no version, yet?
3792	'evilwm' => ['evilwm',3,'-V','evilwm',0,1,0,'',''],# might use full path in match
3793	'fireplace' => ['^fireplace',0,'0','fireplace',0,1,0,'',''], # unverified
3794	'fluxbox' => ['^fluxbox',2,'-v','Fluxbox',0,1,0,'',''],
3795	'flwm' => ['^flwm',0,'0','FLWM',0,0,1,'',''], # no version
3796	# openbsd changed: version string: [FVWM[[main] Fvwm.. sigh, and outputs to stderr. Why?
3797	'fvwm' => ['^fvwm',2,'-version','FVWM',0,1,0,'',''],
3798	'fvwm1' => ['^Fvwm',3,'-version','FVWM1',0,1,1,'',''],
3799	'fvwm2' => ['^fvwm',2,'--version','FVWM2',0,1,0,'',''],
3800	'fvwm3' => ['^fvwm',2,'--version','FVWM3',0,1,0,'',''],
3801	'fvwm95' => ['^fvwm',2,'--version','FVWM95',0,1,1,'',''],
3802	'fvwm-crystal' => ['^fvwm',2,'--version','FVWM-Crystal',0,0,0,'',''], # for print name fvwm
3803	'gala' => ['^gala',0,'0','gala',0,1,0,'',''], # pantheon wm: super slow result, 2, '--version' works?
3804	'glass' => ['^glass',3,'-v','Glass',0,1,0,'',''],
3805	'gnome' => ['^gnome',3,'--version','GNOME',0,1,0,'',''], # no version, print name
3806	'gnome-about' => ['^gnome',3,'--version','GNOME',0,1,0,'',''],
3807	'gnome-shell' => ['^gnome',3,'--version','gnome-shell',0,1,0,'',''],
3808	'grefson' => ['^grefson',0,'0','grefson',0,1,0,'',''], # unverified
3809	'hackedbox' => ['^hackedbox',2,'-version','HackedBox',0,1,0,'',''], # unverified, assume blackbox
3810	# note, herbstluftwm when launched with full path returns full path in version string
3811	'herbstluftwm' => ['herbstluftwm',2,'--version','herbstluftwm',0,1,0,'',''],
3812	'i3' => ['^i3',3,'--version','i3',0,1,0,'',''],
3813	'icewm' => ['^icewm',2,'--version','IceWM',0,1,0,'',''],
3814	'instantwm' => ['^instantwm',1,'-v','instantWM',0,1,1,'^instantwm-?(instantos-?)?',''],
3815	'ion3' => ['^ion3',0,'--version','Ion3',0,1,0,'',''], # unverified; also shell called ion
3816	'jbwm' => ['jbwm',3,'-v','JBWM',0,1,0,'',''], # might use full path in match
3817	'jwm' => ['^jwm',2,'--version','JWM',0,1,0,'',''],
3818	'kded' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''],
3819	'kded1' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''],
3820	'kded2' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''],
3821	'kded3' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''],
3822	'kded4' => ['^KDE( Development Platform)?:',2,'--version','KDE',0,1,0,'\sDevelopment Platform',''],
3823	'ksmcon' => ['^ksmcon',0,'0','ksmcon',0,1,0,'',''],# no version
3824	'kwin' => ['^kwin',0,'0','kwin',0,1,0,'',''],# no version
3825	'kwin_wayland' => ['^kwin_wayland',0,'0','kwin_wayland',0,1,0,'',''],# no version
3826	'kwin_x11' => ['^kwin_x11',0,'0','kwin_x11',0,1,0,'',''],# no version
3827	'larswm' => ['^larswm',2,'-v','larswm',0,1,1,'',''],
3828	'leftwm' => ['^leftwm',0,'0','LeftWM',0,1,0,'',''],# no version, in CHANGELOG
3829	'liri' => ['^liri',0,'0','liri',0,1,0,'',''],
3830	'lumina-desktop' => ['^\S',1,'--version','Lumina',0,1,1,'',''],
3831	'lwm' => ['^lwm',0,'0','lwm',0,1,0,'',''], # no version
3832	'lxpanel' => ['^lxpanel',2,'--version','LXDE',0,1,0,'',''],
3833	# command: lxqt-panel
3834	'lxqt-panel' => ['^lxqt-panel',2,'--version','LXQt',0,1,0,'',''],
3835	'lxqt-variant' => ['^lxqt-panel',0,'0','LXQt-Variant',0,1,0,'',''],
3836	'lxsession' => ['^lxsession',0,'0','lxsession',0,1,0,'',''],
3837	'manokwari' => ['^manokwari',0,'0','Manokwari',0,1,0,'',''],
3838	'marco' => ['^marco',2,'--version','marco',0,1,0,'',''],
3839	'matchbox' => ['^matchbox',0,'0','Matchbox',0,1,0,'',''],
3840	'matchbox-window-manager' => ['^matchbox',2,'--help','Matchbox',0,0,0,'',''],
3841	'mate-about' => ['^MATE[[:space:]]DESKTOP',-1,'--version','MATE',0,1,0,'',''],
3842	# note, mate-session when launched with full path returns full path in version string
3843	'mate-session' => ['mate-session',-1,'--version','MATE',0,1,0,'',''],
3844	'mcwm' => ['^mcwm',0,'0','mcwm',0,1,0,'',''], # unverified/see 2bwm
3845	'metacity' => ['^metacity',2,'--version','Metacity',0,1,0,'',''],
3846	'metisse' => ['^metisse',0,'0','metisse',0,1,0,'',''],
3847	'mini' => ['^Mini',5,'--version','Mini',0,1,0,'',''],
3848	'mir' => ['^mir',0,'0','mir',0,1,0,'',''],# unverified
3849	'moblin' => ['^moblin',0,'0','moblin',0,1,0,'',''],# unverified
3850	'monsterwm' => ['^monsterwm',0,'0','monsterwm',0,1,0,'',''],# unverified
3851	'motorcar' => ['^motorcar',0,'0','motorcar',0,1,0,'',''],# unverified
3852	'muffin' => ['^muffin',2,'--version','Muffin',0,1,0,'',''],
3853	'musca' => ['^musca',0,'-v','Musca',0,1,0,'',''], # unverified
3854	'mutter' => ['^mutter',2,'--version','Mutter',0,1,0,'',''],
3855	'mwm' => ['^mwm',0,'0','MWM',0,1,0,'',''],# no version
3856	'nawm' => ['^nawm',0,'0','nawm',0,1,0,'',''],# unverified
3857	'notion' => ['^.',1,'--version','Notion',0,1,0,'',''],
3858	'openbox' => ['^openbox',2,'--version','Openbox',0,1,0,'',''],
3859	'orbital' => ['^orbital',0,'0','orbital',0,1,0,'',''],# unverified
3860	'pantheon' => ['^pantheon',0,'0','Pantheon',0,1,0,'',''],# no version
3861	'papyros' => ['^papyros',0,'0','papyros',0,1,0,'',''],# no version
3862	'pekwm' => ['^pekwm',3,'--version','PekWM',0,1,0,'',''],
3863	'penrose' => ['^penrose',0,'0','Penrose',0,1,0,'',''],# no version?
3864	'perceptia' => ['^perceptia',0,'0','perceptia',0,1,0,'',''],
3865	'picom' => ['^\S',1,'--version','Picom',0,1,0,'^v',''],
3866	'plasmashell' => ['^plasmashell',2,'--version','KDE Plasma',0,1,0,'',''],
3867	'qtile' => ['^',1,'--version','Qtile',0,1,0,'',''],
3868	'qvwm' => ['^qvwm',0,'0','qvwm',0,1,0,'',''], # unverified
3869	'razor-session' => ['^razor',0,'0','Razor-Qt',0,1,0,'',''],
3870	'ratpoison' => ['^ratpoison',2,'--version','Ratpoison',0,1,0,'',''],
3871	'rustland' => ['^rustland',0,'0','rustland',0,1,0,'',''], # unverified
3872	'sawfish' => ['^sawfish',3,'--version','Sawfish',0,1,0,'',''],
3873	'scrotwm' => ['^scrotwm.*welcome.*',5,'-v','scrotwm',0,1,1,'',''],
3874	'sommelier' => ['^sommelier',0,'0','sommelier',0,1,0,'',''], # unverified
3875	'snapwm' => ['^snapwm',0,'0','snapwm',0,1,0,'',''], # unverified
3876	'spectrwm' => ['^spectrwm.*welcome.*wm',5,'-v','spectrwm',0,1,1,'',''],
3877	# out of stump, 2 --version, but in tries to start new wm instance endless hang
3878	'stumpwm' => ['^SBCL',0,'--version','StumpWM',0,1,0,'',''], # hangs when run in wm
3879	'sway' => ['^sway',3,'-v','sway',0,1,0,'',''],
3880	'swc' => ['^swc',0,'0','swc',0,1,0,'',''], # unverified
3881	'tinywm' => ['^tinywm',0,'0','TinyWM',0,1,0,'',''], # no version
3882	'tvtwm' => ['^tvtwm',0,'0','tvtwm',0,1,0,'',''], # unverified
3883	'twin' => ['^Twin:',2,'--version','Twin',0,0,0,'',''],
3884	'twm' => ['^twm',0,'0','TWM',0,1,0,'',''], # no version
3885	'ukui' => ['^ukui-session',2,'--version','UKUI',0,1,0,'',''],
3886	'ukwm' => ['^ukwm',2,'--version','ukwm',0,1,0,'',''],
3887	'unagi' => ['^\S',1,'--version','unagi',0,1,0,'',''],
3888	'unity' => ['^unity',2,'--version','Unity',0,1,0,'',''],
3889	'unity-system-compositor' => ['^unity-system-compositor',2,'--version',
3890	'unity-system-compositor (mir)',0,0,0,'',''],
3891	'uwm' => ['^uwm',0,'0','UWM',0,1,0,'',''], # unverified
3892	'wavy' => ['^wavy',0,'0','wavy',0,1,0,'',''], # unverified
3893	'waycooler' => ['^way',3,'--version','way-cooler',0,1,0,'',''],
3894	'way-cooler' => ['^way',3,'--version','way-cooler',0,1,0,'',''],
3895	'wayfire' => ['^way',0,'0','wayfire',0,1,0,'',''], # unverified
3896	'wayhouse' => ['^wayhouse',0,'0','wayhouse',0,1,0,'',''], # unverified
3897	'westford' => ['^westford',0,'0','westford',0,1,0,'',''], # unverified
3898	'weston' => ['^weston',0,'0','weston',0,1,0,'',''], # unverified
3899	'windowlab' => ['^windowlab',2,'-about','WindowLab',0,1,0,'',''],
3900	'wingo' => ['^wingo',0,'0','Wingo',0,1,0,'',''], # unverified
3901	'wm2' => ['^wm2',0,'0','wm2',0,1,0,'',''], # no version
3902	'wmaker' => ['^Window[[:space:]]*Maker',-1,'--version','WindowMaker',0,1,0,'',''],
3903	'wmfs' => ['^wmfs',0,'0','WMFS',0,1,0,'',''], # unverified
3904	'wmfs2' => ['^wmfs',0,'0','WMFS',0,1,0,'',''], # unverified
3905	'wmii' => ['^wmii',1,'-v','wmii',0,1,0,'^wmii[234]?-',''], # wmii is wmii3
3906	'wmii2' => ['^wmii2',1,'--version','wmii2',0,1,0,'^wmii[234]?-',''],
3907	'wmx' => ['^wmx',0,'0','wmx',0,1,0,'',''], # no version
3908	'xcompmgr' => ['^xcompmgr',0,'0','xcompmgr',0,1,0,'',''], # no version
3909	'xfce4-panel' => ['^xfce4-panel',2,'--version','Xfce',0,1,0,'',''],
3910	'xfce5-panel' => ['^xfce5-panel',2,'--version','Xfce',0,1,0,'',''],
3911	'xfdesktop' => ['xfdesktop[[:space:]]version',5,'--version','Xfce',0,1,0,'',''],
3912	# command: xfdesktop
3913	'xfdesktop-toolkit' => ['Built[[:space:]]with[[:space:]]GTK',4,'--version','Gtk',0,1,0,'',''],
3914	# '        This is xfwm4 version 4.16.1 (revision 5f61a84ad) for Xfce 4.16'
3915	'xfwm' => ['xfwm[3-8]? version',5,'--version','xfwm',0,1,0,'^^\s+',''],# unverified
3916	'xfwm4' => ['xfwm4? version',5,'--version','xfwm',0,1,0,'^^\s+',''],
3917	'xfwm5' => ['xfwm5? version',5,'--version','xfwm',0,1,0,'^^\s+',''], # unverified
3918	'xmonad' => ['^xmonad',2,'--version','XMonad',0,1,0,'',''],
3919	'yeahwm' => ['^yeahwm',0,'--version','YeahWM',0,1,0,'',''], # unverified
3920	## Toolkits ##
3921	'gtk-launch' => ['^\S',1,'--version','GTK',0,1,0,'',''],
3922	'qmake' => ['^^Using Qt version',4,'--version','Qt',0,0,0,'',''],
3923	'qtdiag' => ['^qt',2,'--version','Qt',0,1,0,'',''],
3924	## Display Managers (dm) ##
3925	'cdm' => ['^cdm',0,'0','CDM',0,1,0,'',''],
3926	'entrance' => ['^entrance',0,'0','Entrance',0,1,0,'',''],
3927	'gdm' => ['^gdm',2,'--version','GDM',0,1,0,'',''],
3928	'gdm3' => ['^gdm',2,'--version','GDM3',0,1,0,'',''],
3929	'kdm' => ['^kdm',0,'0','KDM',0,1,0,'',''],
3930	'kdm3' => ['^kdm',0,'0','KDM',0,1,0,'',''],
3931	'ldm' => ['^ldm',0,'0','LDM',0,1,0,'',''],
3932	'lightdm' => ['^lightdm',2,'--version','LightDM',0,1,1,'',''],
3933	'lxdm' => ['^lxdm',0,'0','LXDM',0,1,0,'',''],
3934	'ly' => ['^ly',3,'--version','Ly',0,1,0,'',''],
3935	'mdm' => ['^mdm',0,'0','MDM',0,1,0,'',''],
3936	'nodm' => ['^nodm',0,'0','nodm',0,1,0,'',''],
3937	'pcdm' => ['^pcdm',0,'0','PCDM',0,1,0,'',''],
3938	'sddm' => ['^sddm',0,'0','SDDM',0,1,0,'',''],
3939	'slim' => ['slim version',3,'-v','SLiM',0,1,0,'',''],
3940	'tdm' => ['^tdm',0,'0','TDM',0,1,0,'',''],
3941	'udm' => ['^udm',0,'0','udm',0,1,0,'',''],
3942	'wdm' => ['^wdm',0,'0','WINGs DM',0,1,0,'',''],
3943	'xdm' => ['^xdm',0,'0','XDM',0,1,0,'',''],
3944	'xenodm' => ['^xenodm',0,'0','xenodm',0,1,0,'',''],
3945	## Shells - not checked: ion, eshell ##
3946	## See ShellData::shell_test() for unhandled but known shells
3947	'ash' => ['',3,'pkg','ash',1,0,0,'',''], # special; dash precursor
3948	'bash' => ['^GNU[[:space:]]bash',4,'--version','Bash',1,1,0,'',''],
3949	'busybox' => ['^busybox',0,'0','BusyBox',1,0,0,'',''], # unverified, hush/ash likely
3950	'cicada' => ['^\s*version',2,'cmd','cicada',1,1,0,'',''], # special
3951	'csh' => ['^tcsh',2,'--version','csh',1,1,0,'',''], # mapped to tcsh often
3952	'dash' => ['',3,'pkg','DASH',1,0,0,'',''], # no version, pkg query
3953	'elvish' => ['^\S',1,'--version','Elvish',1,0,0,'',''],
3954	'fish' => ['^fish',3,'--version','fish',1,0,0,'',''],
3955	'fizsh' => ['^fizsh',3,'--version','FIZSH',1,0,0,'',''],
3956	# ksh/lksh/loksh/mksh/posh//pdksh need to print their own $VERSION info
3957	'ksh' => ['^\S',1,'cmd','ksh',1,0,0,'^(Version|.*KSH)\s*',''], # special
3958	'ksh93' => ['^\S',1,'cmd','ksh93',1,0,0,'^(Version|.*KSH)\s*',''], # special
3959	'lksh' => ['^\S',1,'cmd','lksh',1,0,0,'^.*KSH\s*',''], # special
3960	'loksh' => ['^\S',1,'cmd','loksh',1,0,0,'^.*KSH\s*',''], # special
3961	'mksh' => ['^\S',1,'cmd','mksh',1,0,0,'^.*KSH\s*',''], # special
3962	'nash' => ['^nash',0,'0','Nash',1,0,0,'',''], # unverified; rc based [no version]
3963	'oh' => ['^oh',0,'0','Oh',1,0,0,'',''], # no version yet
3964	'oil' => ['^Oil',3,'--version','Oil',1,1,0,'',''], # could use cmd $OIL_SHELL
3965	'osh' => ['^osh',3,'--version','OSH',1,1,0,'',''], # precursor of oil
3966	'pdksh' => ['^\S',1,'cmd','pdksh',1,0,0,'^.*KSH\s*',''], # special, in  ksh family
3967	'posh' => ['^\S',1,'cmd','posh',1,0,0,'',''], # special, in ksh family
3968	'tcsh' => ['^tcsh',2,'--version','tcsh',1,1,0,'',''], # enhanced csh
3969	'xonsh' => ['^xonsh',1,'--version','xonsh',1,0,0,'^xonsh[\/-]',''],
3970	'yash' => ['^Y',5,'--version','yash',1,0,0,'',''],
3971	'zsh' => ['^zsh',2,'--version','Zsh',1,0,0,'',''],
3972	## Tools ##
3973	'clang' => ['clang',3,'--version','Clang',1,0,0,'',''],
3974	'gcc' => ['^gcc',3,'--version','GCC',1,0,0,'',''],
3975	'gcc-apple' => ['Apple[[:space:]]LLVM',2,'--version','LLVM',1,0,0,'',''],
3976	'sudo' => ['^Sudo',3,'-V','Sudo',1,1,0,'',''], # sudo pre 1.7 does not have --version
3977	);
3978}
3979
3980# returns array of:
3981# 0 - match string; 1 - search number; 2 - version string [alt: file];
3982# 3 - Print name; 4 - console 0/1;
3983# 5 - 0/1 exit version loop at 1 [alt: if version=file replace value with \s];
3984# 6 - 0/1 write to stderr [alt: if version=file, path for file]
3985# 7 - replace regex for further cleanup; 8 - extra data
3986# note: setting index 1 or 2 to 0 will trip flags to not do version
3987# arg: 1 - program lower case name
3988sub program_values {
3989	my ($app) = @_;
3990	my (@program_data);
3991	set_program_values() if !%program_values;
3992	if (defined $program_values{$app}){
3993		@program_data = @{$program_values{$app}};
3994	}
3995	# my $debug = Dumper \@program_data;
3996	log_data('dump',"Program Data",\@program_data) if $b_log;
3997	return @program_data;
3998}
3999
4000# args: 1 - desktop/app command for --version; 2 - search string;
4001# 3 - space print number; 4 - [optional] version arg: -v, version, etc
4002# 5 - [optional] exit first find 0/1; 6 - [optional] 0/1 stderr output
4003# 7 - replace regex; 8 - extra data
4004sub program_version {
4005	eval $start if $b_log;
4006	my ($app,$search,$num,$version,$exit,$stderr,$replace,$extra) = @_;
4007	my ($b_no_space,$cmd,$line,$output);
4008	my $version_nu = '';
4009	my $count = 0;
4010	my $app_name = $app;
4011	$app_name =~ s%^.*/%%;
4012	# print "app: $app :: appname: $app_name\n";
4013	$exit ||= 100; # basically don't exit ever
4014	$version ||= '--version';
4015	# adjust to array index, not human readable
4016	$num-- if (defined $num && $num > 0);
4017	# konvi in particular doesn't like using $ENV{'PATH'} as set, so we need
4018	# to always assign the full path if it hasn't already been done
4019	if ($version ne 'file' && $app !~ /^\//){
4020		if (my $program = check_program($app)){
4021			$app = $program;
4022		}
4023		else {
4024			log_data('data',"$app not found in path.") if $b_log;
4025			return 0;
4026		}
4027	}
4028	if ($version eq 'file'){
4029		return 0 unless $extra && -r $extra;
4030		my @data = reader($extra,'strip');
4031		@data = map {s/$stderr/ /;$_} @data if $stderr; # $stderr is the splitter
4032		$output = join("\n", @data);
4033		$cmd = '';
4034	}
4035	# These will mostly be shells that require running the shell command -c to get info data
4036	elsif ($version eq 'cmd'){
4037		($cmd,$b_no_space) = program_version_cmd($app,$app_name,$extra);
4038		return 0 if !$cmd;
4039	}
4040	# slow: use pkg manager to get version, avoid unless you really want version
4041	elsif ($version eq 'pkg'){
4042		($cmd,$search) = program_version_pkg($app_name);
4043		return 0 if !$cmd;
4044	}
4045	# note, some wm/apps send version info to stderr instead of stdout
4046	elsif ($stderr){
4047		$cmd = "$app $version 2>&1";
4048	}
4049	else {
4050		$cmd = "$app $version 2>/dev/null";
4051	}
4052	log_data('data',"version: $version num: $num search: $search command: $cmd") if $b_log;
4053	# special case, in rare instances version comes from file
4054	if ($version ne 'file'){
4055		$output = qx($cmd);
4056		log_data('data',"output: $output") if $b_log;
4057	}
4058	# print "cmd: $cmd\noutput:\n$output\n";
4059	# sample: dwm-5.8.2, ©.. etc, why no space? who knows. Also get rid of v in number string
4060	# xfce, and other, output has , in it, so dump all commas and parentheses
4061	if ($output){
4062		open(my $ch, '<', \$output) or error_handler('open-data',"$cmd", "$!");
4063		while (<$ch>){
4064			#chomp;
4065			last if $count > $exit;
4066			if ($_ =~ /$search/i){
4067				$_ = trimmer($_);
4068				# print "loop: $_ :: num: $num\n";
4069				$_ =~ s/$replace//i if $replace;
4070				$_ =~ s/\s/_/g if $b_no_space; # needed for some items with version > 1 word
4071				my @data = split(/\s+/, $_);
4072				$version_nu = $data[$num];
4073				last if ! defined $version_nu;
4074				# some distros add their distro name before the version data, which
4075				# breaks version detection. A quick fix attempt is to just add 1 to $num
4076				# to get the next value.
4077				$version_nu = $data[$num+1] if $data[$num+1] && $version_nu =~ /version/i;
4078				$version_nu =~ s/(\([^)]+\)|,|"|\||\(|\))//g if $version_nu;
4079				# trim off leading v but only when followed by a number
4080				$version_nu =~ s/^v([0-9])/$1/i if $version_nu;
4081				# print "$version_nu\n";
4082				last;
4083			}
4084			$count++;
4085		}
4086		close $ch if $ch;
4087	}
4088	log_data('data',"Program version: $version_nu") if $b_log;
4089	eval $end if $b_log;
4090	return $version_nu;
4091}
4092# print program_version('bash', 'bash', 4) . "\n";
4093
4094# returns ($cmdd, $b_no_space)
4095# ksh: Version JM 93t+ 2010-03-05 [OR] Version A 2020.0.0
4096# mksh: @(#)MIRBSD KSH R56 2018/03/09; lksh/pdksh: @(#)LEGACY KSH R56 2018/03/09
4097# loksh: @(#)PD KSH v5.2.14 99/07/13.2; posh: 0.13.2
4098sub program_version_cmd {
4099	eval $start if $b_log;
4100	my ($app,$app_name,$extra) = @_;
4101	my @data = ('',0);
4102	if ($app_name eq 'cicada'){
4103		$data[0] = $app . ' -c "' . $extra . '" 2>/dev/null';}
4104	elsif ($app_name =~ /^(|l|lo|m|pd)ksh(93)?$/){
4105		$data[0] = $app . ' -c \'printf %s "$KSH_VERSION"\' 2>/dev/null';
4106		$data[1] = 1;}
4107	elsif ($app_name eq 'posh'){
4108		$data[0] =  $app . ' -c \'printf %s "$POSH_VERSION"\' 2>/dev/null'}
4109	# print "$data[0] :: $data[1]\n";
4110	eval $end if $b_log;
4111	return @data;
4112}
4113
4114# returns $cmd, $search
4115sub program_version_pkg  {
4116	eval $start if $b_log;
4117	my ($app) = @_;
4118	my ($program,@data);
4119	# note: version $num is 3 in dpkg-query/pacman/rpm, which is convenient
4120	if ($program = check_program('dpkg-query')){
4121		$data[0] = "$program -W -f='\${Package}\tversion\t\${Version}\n' $app 2>/dev/null";
4122		$data[1] = "^$app\\b";
4123	}
4124	elsif ($program = check_program('pacman')){
4125		$data[0] = "$program -Q --info $app 2>/dev/null";
4126		$data[1] = '^Version';
4127	}
4128	elsif ($program = check_program('rpm')){
4129		$data[0] = "$program -qi --nodigest --nosignature $app 2>/dev/null";
4130		$data[1] = '^Version';
4131	}
4132	# print "$data[0] :: $data[1]\n";
4133	eval $end if $b_log;
4134	return @data;
4135}
4136
4137# arg: 1 - full file path, returns array of file lines.
4138# 2 - optionsl, strip and clean data
4139# 3 - optional, return specific index, if it exists, else undef
4140# note: chomp has to chomp the entire action, not just <$fh>
4141sub reader {
4142	eval $start if $b_log;
4143	my ($file,$strip,$index) = @_;
4144	return if !$file || ! -r $file; # not all OS respect -r tests!!
4145	my ($error,@rows);
4146	open(my $fh, '<', $file) or $error = $!; # $fh always non null, even on error
4147	if ($error){
4148		error_handler('open', $file, $error);
4149	}
4150	else {
4151		chomp(@rows = <$fh>);
4152		close $fh;
4153		if (@rows && $strip){
4154			my @temp;
4155			for (@rows){
4156				next if /^\s*(#|$)/;
4157				$_ =~ s/^\s+|\s+$//g;
4158				push(@temp,$_);
4159			}
4160			@rows = @temp;
4161		}
4162	}
4163	eval $end if $b_log;
4164	# note: returns undef scalar value if $rows[index] does not exist
4165	return (defined $index) ? $rows[$index] : @rows;
4166}
4167
4168# args: 1 - the file to create if not exists
4169sub toucher {
4170	my $file = shift;
4171	if (! -e $file){
4172		open(my $fh, '>', $file) or error_handler('create', $file, $!);
4173	}
4174}
4175
4176# calling it trimmer to avoid conflicts with existing trim stuff
4177# arg: 1 - string to be right left trimmed. Also slices off \n so no chomp needed
4178# this thing is super fast, no need to log its times etc, 0.0001 seconds or less
4179sub trimmer {
4180	# eval $start if $b_log;
4181	my ($str) = @_;
4182	$str =~ s/^\s+|\s+$|\n$//g;
4183	# eval $end if $b_log;
4184	return $str;
4185}
4186
4187# args: 1 - array, by ref, modifying by ref
4188# send array, assign to hash, changed array by reference, uniq values only.
4189sub uniq {
4190	my %seen;
4191	@{$_[0]} = grep !$seen{$_}++, @{$_[0]};
4192}
4193
4194# arg: 1 file full  path to write to; 2 - array ref or scalar of data to write.
4195# note: turning off strict refs so we can pass it a scalar or an array reference.
4196sub writer {
4197	my ($path, $content) = @_;
4198	my ($contents);
4199	no strict 'refs';
4200	# print Dumper $content, "\n";
4201	if (ref $content eq 'ARRAY'){
4202		$contents = join("\n", @$content); # or die "failed with error $!";
4203	}
4204	else {
4205		$contents = $content;
4206	}
4207	open(my $fh, ">", $path) or error_handler('open',"$path", "$!");
4208	print $fh $contents;
4209	close $fh;
4210}
4211
4212#### -------------------------------------------------------------------
4213#### UPDATER
4214#### -------------------------------------------------------------------
4215
4216# arg 1: type to return
4217sub get_defaults {
4218	my ($type) = @_;
4219	my %defaults = (
4220	'ftp-upload' => 'ftp.smxi.org/incoming',
4221	'inxi-branch-1' => 'https://github.com/smxi/inxi/raw/one/',
4222	'inxi-branch-2' => 'https://github.com/smxi/inxi/raw/two/',
4223	'inxi-dev' => 'https://smxi.org/in/',
4224	'inxi-main' => 'https://github.com/smxi/inxi/raw/master/',
4225	'inxi-pinxi' => 'https://github.com/smxi/inxi/raw/inxi-perl/',
4226	'inxi-man' => "https://smxi.org/in/$self_name.1",
4227	'inxi-man-gh' => "https://github.com/smxi/inxi/raw/master/$self_name.1",
4228	'pinxi-man' => "https://smxi.org/in/$self_name.1",
4229	'pinxi-man-gh' => "https://github.com/smxi/inxi/raw/inxi-perl/$self_name.1",
4230	);
4231	if (exists $defaults{$type}){
4232		return $defaults{$type};
4233	}
4234	else {
4235		error_handler('bad-arg-int', $type);
4236	}
4237}
4238
4239# args: 1 - download url, not including file name; 2 - string to print out
4240# 3 - update type option
4241# note that 1 must end in / to properly construct the url path
4242sub update_me {
4243	eval $start if $b_log;
4244	my ($self_download,$download_id) = @_;
4245	my $downloader_error=1;
4246	my $file_contents='';
4247	my $output = '';
4248	$self_path =~ s/\/$//; # dirname sometimes ends with /, sometimes not
4249	$self_download =~ s/\/$//; # dirname sometimes ends with /, sometimes not
4250	my $full_self_path = "$self_path/$self_name";
4251
4252	if ($b_irc){
4253		error_handler('not-in-irc', "-U/--update")
4254	}
4255	if (! -w $full_self_path){
4256		error_handler('not-writable', "$self_name", '');
4257	}
4258	$output .= "Starting $self_name self updater.\n";
4259	$output .= "Using $dl{'dl'} as downloader.\n";
4260	$output .= "Currently running $self_name version number: $self_version\n";
4261	$output .= "Current version patch number: $self_patch\n";
4262	$output .= "Current version release date: $self_date\n";
4263	$output .= "Updating $self_name in $self_path using $download_id as download source...\n";
4264	print $output;
4265	$output = '';
4266	$self_download = "$self_download/$self_name";
4267	$file_contents = download_file('stdout', $self_download);
4268
4269	# then do the actual download
4270	if ($file_contents){
4271		# make sure the whole file got downloaded and is in the variable
4272		print "Validating downloaded data...\n";
4273		if ($file_contents =~ /###\*\*EOF\*\*###/){
4274			open(my $fh, '>', $full_self_path);
4275			print $fh $file_contents or error_handler('write', $full_self_path, "$!");
4276			close $fh;
4277			qx(chmod +x '$self_path/$self_name');
4278			set_version_data();
4279			$output .= "Successfully updated to $download_id version: $self_version\n";
4280			$output .= "New $download_id version patch number: $self_patch\n";
4281			$output .= "New $download_id version release date: $self_date\n";
4282			$output .= "To run the new version, just start $self_name again.\n";
4283			$output .= "$line3\n";
4284			print $output;
4285			$output = '';
4286			if ($use{'man'}){
4287				update_man($download_id);
4288			}
4289			else {
4290				print "Skipping man download because branch version is being used.\n";
4291			}
4292			exit 0;
4293		}
4294		else {
4295			error_handler('file-corrupt', "$self_name");
4296		}
4297	}
4298	# now run the error handlers on any downloader failure
4299	else {
4300		error_handler('download-error', $self_download, $download_id);
4301	}
4302	eval $end if $b_log;
4303}
4304
4305sub update_man {
4306	eval $start if $b_log;
4307	my ($download_id) = @_;
4308	my $man_file_location = set_man_location();
4309	my $man_file_path = "$man_file_location/$self_name.1" ;
4310	my ($file_contents,$man_file_url,$output,$program) = ('','','','');
4311	print "Starting download of man page file now.\n";
4312	if (! -d $man_file_location){
4313		print "The required man directory was not detected on your system.\n";
4314		print "Unable to continue: $man_file_location\n";
4315		return 0;
4316	}
4317	if (! -w $man_file_location){
4318		print "Cannot write to $man_file_location! Root privileges required.\n";
4319		print "Unable to continue: $man_file_location\n";
4320		return 0;
4321	}
4322	if (-f "/usr/share/man/man8/inxi.8.gz"){
4323		print "Updating man page location to man1.\n";
4324		rename "/usr/share/man/man8/inxi.8.gz", "$man_file_location/inxi.1.gz";
4325		if (check_program('mandb')){
4326			system('mandb');
4327		}
4328	}
4329	if (!($program = check_program('gzip'))){
4330		print "Required program gzip not found. Unable to install man page.\n";
4331		return 0;
4332	}
4333	 # first choice is inxi.1/pinxi.1 from gh, second from smxi.org
4334	if ($download_id ne 'dev server'){
4335		$man_file_url = get_defaults($self_name . '-man-gh');
4336	}
4337	else {
4338		$man_file_url = get_defaults($self_name . '-man');
4339	}
4340	print "Updating $self_name.1 in $man_file_location\n";
4341	print "using $download_id branch as download source\n";
4342	print "Downloading man page file...\n";
4343	print "Download URL: $man_file_url\n" if $dbg[1];
4344	$file_contents = download_file('stdout', $man_file_url);
4345	if ($file_contents){
4346		# make sure the whole file got downloaded and is in the variable
4347		print "Download successful. Validating downloaded man file data...\n";
4348		if ($file_contents =~ m|\.\\" EOF|){
4349			print "Contents validated. Writing to man location...\n";
4350			open(my $fh, '>', $man_file_path);
4351			print $fh $file_contents or error_handler('write', $man_file_path, "$!");
4352			close $fh;
4353			print "Writing successful. Compressing file...\n";
4354			system("$program -9 -f $man_file_path > $man_file_path.gz");
4355			my $err = $?;
4356			if ($err > 0){
4357				print "Oh no! Something went wrong compressing the man file!\n";
4358				print "Error: $err\n";
4359			}
4360			else {
4361				print "Download, install, and compression of man page successful.\n";
4362				print "Check to make sure it works: man $self_name\n";
4363			}
4364		}
4365		else {
4366			error_handler('file-corrupt', "$self_name.1");
4367		}
4368	}
4369	# now run the error handlers on any downloader failure
4370	else {
4371		error_handler('download-error', $man_file_url, $download_id);
4372	}
4373	eval $end if $b_log;
4374}
4375
4376sub set_man_location {
4377	my $location='';
4378	my $default_location='/usr/share/man/man1';
4379	my $man_paths=qx(man --path 2>/dev/null);
4380	my $man_local='/usr/local/share/man';
4381	my $b_use_local=0;
4382	if ($man_paths && $man_paths =~ /$man_local/){
4383		$b_use_local=1;
4384	}
4385	# for distro installs
4386	if (-f "$default_location/inxi.1.gz"){
4387		$location=$default_location;
4388	}
4389	else {
4390		if ($b_use_local){
4391			if (! -d "$man_local/man1"){
4392				mkdir "$man_local/man1";
4393			}
4394			$location="$man_local/man1";
4395		}
4396	}
4397	if (!$location){
4398		$location=$default_location;
4399	}
4400	return $location;
4401}
4402
4403# update for updater output version info
4404# note, this is only now used for self updater function so it can get
4405# the values from the UPDATED file, NOT the running program!
4406sub set_version_data {
4407	open(my $fh, '<', "$self_path/$self_name");
4408	while (my $row = <$fh>){
4409		chomp($row);
4410		$row =~ s/'|;//g;
4411		if ($row =~ /^my \$self_name/){
4412			$self_name = (split('=', $row))[1];
4413		}
4414		elsif ($row =~ /^my \$self_version/){
4415			$self_version = (split('=', $row))[1];
4416		}
4417		elsif ($row =~ /^my \$self_date/){
4418			$self_date = (split('=', $row))[1];
4419		}
4420		elsif ($row =~ /^my \$self_patch/){
4421			$self_patch = (split('=', $row))[1];
4422		}
4423		elsif ($row =~ /^## END INXI INFO/){
4424			last;
4425		}
4426	}
4427	close $fh;
4428}
4429
4430########################################################################
4431#### OPTIONS HANDLER / VERSION
4432########################################################################
4433
4434## OptionsHandler
4435{
4436package OptionsHandler;
4437# note: had %trigger local but tripped odd perl 5.008 failures unless global
4438# so moved to %use and %show globals.
4439my ($self_download,$download_id);
4440sub get {
4441	eval $start if $b_log;
4442	$show{'short'} = 1;
4443	Getopt::Long::GetOptions (
4444	'a|admin' => sub {
4445		$b_admin = 1;},
4446	'A|audio' => sub {
4447		$show{'short'} = 0;
4448		$show{'audio'} = 1;},
4449	'b|basic' => sub {
4450		$show{'short'} = 0;
4451		$show{'battery'} = 1;
4452		$show{'cpu-basic'} = 1;
4453		$show{'raid-basic'} = 1;
4454		$show{'disk-total'} = 1;
4455		$show{'graphic'} = 1;
4456		$show{'graphic-basic'} = 1;
4457		$show{'info'} = 1;
4458		$show{'machine'} = 1;
4459		$show{'network'} = 1;
4460		$show{'system'} = 1;},
4461	'B|battery' => sub {
4462		$show{'short'} = 0;
4463		$show{'battery'} = 1;
4464		$show{'battery-forced'} = 1; },
4465	'c|color:i' => sub {
4466		my ($opt,$arg) = @_;
4467		if ($arg >= 0 && $arg < main::get_color_scheme('count')){
4468			main::set_color_scheme($arg);
4469		}
4470		elsif ($arg >= 94 && $arg <= 99){
4471			$colors{'selector'} = $arg;
4472		}
4473		else {
4474			main::error_handler('bad-arg', $opt, $arg);
4475		} },
4476	'C|cpu' => sub {
4477		$show{'short'} = 0;
4478		$show{'cpu'} = 1; },
4479	'd|disk-full|optical' => sub {
4480		$show{'short'} = 0;
4481		$show{'disk'} = 1;
4482		$show{'optical'} = 1; },
4483	'D|disk' => sub {
4484		$show{'short'} = 0;
4485		$show{'disk'} = 1; },
4486	'E|bluetooth' => sub {
4487		$show{'short'} = 0;
4488		$show{'bluetooth'} = 1;
4489		$show{'bluetooth-forced'} = 1;},
4490	'f|flags|flag' => sub {
4491		$show{'short'} = 0;
4492		$show{'cpu'} = 1;
4493		$show{'cpu-flag'} = 1; },
4494	'F|full' => sub {
4495		$show{'short'} = 0;
4496		$show{'audio'} = 1;
4497		$show{'battery'} = 1;
4498		$show{'bluetooth'} = 1;
4499		$show{'cpu'} = 1;
4500		$show{'disk'} = 1;
4501		$show{'graphic'} = 1;
4502		$show{'graphic-basic'} = 1;
4503		$show{'info'} = 1;
4504		$show{'machine'} = 1;
4505		$show{'network'} = 1;
4506		$show{'network-advanced'} = 1;
4507		$show{'partition'} = 1;
4508		$show{'raid'} = 1;
4509		$show{'sensor'} = 1;
4510		$show{'swap'} = 1;
4511		$show{'system'} = 1; },
4512	'G|graphics|graphic' => sub {
4513		$show{'short'} = 0;
4514		$show{'graphic'} = 1;
4515		$show{'graphic-basic'} = 1; },
4516	'h|help|?' => sub {
4517		$show{'help'} = 1; },
4518	'i|ip' => sub {
4519		$show{'short'} = 0;
4520		$show{'ip'} = 1;
4521		$show{'network'} = 1;
4522		$show{'network-advanced'} = 1;
4523		$use{'downloader'} = 1 if ! main::check_program('dig');},
4524	'I|info' => sub {
4525		$show{'short'} = 0;
4526		$show{'info'} = 1; },
4527	'j|swap|swaps' => sub {
4528		$show{'short'} = 0;
4529		$show{'swap'} = 1;},
4530	'J|usb' => sub {
4531		$show{'short'} = 0;
4532		$show{'usb'} = 1; },
4533	'l|labels|label' => sub {
4534		$show{'label'} = 1;},
4535	'limit:i' => sub {
4536		my ($opt,$arg) = @_;
4537		if ($arg != 0){
4538			$limit = $arg;
4539		}
4540		else {
4541			main::error_handler('bad-arg',$opt,$arg);
4542		} },
4543	'L|logical|lvm' => sub {
4544		$show{'short'} = 0;
4545		$show{'logical'} = 1; },
4546	'm|memory' => sub {
4547		$show{'short'} = 0;
4548		$show{'ram'} = 1; },
4549	'memory-modules' => sub {
4550		$show{'short'} = 0;
4551		$show{'ram'} = 1;
4552		$show{'ram-modules'} = 1;},
4553	'memory-short' => sub {
4554		$show{'short'} = 0;
4555		$show{'ram'} = 1;
4556		$show{'ram-short'} = 1;},
4557	'M|machine' => sub {
4558		$show{'short'} = 0;
4559		$show{'machine'} = 1; },
4560	'n|network-advanced' => sub {
4561		$show{'short'} = 0;
4562		$show{'network'} = 1;
4563		$show{'network-advanced'} = 1; },
4564	'N|network' => sub {
4565		$show{'short'} = 0;
4566		$show{'network'} = 1; },
4567	'o|unmounted' => sub {
4568		$show{'short'} = 0;
4569		$show{'unmounted'} = 1; },
4570	'p|partition-full|partitions-full' => sub {
4571		$show{'short'} = 0;
4572		$show{'partition'} = 0;
4573		$show{'partition-full'} = 1; },
4574	'P|partitions|partition' => sub {
4575		$show{'short'} = 0;
4576		$show{'partition'} = 1; },
4577	'partition-sort:s' => sub {
4578		my ($opt,$arg) = @_;
4579		if ($arg =~ /^(dev-base|fs|id|label|percent-used|size|uuid|used)$/){
4580			$show{'partition-sort'} = $arg;
4581		}
4582		else {
4583			main::error_handler('bad-arg',$opt,$arg);
4584		} },
4585	'r|repos|repo' => sub {
4586		$show{'short'} = 0;
4587		$show{'repo'} = 1; },
4588	'R|raid' => sub {
4589		$show{'short'} = 0;
4590		$show{'raid'} = 1;
4591		$show{'raid-forced'} = 1; },
4592	's|sensors|sensor' => sub {
4593		$show{'short'} = 0;
4594		$show{'sensor'} = 1; },
4595	'sleep:s' => sub {
4596		my ($opt,$arg) = @_;
4597		$arg ||= 0;
4598		if ($arg >= 0){
4599			$cpu_sleep = $arg;
4600		}
4601		else {
4602			main::error_handler('bad-arg',$opt,$arg);
4603		} },
4604	'slots|slot' => sub {
4605		$show{'short'} = 0;
4606		$show{'slot'} = 1; },
4607	'S|system' => sub {
4608		$show{'short'} = 0;
4609		$show{'system'} = 1; },
4610	't|processes|process:s' => sub {
4611		my ($opt,$arg) = @_;
4612		$show{'short'} = 0;
4613		$arg ||= 'cm';
4614		my $num = $arg;
4615		$num =~ s/^[cm]+// if $num;
4616		if ($arg =~ /^([cm]+)([0-9]+)?$/ && (!$num || $num =~ /^\d+/)){
4617			$show{'process'} = 1;
4618			if ($arg =~ /c/){
4619				$show{'ps-cpu'} = 1;
4620			}
4621			if ($arg =~ /m/){
4622				$show{'ps-mem'} = 1;
4623			}
4624			$ps_count = $num if $num;
4625		}
4626		else {
4627			main::error_handler('bad-arg',$opt,$arg);
4628		} },
4629	'u|uuid' => sub {
4630		$show{'uuid'} = 1;},
4631	'v|verbosity:i' => sub {
4632		my ($opt,$arg) = @_;
4633		$show{'short'} = 0;
4634		if ($arg =~ /^[0-8]$/){
4635			if ($arg == 0){
4636				$show{'short'} = 1;
4637			}
4638			if ($arg >= 1){
4639				$show{'cpu-basic'} = 1;
4640				$show{'disk-total'} = 1;
4641				$show{'graphic'} = 1;
4642				$show{'graphic-basic'} = 1;
4643				$show{'info'} = 1;
4644				$show{'system'} = 1;
4645			}
4646			if ($arg >= 2){
4647				$show{'battery'} = 1;
4648				$show{'disk-basic'} = 1;
4649				$show{'raid-basic'} = 1;
4650				$show{'machine'} = 1;
4651				$show{'network'} = 1;
4652			}
4653			if ($arg >= 3){
4654				$show{'network-advanced'} = 1;
4655				$show{'cpu'} = 1;
4656				$extra = 1;
4657			}
4658			if ($arg >= 4){
4659				$show{'disk'} = 1;
4660				$show{'partition'} = 1;
4661			}
4662			if ($arg >= 5){
4663				$show{'audio'} = 1;
4664				$show{'bluetooth'} = 1;
4665				$show{'ram'} = 1;
4666				$show{'label'} = 1;
4667				$show{'optical-basic'} = 1;
4668				$show{'ram'} = 1;
4669				$show{'raid'} = 1;
4670				$show{'sensor'} = 1;
4671				$show{'swap'} = 1;
4672				$show{'uuid'} = 1;
4673			}
4674			if ($arg >= 6){
4675				$show{'optical'} = 1;
4676				$show{'partition-full'} = 1;
4677				$show{'unmounted'} = 1;
4678				$show{'usb'} = 1;
4679				$extra = 2;
4680			}
4681			if ($arg >= 7){
4682				$use{'downloader'} = 1 if !main::check_program('dig');
4683				$show{'battery-forced'} = 1;
4684				$show{'bluetooth-forced'} = 1;
4685				$show{'cpu-flag'} = 1;
4686				$show{'ip'} = 1;
4687				$show{'logical'} = 1;
4688				$show{'raid-forced'} = 1;
4689				$extra = 3;
4690			}
4691			if ($arg >= 8){
4692				$b_admin = 1;
4693				$use{'downloader'} = 1;
4694				$show{'process'} = 1;
4695				$show{'ps-cpu'} = 1;
4696				$show{'ps-mem'} = 1;
4697				$show{'repo'} = 1;
4698				$show{'slot'} = 1;
4699				#$show{'weather'} = 1;
4700			}
4701		}
4702		else {
4703			main::error_handler('bad-arg',$opt,$arg);
4704		} },
4705	'V|version' => sub {
4706		$show{'version'} = 1 },
4707	'w|weather' => sub {
4708		my ($opt) = @_;
4709		$show{'short'} = 0;
4710		$use{'downloader'} = 1;
4711		if ($use{'weather'}){
4712			$show{'weather'} = 1;
4713		}
4714		else {
4715			main::error_handler('distro-block', $opt);
4716		} },
4717	'W|weather-location:s' => sub {
4718		my ($opt,$arg) = @_;
4719		$arg ||= '';
4720		$arg =~ s/\s//g;
4721		$show{'short'} = 0;
4722		$use{'downloader'} = 1;
4723		if ($use{'weather'}){
4724			if ($arg){
4725				$show{'weather'} = 1;
4726				$show{'weather-location'} = $arg;
4727			}
4728			else {
4729				main::error_handler('bad-arg',$opt,$arg);
4730			}
4731		}
4732		else {
4733			main::error_handler('distro-block', $opt);
4734		} },
4735	'ws|weather-source:s' => sub {
4736		my ($opt,$arg) = @_;
4737		# let api processor handle checks if valid, this
4738		# future proofs this
4739		if ($arg =~ /^[1-9]$/){
4740			$weather_source = $arg;
4741		}
4742		else {
4743			main::error_handler('bad-arg',$opt,$arg);
4744		} },
4745	'weather-unit:s' => sub {
4746		my ($opt,$arg) = @_;
4747		$arg ||= '';
4748		$arg =~ s/\s//g;
4749		$arg = lc($arg) if $arg;
4750		if ($arg && $arg =~ /^(c|f|cf|fc|i|m|im|mi)$/){
4751			my %units = ('c'=>'m','f'=>'i','cf'=>'mi','fc'=>'im');
4752			$arg = $units{$arg} if defined $units{$arg};
4753			$weather_unit = $arg;
4754		}
4755		else {
4756			main::error_handler('bad-arg',$opt,$arg);
4757		} },
4758	'x|extra:i' => sub {
4759		my ($opt,$arg) = @_;
4760		if ($arg > 0){
4761			$extra = $arg;
4762		}
4763		else {
4764			$extra++;
4765		} },
4766	'y|width:i' => sub {
4767		my ($opt, $arg) = @_;
4768		if (defined $arg && $arg == -1){
4769			$arg = 2000;
4770		}
4771		# note: :i creates 0 value if not supplied even though means optional
4772		elsif (!$arg){
4773			$arg = 80;
4774		}
4775		if ($arg =~ /\d/ && ($arg == 1 || $arg >= 80)){
4776			main::set_display_width($arg);
4777		}
4778		else {
4779			main::error_handler('bad-arg', $opt, $arg);
4780		} },
4781	'z|filter' => sub {
4782		$use{'filter'} = 1; },
4783	'filter-label' => sub {
4784		$use{'filter-label'} = 1; },
4785	'Z|filter-override' => sub {
4786		$use{'filter-override'} = 1; },
4787	'filter-uuid' => sub {
4788		$use{'filter-uuid'} = 1; },
4789	## Start non data options
4790	'alt:i' => sub {
4791		my ($opt,$arg) = @_;
4792		if ($arg == 40){
4793			$dl{'tiny'} = 0;
4794			$use{'downloader'} = 1;}
4795		elsif ($arg == 41){
4796			$dl{'curl'} = 0;
4797			$use{'downloader'} = 1;}
4798		elsif ($arg == 42){
4799			$dl{'fetch'} = 0;
4800			$use{'downloader'} = 1;}
4801		elsif ($arg == 43){
4802			$dl{'wget'} = 0;
4803			$use{'downloader'} = 1;}
4804		elsif ($arg == 44){
4805			$dl{'curl'} = 0;
4806			$dl{'fetch'} = 0;
4807			$dl{'wget'} = 0;
4808			$use{'downloader'} = 1;}
4809		else {
4810			main::error_handler('bad-arg', $opt, $arg);
4811		}},
4812	'arm' => sub {
4813		$b_arm = 1 },
4814	'bsd:s' => sub {
4815		my ($opt,$arg) = @_;
4816		if ($arg =~ /^(darwin|dragonfly|freebsd|openbsd|netbsd)$/i){
4817			$bsd_type = lc($arg);
4818			$fake{'bsd'} = 1;
4819		}
4820		else {
4821			main::error_handler('bad-arg', $opt, $arg);
4822		}
4823	},
4824	'bt-tool:s' => sub {
4825		my ($opt,$arg) = @_;
4826		if ($arg =~ /^(bluetoothctl|bt-adapter|hciconfig|rfkill)$/i){
4827			$bt_tool = lc($arg);
4828		}
4829		else {
4830			main::error_handler('bad-arg', $opt, $arg);
4831		}
4832	},
4833	'dbg:i' => sub {
4834		my ($opt,$arg) = @_;
4835		if ($arg > 0){
4836			$dbg[$arg] = 1;
4837		}
4838		else {
4839			main::error_handler('bad-arg', $opt, $arg);
4840		}},
4841	'debug:i' => sub {
4842		my ($opt,$arg) = @_;
4843		if ($arg =~ /^[1-3]|1[0-3]|2[0-4]$/){
4844			$debugger{'level'} = $arg;
4845		}
4846		else {
4847			main::error_handler('bad-arg', $opt, $arg);
4848		} },
4849	'debug-filter|debug-z|debug-zy' => sub {
4850		$debugger{'filter'} = 1 },
4851	'debug-id:s' => sub {
4852		my ($opt,$arg) = @_;
4853		if ($arg){
4854			$debugger{'id'} = $arg;
4855		}
4856		else {
4857			main::error_handler('bad-arg', $opt, $arg);
4858		} },
4859	'debug-no-eps' => sub {
4860		$debugger{'no-exit'} = 1;
4861		$debugger{'no-proc'} = 1;
4862		$debugger{'sys'} = 0;
4863	},
4864	'debug-no-exit' => sub {
4865		$debugger{'no-exit'} = 1 },
4866	'debug-no-proc' => sub {
4867		$debugger{'no-proc'} = 1; },
4868	'debug-no-sys' => sub {
4869		$debugger{'sys'} = 0; },
4870	'debug-proc' => sub {
4871		$debugger{'proc'} = 1; },
4872	'debug-proc-print' => sub {
4873		$debugger{'proc-print'} = 1;},
4874	'debug-sys-print' => sub {
4875		$debugger{'sys-print'} = 1; },
4876	'debug-test-1' => sub {
4877		$debugger{'test-1'} = 1; },
4878	'debug-width|debug-y|debug-zy:i' => sub {
4879		my ($opt,$arg) = @_;
4880		$arg ||= 80;
4881		if ($arg =~ /^[0-9]+$/ && ($arg == 1 || $arg >= 80)){
4882			$debugger{'width'} = $arg;
4883		}
4884		else {
4885			main::error_handler('bad-arg', $opt, $arg);
4886		} },
4887	'dig' => sub {
4888		$force{'no-dig'} = 0; },
4889	'display:s' => sub {
4890		my ($opt,$arg) = @_;
4891		if ($arg =~ /^:?([0-9\.]+)?$/){
4892			$display=$arg;
4893			$display ||= ':0';
4894			$display = ":$display" if $display !~ /^:/;
4895			$b_display = ($b_root) ? 0 : 1;
4896			$force{'display'} = 1;
4897			$display_opt = "-display $display";
4898		}
4899		else {
4900			main::error_handler('bad-arg', $opt, $arg);
4901		} },
4902	'dmi|dmidecode' => sub {
4903		$force{'dmidecode'} = 1 },
4904	'downloader:s' => sub {
4905		my ($opt,$arg) = @_;
4906		$arg = lc($arg);
4907		if ($arg =~ /^(curl|fetch|ftp|perl|wget)$/){
4908			if ($arg eq 'perl' && (!main::check_perl_module('HTTP::Tiny') ||
4909			 !main::check_perl_module('IO::Socket::SSL'))){
4910				main::error_handler('missing-perl-downloader', $opt, $arg);
4911			}
4912			elsif (!main::check_program($arg)){
4913				main::error_handler('missing-downloader', $opt, $arg);
4914			}
4915			else {
4916				# this dumps all the other data and resets %dl for only the
4917				# desired downloader.
4918				$arg = main::set_perl_downloader($arg);
4919				%dl = ('dl' => $arg, $arg => 1);
4920				$use{'downloader'} = 1;
4921			}
4922		}
4923		else {
4924			main::error_handler('bad-arg', $opt, $arg);
4925		} },
4926	'fake:s' => sub {
4927		my ($opt,$arg) = @_;
4928		if ($arg){
4929			my $wl = 'bluetooth|compiler|cpu|dboot|dmidecode|ipmi|logical|lspci|';
4930			$wl .= 'partitions|pciconf|pcictl|pcidump|raid-btrfs|raid-hw|raid-lvm|';
4931			$wl .= 'raid-md|raid-soft|raid-zfs|sensors|sysctl|uptime|usbconfig|';
4932			$wl .= 'usbdevs|vmstat|xorg-log';
4933			for (split(',',$arg)){
4934				if ($_ =~ /\b($wl)\b/){
4935					$fake{lc($1)} = 1;
4936				}
4937				else {
4938					main::error_handler('bad-arg', $opt, $_);
4939				}
4940			}
4941		}
4942		else {
4943			main::error_handler('bad-arg', $opt, $arg);
4944		}},
4945	'force:s' => sub {
4946		my ($opt,$arg) = @_;
4947		if ($arg){
4948			my $wl = 'display|dmidecode|hddtemp|lsusb|man|meminfo|no-dig|';
4949			$wl .= 'no-doas|no-html-wan|no-sudo|pkg|usb-sys|vmstat|wmctrl';
4950			for (split(',',$arg)){
4951				if ($_ =~ /\b($wl)\b/){
4952					$force{lc($1)} = 1;
4953				}
4954				else {
4955					main::error_handler('bad-arg', $opt, $_);
4956				}
4957			}
4958		}
4959		else {
4960			main::error_handler('bad-arg', $opt, $arg);
4961		}},
4962	'ftp:s'  => sub {
4963		my ($opt,$arg) = @_;
4964		# pattern: ftp.x.x/x
4965		if ($arg =~ /^ftp\..+\..+\/[^\/]+$/){
4966			$ftp_alt = $arg;
4967		}
4968		else {
4969			main::error_handler('bad-arg', $opt, $arg);
4970		}},
4971	'hddtemp' => sub {
4972		$force{'hddtemp'} = 1 },
4973	'host|hostname' => sub {
4974		$show{'host'} = 1;
4975		$show{'no-host'} = 0},
4976	'html-wan' => sub {
4977		$force{'no-html-wan'} = 0; },
4978	'irc' => sub {
4979		$b_irc = 1; },
4980	'man' => sub {
4981		$use{'yes-man'} = 1; },
4982	'mips' => sub {
4983		$b_mips = 1 },
4984	'output:s' => sub {
4985		my ($opt,$arg) = @_;
4986		if ($arg =~ /^(json|screen|xml)$/){
4987			$output_type = $arg;
4988		}
4989		else {
4990			main::error_handler('bad-arg', $opt, $arg);
4991		}},
4992	'no-dig' => sub {
4993		$force{'no-dig'} = 1; },
4994	'no-doas' => sub {
4995		$force{'no-doas'} = 1; },
4996	'no-host|no-hostname' => sub {
4997		$show{'host'} = 0 ;
4998		$show{'no-host'} = 1},
4999	'no-html-wan' => sub {
5000		$force{'no-html-wan'}= 1;},
5001	'no-man' => sub {
5002		$use{'no-man'} = 0; },
5003	'no-ssl' => sub {
5004		$dl{'no-ssl-opt'}=1 },
5005	'no-sudo' => sub {
5006		$force{'no-sudo'} = 1; },
5007	'output-file:s' => sub {
5008		my ($opt,$arg) = @_;
5009		if ($arg){
5010			if ($arg eq 'print' || main::check_output_path($arg)){
5011				$output_file = $arg;
5012			}
5013			else {
5014				main::error_handler('output-file-bad', $opt, $arg);
5015			}
5016		}
5017		else {
5018			main::error_handler('bad-arg', $opt, $arg);
5019		}},
5020	'pkg' => sub {
5021		$force{'pkg'} = 1 },
5022	'ppc' => sub {
5023		$b_ppc = 1 },
5024	'recommends' => sub {
5025		$show{'recommends'} = 1;},
5026	'sensors-default' => sub {
5027		$use{'sensors-default'} = 1; },
5028	'sensors-exclude:s' => sub {
5029		my ($opt,$arg) = @_;
5030		if ($arg){
5031			@sensors_exclude = split(/\s*,\s*/, $arg);
5032		}
5033		else {
5034			main::error_handler('bad-arg',$opt,$arg);
5035		}},
5036	'sensors-use:s' => sub {
5037		my ($opt,$arg) = @_;
5038		if ($arg){
5039			@sensors_use = split(/\s*,\s*/, $arg);
5040		}
5041		else {
5042			main::error_handler('bad-arg',$opt,$arg);
5043		}},
5044	'sparc' => sub {
5045		$b_sparc = 1; },
5046	'sys-debug' => sub {
5047		$debugger{'sys-force'} = 1; },
5048	'tty' => sub { # workaround for ansible running this
5049		$b_irc = 0; },
5050	'U|update:s' => sub { # 1,2,3 OR http://myserver/path/inxi
5051		my ($opt,$arg) = @_;
5052		process_updater($opt,$arg);},
5053	'usb-sys' => sub {
5054		$force{'usb-sys'} = 1 },
5055	'usb-tool' => sub {
5056		$force{'lsusb'} = 1 },
5057	'wan-ip-url:s' => sub {
5058		my ($opt,$arg) = @_;
5059		if ($arg && $arg =~ /^(f|ht)tp[s]?:\/\//){
5060			$wan_url = $arg;
5061			$force{'no-dig'} = 1;
5062		}
5063		else {
5064			main::error_handler('bad-arg', $opt, $arg);
5065		}},
5066	'wm' => sub {
5067		$force{'wmctrl'} = 1 },
5068	'wrap-max|indent-min:i' => sub {
5069		my ($opt,$arg) = @_;
5070		if ($arg =~ /^\d+$/){
5071			$size{'wrap-max'} = $arg;
5072		}
5073		else {
5074			main::error_handler('bad-arg', $opt, $arg);
5075		}},
5076	'<>' => sub {
5077		my ($opt) = @_;
5078		main::error_handler('unknown-option', "$opt", "");}
5079	); # or error_handler('unknown-option', "@ARGV", '');
5080	# run all these after so that we can change widths, downloaders, etc
5081	# print Data::Dumper::Dumper \%trigger;
5082	post_process();
5083	eval $end if $b_log;
5084}
5085sub post_process {
5086	CheckRecommends::run() if $show{'recommends'};
5087	# sets for either config or arg here
5088	if ($use{'downloader'} || $wan_url || ($force{'no-dig'} && $show{'ip'})){
5089		main::set_downloader();
5090	}
5091	main::set_xorg_log() if $show{'graphic'};
5092	main::show_version() if $show{'version'};
5093	main::show_options() if $show{'help'};
5094	$use{'man'} = 0 if (!$use{'yes-man'} || $use{'no-man'});
5095	main::update_me($self_download, $download_id) if $use{'update-trigger'};
5096	if ($b_pledge){
5097		my $b_update;
5098		# if -c 9x, remove in SelectColors::set_selection(), else remove here
5099		if (!$colors{'selector'} && $debugger{'level'} < 21){
5100			@pledges = grep {$_ ne 'getpw'} @pledges;
5101			$b_update = 1;
5102		}
5103		if ($debugger{'level'} < 21){ # remove ftp upload
5104			@pledges = grep {!/(dns|inet)/} @pledges;
5105			$b_update = 1;
5106		}
5107		# not writing/creating .inxi data dirs colors selector launches set_color()
5108		if (!$show{'weather'} && !$colors{'selector'} && $debugger{'level'} < 10 &&
5109		 $output_type eq 'screen'){
5110			@pledges = grep {!/(cpath|wpath)/} @pledges;
5111			$b_update = 1;
5112		}
5113		OpenBSD::Pledge::pledge(@pledges) if $b_update;
5114	}
5115	if ($output_type){
5116		if ($output_type ne 'screen' && !$output_file){
5117			main::error_handler('bad-arg', '--output', '--output-file not provided');
5118		}
5119	}
5120	if (($show{'label'} || $show{'uuid'}) && !$show{'partition'} &&
5121	 !$show{'partition-full'} && !$show{'swap'} && !$show{'unmounted'}){
5122		main::error_handler('bad-arg', '-l/-u', 'missing required option(s) -j, -o, -p, -P');
5123	}
5124	$show{'graphic-basic'} = 0 if $b_admin;
5125	if ($use{'sensors-default'}){
5126		@sensors_exclude = ();
5127		@sensors_use = ();
5128	}
5129	if ($show{'short'} || $show{'disk'} || $show{'disk-basic'} || $show{'disk-total'} ||
5130	    $show{'logical'} || $show{'partition'} || $show{'partition-full'} || $show{'raid'} ||
5131	    $show{'unmounted'}){
5132		$use{'block-tool'} = 1;
5133	}
5134	if ($show{'raid'} || $show{'disk'} || $show{'disk-total'} || $show{'disk-basic'}
5135	 || $show{'unmounted'}){
5136		$use{'btrfs'} = 1;
5137		$use{'mdadm'} = 1;
5138	}
5139	if ($b_admin && $show{'disk'}){
5140		$use{'smartctl'} = 1;
5141	}
5142	# triggers may extend to -D, -pP
5143	if ($show{'short'} || $show{'logical'} || $show{'raid'} || $show{'disk'} ||
5144	    $show{'disk-total'} || $show{'disk-basic'} || $show{'unmounted'}){
5145		$use{'logical'} = 1;
5146	}
5147	main::set_sudo() if ($show{'unmounted'} || ($extra > 0 && $show{'disk'}));
5148	$extra = 3 if $b_admin;
5149	$use{'filter'} = 0 if $use{'filter-override'};
5150	# override for things like -b or -v2 to -v3
5151	$show{'cpu-basic'} = 0 if $show{'cpu'};
5152	$show{'optical-basic'} = 0 if $show{'optical'};
5153	$show{'partition'} = 0 if $show{'partition-full'};
5154	$show{'host'} = 0 if $show{'no-host'};
5155	$show{'host'} = 1 if ($show{'host'} || (!$use{'filter'} && !$show{'no-host'}));
5156	if ($show{'disk'} || $show{'optical'}){
5157		$show{'disk-basic'} = 0;
5158		$show{'disk-total'} = 0;
5159	}
5160	if ($show{'ram'} || $show{'slot'} || ($show{'cpu'} && ($extra > 1 || $bsd_type)) ||
5161	 (($bsd_type || $force{'dmidecode'}) && ($show{'machine'} || $show{'battery'}))){
5162		$use{'dmidecode'} = 1;
5163	}
5164	if ($show{'audio'} || $show{'bluetooth'} || $show{'graphic'} ||
5165	 $show{'network'} || $show{'raid'}){
5166		$use{'pci'} = 1;
5167	}
5168	if ($show{'usb'} || $show{'audio'} || $show{'bluetooth'} ||
5169	 $show{'graphic'} || $show{'network'}){
5170		$use{'usb'} = 1;
5171	}
5172	if ($bsd_type){
5173		if ($show{'audio'}){
5174			$use{'bsd-audio'} = 1;}
5175		if ($show{'battery'}){
5176			$use{'bsd-battery'} = 1;}
5177		if ($show{'short'} || $show{'cpu-basic'} || $show{'cpu'}){
5178			$use{'bsd-cpu'} = 1;
5179			$use{'bsd-sleep'} = 1;}
5180		if ($show{'short'} || $show{'disk-basic'} || $show{'disk-total'} ||
5181		 $show{'disk'} || $show{'partition'} || $show{'partition-full'} ||
5182		 $show{'raid'} || $show{'swap'} || $show{'unmounted'}){
5183			$use{'bsd-disk'} = 1;
5184			$use{'bsd-partition'} = 1;
5185			$use{'bsd-raid'} = 1;}
5186		if ($show{'system'}){
5187			$use{'bsd-kernel'} = 1;}
5188		if ($show{'machine'}){
5189			$use{'bsd-machine'} = 1;}
5190		if ($show{'short'} || $show{'info'} || $show{'ps-mem'} || $show{'ram'}){
5191			$use{'bsd-memory'} = 1;}
5192		if ($show{'optical-basic'} || $show{'optical'}){
5193			$use{'bsd-optical'} = 1;}
5194		# strictly only used to fill in pci drivers if tool doesn't support that
5195		if ($use{'pci'}){
5196			$use{'bsd-pci'} = 1;}
5197		if ($show{'raid'}){
5198			$use{'bsd-ram'} = 1;}
5199		if ($show{'sensor'}){
5200			$use{'bsd-sensor'} = 1;}
5201		# always use this, it's too core
5202		$use{'sysctl'} = 1;
5203	}
5204}
5205sub process_updater {
5206	my ($opt,$arg) = @_;
5207	$use{'downloader'} = 1;
5208	if ($use{'update'}){
5209		$use{'update-trigger'} = 1;
5210		if (!$arg && $self_name eq 'pinxi'){
5211			$use{'man'} = 1;
5212			$download_id = 'inxi-perl branch';
5213			$self_download = main::get_defaults('inxi-pinxi');
5214		}
5215		elsif ($arg && $arg eq '3'){
5216			$use{'man'} = 1;
5217			$download_id = 'dev server';
5218			$self_download = main::get_defaults('inxi-dev');
5219		}
5220		else {
5221			if (!$arg){
5222				$download_id = 'main branch';
5223				$self_download = main::get_defaults('inxi-main');
5224				$use{'man'} = 1;
5225				$use{'yes-man'} = 1;
5226			}
5227			elsif ($arg =~ /^[12]$/){
5228				$download_id = "branch $arg";
5229				$self_download = main::get_defaults("inxi-branch-$arg");
5230			}
5231			elsif ($arg =~ /^http/){
5232				$download_id = 'alt server';
5233				$self_download = $arg;
5234			}
5235		}
5236		if (!$self_download){
5237			main::error_handler('bad-arg', $opt, $arg);
5238		}
5239	}
5240	else {
5241		main::error_handler('distro-block', $opt);
5242	}
5243}
5244}
5245
5246sub show_options {
5247	error_handler('not-in-irc', 'help') if $b_irc;
5248	my (@data);
5249	my $line = '';
5250	my $color_scheme_count = get_color_scheme('count') - 1;
5251	my $partition_string='partition';
5252	my $partition_string_u='Partition';
5253	my $flags = ($b_arm) ? 'features' : 'flags' ;
5254	if ($bsd_type){
5255		$partition_string='slice';
5256		$partition_string_u='Slice';
5257	}
5258	# fit the line to the screen!
5259	for my $i (0 .. (($size{'max'} / 2) - 2)){
5260		$line = $line . '- ';
5261	}
5262	push(@data,
5263	['0', '', '', "$self_name supports the following options. For more detailed
5264	information, see man^$self_name. If you start $self_name with no arguments,
5265	it will display a short system summary." ],
5266	['0', '', '', '' ],
5267	['0', '', '', "You can use these options alone or together,
5268	to show or add the item(s) you want to see: A, B, C, D, E, G, I, J, L, M, N,
5269	P, R, S, W, d, f, i, j, l, m, n, o, p, r, s, t, u, w, --slots.
5270	If you use them with -v [level], -b or -F, $self_name will add the requested
5271	lines to the output." ],
5272	['0', '', '', '' ],
5273	['0', '', '', "Examples:^$self_name^-v4^-c6 OR $self_name^-bDc^6 OR
5274	$self_name^-FzjJxy^80" ],
5275	['0', '', '', $line ],
5276	['0', '', '', "Output Control Options (see Extra Data Options to extend output):" ],
5277	['1', '-A', '--audio', "Audio/sound devices(s), driver, running sound servers." ],
5278	['1', '-b', '--basic', "Basic output, short form. Same as $self_name^-v^2." ],
5279	['1', '-B', '--battery', "System battery info, including charge, condition
5280	voltage (if critical), plus extra info (if battery present/detected)." ],
5281	['1', '-c', '--color', "Set color scheme (0-42). For piped or redirected output,
5282	you must use an explicit color selector. Example:^$self_name^-c^11" ],
5283	['1', '', '', "Color selectors let you set the config file value for the
5284	selection (NOTE: IRC and global only show safe color set)" ],
5285	['2', '94', '', "Console, out of X" ],
5286	['2', '95', '', "Terminal, running in X - like xTerm" ],
5287	['2', '96', '', "Gui IRC, running in X - like Xchat, Quassel, Konversation etc." ],
5288	['2', '97', '', "Console IRC running in X - like irssi in xTerm" ],
5289	['2', '98', '', "Console IRC not in  X" ],
5290	['2', '99', '', "Global - Overrides/removes all settings. Setting specific
5291	removes global." ],
5292	['1', '-C', '--cpu', "CPU output, including per CPU clock speed and max
5293	CPU speed (if available)." ],
5294	['1', '-d', '--disk-full, --optical', "Optical drive data (and floppy disks,
5295	if present). Triggers -D." ],
5296	['1', '-D', '--disk', "Hard Disk info, including total storage and details
5297	for each disk. Disk total used percentage includes swap ${partition_string}
5298	size(s)." ],
5299	['1', '-E', '--bluetooth', "Show bluetooth device data and report, if available.
5300	Shows state, address, IDs, version info." ],
5301	['1', '-f', '--flags', "All CPU $flags. Triggers -C. Not shown with -F to
5302	avoid spamming." ],
5303	['1', '-F', '--full', "Full output. Includes all Upper Case line letters
5304	(except -J, -W) plus --swap, -s and -n. Does not show extra verbose options such
5305	as -d -f -i -J -l -m -o -p -r -t -u -x, unless specified." ],
5306	['1', '-G', '--graphics', "Graphics info (devices(s), drivers, display protocol
5307	(if available), display server/Wayland compositor, resolution, renderer,
5308	OpenGL version)." ],
5309	['1', '-i', '--ip', "WAN IP address and local interfaces (requires ifconfig
5310	or ip network tool). Triggers -n. Not shown with -F for user security reasons.
5311	You shouldn't paste your local/WAN IP." ],
5312	['1', '-I', '--info', "General info, including processes, uptime, memory,
5313	IRC client or shell type, $self_name version." ],
5314	['1', '-j', '--swap', "Swap in use. Includes ${partition_string}s, zram, file." ],
5315	['1', '-J', '--usb', "Show USB data: Hubs and Devices." ],
5316	['1', '-l', '--label', "$partition_string_u labels. Use with -j, -o, -p, -P." ],
5317	['1', '-L', '--logical', "Logical devices, LVM (VG, LV),
5318	LUKS, Crypto, bcache, etc. Shows components/devices, sizes, etc." ],
5319	['1', '-m', '--memory', "Memory (RAM) data. Requires root. Numbers of
5320	devices (slots) supported and individual memory devices (sticks of memory etc).
5321	For devices, shows device locator, size, speed, type (e.g. DDR3).
5322	If neither -I nor -tm are selected, also shows RAM used/total." ],
5323	['1', '', '--memory-modules', "Memory (RAM) data. Exclude empty module slots." ],
5324	['1', '', '--memory-short', "Memory (RAM) data. Show only short Memory RAM report,
5325	number of arrays, slots, modules, and RAM type." ],
5326	['1', '-M', '--machine', "Machine data. Device type (desktop, server, laptop,
5327	VM etc.), motherboard, BIOS and, if    present, system builder (e.g. Lenovo).
5328	Shows UEFI/BIOS/UEFI [Legacy]. Older systems/kernels without the required /sys
5329	data can use dmidecode instead, run as root. Dmidecode can be forced with --dmidecode" ],
5330	['1', '-n', '--network-advanced', "Advanced Network device info. Triggers -N. Shows
5331	interface, speed, MAC id, state, etc. " ],
5332	['1', '-N', '--network', "Network device(s), driver." ],
5333	['1', '-o', '--unmounted', "Unmounted $partition_string info (includes UUID
5334	and Label if available). Shows file system type if you have lsblk installed
5335	(Linux) or, for BSD/GNU Linux, if 'file' installed and you are root or if
5336	you have added to /etc/sudoers (sudo v. 1.7 or newer)(BSDs: see doas)." ],
5337	['1', '', '', "Example: ^<username>^ALL^=^NOPASSWD:^/usr/bin/file^" ],
5338	['1', '-p', '--partitions-full', "Full $partition_string information (-P plus all other
5339	detected ${partition_string}s)." ],
5340	['1', '-P', '--partitions', "Basic $partition_string info. Shows, if detected:
5341	/ /boot /home /opt /tmp /usr /usr/home /var /var/log /var/tmp. Swap
5342	${partition_string}s show if --swap is not used. Use -p to see all
5343	mounted ${partition_string}s." ],
5344	['1', '-r', '--repos', "Distro repository data. Supported repo types: APK,
5345	APT, CARDS, EOPKG, NIX, PACMAN, PACMAN-G2, PISI, PKG (BSDs), PORTAGE, PORTS
5346	(BSDs), SCRATCHPKG, SLACKPKG, TCE, URPMQ, XBPS, YUM/ZYPP." ],
5347	['1', '-R', '--raid', "RAID data. Shows RAID devices, states, levels, array sizes,
5348	and components. md-raid: If device is resyncing, also shows resync progress line." ],
5349	['1', '-s', '--sensors', "Sensors output (if sensors installed/configured):
5350	mobo/CPU/GPU temp; detected fan speeds. GPU temp only for Fglrx/Nvidia drivers.
5351	Nvidia shows screen number for > 1 screen. IPMI sensors if present." ],
5352	['1', '', '--slots', "PCI slots: type, speed, status. Requires root." ],
5353	['1', '-S', '--system', "System info: host name, kernel, desktop environment
5354	(if in X/Wayland), distro." ],
5355	['1', '-t', '--processes', "Processes. Requires extra options: c (CPU), m
5356	(memory), cm (CPU+memory). If followed by numbers 1-x, shows that number
5357	of processes for each type (default: 5; if in IRC, max: 5). " ],
5358	['1', '', '', "Make sure that there is no space between letters and
5359	numbers (e.g.^-t^cm10)." ],
5360	['1', '-u', '--uuid', "$partition_string_u UUIDs.  Use with -j, -o, -p, -P." ],
5361	['1', '-v', '--verbosity', "Set $self_name verbosity level (0-8).
5362	Should not be used with -b or -F. Example: $self_name^-v^4" ],
5363	['2', '0', '', "Same as: $self_name" ],
5364	['2', '1', '', "Basic verbose, -S + basic CPU + -G + basic Disk + -I." ],
5365	['2', '2', '', "Networking device (-N), Machine (-M), Battery (-B; if present),
5366	and, if present, basic RAID (devices only; notes if inactive).
5367	Same as $self_name^-b" ],
5368	['2', '3', '', "Advanced CPU (-C), battery (-B), network (-n);
5369	triggers -x. " ],
5370	['2', '4', '', "$partition_string_u size/used data (-P) for
5371	(if present) /, /home, /var/, /boot. Shows full disk data (-D). " ],
5372	['2', '5', '', "Audio device (-A), sensors (-s), memory/RAM (-m),
5373	bluetooth (if present), $partition_string label^(-l), full swap (-j),
5374	UUID^(-u), short form of optical drives, RAID data (if present)." ],
5375	['2', '6', '', "Full $partition_string (-p),
5376	unmounted $partition_string (-o), optical drive (-d), USB (-J),
5377	full RAID; triggers -xx." ],
5378	['2', '7', '', "Network IP data (-i), bluetooth, logical (-L),
5379	RAID forced; triggers -xxx."],
5380	['2', '8', '', "Everything available, including	repos (-r),
5381	processes (-tcm), PCI slots (--slots); triggers admin (-a)."],
5382	);
5383	# if distro maintainers don't want the weather feature disable it
5384	if ($use{'weather'}){
5385		push(@data,
5386		['1', '-w', '--weather', "Local weather data/time. To check an alternate
5387		location, see -W. NO AUTOMATED QUERIES OR EXCESSIVE USE ALLOWED!"],
5388		['1', '-W', '--weather-location', "[location] Supported options for
5389		[location]: postal code[,country/country code]; city, state (USA)/country
5390		(country/two character country code); latitude, longitude. Only use if you
5391		want the weather somewhere other than the machine running $self_name. Use
5392		only ASCII characters, replace spaces in city/state/country names with '+'.
5393		Example:^$self_name^-W^[new+york,ny^london,gb^madrid,es]"],
5394		['1', '', '--weather-source', "[1-9] Change weather data source. 1-4 generally
5395		active, 5-9 check. See man."],
5396		['1', '', '--weather-unit', "Set weather units to metric (m), imperial (i),
5397		metric/imperial (mi), or imperial/metric (im)."],
5398		);
5399	}
5400	push(@data,
5401	['1', '-y', '--width', "Output line width max (integer >= 80). Overrides IRC/Terminal
5402	settings or actual widths. If no integer give, defaults to 80. -1 removes line lengths.
5403	1 switches output to 1 key/value pair per line. Example:^inxi^-y^130" ],
5404	['1', '-z', '--filter', "Adds security filters for IP/MAC addresses, serial numbers,
5405	location (-w), user home directory name, host name. Default on for IRC clients." ],
5406	['1', '', '--filter-label', "Filters out ${partition_string} labels in -j,
5407	-o, -p, -P, -Sa." ],
5408	['1', '-Z', '--filter-override', "Override for output filters. Useful for
5409	debugging networking issues in IRC, for example." ],
5410	['1', '', '--filter-uuid', "Filters out ${partition_string} UUIDs in -j,
5411	-o, -p, -P, -Sa." ],
5412	['0', '', '', "$line" ],
5413	['0', '', '', "Extra Data Options:" ],
5414	['1', '-a', '--admin', "Adds advanced sys admin data (only works with
5415	verbose or line output, not short form); check man page for explanations!;
5416	also sets --extra=3:" ],
5417	['2', '-A', '', "If available: list of alternate kernel modules/drivers
5418	for device(s)." ],
5419	['2', '-C', '', "If available: CPU socket type, base/boost speeds
5420	(dmidecode+root/sudo/doas[BSDs] required); CPU vulnerabilities (bugs);
5421	family, model-id, stepping - format: hex (decimal) if greater
5422	than 9, otherwise hex; microcode - format: hex." ],
5423	['2', '-d,-D', '', "If available: logical and physical block sizes; drive family;
5424	maj:min, USB drive specifics; SMART report." ],
5425	['2', '-E', '', "If available: in Report:, adds Info: line: acl-mtu,
5426	sco-mtu, link-policy, link-mode, service-classes." ],
5427	['2', '-G', '', "If available: Xorg Display ID, Screens total, default Screen,
5428	current Screen; per X Screen: resolution, dpi, size, diagonal; per Monitor:
5429	resolution; hz; dpi; size; diagonal; list of alternate kernel modules/drivers
5430	for device(s)." ],
5431	['2', '-I', '', "As well as per package manager counts, also adds total
5432	number of lib files found for each package manager if not -r; adds init
5433	service tool." ],
5434	['2', '-j,-p,-P', '', "For swap (if available): swappiness and vfs cache
5435	pressure, and if values are default or not." ],
5436	['2', '-L', '', "LV, Crypto, devices, components: add maj:min; show
5437	full device/components report (speed, mapped names)." ],
5438	['2', '-n,-N', '', "If available: list of alternate kernel modules/drivers
5439	for device(s)." ],
5440	['2', '-o', '', "If available: maj:min of device." ],
5441	['2', '-p,-P', '', "If available: raw size of ${partition_string}s, maj:min,
5442	percent available for user, block size of file system (root required)." ],
5443	['2', '-r', '', "Packages, see -Ia." ],
5444	['2', '-R', '', "mdraid: device maj:min; per component: size, maj:min, state." ],
5445	['2', '-S', '', "If available: kernel boot parameters." ],
5446	['0', '', '', ''],
5447	['1', '-x', '--extra', "Adds the following extra data (only works with
5448	verbose or line output, not short form):" ],
5449	['2', '-A', '', "Specific vendor/product information (if relevant);
5450	PCI/USB ID of device; Version/port(s)/driver version (if available);
5451	non-running sound servers." ],
5452	['2', '-B', '', "Current/minimum voltage, vendor/model, status (if available);
5453	attached devices (e.g. wireless mouse, keyboard, if present)." ],
5454	['2', '-C', '', "CPU $flags (short list, use -f to see full list);
5455	CPU boost (turbo) enabled/disabled, if present;
5456	Bogomips on CPU; CPU microarchitecture + 	revision (if found, or
5457	unless --admin, then shows as 'stepping')." ],
5458	['2', '-d', '', "Extra optical drive features data; adds rev version to
5459	optical drive." ],
5460	['2', '-D', '', "HDD temp with disk data. Kernels >= 5.6: enable module
5461	drivetemp if not enabled. Older systems require hddtemp, run as
5462	as superuser, or as user if you have added hddtemp to /etc/sudoers
5463	(sudo v. 1.7 or newer)(BSDs see doas).
5464	Example:^<username>^ALL^=^NOPASSWD:^/usr/sbin/hddtemp" ],
5465	['2', '-E', '', "PCI/USB Bus ID of device, driver version,
5466	LMP version." ],
5467	['2', '-G', '', "Specific vendor/product information (if relevant);
5468	PCI/USB ID of device; Direct rendering status (in X); Screen
5469	number GPU is running on (Nvidia only)." ],
5470	['2', '-i', '', "For IPv6, show additional scope addresses: Global, Site,
5471	Temporary, Unknown. See --limit for large counts of IP addresses." ],
5472	['2', '-I', '', "Default system GCC. With -xx, also shows other installed
5473	GCC versions. If running in shell, not in IRC client, shows shell version
5474	number, if detected. Init/RC type and runlevel (if available). Total
5475	count of all packages discovered in system and not -r." ],
5476	['2', '-j', '', "Add mapped: name if partition mapped." ],
5477	['2', '-J', '', "For Device: driver." ],
5478	['2', '-L', '', "For VG > LV, and other Devices, dm:" ],
5479	['2', '-m,--memory-modules', '', "Max memory module size (if available), device type." ],
5480	['2', '-N', '', "Specific vendor/product information (if relevant);
5481	PCI/USB ID of device; Version/port(s)/driver version (if available)." ],
5482	['2', '-o,-p,-P', '', "Add mapped: name if partition mapped." ],
5483	['2', '-r', '', "Packages, see -Ix." ],
5484	['2', '-R', '', "md-raid: second RAID Info line with extra data:
5485	blocks, chunk size, bitmap (if present). Resync line, shows blocks
5486	synced/total blocks. Hardware RAID driver version, bus-ID." ],
5487	['2', '-s', '', "Basic voltages (ipmi, lm-sensors if present): 12v, 5v, 3.3v, vbat." ],
5488	['2', '-S', '', "Kernel gcc version; system base of distro (if relevant
5489	and detected)" ],
5490	['2', '-t', '', "Adds memory use output to CPU (-xt c), and CPU use to
5491	memory (-xt m)." ],
5492	);
5493	if ($use{'weather'}){
5494		push(@data,
5495		['2', '-w,-W', '', "Wind speed and direction, humidity, pressure,
5496		and time zone, if available." ]);
5497	}
5498	push(@data,
5499	['0', '', '', ''],
5500	['1', '-xx', '--extra 2', "Show extra, extra data (only works with verbose
5501	or line output, not short form):" ],
5502	['2', '-A', '', "Chip vendor:product ID for each audio device." ],
5503	['2', '-B', '', "Serial number." ],
5504	['2', '-C', '', "L1/L3 cache (if root and dmidecode installed)." ],
5505	['2', '-D', '', "Disk transfer speed; NVMe lanes; Disk serial number; LVM
5506	volume group free space (if available); disk duid (some BSDs)." ],
5507	['2', '-E', '', "Chip vendor:product ID, LMP subversion." ],
5508	['2', '-G', '', "Chip vendor:product ID for each video device; OpenGL
5509	compatibility version, if free drivers and available; Xorg compositor;
5510	alternate Xorg drivers (if available). Alternate means driver is on automatic
5511	driver check list of Xorg for the device vendor, but is not installed on system;
5512	Xorg dpi." ],
5513	['2', '-I', '', "Other detected installed gcc versions (if present). System
5514	default runlevel. Adds parent program (or pty/tty) for shell info if not in
5515	IRC. Adds Init version number, RC (if found). Adds per package manager
5516	installed package counts if not -r." ],
5517	['2', '-j,-p,-P', '', "Swap priority." ],
5518	['2', '-J', '', "Vendor:chip-ID." ],
5519	['2', '-L', '', "Show internal LVM volumes, like raid image/meta volumes;
5520	for LVM RAID, adds RAID report line (if not -R); show all components >
5521	devices, number of 'c' or 'p' indicate depth of device." ],
5522	['2', '-m,--memory-modules', '', "Manufacturer, part number; single/double bank (if found)." ],
5523	['2', '-M', '', "Chassis info, BIOS ROM size (dmidecode only), if available." ],
5524	['2', '-N', '', "Chip vendor:product ID." ],
5525	['2', '-r', '', "Packages, see -Ixx." ],
5526	['2', '-R', '', "md-raid: Superblock (if present), algorithm. If resync,
5527	shows progress bar. Hardware RAID Chip vendor:product ID." ],
5528	['2', '-s', '', "DIMM/SOC voltages (ipmi only)." ],
5529	['2', '-S', '', "Display manager (dm) in desktop output (e.g. kdm,
5530	gdm3, lightdm); active window manager if detected; desktop toolkit,
5531	if available (Xfce/KDE/Trinity only)." ],
5532	['2', '--slots', '', "Slot length." ],
5533	);
5534	if ($use{'weather'}){
5535		push(@data,
5536		['2', '-w,-W', '', "Snow, rain, precipitation, (last observed hour),
5537		cloud cover, wind chill, dew point, heat index, if available." ]
5538		);
5539	}
5540	push(@data,
5541	['0', '', '', ''],
5542	['1', '-xxx', '--extra 3', "Show extra, extra, extra data (only works
5543	with verbose or line output, not short form):" ],
5544	['2', '-A', '', "Serial number, class ID." ],
5545	['2', '-B', '', "Chemistry, cycles, location (if available)." ],
5546	['2', '-C', '', "CPU voltage, external clock speed (if root and dmidecode installed)." ],
5547	['2', '-D', '', "Firmware rev. if available; partition scheme, in some cases; disk
5548	type, rotation rpm (if available)." ],
5549	['2', '-E', '', "Serial number, class ID, HCI version and revision." ],
5550	['2', '-G', '', "Serial number, class ID." ],
5551	['2', '-I', '', "For 'Shell:' adds ([doas|su|sudo|login]) to shell name if present;
5552	adds default shell+version if different; for 'running in:' adds (SSH) if SSH session;
5553	adds wakeups: (from suspend) to Uptime." ],
5554	['2', '-J', '', "If present: Devices: serial number, interface count; USB speed; max power." ],
5555	['2', '-m,--memory-modules', '', "Width of memory bus, data and total (if present and greater
5556	than data); Detail for Type, if present; module voltage, if available; serial
5557	number." ],
5558	['2', '-N', '', "Serial number, class ID." ],
5559	['2', '-R', '', "zfs-raid: portion allocated (used) by RAID devices/arrays.
5560	md-raid: system md-raid support types (kernel support, read ahead, RAID events).
5561	Hardware RAID rev, ports, specific vendor/product information." ],
5562	['2', '-S', '', "Panel/tray/bar/dock info in desktop output, if in X (like lxpanel,
5563	xfce4-panel, mate-panel); (if available) dm version number, window manager
5564	version number, virtual terminal number."],
5565	);
5566	if ($use{'weather'}){
5567		push(@data,
5568		['2', '-w,-W', '', "Location (uses -z/irc filter), weather observation
5569		time, altitude, sunrise/sunset, if available." ]
5570		);
5571	}
5572	push(@data,
5573	[0, '', '', "$line" ],
5574	[0, '', '', "Additional Options:" ],
5575	['1', '-h', '--help', "This help menu." ],
5576 	['1', '', '--recommends', "Checks $self_name application dependencies + recommends,
5577 	and directories, then shows what package(s) you need to install to add support
5578 	for that feature." ],
5579	);
5580	if ($use{'update'}){
5581		push(@data,
5582		['1', '-U', '--update', "Auto-update $self_name. Will also install/update man
5583		page. Note: if you installed as root, you must be root to update, otherwise
5584		user is fine. Man page installs require root. No arguments downloads from
5585		main $self_name git repo." ],
5586		['1', '', '', "Use alternate sources for updating $self_name" ],
5587		['2', '1', '', "Get the git branch one version." ],
5588		['2', '2', '', "Get the git branch two version." ],
5589		['3', '3', '', "Get the dev server (smxi.org) version." ],
5590		['2', '<http>', '', "Get a version of $self_name from your own server.
5591		Use the full download path, e.g.^$self_name^-U^https://myserver.com/inxi" ],
5592		);
5593	}
5594	push(@data,
5595	['1', '-V', '--version', "Prints $self_name version info then exits." ],
5596	['0', '', '', "$line" ],
5597	['0', '', '', "Advanced Options:" ],
5598	['1', '', '--alt', "Trigger for various advanced options:" ],
5599	['2', '40', '', "Bypass Perl as a downloader option." ],
5600	['2', '41', '', "Bypass Curl as a downloader option." ],
5601	['2', '42', '', "Bypass Fetch as a downloader option." ],
5602	['2', '43', '', "Bypass Wget as a downloader option." ],
5603	['2', '44', '', "Bypass Curl, Fetch, and Wget as downloader options. Forces
5604	Perl if HTTP::Tiny present." ],
5605	['1', '', '--bt-tool', "[bt-adapter|hciconfig|rfkill] Force use of given tool for
5606	bluetooth report." ],
5607	['1', '', '--dig', "Overrides configuration item NO_DIG (resets to default)." ],
5608	['1', '', '--display', "[:[0-9]] Try to get display data out of X (default: display 0)." ],
5609	['1', '', '--dmidecode', "Force use of dmidecode data instead of /sys where relevant
5610	(e.g. -M, -B)." ],
5611	['1', '', '--downloader', "Force $self_name to use [curl|fetch|perl|wget] for downloads." ],
5612	['1', '', '--force', "[dmidecode|hddtemp|lsusb|meminfo|usb-sys|vmstat|wmctl]. 1 or more
5613	in comma separated list. Force use of item(s).
5614	See --hddtemp, --dmidecode, --wm, --usb-tool, --usb-sys." ],
5615	['1', '', '--hddtemp', "Force use of hddtemp for disk temps." ],
5616	['1', '', '--host', "Turn on hostname for -S." ],
5617	['1', '', '--html-wan', "Overrides configuration item NO_HTML_WAN (resets to default)." ],
5618	['1', '', '--limit', "[-1; 1-x] Set max output limit of IP addresses for -i
5619	(default 10; -1 removes limit)." ],
5620	);
5621	if ($use{'update'}){
5622		push(@data,
5623		['1', '', '--man', "Install correct man version for dev branch (-U 3) or pinxi using -U." ],
5624		);
5625	}
5626	push(@data,
5627	['1', '', '--no-dig', "Skip dig for WAN IP checks, use downloader program." ],
5628	['1', '', '--no-doas', "Skip internal program use of doas features (not related
5629	to starting $self_name with doas)." ],
5630	['1', '', '--no-host', "Turn off hostname for -S. Useful if showing output from servers etc.
5631	-z triggers --no-host." ],
5632	['1', '', '--no-html-wan', "Skip HTML IP sources for WAN IP checks, use dig only,
5633	or nothing if --no-dig." ],
5634	);
5635	if ($use{'update'}){
5636		push(@data,
5637		['1', '', '--no-man', "Disable man install for all -U update actions." ],
5638		);
5639	}
5640	push(@data,
5641	['1', '', '--no-ssl', "Skip SSL certificate checks for all downloader actions
5642	(Wget/Fetch/Curl/Perl-HTTP::Tiny)." ],
5643	['1', '', '--no-sudo', "Skip internal program use of sudo features (not related
5644	to starting $self_name with sudo)." ],
5645	['1', '', '--output', "[json|screen|xml] Change data output type. Requires --output-file
5646	if not screen." ],
5647	['1', '', '--output-file', "[Full filepath|print] Output file to be used for --output." ],
5648	['1', '', '--partition-sort', "[dev-base|fs|id|label|percent-used|size|uuid|used]
5649	Change sort order of ${partition_string} output. See man page for specifics." ],
5650	['1', '', '--pkg', "Force use of disabled package manager counts for packages feature.
5651	RPM disabled by default due to possible massive rpm package query times." ],
5652	['1', '', '--sensors-default', "Removes configuration item SENSORS_USE and SENSORS_EXCLUDE.
5653	Same as default behavior." ],
5654	['1', '', '--sensors-exclude', "[sensor[s] name, comma separated] Exclude supplied sensor
5655	array[s] for -s output (lm-sensors, Linux only)." ],
5656	['1', '', '--sensors-use', "[sensor[s] name, comma separated] Use only supplied sensor
5657	array[s] for -s output (lm-sensors, Linux only)." ],
5658	['1', '', '--sleep', "[0-x.x] Change CPU sleep time, in seconds, for -C
5659	(default:^$cpu_sleep). Allows system to catch up and show a more accurate CPU
5660	use. Example:^$self_name^-Cxxx^--sleep^0.15" ],
5661	['1', '', '--tty', "Forces irc flag to false. Generally useful if $self_name is running
5662	inside of another tool like Chef or MOTD and returns corrupted color codes. Please see
5663	man page or file an issue if you need to use this flag. Must use -y [width] option if
5664	you want a specific output width. Always put this option first in an option list."],
5665	['1', '', '--usb-sys', "Force USB data to use only /sys as data source (Linux only)." ],
5666	['1', '', '--usb-tool', "Force USB data to use lsusb as data source [default]
5667	(Linux only)." ],
5668	['1', '', '--wan-ip-url', "[URL] Skips dig, uses supplied URL for WAN IP (-i).
5669	URL output must end in the IP address. See man.
5670	Example:^$self_name^-i^--wan-ip-url^https://yoursite.com/ip.php" ],
5671	['1', '', '--wm', "Force wm: to use wmctrl as data source. Default uses ps." ],
5672	['1', '', '--wrap-max', "Set maximum width where $self_name autowraps line starters
5673	(previously --indent-min). Current: $size{'wrap-max'}" ],
5674	['0', '', '', $line ],
5675	['0', '', '', "Debugging Options:" ],
5676	['1', '', '--dbg', "Specific debuggers, change often. Only 1 is constant:" ],
5677	['2', '1', '', "Show downloader output. Turns off quiet mode." ],
5678	['1', '', '--debug', "Triggers debugging modes." ],
5679	['2', '1-3', '', "On screen debugger output." ],
5680	['2', '10', '', "Basic logging." ],
5681	['2', '11', '', "Full file/system info logging." ],
5682	['1', '', ,'', "The following create a tar.gz file of system data, plus $self_name
5683	output. To automatically upload debugger data tar.gz file
5684	to ftp.smxi.org: $self_name^--debug^21" ],
5685	['2', '20', '', "Full system data collection: /sys; xorg conf and log data, xrandr,
5686	xprop, xdpyinfo, glxinfo etc.; data from dev, disks,
5687	${partition_string}s, etc." ],
5688	['2', '21', '', "Upload debugger dataset to $self_name debugger server
5689	automatically, removes debugger data directory, leaves tar.gz debugger file." ],
5690	['2', '22', '', "Upload debugger dataset to $self_name debugger server
5691	automatically, removes debugger data directory and debugger tar.gz file." ],
5692	# ['1', '', '--debug-filter', "Add -z flag to debugger $self_name optiions." ],
5693	['1', '', '--debug-proc', "Force debugger parsing of /proc as sudo/doas/root." ],
5694	['1', '', '--debug-proc-print', "To locate file that /proc debugger hangs on." ],
5695	['1', '', '--debug-no-exit', "Skip exit on error to allow completion." ],
5696	['1', '', '--debug-no-proc', "Skip /proc debugging in case of a hang." ],
5697	['1', '', '--debug-no-sys', "Skip /sys debugging in case of a hang." ],
5698	['1', '', '--debug-sys', "Force PowerPC debugger parsing of /sys as sudo/doas/root." ],
5699	['1', '', '--debug-sys-print', "To locate file that /sys debugger hangs on." ],
5700	['1', '', '--ftp', "Use with --debugger 21 to trigger an alternate FTP server for upload.
5701	Format:^[ftp.xx.xx/yy]. Must include a remote directory to upload to.
5702	Example:^$self_name^--debug^21^--ftp^ftp.myserver.com/incoming" ],
5703	['0', '', '', "$line" ],
5704	);
5705	print_basic(\@data);
5706	exit 0; # shell true
5707}
5708
5709sub show_version {
5710	# if not in PATH could be either . or directory name, no slash starting
5711	my $working_path=$self_path;
5712	my (@data,$link,$self_string);
5713	Cwd->import('getcwd'); # no point loading this on top use, we only use getcwd here
5714	if ($working_path eq '.'){
5715		$working_path = getcwd();
5716	}
5717	elsif ($working_path !~ /^\//){
5718		$working_path = getcwd() . "/$working_path";
5719	}
5720	$working_path =~ s%/$%%;
5721	# handle if it's a symbolic link, rare, but can happen with directories
5722	# in irc clients which would only matter if user starts inxi with -! 30 override
5723	# in irc client
5724	if (-l "$working_path/$self_name"){
5725		$link="$working_path/$self_name";
5726		$working_path = readlink "$working_path/$self_name";
5727		$working_path =~ s/[^\/]+$//;
5728	}
5729	# strange output /./ ending, but just trim it off, I don't know how it happens
5730	$working_path =~ s%/\./%/%;
5731	push(@data, [ 0, '', '', "$self_name $self_version-$self_patch ($self_date)"]);
5732	if (!$b_irc){
5733		push(@data, [ 0, '', '', '']);
5734		my $year = (split/-/, $self_date)[0];
5735		push(@data,
5736		[ 0, '', '', "Copyright^(C)^2008-$year^Harald^Hope^aka^h2"],
5737		[ 0, '', '', "Forked from Infobash 3.02: Copyright^(C)^2005-2007^Michiel^de^Boer^aka^locsmif." ],
5738		[ 0, '', '', "Using Perl version: $]"],
5739		[ 0, '', '', "Program Location: $working_path" ],
5740		);
5741		if ($link){
5742			push(@data, [ 0, '', '', "Started via symbolic link: $link" ]);
5743		}
5744		push(@data,
5745		[ 0, '', '', '' ],
5746		[ 0, '', '', "Website:^https://github.com/smxi/inxi^or^https://smxi.org/" ],
5747		[ 0, '', '', "IRC:^irc.oftc.net channel:^#smxi" ],
5748		[ 0, '', '', "Forums:^https://techpatterns.com/forums/forum-33.html" ],
5749		[ 0, '', '', '' ],
5750		[ 0, '', '', "This program is free software; you can redistribute it and/or modify
5751		it under the terms of the GNU General Public License as published by the Free Software
5752		Foundation; either version 3 of the License, or (at your option) any later version.
5753		(https://www.gnu.org/licenses/gpl.html)" ]
5754		);
5755	}
5756	print_basic(\@data);
5757	exit 0; # shell true
5758}
5759
5760########################################################################
5761#### STARTUP DATA
5762########################################################################
5763
5764## StartClient
5765{
5766package StartClient;
5767# use warnings;
5768# use strict;
5769my $pppid = '';
5770
5771# NOTE: there's no reason to create an object, we can just access
5772# the features statically.
5773# args: none
5774# sub new {
5775# 	my $class = shift;
5776# 	my $self = {};
5777# 	# print "$f\n";
5778# 	# print "$type\n";
5779# 	return bless $self, $class;
5780# }
5781sub set {
5782	eval $start if $b_log;
5783	main::set_ps_aux() if !$loaded{'ps-aux'};
5784	if (!$b_irc){
5785		# we'll run get_shell_data for -I, but only then
5786	}
5787	else {
5788		$use{'filter'} = 1;
5789		get_client_name();
5790		if ($client{'konvi'} == 1 || $client{'konvi'} == 3){
5791			set_konvi_data();
5792		}
5793	}
5794	eval $end if $b_log;
5795}
5796
5797sub get_client_name {
5798	eval $start if $b_log;
5799	my $client_name = '';
5800	# print "$ppid\n";
5801	if ($ppid && -e "/proc/$ppid/exe"){
5802		$client_name = lc(readlink "/proc/$ppid/exe");
5803		$client_name =~ s/^.*\///;
5804		if ($client_name =~ /^(bash|csh|dash|fish|sh|python.*|perl.*|zsh)$/){
5805			$pppid = (main::grabber("ps -wwp $ppid -o ppid"))[1];
5806			# my @temp = (main::grabber("ps -wwp $ppid -o ppid 2>/dev/null"))[1];
5807			$pppid =~ s/^\s+|\s+$//g;
5808			$client_name =~ s/[0-9\.]+$//; # clean things like python2.7
5809			if ($pppid && -f "/proc/$pppid/exe"){
5810				$client_name = lc(readlink "/proc/$pppid/exe");
5811				$client_name =~ s/^.*\///;
5812				$client{'native'} = 0;
5813			}
5814		}
5815		$client{'name'} = $client_name;
5816		get_client_version();
5817		# print "c:$client_name p:$pppid\n";
5818		# print "$client{'name-print'}\n";
5819	}
5820	else {
5821		if (!check_modern_konvi()){
5822			$client_name = (main::grabber("ps -wwp $ppid 2>/dev/null"))[1];
5823			if ($client_name){
5824				my @data = split(/\s+/, $client_name);
5825				if ($bsd_type){
5826					$client_name = lc($data[4]);
5827				}
5828				# gnu/linux uses last value
5829				else {
5830					$client_name = lc($data[-1]);
5831				}
5832				$client_name =~ s/.*\|-(|)//;
5833				$client_name =~ s/[0-9\.]+$//; # clean things like python2.7
5834				$client{'name'} = $client_name;
5835				$client{'native'} = 1;
5836				get_client_version();
5837			}
5838			else {
5839				$client{'name'} = "PPID='$ppid' - Empty?";
5840			}
5841		}
5842	}
5843	if ($b_log){
5844		my $string = "Client: $client{'name'} :: version: $client{'version'} :: konvi: $client{'konvi'} :: PPID: $ppid";
5845		main::log_data('data', $string);
5846	}
5847	eval $end if $b_log;
5848}
5849sub get_client_version {
5850	eval $start if $b_log;
5851	@app = main::program_values($client{'name'});
5852	my (@data,@working,$string);
5853	if (@app){
5854		$string = ($client{'name'} =~ /^gribble|limnoria|supybot$/) ? 'supybot' : $client{'name'};
5855		$client{'version'} = main::program_version($string,$app[0],$app[1],$app[2],$app[4],$app[5],$app[6]);
5856		$client{'name-print'} = $app[3];
5857		$client{'console-irc'} = $app[4];
5858	}
5859	if ($client{'name'} =~ /^(bash|csh|fish|dash|sh|zsh)$/){
5860		$client{'name-print'} = 'shell wrapper';
5861		$client{'console-irc'} = 1;
5862	}
5863	elsif ($client{'name'} eq 'bitchx'){
5864		@data = main::grabber("$client{'name'} -v");
5865		$string = awk(\@data,'Version');
5866		if ($string){
5867			$string =~ s/[()]|bitchx-//g;
5868			@data = split(/\s+/, $string);
5869			$_=lc for @data;
5870			$client{'version'} = ($data[1] eq 'version') ? $data[2] : $data[1];
5871		}
5872	}
5873	# 'hexchat' => ['',0,'','HexChat',0,0], # special
5874	# the hexchat author decided to make --version/-v return a gtk dialogue box, lol...
5875	# so we need to read the actual config file for hexchat. Note that older hexchats
5876	# used xchat config file, so test first for default, then legacy. Because it's possible
5877	# for this file to be user edited, doing some extra checks here.
5878	elsif ($client{'name'} eq 'hexchat'){
5879		if (-f '~/.config/hexchat/hexchat.conf'){
5880			@data = main::reader('~/.config/hexchat/hexchat.conf','strip');
5881		}
5882		elsif (-f '~/.config/hexchat/xchat.conf'){
5883			@data = main::reader('~/.config/hexchat/xchat.conf','strip');
5884		}
5885		if (@data){
5886			$client{'version'} = main::awk(\@data,'version',2,'\s*=\s*');
5887		}
5888		# fingers crossed, hexchat won't open gui!!
5889		if (!$client{'version'}){
5890			@data = main::grabber("$client{'name'} --version 2>/dev/null");
5891			$client{'version'} = main::awk(\@data,'hexchat',2,'\s+');
5892		}
5893		$client{'name-print'} = 'HexChat';
5894	}
5895	# note: see legacy inxi konvi logic if we need to restore any of the legacy code.
5896	elsif ($client{'name'} eq 'konversation'){
5897		$client{'konvi'} = (!$client{'native'}) ? 2 : 1;
5898	}
5899	elsif ($client{'name'} =~ /quassel/i){
5900		@data = main::grabber("$client{'name'} -v 2>/dev/null");
5901		foreach (@data){
5902			if ($_ =~ /^Quassel IRC:/){
5903				$client{'version'} = (split(/\s+/, $_))[2];
5904				last;
5905			}
5906			elsif ($_ =~ /quassel\s[v]?[0-9]/){
5907				$client{'version'} = (split(/\s+/, $_))[1];
5908				last;
5909			}
5910		}
5911		$client{'version'} ||= '(pre v0.4.1)?';
5912	}
5913	# then do some perl type searches, do this last since it's a wildcard search
5914	elsif ($client{'name'} =~ /^(perl.*|ksirc|dsirc)$/){
5915		my @cmdline = main::get_cmdline();
5916		# Dynamic runpath detection is too complex with KSirc, because KSirc is started from
5917		# kdeinit. /proc/<pid of the grandparent of this process>/exe is a link to /usr/bin/kdeinit
5918		# with one parameter which contains parameters separated by spaces(??), first param being KSirc.
5919		# Then, KSirc runs dsirc as the perl irc script and wraps around it. When /exec is executed,
5920		# dsirc is the program that runs inxi, therefore that is the parent process that we see.
5921		# You can imagine how hosed I am if I try to make inxi find out dynamically with which path
5922		# KSirc was run by browsing up the process tree in /proc. That alone is straightjacket material.
5923		# (KSirc sucks anyway ;)
5924		foreach (@cmdline){
5925			if ($_ =~ /dsirc/){
5926				$client{'version'} = main::program_version('ksirc','KSirc:',2,'-v',0,0);
5927				$client{'name'} = 'ksirc';
5928				$client{'name-print'} = 'KSirc';
5929			}
5930		}
5931		$client{'console-irc'} = 1;
5932		perl_python_client();
5933	}
5934	elsif ($client{'name'} =~ /python/){
5935		perl_python_client();
5936	}
5937	# NOTE: these must be empirically determined, not all events that
5938	# show no tty are actually IRC. tmux is not a vt, but runs inside one
5939	if (!$client{'name-print'}){
5940		my $wl_terms = 'alacritty|altyo|black-screen|conhost|doas|evilvte|';
5941		$wl_terms .= 'germinal|guake|havoc|hyper|kate|kitty|kmscon|konsole|login|';
5942		$wl_terms .= 'macwise|minicom|putty|rxvt|sakura|securecrt|shellinabox|';
5943		$wl_terms .= '^st$|sudo|term|tilda|tilix|tmux|tym|wayst|xiki|';
5944		$wl_terms .= 'yaft|yakuake|\bzoc\b';
5945		my $wl_clients = 'ansible|chef|run-parts|sshd';
5946		my $whitelist = "$wl_terms|$wl_clients";
5947		# print "$client{'name'}\n";
5948		if ($client{'name'} =~ /($whitelist)/i){
5949			if ($client{'name'} =~ /($wl_terms)/i){
5950				ShellData::set();
5951			}
5952			else {
5953				$client{'name-print'} = $client{'name'};
5954			}
5955			$b_irc = 0;
5956		}
5957		else {
5958			$client{'name-print'} = 'Unknown Client: ' . $client{'name'};
5959		}
5960	}
5961	eval $end if $b_log;
5962}
5963sub get_cmdline {
5964	eval $start if $b_log;
5965	my @cmdline;
5966	my $i = 0;
5967	if (! -e "/proc/$ppid/cmdline"){
5968		return 1;
5969	}
5970	local $\ = '';
5971	open(my $fh, '<', "/proc/$ppid/cmdline") or
5972	  print_line("Open /proc/$ppid/cmdline failed: $!");
5973	my @rows = <$fh>;
5974	close $fh;
5975	foreach (@rows){
5976		push(@cmdline, $_);
5977		$i++;
5978		last if $i > 31;
5979	}
5980	if ($i == 0){
5981		$cmdline[0] = $rows[0];
5982		$i = ($cmdline[0]) ? 1 : 0;
5983	}
5984	main::log_data('string',"cmdline: @cmdline count: $i") if $b_log;
5985	eval $end if $b_log;
5986	return @cmdline;
5987}
5988sub perl_python_client {
5989	eval $start if $b_log;
5990	return 1 if $client{'version'};
5991	# this is a hack to try to show konversation if inxi is running but started via /cmd
5992	# OR via program shortcuts, both cases in fact now
5993	# main::print_line("konvi: " . scalar grep { $_ =~ /konversation/ } @ps_cmd);
5994	if ($b_display && main::check_program('konversation') &&
5995	 (grep { $_ =~ /konversation/ } @ps_cmd)){
5996		@app = main::program_values('konversation');
5997		$client{'version'} = main::program_version('konversation',$app[0],$app[1],$app[2],$app[5],$app[6]);
5998		$client{'name'} = 'konversation';
5999		$client{'name-print'} = $app[3];
6000		$client{'console-irc'} = $app[4];
6001	}
6002	## NOTE: supybot only appears in ps aux using 'SHELL' command; the 'CALL' command
6003	## gives the user system irc priority, and you don't see supybot listed, so use SHELL
6004	elsif (!$b_display &&
6005	 (main::check_program('supybot') ||
6006	 main::check_program('gribble') || main::check_program('limnoria')) &&
6007	 (grep { $_ =~ /supybot/ } @ps_cmd)){
6008		@app = main::program_values('supybot');
6009		$client{'version'} = main::program_version('supybot',$app[0],$app[1],$app[2],$app[5],$app[6]);
6010		if ($client{'version'}){
6011			if (grep { $_ =~ /gribble/ } @ps_cmd){
6012				$client{'name'} = 'gribble';
6013				$client{'name-print'} = 'Gribble';
6014			}
6015			if (grep { $_ =~ /limnoria/ } @ps_cmd){
6016				$client{'name'} = 'limnoria';
6017				$client{'name-print'} = 'Limnoria';
6018			}
6019			else {
6020				$client{'name'} = 'supybot';
6021				$client{'name-print'} = 'Supybot';
6022			}
6023		}
6024		else {
6025			$client{'name'} = 'supybot';
6026			$client{'name-print'} = 'Supybot';
6027		}
6028		$client{'console-irc'} = 1;
6029	}
6030	else {
6031		$client{'name-print'} = "Unknown $client{'name'} client";
6032	}
6033	if ($b_log){
6034		my $string = "namep: $client{'name-print'} name: $client{'name'} version: $client{'version'}";
6035		main::log_data('data',$string);
6036	}
6037	eval $end if $b_log;
6038}
6039## try to infer the use of Konversation >= 1.2, which shows $PPID improperly
6040## no known method of finding Konvi >= 1.2 as parent process, so we look to see if it is running,
6041## and all other irc clients are not running. As of 2014-03-25 this isn't used in my cases
6042sub check_modern_konvi {
6043	eval $start if $b_log;
6044	return 0 if !$client{'qdbus'};
6045	my $b_modern_konvi = 0;
6046	my $konvi_version = '';
6047	my $konvi = '';
6048	my $pid = '';
6049	my (@temp);
6050	# main::log_data('data',"name: $client{'name'} :: qdb: $client{'qdbus'} :: version: $client{'version'} :: konvi: $client{'konvi'} :: PPID: $ppid") if $b_log;
6051	# sabayon uses /usr/share/apps/konversation as path
6052	if (-d '/usr/share/kde4/apps/konversation' || -d '/usr/share/apps/konversation'){
6053		$pid = main::awk(\@ps_aux,'konversation -session',2,'\s+');
6054		main::log_data('data',"pid: $pid") if $b_log;
6055		$konvi = readlink ("/proc/$pid/exe");
6056		$konvi =~ s/^.*\///; # basename
6057		@app = main::program_values('konversation');
6058		if ($konvi){
6059			@app = main::program_values('konversation');
6060			$konvi_version = main::program_version($konvi,$app[0],$app[1],$app[2],$app[5],$app[6]);
6061			@temp = split('\.', $konvi_version);
6062			$client{'console-irc'} = $app[4];
6063			$client{'konvi'} = 3;
6064			$client{'name'} = 'konversation';
6065			$client{'name-print'} = $app[3];
6066			$client{'version'} = $konvi_version;
6067			# note: we need to change this back to a single dot number, like 1.3, not 1.3.2
6068			$konvi_version = $temp[0] . "." . $temp[1];
6069			if ($konvi_version > 1.1){
6070				$b_modern_konvi = 1;
6071			}
6072		}
6073	}
6074	main::log_data('data',"name: $client{'name'} name print: $client{'name-print'}
6075	qdb: $client{'qdbus'} version: $konvi_version konvi: $konvi PID: $pid") if $b_log;
6076	main::log_data('data',"b_is_qt4: $b_modern_konvi") if $b_log;
6077	## for testing this module
6078# 	my $ppid = getppid();
6079# 	system('qdbus org.kde.konversation', '/irc', 'say', $client{'dserver'}, $client{'dtarget'},
6080# 	"getpid_dir: $konvi_qt4 verNum: $konvi_version pid: $pid ppid: $ppid");
6081	eval $end if $b_log;
6082	return $b_modern_konvi;
6083}
6084
6085sub set_konvi_data {
6086	eval $start if $b_log;
6087	my $config_tool = '';
6088	# https://userbase.kde.org/Konversation/Scripts/Scripting_guide
6089	if ($client{'konvi'} == 3){
6090		$client{'dserver'} = shift @ARGV;
6091		$client{'dtarget'} = shift @ARGV;
6092		$client{'dobject'} = 'default';
6093	}
6094	elsif ($client{'konvi'} == 1){
6095		$client{'dport'} = shift @ARGV;
6096		$client{'dserver'} = shift @ARGV;
6097		$client{'dtarget'} = shift @ARGV;
6098		$client{'dobject'} = 'Konversation';
6099	}
6100	# for some reason this logic hiccups on multiple spaces between args
6101	@ARGV = grep { $_ ne '' } @ARGV;
6102	# there's no current kde 5 konvi config tool that we're aware of. Correct if changes.
6103	if (main::check_program('kde4-config')){
6104		$config_tool = 'kde4-config';
6105	}
6106	elsif (main::check_program('kde5-config')){
6107		$config_tool = 'kde5-config';
6108	}
6109	elsif (main::check_program('kde-config')){
6110		$config_tool = 'kde-config';
6111	}
6112	# The section below is on request of Argonel from the Konversation developer team:
6113	# it sources config files like $HOME/.kde/share/apps/konversation/scripts/inxi.conf
6114	if ($config_tool){
6115		my @data = main::grabber("$config_tool --path data 2>/dev/null",':');
6116		Configs::set(\@data);
6117	}
6118	eval $end if $b_log;
6119}
6120}
6121
6122########################################################################
6123#### OUTPUT
6124########################################################################
6125
6126#### -------------------------------------------------------------------
6127#### FILTERS AND TOOLS
6128#### -------------------------------------------------------------------
6129
6130sub apply_filter {
6131	my ($string) = @_;
6132	if ($string){
6133		$string = ($use{'filter'}) ? $filter_string : $string;
6134	}
6135	else {
6136		$string = 'N/A';
6137	}
6138	return $string;
6139}
6140
6141# note, let the print logic handle N/A cases
6142sub apply_partition_filter {
6143	my ($source,$string,$type) = @_;
6144	return $string if !$string || $string eq 'N/A';
6145	if ($source eq 'system'){
6146		my $test = ($type eq 'label') ? '=LABEL=': '=UUID=';
6147		$string =~ s/$test[^\s]+/$test$filter_string/g;
6148	}
6149	else {
6150		$string = $filter_string;
6151	}
6152	return $string;
6153}
6154
6155sub arm_cleaner {
6156	my ($item) = @_;
6157	$item =~ s/(\([^\(]*Device Tree[^\)]*\))//gi;
6158	$item =~ s/\s\s+/ /g;
6159	$item =~ s/^\s+|\s+$//g;
6160	return $item;
6161}
6162
6163sub clean_characters {
6164	my ($data) = @_;
6165	# newline, pipe, brackets, + sign, with space, then clear doubled
6166	# spaces and then strip out trailing/leading spaces.
6167	# etc/issue often has junk stuff like (\l)  \n \l
6168	return if !$data;
6169	$data =~ s/[:\47]|\\[a-z]|\n|,|\"|\*|\||\+|\[\s\]|n\/a|\s\s+/ /g;
6170	$data =~ s/\(\s*\)//;
6171	$data =~ s/^\s+|\s+$//g;
6172	return $data;
6173}
6174
6175sub cleaner {
6176	my ($item) = @_;
6177	return $item if !$item;# handle cases where it was 0 or ''
6178	# note: |nee trips engineering, but I don't know why nee was filtered
6179	$item =~ s/chipset|company|components|computing|computer|corporation|communications|electronics|electrical|electric|gmbh|group|incorporation|industrial|international|\bnee\b|no\sstring|revision|semiconductor|software|technologies|technology|ltd\.|<ltd>|\bltd\b|inc\.|<inc>|\binc\b|intl\.|co\.|<co>|corp\.|<corp>|\(tm\)|\(r\)|®|\(rev ..\)|\'|\"|\sinc\s*$|\?//gi;
6180	$item =~ s/,|\*/ /g;
6181	$item =~ s/\s\s+/ /g;
6182	$item =~ s/^\s+|\s+$//g;
6183	return $item;
6184}
6185
6186sub disk_cleaner {
6187	my ($item) = @_;
6188	return $item if !$item;
6189	# <?unknown>?|
6190	$item =~ s/vendor.*|product.*|O\.?E\.?M\.?//gi;
6191	$item =~ s/\s\s+/ /g;
6192	$item =~ s/^\s+|\s+$//g;
6193	return $item;
6194}
6195
6196sub dmi_cleaner {
6197	my ($string) = @_;
6198	my $cleaner = '^Base Board .*|^Chassis .*|empty|Undefined.*|.*O\.E\.M\..*|.*OEM.*|^Not .*';
6199	$cleaner .= '|^System .*|.*unknow.*|.*N\/A.*|none|^To be filled.*|^0x[0]+$';
6200	$cleaner .= '|\[Empty\]|<Bad Index>|<OUT OF SPEC>|Default string|^\.\.$|Manufacturer.*';
6201	$cleaner .= '|AssetTagNum|Manufacturer| Or Motherboard|PartNum.*|\bOther\b.*|SerNum';
6202	$string =~ s/$cleaner//i;
6203	$string =~ s/^\s+|\bbios\b|\bacpi\b|\s+$//gi;
6204	$string =~ s/http:\/\/www.abit.com.tw\//Abit/i;
6205	$string =~ s/\s\s+/ /g;
6206	$string =~ s/^\s+|\s+$//g;
6207	$string = remove_duplicates($string) if $string;
6208	return $string;
6209}
6210
6211sub general_cleaner {
6212	my ($string) = @_;
6213	my $cleaner = '\b(defauult string|empty|none|undefined.*|unknown|unspecified)\b';
6214	$string =~ s/$cleaner//i;
6215	return $string;
6216}
6217
6218# args: $1 - vendor id; $2 - product id
6219# returns print ready vendor:chip id string, or na variants
6220sub get_chip_id {
6221	my ($vendor,$product)= @_;
6222	my $id = 'N/A';
6223	if ($vendor && $product){
6224		$id = "$vendor:$product";
6225	}
6226	elsif ($vendor){
6227		$id = "$vendor:n/a";
6228	}
6229	elsif ($product){
6230		$id = "n/a:$product";
6231	}
6232	return $id;
6233}
6234# args: $1 - size in KB, return KB, MB, GB, TB, PB, EB; $2 - 'string';
6235# $3 - default value if null
6236# returns string with units or array or size unmodified if not numeric
6237sub get_size {
6238	my ($size,$type,$empty) = @_;
6239	my (@data);
6240	$type ||= '';
6241	$empty ||= '';
6242	return $empty if !defined $size;
6243	if (!is_numeric($size)){
6244		$data[0] = $size;
6245		$data[1] = '';
6246	}
6247	elsif ($size > 1024**5){
6248		$data[0] = sprintf("%.2f",$size/1024**5);
6249		$data[1] = 'EiB';
6250	}
6251	elsif ($size > 1024**4){
6252		$data[0] = sprintf("%.2f",$size/1024**4);
6253		$data[1] = 'PiB';
6254	}
6255	elsif ($size > 1024**3){
6256		$data[0] = sprintf("%.2f",$size/1024**3);
6257		$data[1] = 'TiB';
6258	}
6259	elsif ($size > 1024**2){
6260		$data[0] = sprintf("%.2f",$size/1024**2);
6261		$data[1] = 'GiB';
6262	}
6263	elsif ($size > 1024){
6264		$data[0] = sprintf("%.1f",$size/1024);
6265		$data[1] = 'MiB';
6266	}
6267	else {
6268		$data[0] = sprintf("%.0f",$size);
6269		$data[1] = 'KiB';
6270	}
6271	$data[0] += 0 if $data[1]; # trim trailing 0s
6272	# note: perl throws strict error if you try to convert string to int
6273	# $data[0] = int($data[0]) if $b_int && $data[0];
6274	if ($type eq 'string'){
6275		return ($data[1]) ? join(' ', @data) : $size;
6276	}
6277	else {
6278		return @data;
6279	}
6280}
6281
6282# not used, but keeping logic for now
6283sub increment_starters {
6284	my ($key,$indexes) = @_;
6285	my $result = $key;
6286	if (defined $indexes->{$key}){
6287		$indexes->{$key}++;
6288		$result = "$key-$indexes->{$key}";
6289	}
6290	return $result;
6291}
6292
6293sub pci_cleaner {
6294	my ($string,$type) = @_;
6295	# print "st1 $type:$string\n";
6296	my $filter = 'and\ssubsidiaries|compatible\scontroller|';
6297	$filter .= '\b(device|controller|connection|multimedia)\b|\([^)]+\)';
6298	# \[[^\]]+\]$| not trimming off ending [...] initial type filters removes end
6299	$filter = '\[[^\]]+\]$|' . $filter if $type eq 'pci';
6300	$string =~ s/($filter)//ig;
6301	$string =~ s/\s\s+/ /g;
6302	$string =~ s/^\s+|\s+$//g;
6303	# print "st2 $type:$string\n";
6304	$string = remove_duplicates($string) if $string;
6305	return $string;
6306}
6307
6308sub pci_cleaner_subsystem {
6309	my ($string) = @_;
6310	# we only need filters for features that might use vendor, -AGN
6311	my $filter = 'and\ssubsidiaries|adapter|(hd\s)?audio|definition|desktop|ethernet|';
6312	$filter .= 'gigabit|graphics|hdmi(\/[\S]+)?|high|integrated|motherboard|network|onboard|';
6313	$filter .= 'raid|pci\s?express';
6314	$string =~ s/\b($filter)\b//ig;
6315	$string =~ s/\s\s+/ /g;
6316	$string =~ s/^\s+|\s+$//g;
6317	return $string;
6318}
6319
6320sub pci_long_filter {
6321	my ($string) = @_;
6322	if ($string =~ /\[AMD(\/ATI)?\]/){
6323		$string =~ s/Advanced\sMicro\sDevices\s\[AMD(\/ATI)?\]/AMD/;
6324	}
6325	return $string;
6326}
6327
6328# Use sparingly, but when we need regex type stuff
6329# stripped out for reliable string compares, it's better.
6330# sometimes the pattern comes from unknown strings
6331# which can contain regex characters, get rid of those
6332sub regex_cleaner {
6333	my ($string) = @_;
6334	return if !$string;
6335	$string =~ s/(\{|\}|\(|\)|\[|\]|\|)/ /g;
6336	$string =~ s/\s\s+/ /g;
6337	$string =~ s/^\s+|\s+$//g;
6338	return $string;
6339}
6340
6341sub remove_duplicates {
6342	my ($string) = @_;
6343	return if !$string;
6344	my $holder = '';
6345	my (@temp);
6346	foreach (split(/\s+/, $string)){
6347		if ($holder ne $_){
6348			push(@temp, $_);
6349		}
6350		$holder = $_;
6351	}
6352	$string = join(' ', @temp);
6353	return $string;
6354}
6355
6356sub row_defaults {
6357	my ($type,$id) = @_;
6358	$id ||= '';
6359	my %unfound = (
6360	'arm-cpu-f' => 'Use -f option to see features',
6361	'arm-pci' => 'No ARM data found for this feature.',
6362	'battery-data' => 'No system battery data found. Is one present?',
6363	'battery-data-bsd' => 'No battery data found. Try with --dmidecode',
6364	'battery-data-sys' => 'No /sys data found.',
6365	'bluetooth-data' => 'No bluetooth data found.',
6366	'bluetooth-down' => "tool can't run",
6367	'cpu-bugs-null' => 'No CPU vulnerability/bugs data available.',
6368	'cpu-model-null' => 'Model N/A',
6369	'cpu-speeds' => 'No per core speed data found.',
6370	'darwin-feature' => 'Feature not supported iu Darwin/OSX.',
6371	'disk-data' => 'No disk data found.',
6372	'disk-data-bsd' => 'No disk data found.',
6373	'disk-size-0' => 'Total N/A',
6374	'display-console' => 'No advanced graphics data found on this system in console.',
6375	'display-driver-na' => 'n/a (using device driver)',
6376	'display-null' => 'No advanced graphics data found on this system.',
6377	'display-root' => 'Advanced graphics data unavailable in console for root.',
6378	'display-root-x' => 'Advanced graphics data unavailable for root.',
6379	'display-server' => 'No display server data found. Headless machine?',
6380	'glxinfo-missing' => 'Unable to show advanced data. Required tool glxinfo missing.',
6381	'gl-empty' => 'Unset. Missing GL driver?',
6382	'display-try' => 'Advanced graphics data unavailable in console. Try -G --display',
6383	'dev' => 'Feature under development',
6384	'dmesg-boot-permissions' => 'dmesg.boot permissions',
6385	'dmesg-boot-missing' => 'dmesg.boot not found',
6386	'IP' => "No $id found. Connected to web? SSL issues?",
6387	'dmidecode-dev-mem' => 'dmidecode is not allowed to read /dev/mem',
6388	'dmidecode-smbios' => 'No SMBIOS data for dmidecode to process',
6389	'IP-dig' => "No $id found. Connected to web? SSL issues? Try --no-dig",
6390	'IP-no-dig' => "No $id found. Connected to web? SSL issues? Try enabling dig",
6391	'logical-data' => 'No logical block device data found.',
6392	'logical-data-bsd' => "Logical block device feature unsupported in $id.",
6393	'machine-data' => 'No machine data: try newer kernel.',
6394	'machine-data-bsd' => 'No machine data: Is dmidecode installed? Try -M --dmidecode.',
6395	'machine-data-dmidecode' => 'No machine data: try newer kernel. Is dmidecode installed? Try -M --dmidecode.',
6396	'machine-data-force-dmidecode' => 'No machine data: try newer kernel. Is dmidecode installed? Try -M --dmidecode.',
6397	'mips-pci' => 'No MIPS data found for this feature.',
6398	'note-check' => 'check',
6399	'note-est' => 'est.',
6400	'optical-data' => 'No optical or floppy data found.',
6401	'optical-data-bsd' => 'No optical or floppy data found.',
6402	'output-limit' => "Output throttled. IPs: $id; Limit: $limit; Override: --limit [1-x;-1 all]",
6403	'package-data' => 'No packages detected. Unsupported package manager?',
6404	'partition-data' => 'No partition data found.',
6405	'partition-hidden' => 'N/A (hidden?)',
6406	'pci-advanced-data' => 'bus/chip ids unavailable',
6407	'pci-card-data' => 'No device data found.',
6408	'pci-card-data-root' => 'Device data requires root.',
6409	'pci-slot-data' => 'No PCI Slot data found.',
6410	'pm-disabled' => 'see --pkg',
6411	'ps-data-null' => 'No process data available.',
6412	'raid-data' => 'No RAID data found.',
6413	'ram-data' => 'No RAM data found.',
6414	'ram-data-complete' => 'For complete report, try with --dmidecode',
6415	'ram-data-dmidecode' => 'No RAM data found. Try with --dmidecode',
6416	'recommends' => 'see --recommends',
6417	'repo-data', "No repo data detected. Does $self_name support your package manager?",
6418	'repo-data-bsd', "No repo data detected. Does $self_name support $id?",
6419	'root-feature' => 'Feature requires superuser permissions.',
6420	'root-item-incomplete' => "Full $id report requires superuser permissions.",
6421	'root-required' => '<superuser required>',
6422	'root-suggested' => 'try sudo/root',# gdm only
6423	'sensors-data-bsd' => "$id sensor data found but not usable.",
6424	'sensors-data-bsd-ok' => 'No sensor data found. Are sensors present?',
6425	'sensors-data-ipmi' => 'No ipmi sensor data found.',
6426	'sensors-data-linux' => 'No sensor data found. Is lm-sensors configured?',
6427	'sensors-ipmi-root' => 'Unable to run ipmi sensors. Root privileges required.',
6428	'smartctl-command' => 'A mandatory SMART command failed. Various possible causes.',
6429	'smartctl-open' => 'Unable to open device. Wrong device ID given?',
6430	'smartctl-udma-crc' => 'Bad cable/connection?',
6431	'smartctl-usb' => 'Unknown USB bridge. Flash drive/Unsupported enclosure?',
6432	'swap-admin' => 'No admin swap data available.',
6433	'swap-data' => 'No swap data was found.',
6434	'tool-missing-basic' => "<missing: $id>",
6435	'tool-missing-incomplete' => "Missing system tool: $id. Output will be incomplete",
6436	'tool-missing-os' => "No OS support. Is a comparable $id tool available?",
6437	'tool-missing-recommends' => "Required tool $id not installed. Check --recommends",
6438	'tool-missing-required' => "Required program $id not available",
6439	'tool-permissions' => "Unable to run $id. Root privileges required.",
6440	'tool-present' => 'Present and working',
6441	'tool-unknown-error' => "Unknown $id error. Unable to generate data.",
6442	'tools-missing' => "This feature requires one of these tools: $id",
6443	'tools-missing-bsd' => "This feature requires one of these tools: $id",
6444	'unmounted-data' => 'No unmounted partitions found.',
6445	'unmounted-data-bsd' => "Unmounted partition feature unsupported in $id.",
6446	'unmounted-file' => 'No /proc/partitions file found.',
6447	'usb-data' => 'No USB data found. Server?',
6448	'unknown-desktop-version' => 'ERR-101',
6449	'unknown-dev' => 'ERR-102',
6450	'unknown-shell' => 'ERR-100',
6451	'weather-error' => "Error: $id",
6452	'weather-null' => "No $id found. Internet connection working?",
6453	);
6454	return $unfound{$type};
6455}
6456
6457# convert string passed to KB, based on GB/MB/TB id
6458# NOTE: K 1024 KB 1000 KiB 1024
6459# The logic will turn false MB to M for this tool
6460# Hopefully one day sizes will all be in KiB type units
6461sub translate_size {
6462	my ($working) = @_;
6463	my ($size,$unit) = (0,'');
6464	# print ":$working:\n";
6465	return if ! defined $working;
6466	my $math = ($working =~ /B$/) ? 1000: 1024;
6467	if ($working =~ /^([0-9\.]+)\s*([kKMGTPE])i?B?$/i){
6468		$size = $1;
6469		$unit = uc($2);
6470	}
6471	if ($unit eq 'K'){
6472		$size = $1;
6473	}
6474	elsif ($unit eq 'M'){
6475		$size = $1 * $math;
6476	}
6477	elsif ($unit eq 'G'){
6478		$size = $1 * $math**2;
6479	}
6480	elsif ($unit eq 'T'){
6481		$size = $1 * $math**3;
6482	}
6483	elsif ($unit eq 'P'){
6484		$size = $1 * $math**4;
6485	}
6486	elsif ($unit eq 'E'){
6487		$size = $1 * $math**5;
6488	}
6489	$size = int($size) if $size;
6490	return $size;
6491}
6492
6493#### -------------------------------------------------------------------
6494#### GENERATE OUTPUT
6495#### -------------------------------------------------------------------
6496
6497sub check_output_path {
6498	my ($path) = @_;
6499	my ($b_good,$dir,$file);
6500	$dir = $path;
6501	$dir =~ s/([^\/]+)$//;
6502	$file = $1;
6503	# print "file: $file : dir: $dir\n";
6504	$b_good = 1 if (-d $dir && -w $dir && $dir =~ /^\// && $file);
6505	return $b_good;
6506}
6507
6508# passing along hash ref
6509sub output_handler {
6510	my ($data) = @_;
6511	# print Dumper \%data;
6512	if ($output_type eq 'screen'){
6513		print_data($data);
6514	}
6515	elsif ($output_type eq 'json'){
6516		generate_json($data);
6517	}
6518	elsif ($output_type eq 'xml'){
6519		generate_xml($data);
6520	}
6521}
6522
6523# passing along hash ref
6524# NOTE: file has already been set and directory verified
6525sub generate_json {
6526	eval $start if $b_log;
6527	my ($data) = @_;
6528	my ($json);
6529	my $b_debug = 0;
6530	my ($b_cpanel,$b_valid);
6531	error_handler('not-in-irc', 'help') if $b_irc;
6532	print Dumper $data if $b_debug;
6533	if (check_perl_module('Cpanel::JSON::XS')){
6534		Cpanel::JSON::XS->import;
6535		$json = Cpanel::JSON::XS::encode_json($data);
6536	}
6537	elsif (check_perl_module('JSON::XS')){
6538		JSON::XS->import;
6539		$json = JSON::XS::encode_json($data);
6540	}
6541	else {
6542		error_handler('required-module', 'json', 'Cpanel::JSON::XS OR JSON::XS');
6543	}
6544	if ($json){
6545		#$json =~ s/"[0-9]+#/"/g;
6546		if ($output_file eq 'print'){
6547			#$json =~ s/\}/}\n/g;
6548			print "$json";
6549		}
6550		else {
6551			print_line("Writing JSON data to: $output_file\n");
6552			open(my $fh, '>', $output_file) or error_handler('open',$output_file,"$!");
6553			print $fh "$json";
6554			close $fh;
6555			print_line("Data written successfully.\n");
6556		}
6557	}
6558	eval $end if $b_log;
6559}
6560
6561# NOTE: So far xml is substantially more difficult than json, so
6562# using a crude dumper rather than making a nice xml file, but at
6563# least xml has some output now.
6564sub generate_xml {
6565	eval $start if $b_log;
6566	my ($data) = @_;
6567	my ($xml);
6568	my $b_debug = 0;
6569	error_handler('not-in-irc', 'help') if $b_irc;
6570	# print Dumper $data if $b_debug;
6571	if (check_perl_module('XML::Dumper')){
6572		XML::Dumper->import;
6573		$xml = XML::Dumper::pl2xml($data);
6574		#$xml =~ s/"[0-9]+#/"/g;
6575		if ($output_file eq 'print'){
6576			print "$xml";
6577		}
6578		else {
6579			print_line("Writing XML data to: $output_file\n");
6580			open(my $fh, '>', $output_file) or error_handler('open',$output_file,"$!");
6581			print $fh "$xml";
6582			close $fh;
6583			print_line("Data written successfully.\n");
6584		}
6585	}
6586	else {
6587		error_handler('required-module', 'xml', 'XML::Dumper');
6588	}
6589	eval $end if $b_log;
6590}
6591
6592sub key {
6593	return sprintf("%03d#%s#%s#%s", $_[0],$_[1],$_[2],$_[3]);
6594}
6595
6596sub print_basic {
6597	my ($data) = @_;
6598	my $indent = 18;
6599	my $indent_static = 18;
6600	my $indent1_static = 5;
6601	my $indent2_static = 8;
6602	my $indent1 = 5;
6603	my $indent2 = 8;
6604	my $length =  @$data;
6605	my ($start,$i,$j,$line);
6606
6607	if ($size{'max'} > 110){
6608		$indent_static = 22;
6609	}
6610	elsif ($size{'max'} < 90){
6611		$indent_static = 15;
6612	}
6613	# print $length . "\n";
6614	for my $i (0 .. $#$data){
6615		# print "0: $data->[$i][0]\n";
6616		if ($data->[$i][0] == 0){
6617			$indent = 0;
6618			$indent1 = 0;
6619			$indent2 = 0;
6620		}
6621		elsif ($data->[$i][0] == 1){
6622			$indent = $indent_static;
6623			$indent1 = $indent1_static;
6624			$indent2= $indent2_static;
6625		}
6626		elsif ($data->[$i][0] == 2){
6627			$indent = ($indent_static + 7);
6628			$indent1 = ($indent_static + 5);
6629			$indent2 = 0;
6630		}
6631		$data->[$i][3] =~ s/\n/ /g;
6632		$data->[$i][3] =~ s/\s+/ /g;
6633		if ($data->[$i][1] && $data->[$i][2]){
6634			$data->[$i][1] = $data->[$i][1] . ', ';
6635		}
6636		$start = sprintf("%${indent1}s%-${indent2}s",$data->[$i][1],$data->[$i][2]);
6637		if ($indent > 1 && (length($start) > ($indent - 1))){
6638			$line = sprintf("%-${indent}s\n", "$start");
6639			print_line($line);
6640			$start = '';
6641			# print "1-print.\n";
6642		}
6643		if (($indent + length($data->[$i][3])) < $size{'max'}){
6644			$data->[$i][3] =~ s/\^/ /g;
6645			$line = sprintf("%-${indent}s%s\n", "$start", $data->[$i][3]);
6646			print_line($line);
6647			# print "2-print.\n";
6648		}
6649		else {
6650			my $holder = '';
6651			my $sep = ' ';
6652			# note: special case, split ' ' trims leading, trailing spaces,
6653			# then splits like awk, on one or more white spaces.
6654			foreach my $word (split(' ', $data->[$i][3])){
6655				# print "$word\n";
6656				if (($indent + length($holder) + length($word)) < $size{'max'}){
6657					$word =~ s/\^/ /g;
6658					$holder .= $word . $sep;
6659					# print "3-hold.\n";
6660				}
6661				# elsif (($indent + length($holder) + length($word)) >= $size{'max'}){
6662				else {
6663					$line = sprintf("%-${indent}s%s\n", "$start", $holder);
6664					print_line($line);
6665					$start = '';
6666					$word =~ s/\^/ /g;
6667					$holder = $word . $sep;
6668					# print "4-print-hold.\n";
6669				}
6670			}
6671			if ($holder !~ /^[ ]*$/){
6672				$line = sprintf("%-${indent}s%s\n", "$start", $holder);
6673				print_line($line);
6674				# print "5-print-last.\n";
6675			}
6676		}
6677	}
6678}
6679
6680# this has to get a hash of hashes, at least for now.
6681# because perl does not retain insertion order, I use a prefix for each
6682# hash key to force sorts.
6683sub print_data {
6684	my ($data) = @_;
6685	my ($array,$counter,$length,$split_count) = (0,0,0,0);
6686	my ($hash_id,$holder,$start,$start2,$start_holder) = ('','','','','');
6687	my $indent = $size{'indent'};
6688	my (@temp,@working,@values,%ids,%row);
6689	my ($holder2,$key,$line,$val2,$val3);
6690	# these 2 sets are single logic items
6691	my $b_single = ($size{'max'} == 1) ? 1: 0;
6692	my ($b_container,$indent_use,$indentx) = (0,0,0);
6693	# $size{'max'} = 88;
6694	# NOTE: indent < 11 would break the output badly in some cases
6695	if ($size{'max'} < $size{'wrap-max'} || $size{'indent'} < 11){
6696		$indent = 2;
6697	}
6698	# foreach my $key1 (sort { (split('#', $a))[0] <=> (split('#', $b))[0] } keys %$data){
6699	foreach my $key1 (sort { substr($a,0,3) <=> substr($b,0,3) } keys %$data){
6700	# foreach my $key1 (sort { $a cmp $b } keys %$data){
6701		$key = (split('#', $key1))[3];
6702		if ($key ne 'SHORT'){
6703			$start = sprintf("$colors{'c1'}%-${indent}s$colors{'cn'}","$key$sep{'s1'}");
6704			$start_holder = $key;
6705			if ($indent < 10){
6706				$line = "$start\n";
6707				print_line($line);
6708				$start = '';
6709				$line = '';
6710			}
6711		}
6712		else {
6713			$indent = 0;
6714		}
6715		next if ref($data->{$key1}) ne 'ARRAY';
6716		# @working = @{$data->{$key1}};
6717		# Line starters that will be -x incremented always
6718		# It's a tiny bit faster manually resetting rather than using for loop
6719		%ids = (
6720		'Array' => 1, # RAM or RAID
6721		'Battery' => 1,
6722		'Card' => 1,
6723		'Device' => 1,
6724		'Floppy' => 1,
6725		'Hardware' => 1, # hardware raid report
6726		'Hub' => 1,
6727		'ID' => 1,
6728		'IF-ID' => 1,
6729		'LV' => 1,
6730		'Monitor' => 1,
6731		'Optical' => 1,
6732		'Screen' => 1,
6733		'Sound Server' => 1,
6734		'variant' => 1, # arm > 1 cpu type
6735		);
6736		foreach my $val1 (@{$data->{$key1}}){
6737			$indent_use = $length = $indent;
6738			if (ref($val1) eq 'HASH'){
6739				#%row = %$val1;
6740				($counter,$split_count) = (0,0);
6741				# foreach my $key2 (sor ({ (split('#', $a))[0] <=> (split('#', $b))[0] } keys %$val1){
6742				foreach my $key2 (sort { substr($a,0,3) <=> substr($b,0,3) } keys %$val1){
6743				# foreach my $key2 (sort { $a cmp $b } keys %$val1){
6744					($hash_id,$b_container,$indentx,$key) = (split('#', $key2));
6745					if ($start_holder eq 'Graphics' && $key eq 'Screen'){
6746						$ids{'Monitor'} = 1;
6747					}
6748					elsif ($start_holder eq 'Memory' && $key eq 'Array'){
6749						$ids{'Device'} = 1;
6750					}
6751					elsif ($start_holder eq 'RAID' && $key eq 'Device'){
6752						$ids{'Array'} = 1;
6753					}
6754					elsif ($start_holder eq 'USB' && $key eq 'Hub'){
6755						$ids{'Device'} = 1;
6756					}
6757					elsif ($start_holder eq 'Logical' && $key eq 'Device'){
6758						$ids{'LV'} = 1;
6759					}
6760					if ($counter == 0 && defined $ids{$key}){
6761						$key .= '-' . $ids{$key}++;
6762					}
6763					$val2 = $val1->{$key2};
6764					# we have to handle cases where $val2 is 0
6765					if (!$b_single && $val2 || $val2 eq '0'){
6766						$val2 .= " ";
6767					}
6768					# see: Use of implicit split to @_ is deprecated. Only get this warning
6769					# in Perl 5.08 oddly enough.
6770					@temp = split(/\s+/, $val2);
6771					$split_count = scalar @temp;
6772					if (!$b_single && (length("$key$sep{'s2'} $val2") + $length) < $size{'max'}){
6773						# print "one\n";
6774						$length += length("$key$sep{'s2'} $val2");
6775						$holder .= "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2";
6776					}
6777					# handle case where the opening key/value pair is > max, and where
6778					# there are a lot of terms, like cpu flags, raid types supported. Raid
6779					# can have the last row have a lot of devices, or many raid types
6780					elsif (!$b_single && (length("$key$sep{'s2'} $val2") + $indent) > $size{'max'} &&
6781								!defined $ids{$key} && $split_count > 2){
6782						# print "two\n";
6783						@values = split(/\s+/, $val2);
6784						$val3 = shift @values;
6785						# $length += length("$key$sep{'s2'} $val3 ") + $indent;
6786						$start2 = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val3 ";
6787						$holder2 = '';
6788						$length += length("$key$sep{'s2'} $val3 ");
6789						# print scalar @values,"\n";
6790						foreach (@values){
6791							# my $l =  (length("$_ ") + $length);
6792							# print "$l\n";
6793							if ((length("$_ ") + $length) < $size{'max'}){
6794								# print "three.1\n";
6795								# print "a\n";
6796								if ($start2){
6797									$holder2 .= "$start2$_ ";
6798									$start2 = '';
6799									#$length += $length2;
6800									#$length2 = 0;
6801								}
6802								else {
6803									$holder2 .= "$_ ";
6804								}
6805								$length += length("$_ ");
6806							}
6807							else {
6808								# print "three.2\n";
6809								if ($start2){
6810									$holder2 = "$start2$holder2";
6811								}
6812								else {
6813									$holder2 = "$colors{'c2'}$holder2";
6814								}
6815								# print "xx:$holder";
6816								$holder2 =~ s/\s+$//;
6817								$line = sprintf("%-${indent}s%s$colors{'cn'}\n","$start","$holder$holder2");
6818								print_line($line);
6819								$holder = '';
6820								$holder2 = "$_ ";
6821								# print "h2: $holder2\n";
6822								$length = length($holder2) + $indent;
6823								$start2 = '';
6824								$start = '';
6825								#$length2 = 0;
6826							}
6827						}
6828						if ($holder2 !~ /^\s*$/){
6829							# print "four\n";
6830							$holder2 =~ s/\s+$//;
6831							$holder2 = "$colors{'c2'}$holder2";
6832							$line = sprintf("%-${indent}s%s$colors{'cn'}\n","$start","$holder$holder2");
6833							print_line($line);
6834							$holder = '';
6835							$holder2 = '';
6836							$length = $indent;
6837							$start2 = '';
6838							$start = '';
6839							#$length2 = 0;
6840						}
6841					}
6842					# NOTE: only these and the last fallback are used for b_single output
6843					else {
6844						# print "H: $counter " . scalar %$val1 . " $indent3 $indent2\n";
6845						if ($holder){
6846							# print "five\n";
6847							$holder =~ s/\s+$//;
6848							$line = sprintf("%-${indent_use}s%s$colors{'cn'}\n",$start,"$holder");
6849							$length = length("$key$sep{'s2'} $val2") + $indent_use;
6850							print_line($line);
6851							$start = '';
6852						}
6853						else {
6854							# print "six\n";
6855							$length = $indent_use;
6856							#$holder = '';
6857						}
6858						$holder = "$colors{'c1'}$key$sep{'s2'}$colors{'c2'} $val2";
6859					}
6860					$counter++;
6861					$indent_use = ($indent * $indentx) if $b_single;
6862				}
6863				if ($holder !~ /^\s*$/){
6864					# print "seven\n";
6865					$holder =~ s/\s+$//;
6866					$line = sprintf("%-${indent_use}s%s$colors{'cn'}\n",$start,"$start2$holder");
6867					print_line($line);
6868					$holder = '';
6869					$length = 0;
6870					$start = '';
6871				}
6872			}
6873			# only for repos currently
6874			elsif (ref($val1) eq 'ARRAY'){
6875				# print "eight\n";
6876				$array=0;
6877				foreach my $item (@$val1){
6878					$array++;
6879					$indent_use = ($b_single) ? $indent + 2: $indent;
6880					$line = "$colors{'c1'}$array$sep{'s2'} $colors{'c2'}$item$colors{'cn'}";
6881					$line = sprintf("%-${indent_use}s%s\n","","$line");
6882					print_line($line);
6883				}
6884			}
6885		}
6886		# we want a space between data blocks for single
6887		print_line("\n") if $b_single;
6888	}
6889}
6890
6891sub print_line {
6892	my ($line) = @_;
6893	if ($b_irc && $client{'test-konvi'}){
6894		$client{'konvi'} = 3;
6895		$client{'dobject'} = 'Konversation';
6896	}
6897	if ($client{'konvi'} == 1 && $client{'dcop'}){
6898		# konvi doesn't seem to like \n characters, it just prints them literally
6899		$line =~ s/\n//g;
6900		#qx('dcop "$client{'dport'}" "$client{'dobject'}" say "$client{'dserver'}" "$client{'dtarget'}" "$line 1");
6901		system('dcop', $client{'dport'}, $client{'dobject'}, 'say', $client{'dserver'}, $client{'dtarget'}, "$line 1");
6902	}
6903	elsif ($client{'konvi'} == 3 && $client{'qdbus'}){
6904		# print $line;
6905		$line =~ s/\n//g;
6906		#qx(qdbus org.kde.konversation /irc say "$client{'dserver'}" "$client{'dtarget'}" "$line");
6907		system('qdbus', 'org.kde.konversation', '/irc', 'say', $client{'dserver'}, $client{'dtarget'}, $line);
6908	}
6909	else {
6910		print $line;
6911	}
6912}
6913
6914########################################################################
6915#### ITEM PROCESSORS
6916########################################################################
6917
6918#### -------------------------------------------------------------------
6919#### ITEM GENERATORS
6920#### -------------------------------------------------------------------
6921
6922## AudioItem
6923{
6924package AudioItem;
6925
6926sub get {
6927	eval $start if $b_log;
6928	my (@rows);
6929	my $num = 0;
6930	if (($b_arm || $b_mips) && !$use{'soc-audio'} && !$use{'pci-tool'}){
6931		my $type = ($b_arm) ? 'arm' : 'mips';
6932		my $key = 'Message';
6933		push(@rows,{
6934		main::key($num++,0,1,$key) => main::row_defaults($type . '-pci',''),
6935		},);
6936	}
6937	else {
6938		push(@rows,device_output());
6939	}
6940	if (((($b_arm || $b_mips) && !$use{'soc-audio'} && !$use{'pci-tool'}) || !@rows) &&
6941	   (my $file = $system_files{'asound-cards'})){
6942		push(@rows,asound_output($file));
6943	}
6944	push(@rows,usb_output());
6945	if (!@rows){
6946		my $key = 'Message';
6947		my $type = 'pci-card-data';
6948		if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){
6949			$type = 'pci-card-data-root';
6950		}
6951		push(@rows,{
6952		main::key($num++,0,1,$key) => main::row_defaults($type,''),
6953		},);
6954	}
6955	push(@rows,sound_server_output());
6956	eval $end if $b_log;
6957	return @rows;
6958}
6959
6960sub device_output {
6961	eval $start if $b_log;
6962	return if !$devices{'audio'};
6963	my (@rows);
6964	my ($j,$num) = (0,1);
6965	foreach my $row (@{$devices{'audio'}}){
6966		$num = 1;
6967		$j = scalar @rows;
6968		my $driver = $row->[9];
6969		$driver ||= 'N/A';
6970		my $device = $row->[4];
6971		$device = ($device) ? main::pci_cleaner($device,'output') : 'N/A';
6972		# have seen absurdly verbose card descriptions, with non related data etc
6973		if (length($device) > 85 || $size{'max'} < 110){
6974			$device = main::pci_long_filter($device);
6975		}
6976		push(@rows, {
6977		main::key($num++,1,1,'Device') => $device,
6978		},);
6979		if ($extra > 0 && $use{'pci-tool'} && $row->[12]){
6980			my $item = main::get_pci_vendor($row->[4],$row->[12]);
6981			$rows[$j]->{main::key($num++,0,2,'vendor')} = $item if $item;
6982		}
6983		$rows[$j]->{main::key($num++,1,2,'driver')} = $driver;
6984		if ($extra > 0 && !$bsd_type){
6985			if ($row->[9]){
6986				my $version = main::get_module_version($row->[9]);
6987				$rows[$j]->{main::key($num++,0,3,'v')} = $version if $version;
6988			}
6989		}
6990		if ($b_admin && $row->[10]){
6991			$row->[10] = main::get_driver_modules($row->[9],$row->[10]);
6992			$rows[$j]->{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10];
6993		}
6994		if ($extra > 0){
6995			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]";
6996		}
6997		if ($extra > 1){
6998			my $chip_id = main::get_chip_id($row->[5],$row->[6]);
6999			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $chip_id;
7000			if ($extra > 2 && $row->[1]){
7001				$rows[$j]->{main::key($num++,0,2,'class-ID')} = $row->[1];
7002			}
7003		}
7004		# print "$row->[0]\n";
7005	}
7006	eval $end if $b_log;
7007	return @rows;
7008}
7009# this handles fringe cases where there is no card on pcibus,
7010# but there is a card present. I don't know the exact architecture
7011# involved but I know this situation exists on at least one old machine.
7012sub asound_output {
7013	eval $start if $b_log;
7014	my ($file) = @_;
7015	my (@asound,@rows);
7016	my ($device,$driver,$j,$num) = ('','',0,1);
7017	@asound = main::reader($file);
7018	foreach (@asound){
7019		# filtering out modems and usb devices like webcams, this might get a
7020		# usb audio card as well, this will take some trial and error
7021		if (!/modem|usb/i && /^\s*[0-9]/){
7022			$num = 1;
7023			my @working = split(/:\s*/, $_);
7024			# now let's get 1 2
7025			$working[1] =~ /(.*)\s+-\s+(.*)/;
7026			$device = $2;
7027			$driver = $1;
7028			if ($device){
7029				$j = scalar @rows;
7030				$driver ||= 'N/A';
7031				push(@rows, {
7032				main::key($num++,1,1,'Device') => $device,
7033				main::key($num++,1,2,'driver') => $driver,
7034				},);
7035				if ($extra > 0){
7036					my $version = main::get_module_version($driver);
7037					$rows[$j]->{main::key($num++,0,3,'v')} = $version if $version;
7038					$rows[$j]->{main::key($num++,0,2,'message')} = main::row_defaults('pci-advanced-data','');
7039				}
7040			}
7041		}
7042	}
7043	# print Data::Dumper:Dumper \s@rows;
7044	eval $end if $b_log;
7045	return @rows;
7046}
7047sub usb_output {
7048	eval $start if $b_log;
7049	my (@rows,@ids,$path_id,$product,@temp2);
7050	my ($j,$num) = (0,1);
7051	return if !$usb{'audio'};
7052	foreach my $row (@{$usb{'audio'}}){
7053		# print Data::Dumper::Dumper $row;
7054		$num = 1;
7055		# make sure to reset, or second device trips last flag
7056		($path_id,$product) = ('','');
7057		$product = main::cleaner($row->[13]) if $row->[13];
7058		$path_id = $row->[2] if $row->[2];
7059		$product ||= 'N/A';
7060		$row->[15] ||= 'N/A';
7061		push(@rows, {
7062		main::key($num++,1,1,'Device') => $product,
7063		main::key($num++,0,2,'type') => 'USB',
7064		main::key($num++,0,2,'driver') => $row->[15],
7065		},);
7066		if ($extra > 0){
7067			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]";
7068		}
7069		if ($extra > 1){
7070			$row->[7] ||= 'N/A';
7071			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $row->[7];
7072		}
7073		if ($extra > 2 && defined $row->[5] && $row->[5] ne ''){
7074			$rows[$j]->{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]";
7075		}
7076		if ($extra > 2 && $row->[16]){
7077			$rows[$j]->{main::key($num++,0,2,'serial')} = main::apply_filter($row->[16]);
7078		}
7079		$j = scalar @rows;
7080	}
7081	eval $end if $b_log;
7082	return @rows;
7083}
7084sub sound_server_output {
7085	eval $start if $b_log;
7086	my (@rows,$program);
7087	my ($j,$num) = (0,0);
7088	my @servers = sound_server_data();
7089	foreach my $server (@servers){
7090		next if $extra < 1 && (!$server->[2] || $server->[2] ne 'yes');
7091		$j = scalar @rows;
7092		$server->[1] ||= 'N/A';
7093		$server->[2] ||= 'N/A';
7094		push(@rows, {
7095		main::key($num++,1,1,'Sound Server') => $server->[0],
7096		main::key($num++,0,2,'v') => $server->[1],
7097		main::key($num++,0,2,'running') => $server->[2],
7098		});
7099	}
7100	eval $end if $b_log;
7101	return @rows;
7102}
7103sub sound_server_data {
7104	eval $start if $b_log;
7105	my (@servers,$program,$running,$server,$version);
7106	if (my $file = $system_files{'asound-version'}){
7107		# avoid possible second line if compiled by user
7108		my $content = main::reader($file,'',0);
7109		# some alsa strings have the build date in (...)
7110		$version = (split(/\s+/, $content))[-1];
7111		$version =~ s/\.$//; # trim off period
7112		$server = 'ALSA';
7113		$running = 'yes';
7114		# not needed I think, if asound is there, it's running, but if that's
7115		# not correct, can use one of the info/list/stat tests for aplay
7116		# if (main::check_program('aplay') && main::grabber('aplay -l 2>/dev/null')){
7117		# 	$running = 'yes';
7118		# }
7119		push(@servers, [$server,$version,$running]);
7120		($running,$server,$version) = ('','','');
7121	}
7122	# sndstat file may be removed in linux oss
7123	if (-e '/dev/sndstat' || ($program = main::check_program('ossinfo'))){
7124		$server = 'OSS';
7125		#$version = main::program_version('oss','\S',2);
7126		$version = (grep {/^hw.snd.version:/} @{$sysctl{'audio'}})[0] if $sysctl{'audio'};
7127		$version = (split(/:\s*/,$version),1)[1] if $version;
7128		$version =~ s|/.*$|| if $version;
7129		# not a great test, but ok for now
7130		$running = (-e '/dev/sndstat') ? 'yes' : 'no?';
7131		push(@servers, [$server,$version,$running]);
7132		($running,$server,$version) = ('','','');
7133	}
7134	if ($program = main::check_program('sndiod')){
7135		$server = 'sndio';
7136		#$version = main::program_version('sndio','\S',2);
7137		$running = (grep {/sndiod/} @ps_cmd) ? 'yes': 'no';
7138		push(@servers, [$server,$version,$running]);
7139		($running,$server,$version) = ('','','');
7140	}
7141	if ($program = main::check_program('jackd')){
7142		$server = 'JACK';
7143		$version = main::program_version($program,'^jackd',3,'--version',1);
7144		$running = (grep {/jackd/} @ps_cmd) ? 'yes':'no' ;
7145		push(@servers, [$server,$version,$running]);
7146		($running,$server,$version) = ('','','');
7147	}
7148	# note: pactl info/list/stat could be used
7149	if ($program = main::check_program('pactl')){
7150		$server = 'PulseAudio';
7151		$version = main::program_version($program,'^pactl',2,'--version',1);
7152		$running = (grep {m|/pulseaudiod?\b|} @ps_cmd) ? 'yes':'no' ;
7153		push(@servers, [$server,$version,$running]);
7154		($running,$server,$version) = ('','','');
7155	}
7156	if ($program = main::check_program('pipewire')){
7157		$server = 'PipeWire';
7158		$version = main::program_version($program,'^Compiled with libpipe',4,'--version',1);
7159		$running = (grep {/pipewire/} @ps_cmd) ? 'yes':'no' ;
7160		push(@servers, [$server,$version,$running]);
7161		($running,$server,$version) = ('','','');
7162	}
7163	main::log_data('dump','sound servers: @servers',\@servers) if $b_log;
7164	print Data::Dumper::Dumper \@servers if $dbg[26];
7165	return @servers;
7166	eval $end if $b_log;
7167}
7168}
7169
7170## BatteryItem
7171{
7172package BatteryItem;
7173
7174my (@upower_items,$b_upower,$upower);
7175sub get {
7176	eval $start if $b_log;
7177	my (@rows,%battery,$key1,$val1);
7178	my $num = 0;
7179	if ($force{'dmidecode'}){
7180		if ($alerts{'dmidecode'}->{'action'} ne 'use'){
7181			$key1 = $alerts{'dmidecode'}->{'action'};
7182			$val1 = $alerts{'dmidecode'}->{'message'};
7183			$key1 = ucfirst($key1);
7184			@rows = ({main::key($num++,0,1,$key1) => $val1,});
7185		}
7186		else {
7187			%battery = battery_data_dmi();
7188			if (!%battery){
7189				if ($show{'battery-forced'}){
7190					$key1 = 'Message';
7191					$val1 = main::row_defaults('battery-data','');
7192					@rows = ({main::key($num++,0,1,$key1) => $val1,});
7193				}
7194			}
7195			else {
7196				@rows = battery_output(\%battery);
7197			}
7198		}
7199	}
7200	elsif ($bsd_type && ($sysctl{'battery'} || $show{'battery-forced'})){
7201		%battery = battery_data_sysctl() if $sysctl{'battery'};
7202		if (!%battery){
7203			if ($show{'battery-forced'}){
7204				$key1 = 'Message';
7205				$val1 = main::row_defaults('battery-data-bsd','');
7206				@rows = ({main::key($num++,0,1,$key1) => $val1,});
7207			}
7208		}
7209		else {
7210			@rows = battery_output(\%battery);
7211		}
7212	}
7213	elsif (-d '/sys/class/power_supply/'){
7214		%battery = battery_data_sys();
7215		if (!%battery){
7216			if ($show{'battery-forced'}){
7217				$key1 = 'Message';
7218				$val1 = main::row_defaults('battery-data','');
7219				@rows = ({main::key($num++,0,1,$key1) => $val1,});
7220			}
7221		}
7222		else {
7223			@rows = battery_output(\%battery);
7224		}
7225	}
7226	else {
7227		if ($show{'battery-forced'}){
7228			$key1 = 'Message';
7229			$val1 = (!$bsd_type) ? main::row_defaults('battery-data-sys'): main::row_defaults('battery-data-bsd');
7230			@rows = ({main::key($num++,0,1,$key1) => $val1,});
7231		}
7232	}
7233	(@upower_items,$b_upower,$upower) = undef;
7234	eval $end if $b_log;
7235	return @rows;
7236}
7237# alarm capacity capacity_level charge_full charge_full_design charge_now
7238# 	cycle_count energy_full energy_full_design energy_now location manufacturer model_name
7239# 	power_now present serial_number status technology type voltage_min_design voltage_now
7240# 0  name - battery id, not used
7241# 1  status
7242# 2  present
7243# 3  technology
7244# 4  cycle_count
7245# 5  voltage_min_design
7246# 6  voltage_now
7247# 7  power_now
7248# 8  energy_full_design
7249# 9  energy_full
7250# 10 energy_now
7251# 11 capacity
7252# 12 capacity_level
7253# 13 of_orig
7254# 14 model_name
7255# 15 manufacturer
7256# 16 serial_number
7257# 17 location
7258sub battery_output {
7259	eval $start if $b_log;
7260	my ($battery) = @_;
7261	my ($key,@rows);
7262	my $num = 0;
7263	my $j = 0;
7264	# print Data::Dumper::Dumper $battery;
7265	foreach $key (sort keys %$battery){
7266		$num = 0;
7267		my ($charge,$condition,$model,$serial,$status) = ('','','','','');
7268		my ($chemistry,$cycles,$location) = ('','','');
7269		next if !$battery->{$key}{'purpose'} || $battery->{$key}{'purpose'} ne 'primary';
7270		# $battery->{$key}{''};
7271		# we need to handle cases where charge or energy full is 0
7272		if (defined $battery->{$key}{'energy_now'} && $battery->{$key}{'energy_now'} ne ''){
7273			$charge = "$battery->{$key}{'energy_now'} Wh";
7274			if ($battery->{$key}{'energy_full'} && main::is_numeric($battery->{$key}{'energy_full'})){
7275				my $percent = sprintf("%.1f", $battery->{$key}{'energy_now'}/$battery->{$key}{'energy_full'}*100);
7276				$charge .= ' (' . $percent  . '%)';
7277			}
7278		}
7279		# better than nothing, shows the charged percent
7280		elsif (defined $battery->{$key}{'capacity'} && $battery->{$key}{'capacity'} ne ''){
7281			$charge = $battery->{$key}{'capacity'} . '%'
7282		}
7283		else {
7284			$charge = 'N/A';
7285		}
7286		if ($battery->{$key}{'energy_full'} || $battery->{$key}{'energy_full_design'}){
7287			$battery->{$key}{'energy_full_design'} ||= 'N/A';
7288			$battery->{$key}{'energy_full'}= (defined $battery->{$key}{'energy_full'} && $battery->{$key}{'energy_full'} ne '') ? $battery->{$key}{'energy_full'} : 'N/A';
7289			$condition = "$battery->{$key}{'energy_full'}/$battery->{$key}{'energy_full_design'} Wh";
7290			if ($battery->{$key}{'of_orig'}){
7291				$condition .= " ($battery->{$key}{'of_orig'}%)";
7292			}
7293		}
7294		$condition ||= 'N/A';
7295		$j = scalar @rows;
7296		push(@rows, {
7297		main::key($num++,1,1,'ID') => $key,
7298		main::key($num++,0,2,'charge') => $charge,
7299		main::key($num++,0,2,'condition') => $condition,
7300		},);
7301		if ($extra > 0 || ($battery->{$key}{'voltage_now'} && $battery->{$key}{'voltage_min_design'} &&
7302		 ($battery->{$key}{'voltage_now'} - $battery->{$key}{'voltage_min_design'}) < 0.5)){
7303			$battery->{$key}{'voltage_now'} ||= 'N/A';
7304			$rows[$j]->{main::key($num++,1,2,'volts')} = $battery->{$key}{'voltage_now'};
7305			if ($battery->{$key}{'voltage_now'} ne 'N/A' || $battery->{$key}{'voltage_min_design'}){
7306				$battery->{$key}{'voltage_min_design'} ||= 'N/A';
7307				$rows[$j]->{main::key($num++,0,3,'min')} = $battery->{$key}{'voltage_min_design'};
7308			}
7309		}
7310		if ($extra > 0){
7311			if ($battery->{$key}{'manufacturer'} || $battery->{$key}{'model_name'}){
7312				if ($battery->{$key}{'manufacturer'} && $battery->{$key}{'model_name'}){
7313					$model = "$battery->{$key}{'manufacturer'} $battery->{$key}{'model_name'}";
7314				}
7315				elsif ($battery->{$key}{'manufacturer'}){
7316					$model = $battery->{$key}{'manufacturer'};
7317				}
7318				elsif ($battery->{$key}{'model_name'}){
7319					$model = $battery->{$key}{'model_name'};
7320				}
7321			}
7322			else {
7323				$model = 'N/A';
7324			}
7325			$rows[$j]->{main::key($num++,0,2,'model')} = $model;
7326			if ($extra > 2){
7327				$chemistry = ($battery->{$key}{'technology'}) ? $battery->{$key}{'technology'}: 'N/A';
7328				$rows[$j]->{main::key($num++,0,2,'type')} = $chemistry;
7329			}
7330			if ($extra > 1){
7331				$serial = main::apply_filter($battery->{$key}{'serial_number'});
7332				$rows[$j]->{main::key($num++,0,2,'serial')} = $serial;
7333			}
7334			$status = ($battery->{$key}{'status'}) ? $battery->{$key}{'status'}: 'N/A';
7335			$rows[$j]->{main::key($num++,0,2,'status')} = $status;
7336			if ($extra > 2){
7337				if ($battery->{$key}{'cycle_count'}){
7338					$rows[$j]->{main::key($num++,0,2,'cycles')} = $battery->{$key}{'cycle_count'};
7339				}
7340				if ($battery->{$key}{'location'}){
7341					$rows[$j]->{main::key($num++,0,2,'location')} = $battery->{$key}{'location'};
7342				}
7343			}
7344		}
7345		$battery->{$key} = undef;
7346	}
7347	# print Data::Dumper::Dumper \%$battery;
7348	# now if there are any devices left, print them out, excluding Mains
7349	if ($extra > 0){
7350		$upower = main::check_program('upower');
7351		foreach $key (sort keys %$battery){
7352			$num = 0;
7353			next if !defined $battery->{$key} || $battery->{$key}{'purpose'} eq 'mains';
7354			my ($charge,$model,$serial,$percent,$status,$vendor) = ('','','','','','');
7355			my (%upower_data);
7356			$j = scalar @rows;
7357			%upower_data = upower_data($key) if $upower;
7358			if ($upower_data{'percent'}){
7359				$charge = $upower_data{'percent'};
7360			}
7361			elsif ($battery->{$key}{'capacity_level'} && lc($battery->{$key}{'capacity_level'}) ne 'unknown'){
7362				$charge = $battery->{$key}{'capacity_level'};
7363			}
7364			else {
7365				$charge = 'N/A';
7366			}
7367			$model = $battery->{$key}{'model_name'} if $battery->{$key}{'model_name'};
7368			$status = ($battery->{$key}{'status'} && lc($battery->{$key}{'status'}) ne 'unknown') ? $battery->{$key}{'status'}: 'N/A' ;
7369			$vendor = $battery->{$key}{'manufacturer'} if $battery->{$key}{'manufacturer'};
7370			if ($vendor || $model){
7371				if ($vendor && $model){
7372					$model = "$vendor $model";
7373				}
7374				elsif ($vendor){
7375					$model = $vendor;
7376				}
7377			}
7378			else {
7379				$model = 'N/A';
7380			}
7381			push(@rows, {
7382			main::key($num++,1,1,'Device') => $key,
7383			main::key($num++,0,2,'model') => $model,
7384			},);
7385			if ($extra > 1){
7386				$serial = main::apply_filter($battery->{$key}{'serial_number'});
7387				$rows[$j]->{main::key($num++,0,2,'serial')} = $serial;
7388			}
7389			$rows[$j]->{main::key($num++,0,2,'charge')} = $charge;
7390			if ($extra > 2 && $upower_data{'rechargeable'}){
7391				$rows[$j]->{main::key($num++,0,2,'rechargeable')} = $upower_data{'rechargeable'};
7392			}
7393			$rows[$j]->{main::key($num++,0,2,'status')} = $status;
7394		}
7395	}
7396	eval $end if $b_log;
7397	return @rows;
7398}
7399
7400# charge: mAh energy: Wh
7401sub battery_data_sys {
7402	eval $start if $b_log;
7403	my ($b_ma,%battery,$file,$id,$item,$path,$value);
7404	my $num = 0;
7405	my @batteries = main::globber("/sys/class/power_supply/*");
7406	# note: there is no 'location' file, but dmidecode has it
7407	# 'type' is generic, like: Battery, Mains
7408	# capacity_level is a string, like: Normal
7409	my @items = qw(alarm capacity capacity_level charge_full charge_full_design
7410	charge_now constant_charge_current constant_charge_current_max cycle_count
7411	energy_full energy_full_design energy_now location manufacturer model_name
7412	power_now present scope serial_number status technology type voltage_min_design voltage_now);
7413	foreach $item (@batteries){
7414		$b_ma = 0;
7415		$id = $item;
7416		$id =~ s%/sys/class/power_supply/%%g;
7417		foreach $file (@items){
7418			$path = "$item/$file";
7419			# android shows some files only root readable
7420			$value = (-r $path) ? main::reader($path,'',0): '';
7421			# mains, plus in psu
7422			if ($file eq 'type' && $value && lc($value) ne 'battery'){
7423				$battery{$id}->{'purpose'} = 'mains';
7424			}
7425			if ($value){
7426				$value = main::trimmer($value);
7427				if ($file eq 'voltage_min_design'){
7428					$value = sprintf("%.1f", $value/1000000);
7429				}
7430				elsif ($file eq 'voltage_now'){
7431					$value = sprintf("%.1f", $value/1000000);
7432				}
7433				elsif ($file eq 'energy_full_design'){
7434					$value = $value/1000000;
7435				}
7436				elsif ($file eq 'energy_full'){
7437					$value = $value/1000000;
7438				}
7439				elsif ($file eq 'energy_now'){
7440					$value = sprintf("%.1f", $value/1000000);
7441				}
7442				# note: the following 3 were off, 100000 instead of 1000000
7443				# why this is, I do not know. I did not document any reason for that
7444				# so going on assumption it is a mistake.
7445				# CHARGE is mAh, which are converted to Wh by: mAh x voltage.
7446				# Note: voltage fluctuates so will make results vary slightly.
7447				elsif ($file eq 'charge_full_design'){
7448					$value = $value/1000000;
7449					$b_ma = 1;
7450				}
7451				elsif ($file eq 'charge_full'){
7452					$value = $value/1000000;
7453					$b_ma = 1;
7454				}
7455				elsif ($file eq 'charge_now'){
7456					$value = $value/1000000;
7457					$b_ma = 1;
7458				}
7459				elsif ($file eq 'manufacturer'){
7460					$value = main::dmi_cleaner($value);
7461				}
7462				elsif ($file eq 'model_name'){
7463					$value = main::dmi_cleaner($value);
7464				}
7465			}
7466			elsif ($b_root && -e $path && ! -r $path){
7467				$value = main::row_defaults('root-required');
7468			}
7469			$battery{$id}->{$file} = $value;
7470			# print "$battery{$id}->{$file}\n";
7471		}
7472		# note, too few data sets, there could be sbs-charger but not sure
7473		if (!$battery{$id}->{'purpose'}){
7474			# NOTE: known ids: BAT[0-9] CMB[0-9]. arm may be like: sbs- sbm- but just check
7475			# if the energy/charge values exist for this item, if so, it's a battery, if not,
7476			# it's a device.
7477			if ($id =~ /^(BAT|CMB).*$/i ||
7478			 ($battery{$id}->{'energy_full'} || $battery{$id}->{'charge_full'} ||
7479			 $battery{$id}->{'energy_now'} || $battery{$id}->{'charge_now'} ||
7480			 $battery{$id}->{'energy_full_design'} || $battery{$id}->{'charge_full_design'}) ||
7481			 $battery{$id}->{'voltage_min_design'} || $battery{$id}->{'voltage_now'}){
7482				$battery{$id}->{'purpose'} =  'primary';
7483			}
7484			else {
7485				$battery{$id}->{'purpose'} =  'device';
7486			}
7487		}
7488		# note:voltage_now fluctuates, which will make capacity numbers change a bit
7489		# if any of these values failed, the math will be wrong, but no way to fix that
7490		# tests show more systems give right capacity/charge with voltage_min_design
7491		# than with voltage_now
7492		if ($b_ma && $battery{$id}->{'voltage_min_design'}){
7493			if ($battery{$id}->{'charge_now'}){
7494				$battery{$id}->{'energy_now'} = $battery{$id}->{'charge_now'} * $battery{$id}->{'voltage_min_design'};
7495			}
7496			if ($battery{$id}->{'charge_full'}){
7497				$battery{$id}->{'energy_full'} = $battery{$id}->{'charge_full'}*$battery{$id}->{'voltage_min_design'};
7498			}
7499			if ($battery{$id}->{'charge_full_design'}){
7500				$battery{$id}->{'energy_full_design'} = $battery{$id}->{'charge_full_design'} * $battery{$id}->{'voltage_min_design'};
7501			}
7502		}
7503		if ($battery{$id}->{'energy_now'} && $battery{$id}->{'energy_full'}){
7504			$battery{$id}->{'capacity'} = 100 * $battery{$id}->{'energy_now'}/$battery{$id}->{'energy_full'};
7505			$battery{$id}->{'capacity'} = sprintf("%.1f", $battery{$id}->{'capacity'});
7506		}
7507		if ($battery{$id}->{'energy_full_design'} && $battery{$id}->{'energy_full'}){
7508			$battery{$id}->{'of_orig'} = 100 * $battery{$id}->{'energy_full'}/$battery{$id}->{'energy_full_design'};
7509			$battery{$id}->{'of_orig'} = sprintf("%.1f", $battery{$id}->{'of_orig'});
7510		}
7511		if ($battery{$id}->{'energy_now'}){
7512			$battery{$id}->{'energy_now'} = sprintf("%.1f", $battery{$id}->{'energy_now'});
7513		}
7514		if ($battery{$id}->{'energy_full_design'}){
7515			$battery{$id}->{'energy_full_design'} = sprintf("%.1f",$battery{$id}->{'energy_full_design'});
7516		}
7517		if ($battery{$id}->{'energy_full'}){
7518			$battery{$id}->{'energy_full'} = sprintf("%.1f", $battery{$id}->{'energy_full'});
7519		}
7520	}
7521	print Data::Dumper::Dumper \%battery if $dbg[33];
7522	main::log_data('dump','sys: %battery',\%battery) if $b_log;
7523	eval $end if $b_log;
7524	return %battery;
7525}
7526sub battery_data_sysctl {
7527	eval $start if $b_log;
7528	my (%battery,$id);
7529	for (@{$sysctl{'battery'}}){
7530		if (/^(hw\.sensors\.)acpi([^\.]+)(\.|:)/){
7531			$id = uc($2);
7532		}
7533		if (/volt[^:]+:([0-9\.]+)\s+VDC\s+\(voltage\)/){
7534			$battery{$id}->{'voltage_min_design'} = $1;
7535		}
7536		elsif (/volt[^:]+:([0-9\.]+)\s+VDC\s+\(current voltage\)/){
7537			$battery{$id}->{'voltage_now'} = $1;
7538		}
7539		elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(design capacity\)/){
7540			$battery{$id}->{'energy_full_design'} = $1;
7541		}
7542		elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(last full capacity\)/){
7543			$battery{$id}->{'energy_full'} = $1;
7544		}
7545		elsif (/watthour[^:]+:([0-9\.]+)\s+Wh\s+\(remaining capacity\)/){
7546			$battery{$id}->{'energy_now'} = $1;
7547		}
7548		elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(design capacity\)/){
7549			$battery{$id}->{'charge_full_design'} = $1;
7550		}
7551		elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(last full capacity\)/){
7552			$battery{$id}->{'charge_full'} = $1;
7553		}
7554		elsif (/amphour[^:]+:([0-9\.]+)\s+Ah\s+\(remaining capacity\)/){
7555			$battery{$id}->{'charge_now'} = $1;
7556		}
7557		elsif (/raw[^:]+:[0-9\.]+\s+\((battery) ([^\)]+)\)/){
7558			$battery{$id}->{'status'} = $2;
7559		}
7560		elsif (/^acpi[\S]+:at [^:]+:\s*$id\s+model\s+(.*?)\s*serial\s+([\S]*?)\s*type\s+(.*?)\s*oem\s+(.*)/i){
7561			$battery{$id}->{'model_name'} = main::dmi_cleaner($1);
7562			$battery{$id}->{'serial_number'} = $2;
7563			$battery{$id}->{'technology'} = $3;
7564			$battery{$id}->{'manufacturer'} = main::dmi_cleaner($4);
7565		}
7566	}
7567	# then do the condition/charge percent math
7568	for my $id (keys %battery){
7569		$battery{$id}->{'purpose'} = 'primary';
7570		# CHARGE is Ah, which are converted to Wh by: Ah x voltage.
7571		if ($battery{$id}->{'voltage_min_design'}){
7572			if ($battery{$id}->{'charge_now'}){
7573				$battery{$id}->{'energy_now'} = $battery{$id}->{'charge_now'} * $battery{$id}->{'voltage_min_design'};
7574			}
7575			if ($battery{$id}->{'charge_full'}){
7576				$battery{$id}->{'energy_full'} = $battery{$id}->{'charge_full'}*$battery{$id}->{'voltage_min_design'};
7577			}
7578			if ($battery{$id}->{'charge_full_design'}){
7579				$battery{$id}->{'energy_full_design'} = $battery{$id}->{'charge_full_design'} * $battery{$id}->{'voltage_min_design'};
7580			}
7581		}
7582		if ($battery{$id}->{'energy_full_design'} && $battery{$id}->{'energy_full'}){
7583			$battery{$id}->{'of_orig'} = 100 * $battery{$id}->{'energy_full'}/$battery{$id}->{'energy_full_design'};
7584			$battery{$id}->{'of_orig'} = sprintf("%.1f", $battery{$id}->{'of_orig'});
7585		}
7586		if ($battery{$id}->{'energy_now'} && $battery{$id}->{'energy_full'}){
7587			$battery{$id}->{'capacity'} = 100 * $battery{$id}->{'energy_now'}/$battery{$id}->{'energy_full'};
7588			$battery{$id}->{'capacity'} = sprintf("%.1f", $battery{$id}->{'capacity'});
7589		}
7590		if ($battery{$id}->{'energy_now'}){
7591			$battery{$id}->{'energy_now'} = sprintf("%.1f", $battery{$id}->{'energy_now'});
7592		}
7593		if ($battery{$id}->{'energy_full'}){
7594			$battery{$id}->{'energy_full'} = sprintf("%.1f", $battery{$id}->{'energy_full'});
7595		}
7596		if ($battery{$id}->{'energy_full_design'}){
7597			$battery{$id}->{'energy_full_design'} = sprintf("%.1f", $battery{$id}->{'energy_full_design'});
7598		}
7599	}
7600	print Data::Dumper::Dumper \%battery if $dbg[33];
7601	main::log_data('dump','dmi: %battery',\%battery) if $b_log;
7602	eval $end if $b_log;
7603	return %battery;
7604}
7605# note, dmidecode does not have charge_now or charge_full
7606sub battery_data_dmi {
7607	eval $start if $b_log;
7608	my (%battery,$id);
7609	my $i = 0;
7610	foreach my $row (@dmi){
7611		# Portable Battery
7612		if ($row->[0] == 22){
7613			$id = "BAT$i";
7614			$i++;
7615			$battery{$id}->{'purpose'} = 'primary';
7616			# skip first three row, we don't need that data
7617			foreach my $item (@$row[3 .. $#$row]){
7618				my @value = split(/:\s+/, $item);
7619				next if !$value[0];
7620				if ($value[0] eq 'Location'){$battery{$id}->{'location'} = $value[1] }
7621				elsif ($value[0] eq 'Manufacturer'){$battery{$id}->{'manufacturer'} = main::dmi_cleaner($value[1]) }
7622				elsif ($value[0] =~ /Chemistry/){$battery{$id}->{'technology'} = $value[1] }
7623				elsif ($value[0] =~ /Serial Number/){$battery{$id}->{'serial_number'} = $value[1] }
7624				elsif ($value[0] =~ /^Name/){$battery{$id}->{'model_name'} = main::dmi_cleaner($value[1]) }
7625				elsif ($value[0] eq 'Design Capacity'){
7626					$value[1] =~ s/\s*mwh$//i;
7627					$battery{$id}->{'energy_full_design'} = sprintf("%.1f", $value[1]/1000);
7628				}
7629				elsif ($value[0] eq 'Design Voltage'){
7630					$value[1] =~ s/\s*mv$//i;
7631					$battery{$id}->{'voltage_min_design'} = sprintf("%.1f", $value[1]/1000);
7632				}
7633			}
7634			if ($battery{$id}->{'energy_now'} && $battery{$id}->{'energy_full'}){
7635				$battery{$id}->{'capacity'} = 100 * $battery{$id}->{'energy_now'} / $battery{$id}->{'energy_full'};
7636				$battery{$id}->{'capacity'} = sprintf("%.1f%", $battery{$id}->{'capacity'});
7637			}
7638			if ($battery{$id}->{'energy_full_design'} && $battery{$id}->{'energy_full'}){
7639				$battery{$id}->{'of_orig'} = 100 * $battery{$id}->{'energy_full'} / $battery{$id}->{'energy_full_design'};
7640				$battery{$id}->{'of_orig'} = sprintf("%.0f%", $battery{$id}->{'of_orig'});
7641			}
7642		}
7643		elsif ($row->[0] > 22){
7644			last;
7645		}
7646	}
7647	print Data::Dumper::Dumper \%battery if $dbg[33];
7648	main::log_data('dump','dmi: %battery',\%battery) if $b_log;
7649	eval $end if $b_log;
7650	return %battery;
7651}
7652sub upower_data {
7653	my ($id) = @_;
7654	eval $start if $b_log;
7655	my (%data);
7656	if (!$b_upower && $upower){
7657		@upower_items = main::grabber("$upower -e",'','strip');
7658		$b_upower = 1;
7659	}
7660	if ($upower && @upower_items){
7661		foreach (@upower_items){
7662			if ($_ =~ /$id/){
7663				my @working = main::grabber("$upower -i $_",'','strip');
7664				foreach my $row (@working){
7665					my @temp = split(/\s*:\s*/, $row);
7666					if ($temp[0] eq 'percentage'){
7667						$data{'percent'} = $temp[1];
7668					}
7669					elsif ($temp[0] eq 'rechargeable'){
7670						$data{'rechargeable'} = $temp[1];
7671					}
7672				}
7673				last;
7674			}
7675		}
7676	}
7677	main::log_data('dump','upower: %data',\%data) if $b_log;
7678	eval $end if $b_log;
7679	return %data;
7680}
7681}
7682
7683## BluetoothItem
7684{
7685package BluetoothItem;
7686
7687my ($b_bluetooth,$b_hci_error,$b_hci,$b_rfk,$b_service);
7688my ($service);
7689my (%hci);
7690sub get {
7691	eval $start if $b_log;
7692	my (@rows);
7693	my $num = 0;
7694	$b_bluetooth = 1 if @ps_cmd && (grep {m|/bluetoothd\b|} @ps_cmd);
7695	# note: rapi 4 has pci bus
7696	if (($b_arm || $b_mips) && !$use{'soc-bluetooth'} && !$use{'pci-tool'}){
7697		# do nothing, but keep the test conditions to force
7698		# the non arm case to always run
7699		# my $type = ($b_arm) ? 'arm' : 'mips';
7700		# my $key = 'Message';
7701		# push(@rows,{
7702		# main::key($num++,0,1,$key) => main::row_defaults($type . '-pci',''),
7703		# },);
7704	}
7705	else {
7706		push(@rows,device_output());
7707	}
7708	push(@rows,usb_output());
7709	if (!@rows){
7710		if ($show{'bluetooth-forced'}){
7711			my $key = 'Message';
7712			push(@rows,{
7713			main::key($num++,0,1,$key) => main::row_defaults('bluetooth-data'),
7714			},);
7715		}
7716	}
7717	# if there are any unhandled hci items print them out
7718	if (%hci){
7719		push(@rows,advanced_output('check',''));
7720	}
7721	eval $end if $b_log;
7722	return @rows;
7723}
7724
7725sub device_output {
7726	eval $start if $b_log;
7727	return if !$devices{'bluetooth'};
7728	my ($bus_id,@rows);
7729	my ($j,$num) = (0,1);
7730	foreach my $row (@{$devices{'bluetooth'}}){
7731		$num = 1;
7732		$bus_id = '';
7733		$j = scalar @rows;
7734		my $driver = ($row->[9]) ? $row->[9] : 'N/A';
7735		my $device = $row->[4];
7736		$device = ($device) ? main::pci_cleaner($device,'output') : 'N/A';
7737		# have seen absurdly verbose card descriptions, with non related data etc
7738		if (length($device) > 85 || $size{'max'} < 110){
7739			$device = main::pci_long_filter($device);
7740		}
7741		push(@rows, {
7742		main::key($num++,1,1,'Device') => $device,
7743		},);
7744		if ($extra > 0 && $use{'pci-tool'} && $row->[12]){
7745			my $item = main::get_pci_vendor($row->[4],$row->[12]);
7746			$rows[$j]->{main::key($num++,0,2,'vendor')} = $item if $item;
7747		}
7748		$rows[$j]->{main::key($num++,1,2,'driver')} = $driver;
7749		if ($extra > 0 && $row->[9] && !$bsd_type){
7750			my $version = main::get_module_version($row->[9]);
7751			$rows[$j]->{main::key($num++,0,3,'v')} = $version if $version;
7752		}
7753		if ($b_admin && $row->[10]){
7754			$row->[10] = main::get_driver_modules($row->[9],$row->[10]);
7755			$rows[$j]->{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10];
7756		}
7757		if ($extra > 0){
7758			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]";
7759		}
7760		if ($extra > 1){
7761			my $chip_id = main::get_chip_id($row->[5],$row->[6]);
7762			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $chip_id;
7763			if ($extra > 2 && $row->[1]){
7764				$rows[$j]->{main::key($num++,0,2,'class-ID')} = $row->[1];
7765			}
7766		}
7767		# weird serial rpi bt
7768		if ($use{'soc-bluetooth'}){
7769			# /sys/devices/platform/soc/fe201000.serial/
7770			$bus_id = "$row->[6].$row->[1]" if defined $row->[1] && defined $row->[6];
7771		}
7772		else {
7773			# only theoretical, never seen one
7774			$bus_id = "$row->[2].$row->[3]" if defined $row->[2] && defined $row->[3];
7775		}
7776		push(@rows,advanced_output('pci',$bus_id)) if $bus_id;
7777		# print "$row->[0]\n";
7778	}
7779	eval $end if $b_log;
7780	return @rows;
7781}
7782sub usb_output {
7783	eval $start if $b_log;
7784	return if !$usb{'bluetooth'};
7785	my (@rows,$path_id,$product);
7786	my ($j,$num) = (0,1);
7787	foreach my $row (@{$usb{'bluetooth'}}){
7788		# print Data::Dumper::Dumper $row;
7789		$num = 1;
7790		$j = scalar @rows;
7791		# makre sure to reset, or second device trips last flag
7792		($path_id,$product) = ('','');
7793		$product = main::cleaner($row->[13]) if $row->[13];
7794		$product ||= 'N/A';
7795		$row->[15] ||= 'N/A';
7796		$path_id = $row->[2] if $row->[2];
7797		push(@rows, {
7798		main::key($num++,1,1,'Device') => $product,
7799		main::key($num++,0,2,'type') => 'USB',
7800		main::key($num++,1,2,'driver') => $row->[15],
7801		},);
7802		if ($extra > 0 && $row->[15] && !$bsd_type){
7803			my $version = main::get_module_version($row->[15]);
7804			$rows[$j]->{main::key($num++,0,3,'v')} = $version if $version;
7805		}
7806		if ($extra > 0){
7807			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]";
7808		}
7809		if ($extra > 1){
7810			$row->[7] ||= 'N/A';
7811			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $row->[7];
7812		}
7813		if ($extra > 2 && defined $row->[5] && $row->[5] ne ''){
7814			$rows[$j]->{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]";
7815		}
7816		if ($extra > 2 && $row->[16]){
7817			$rows[$j]->{main::key($num++,0,2,'serial')} = main::apply_filter($row->[16]);
7818		}
7819		push(@rows,advanced_output('usb',$path_id)) if $path_id;
7820	}
7821	eval $end if $b_log;
7822	return @rows;
7823}
7824sub advanced_output {
7825	my ($type,$bus_id) = @_;
7826	eval $start if $b_log;
7827	my (@rows,@temp);
7828	my ($j,$num,$k,$l,$m,$n,$address,$id,$note,$tool) = (0,1,2,3,4,5,'','','','');
7829	if (!$b_hci && $alerts{'hciconfig'}->{'action'} eq 'use'){
7830		hciconfig_data();
7831		$tool = 'hciconfig';
7832	}
7833	elsif (!$b_hci && $alerts{'bt-adapter'}->{'action'} eq 'use'){
7834		bt_tool_data();
7835		$tool = 'bt-adapter';
7836	}
7837	if (!$b_rfk && -e '/sys/class/bluetooth/'){
7838		rfkill_data();
7839		$tool = 'rfkill' if !$tool;
7840	}
7841	# print "bid: $bus_id\n";
7842	if ($type ne 'check'){
7843		@temp = main::globber('/sys/class/bluetooth/*');
7844		@temp = map {$_ = Cwd::abs_path($_);$_} @temp if @temp;
7845		# print Data::Dumper::Dumper \@temp;
7846		@temp = grep {/$bus_id/} @temp if @temp;
7847		@temp = map {$_ =~ s|^/.*/||;$_;} @temp if @temp;
7848		# print Data::Dumper::Dumper \@temp;
7849	}
7850	elsif ($type eq 'check' && %hci){
7851		@temp = keys %hci;
7852		$id = '-ID';
7853		($k,$l,$m,$n) = (1,2,3,4);
7854	}
7855	if (@temp && %hci){
7856		if ($hci{'alert'}){
7857			if (keys %hci == 1){
7858				check_service(); # sets $service
7859				$j = scalar @rows;
7860				$rows[$j]->{main::key($num++,1,$k,'Report')} = $tool;
7861				$rows[$j]->{main::key($num++,0,$l,'bt-service')} = $service;
7862				$rows[$j]->{main::key($num++,0,$l,'note')} = $hci{'alert'};
7863			}
7864			else {
7865				$note = $hci{'alert'};
7866			}
7867			delete $hci{'alert'};
7868		}
7869		foreach my $item (@temp){
7870			if ($hci{$item}){
7871				$j = scalar @rows;
7872				push(@rows,{
7873				main::key($num++,1,$k,'Report' . $id) => $tool,
7874				},);
7875				if ($note){
7876					$rows[$j]->{main::key($num++,0,$l,'note')} = $note;
7877				}
7878				# synthesize for rfkill
7879				if (!$hci{$item}->{'state'}){
7880					$hci{$item}->{'state'} = ($b_bluetooth) ? 'up' : 'down';
7881				}
7882				$rows[$j]->{main::key($num++,0,$l,'ID')} = $item;
7883				if (defined $hci{$item}->{'rf-index'} &&
7884				 ($extra > 0 || $hci{$item}->{'state'} eq 'down')){
7885					$rows[$j]->{main::key($num++,0,$m,'rfk-id')} = $hci{$item}->{'rf-index'};
7886				}
7887				$rows[$j]->{main::key($num++,1,$l,'state')} = $hci{$item}->{'state'};
7888				# this only appears for hciconfig, bt-adapter does not run without bt service
7889				if (!$b_bluetooth || $hci{$item}->{'state'} eq 'down'){
7890					if (!$b_bluetooth || $hci{$item}->{'state'} eq 'down'){
7891						check_service(); # sets $service
7892						$rows[$j]->{main::key($num++,0,$m,'bt-service')} = $service;
7893					}
7894					if ($hci{$item}->{'hard-blocked'}){
7895						$rows[$j]->{main::key($num++,1,$m,'rfk-block')} = '';
7896						$rows[$j]->{main::key($num++,0,$n,'hardware')} = $hci{$item}->{'hard-blocked'};
7897						$rows[$j]->{main::key($num++,0,$n,'software')} = $hci{$item}->{'soft-blocked'};
7898					}
7899				}
7900				if (!$hci{$item}->{'address'} && $tool eq 'rfkill'){
7901					$address = main::row_defaults('recommends');
7902				}
7903				else {
7904					$address = main::apply_filter($hci{$item}->{'address'});
7905				}
7906				$rows[$j]->{main::key($num++,0,$l,'address')} = $address;
7907				# lmp/hci version only hciconfig sadly
7908				if (defined $hci{$item}->{'lmp-version'} &&
7909				 (my $btv = bluetooth_version($hci{$item}->{'lmp-version'}))){
7910					$rows[$j]->{main::key($num++,0,$l,'bt-v')} = $btv;
7911				}
7912				if ($extra > 0 && defined $hci{$item}->{'lmp-version'}){
7913					$rows[$j]->{main::key($num++,0,$l,'lmp-v')} = $hci{$item}->{'lmp-version'};
7914					if ($extra > 1 && $hci{$item}->{'lmp-subversion'}){
7915						$rows[$j]->{main::key($num++,0,$m,'sub-v')} = $hci{$item}->{'lmp-subversion'};
7916					}
7917				}
7918				if ($extra > 0 && defined $hci{$item}->{'hci-version'} && ($extra > 2 || !$hci{$item}->{'lmp-version'} ||
7919				 ($hci{$item}->{'lmp-version'} && $hci{$item}->{'lmp-version'} ne $hci{$item}->{'hci-version'}))){
7920					$rows[$j]->{main::key($num++,0,$l,'hci-v')} = $hci{$item}->{'hci-version'};
7921					if ($extra > 1 && $hci{$item}->{'hci-revision'}){
7922						$rows[$j]->{main::key($num++,0,$m,'rev')} = $hci{$item}->{'hci-revision'};
7923					}
7924				}
7925				# if ($extra > 1 && $hci{$item}->{'discoverable'}){
7926				# 	$rows[$j]->{main::key($num++,1,$l,'discover')} = $hci{$item}->{'discoverable'};
7927				# 	if ($extra > 2 && $hci{$item}->{'discovering'}){
7928				# 		$rows[$j]->{main::key($num++,1,$m,'active')} = $hci{$item}->{'discovering'};
7929				# 	}
7930				# }
7931				# if ($extra > 1 && $hci{$item}->{'pairable'}){
7932				# 	$rows[$j]->{main::key($num++,0,$l,'pair')} = $hci{$item}->{'pairable'};
7933				# }
7934				# this data only from hciconfig
7935				if ($b_admin &&
7936				   ($hci{$item}->{'acl-mtu'} || $hci{$item}->{'sco-mtu'} || $hci{$item}->{'link-policy'})){
7937					$j = scalar @rows;
7938					push(@rows,{
7939					main::key($num++,1,$l,'Info') => '',
7940					},);
7941					if ($hci{$item}->{'acl-mtu'}){
7942						$rows[$j]->{main::key($num++,0,$m,'acl-mtu')} = $hci{$item}->{'acl-mtu'};
7943					}
7944					if ($hci{$item}->{'sco-mtu'}){
7945						$rows[$j]->{main::key($num++,0,$m,'sco-mtu')} = $hci{$item}->{'sco-mtu'};
7946					}
7947					if ($hci{$item}->{'link-policy'}){
7948						$rows[$j]->{main::key($num++,0,$m,'link-policy')} = $hci{$item}->{'link-policy'};
7949					}
7950					if ($hci{$item}->{'link-mode'}){
7951						$rows[$j]->{main::key($num++,0,$m,'link-mode')} = $hci{$item}->{'link-mode'};
7952					}
7953					if ($hci{$item}->{'service-classes'}){
7954						$rows[$j]->{main::key($num++,0,$m,'service-classes')} = $hci{$item}->{'service-classes'};
7955					}
7956				}
7957				delete $hci{$item};
7958			}
7959		}
7960	}
7961	if (!@rows && !$b_hci_error && ($alerts{'hciconfig'}->{'action'} ne 'use' &&
7962	  $alerts{'bt-adapter'}->{'action'} ne 'use')){
7963		my $key = 'Report';
7964		my $value = '';
7965		if ($alerts{'hciconfig'}->{'action'} eq 'platform' ||
7966		 $alerts{'bt-adapter'}->{'action'} eq 'platform'){
7967			$value = main::row_defaults('tool-missing-os','bluetooth');
7968		}
7969		else {
7970			$value = main::row_defaults('tools-missing','hciconfig/bt-adapter');
7971		}
7972		push(@rows,{
7973		main::key($num++,0,1,$key) => $value,
7974		},);
7975		$b_hci_error = 1;
7976	}
7977	eval $end if $b_log;
7978	return @rows;
7979}
7980
7981sub bt_tool_data {
7982	eval $start if $b_log;
7983	$b_hci = 1;
7984	my (@data,$id);
7985	if ($fake{'bluetooth'}){
7986		my $file;
7987		$file = "";
7988		@data = main::reader($file,'strip');
7989	}
7990	else {
7991		if ($b_bluetooth){
7992			my $cmd = "$alerts{'bt-adapter'}->{'path'} --info 2>/dev/null";
7993			@data = main::grabber($cmd,'', 'strip');
7994		}
7995	}
7996	# print Data::Dumper::Dumper \@data;
7997	main::log_data('dump','@data', \@data) if $b_log;
7998	foreach (@data){
7999		my @working = split(/:\s*/,$_);
8000		# print Data::Dumper::Dumper \@working;
8001		next if ! @working;
8002		if ($working[0] =~ /^\[([^\]]+)\]/){
8003			$id = $1;
8004		}
8005		elsif ($working[0] eq 'Address'){
8006			$hci{$id}->{'address'} = join(':',@working[1 .. $#working]);
8007		}
8008		elsif ($working[0] eq 'Powered'){
8009			$hci{$id}->{'state'} = ($working[1] =~ /^(1|yes)\b/) ? 'up': 'down';
8010		}
8011		elsif ($working[0] eq 'Discoverable'){
8012			$hci{$id}->{'discoverable'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no';
8013		}
8014		elsif ($working[0] eq 'Pairable'){
8015			$hci{$id}->{'pairable'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no';
8016		}
8017		elsif ($working[0] eq 'Discovering'){
8018			$hci{$id}->{'discovering'} = ($working[1] =~ /^(1|yes)\b/) ? 'yes': 'no';
8019		}
8020	}
8021	if (!@data && !$b_bluetooth){
8022		$hci{'alert'} = main::row_defaults('bluetooth-down');
8023	}
8024	print Data::Dumper::Dumper \%hci if $dbg[27];
8025	main::log_data('dump','%hci', \%hci) if $b_log;
8026	eval $end if $b_log;
8027}
8028sub hciconfig_data {
8029	eval $start if $b_log;
8030	$b_hci = 1;
8031	my (@data,$id);
8032	if ($fake{'bluetooth'}){
8033		my $file;
8034		$file = "";
8035		@data = main::reader($file,'strip');
8036	}
8037	else {
8038		my $cmd = "$alerts{'hciconfig'}->{'path'} -a 2>/dev/null";
8039		@data = main::grabber($cmd,'', 'strip');
8040	}
8041	# print Data::Dumper::Dumper \@data;
8042	main::log_data('dump','@data', \@data) if $b_log;
8043	foreach (@data){
8044		if (/^(hci[0-9]+):\s+Type:\s+(.*)\s+Bus:\s+([\S]+)/){
8045			$id = $1;
8046			$hci{$id} = {
8047			'type'=> $2,
8048			'bus' => $3,
8049			};
8050		}
8051		elsif (/^BD Address:\s+([0-9A-F:]*)\s+ACL\s+MTU:\s+([0-9:]+)\s+SCO MTU:\s+([0-9:]+)/){
8052			$hci{$id}->{'address'} = $1;
8053			$hci{$id}->{'acl-mtu'} = $2;
8054			$hci{$id}->{'sco-mtu'} = $3;
8055		}
8056		elsif (/^(UP|DOWN).*/){
8057			$hci{$id}->{'state'} = lc($1);
8058		}
8059		elsif (/^HCI Version:\s+([0-9\.]+)\s+.*Revision:\s+0x([0-9a-f]+)/){
8060			$hci{$id}->{'hci-version'} = $1;
8061			$hci{$id}->{'hci-revision'} = $2;
8062		}
8063		elsif (/^LMP Version:\s+([0-9\.]+)\s+.*Subversion:\s+0x([0-9a-f]+)/){
8064			$hci{$id}->{'lmp-version'} = $1;
8065			$hci{$id}->{'lmp-subversion'} = $2;
8066		}
8067		elsif (/^Link policy:\s+(.*)/){
8068			$hci{$id}->{'link-policy'} = lc($1);
8069		}
8070		elsif (/^Link mode:\s+(.*)/){
8071			$hci{$id}->{'link-mode'} = lc($1);
8072		}
8073		elsif (/^Service Classes?:\s+(.+)/){
8074			$hci{$id}->{'service-classes'} = main::general_cleaner(lc($1));
8075		}
8076	}
8077	print Data::Dumper::Dumper \%hci if $dbg[27];
8078	main::log_data('dump','%hci', \%hci) if $b_log;
8079	eval $end if $b_log;
8080}
8081sub rfkill_data {
8082	eval $start if $b_log;
8083	$b_rfk = 1;
8084	my (@data,$id,$value);
8085	if ($fake{'bluetooth'}){
8086		my $file;
8087		$file = "";
8088		@data = main::reader($file,'strip');
8089	}
8090	else {
8091		# /state is the state of rfkill, NOT bluetooth!
8092		@data = main::globber('/sys/class/bluetooth/hci*/rfkill*/{hard,index,soft}');
8093	}
8094	# print Data::Dumper::Dumper \@data;
8095	main::log_data('dump','@data', \@data) if $b_log;
8096	foreach (@data){
8097		$id = (split(/\//,$_))[4];
8098		if (m|/soft$|){
8099			$value = main::reader($_,'strip',0);
8100			$hci{$id}->{'soft-blocked'} = ($value) ? 'yes': 'no';
8101			$hci{$id}->{'state'} = 'down' if $hci{$id}->{'soft-blocked'} eq 'yes';
8102		}
8103		elsif (m|/hard$|){
8104			$value = main::reader($_,'strip',0);
8105			$hci{$id}->{'hard-blocked'} = ($value) ? 'yes': 'no';
8106			$hci{$id}->{'state'} = 'down' if $hci{$id}->{'hard-blocked'} eq 'yes';
8107		}
8108		elsif (m|/index$|){
8109			$value = main::reader($_,'strip',0);
8110			$hci{$id}->{'rf-index'} = $value;
8111		}
8112	}
8113	print Data::Dumper::Dumper \%hci if $dbg[27];
8114	main::log_data('dump','%hci', \%hci) if $b_log;
8115	eval $end if $b_log;
8116}
8117sub check_service {
8118	eval $start if $b_log;
8119	if (!$b_service){
8120		$service = ServiceData::get('status','bluetooth');
8121		$service ||= 'N/A';
8122		$b_service = 1;
8123	}
8124	eval $end if $b_log;
8125}
8126sub bluetooth_version {
8127	eval $start if $b_log;
8128	my ($lmp) = @_;
8129	return if !defined $lmp || !main::is_numeric($lmp);
8130	$lmp = int($lmp);
8131	# conveniently, LMP starts with 0, so perfect for array indexes
8132	my @bt = qw(1.0b 1.1 1.2 2.0 2.1 3.0 4.0 4.1 4.2 5.0 5.1 5.2);
8133	return $bt[$lmp];
8134	eval $end if $b_log;
8135}
8136}
8137
8138## CpuItem
8139{
8140package CpuItem;
8141
8142sub get {
8143	eval $start if $b_log;
8144	my ($type) = @_;
8145	my (@rows);
8146	if ($type eq 'short' || $type eq 'basic'){
8147		# note, for short form, just return the raw data, not the processed output
8148		@rows = short_data($type);
8149		if ($type eq 'basic'){
8150			@rows = short_output(\@rows);
8151		}
8152	}
8153	else {
8154		@rows = full_output();
8155	}
8156	eval $end if $b_log;
8157	return @rows;
8158}
8159sub full_output {
8160	eval $start if $b_log;
8161	my $num = 0;
8162	my ($b_flags,$b_speeds,$core_speeds_value,$flag_key,@flags,%cpu,@rows);
8163	my $sleep = $cpu_sleep * 1000000;
8164	if (my $file = $system_files{'proc-cpuinfo'}){
8165		# bsd sleep is set before sysctl runs, same idea
8166		if ($b_hires){
8167			eval 'Time::HiRes::usleep($sleep)';
8168		}
8169		else {
8170			select(undef, undef, undef, $cpu_sleep);
8171		}
8172		%cpu = cpuinfo_data($file,'full');
8173	}
8174	elsif ($bsd_type){
8175		my ($key1,$val1) = ('','');
8176		if ($alerts{'sysctl'}){
8177			if ($alerts{'sysctl'}->{'action'} eq 'use'){
8178# 				$key1 = 'Status';
8179# 				$val1 = main::row_defaults('dev');
8180				%cpu = sysctl_data('full');
8181			}
8182			else {
8183				$key1 = ucfirst($alerts{'sysctl'}->{'action'});
8184				$val1 = $alerts{'sysctl'}->{$alerts{'sysctl'}->{'action'}};
8185				@rows = ({main::key($num++,0,1,$key1) => $val1,});
8186				return @rows;
8187			}
8188		}
8189	}
8190	my %properties = cpu_properties(\%cpu);
8191	my $type = ($properties{'cpu-type'}) ? $properties{'cpu-type'}: '';
8192	my @processors = @{$cpu{'processors'}};
8193	my @speeds = cpu_speeds(\@processors);
8194	my $j = scalar @rows;
8195	$cpu{'model_name'} ||= 'N/A';
8196	push(@rows, {
8197	main::key($num++,1,1,'Info') => $properties{'cpu-layout'},
8198	main::key($num++,0,2,'model') => $cpu{'model_name'},
8199	},);
8200	if ($cpu{'system-cpus'}){
8201		my %system_cpus = %{$cpu{'system-cpus'}};
8202		my $i = 1;
8203		my $counter = (%system_cpus && scalar keys %system_cpus > 1) ? '-' : '';
8204		foreach my $key (keys %system_cpus){
8205			$counter = '-' . $i++ if $counter;
8206			$rows[$j]->{main::key($num++,0,2,'variant'.$counter)} = $key;
8207		}
8208	}
8209	if ($b_admin && $properties{'socket'}){
8210		if ($properties{'upgrade'}){
8211			$rows[$j]->{main::key($num++,1,2,'socket')} = $properties{'socket'} . ' (' . $properties{'upgrade'} . ')';
8212			$rows[$j]->{main::key($num++,0,3,'note')} = main::row_defaults('note-check');
8213		}
8214		else {
8215			$rows[$j]->{main::key($num++,0,2,'socket')} = $properties{'socket'};
8216		}
8217	}
8218	$properties{'bits-sys'} ||= 'N/A';
8219	$rows[$j]->{main::key($num++,0,2,'bits')} = $properties{'bits-sys'};
8220	if ($type){
8221		$rows[$j]->{main::key($num++,0,2,'type')} = $type;
8222	}
8223	if ($extra > 0){
8224		$cpu{'arch'} ||= 'N/A';
8225		$rows[$j]->{main::key($num++,1,2,'arch')} = $cpu{'arch'};
8226		if ($cpu{'arch-note'}){
8227			$rows[$j]->{main::key($num++,0,3,'note')} = $cpu{'arch-note'};
8228		}
8229		# ntoe: had if arch, but stepping can be defined where arch failed, stepping can be 0
8230		if (!$b_admin && defined $cpu{'stepping'}){
8231			$rows[$j]->{main::key($num++,0,2,'rev')} = $cpu{'stepping'};
8232		}
8233	}
8234	if ($b_admin){
8235		$rows[$j]->{main::key($num++,0,2,'family')} = hex_and_decimal($cpu{'family'});
8236		$rows[$j]->{main::key($num++,0,2,'model-id')} = hex_and_decimal($cpu{'model_id'});
8237		$rows[$j]->{main::key($num++,0,2,'stepping')} = hex_and_decimal($cpu{'stepping'});
8238		if (!$b_arm && !$b_mips && !$b_ppc && $cpu{'type'} ne 'elbrus'){
8239			$cpu{'microcode'} ||= 'N/A';
8240			$rows[$j]->{main::key($num++,0,2,'microcode')} = $cpu{'microcode'};
8241		}
8242	}
8243	if (($extra > 1 && ($properties{'l1-cache'} || $properties{'l3-cache'})) ||
8244	 ((!$b_arm && !$b_mips && !$b_ppc) || $properties{'l2-cache'})){
8245		$rows[$j]->{main::key($num++,1,2,'cache')} = '';
8246		if ($extra > 1 && $properties{'l1-cache'}){
8247			$rows[$j]->{main::key($num++,0,3,'L1')} = main::get_size($properties{'l1-cache'},'string');
8248		}
8249		# the arm + l2 will never be true since arm cpus don't have l2 cache
8250		$properties{'l2-cache'} = ($properties{'l2-cache'}) ? main::get_size($properties{'l2-cache'},'string') : 'N/A';
8251		$rows[$j]->{main::key($num++,0,3,'L2')} = $properties{'l2-cache'};
8252		if ($extra > 1 && $properties{'l3-cache'}){
8253			$rows[$j]->{main::key($num++,0,3,'L3')} = main::get_size($properties{'l3-cache'},'string');
8254		}
8255		if ($properties{'cache-check'}){
8256			$rows[$j]->{main::key($num++,0,3,'note')} = $properties{'cache-check'};
8257		}
8258	}
8259	if ($extra > 0 && !$show{'cpu-flag'}){
8260		$j = scalar @rows;
8261		@flags = split(/\s+/, $cpu{'flags'}) if $cpu{'flags'};
8262		$flag_key = ($b_arm || $bsd_type) ? 'features': 'flags';
8263		my $flag = 'N/A';
8264		if (@flags){
8265			# failure to read dmesg.boot: dmesg.boot permissions; then short -Cx list flags
8266			@flags = grep {/^(dmesg.boot|permissions|avx[2-9]?|lm|nx|pae|pni|(sss|ss)e([2-9])?([a-z])?(_[0-9])?|svm|vmx)$/} @flags;
8267			@flags = map {s/pni/sse3/; $_} @flags;
8268			@flags = sort @flags;
8269			$flag = join(' ', @flags) if @flags;
8270		}
8271		if ($b_arm && $flag eq 'N/A'){
8272			$flag = main::row_defaults('arm-cpu-f');
8273		}
8274		push(@rows, {
8275		main::key($num++,0,2,$flag_key) => $flag,
8276		});
8277		$b_flags = 1;
8278	}
8279	if ($extra > 0 && !$bsd_type){
8280		my $bogomips = (main::is_numeric($cpu{'bogomips'})) ? int($cpu{'bogomips'}) : 'N/A';
8281		$rows[$j]->{main::key($num++,0,2,'bogomips')} = $bogomips;
8282	}
8283	$j = scalar @rows;
8284	my $core_key = (scalar @speeds > 1) ? 'Core speeds (MHz)' : 'Core speed (MHz)';
8285	my $speed_key = ($properties{'speed-key'}) ? $properties{'speed-key'}: 'Speed';
8286	my $min_max = ($properties{'min-max'}) ? $properties{'min-max'}: 'N/A';
8287	my $min_max_key = ($properties{'min-max-key'}) ? $properties{'min-max-key'}: 'min/max';
8288	my $speed = (defined $properties{'speed'}) ? $properties{'speed'}: 'N/A';
8289	# aren't able to get per core speeds in bsds yet
8290	if (@speeds){
8291		if (grep {$_ ne '0'} @speeds){
8292			$core_speeds_value = '';
8293			$b_speeds = 1;
8294		}
8295		else {
8296			$core_speeds_value = main::row_defaults('cpu-speeds');
8297		}
8298	}
8299	else {
8300		$core_speeds_value = 'N/A';
8301	}
8302	$j = scalar @rows;
8303	push(@rows, {
8304	main::key($num++,1,1,$speed_key) => $speed,
8305	main::key($num++,0,2,$min_max_key) => $min_max,
8306	});
8307	if ($b_admin && $properties{'dmi-speed'} && $properties{'dmi-max-speed'}){
8308		$rows[$j]->{main::key($num++,0,2,'base/boost')} = $properties{'dmi-speed'} . '/' . $properties{'dmi-max-speed'};
8309	}
8310	if ($extra > 0){
8311		my $boost = get_boost_status();
8312		$rows[$j]->{main::key($num++,0,2,'boost')} = $boost if $boost;
8313	}
8314	if ($extra > 2){
8315		if ($properties{'volts'}){
8316			$rows[$j]->{main::key($num++,0,2,'volts')} = $properties{'volts'} . ' V';
8317		}
8318		if ($properties{'ext-clock'}){
8319			$rows[$j]->{main::key($num++,0,2,'ext-clock')} = $properties{'ext-clock'};
8320		}
8321	}
8322	$rows[$j]->{main::key($num++,1,2,$core_key)} = $core_speeds_value;
8323	my $i = 1;
8324	# if say 96 0 speed cores, no need to print all those 0s
8325	if ($b_speeds){
8326		foreach (@speeds){
8327			$rows[$j]->{main::key($num++,0,3,$i++)} = $_;
8328		}
8329	}
8330	if ($show{'cpu-flag'} && !$b_flags){
8331		$flag_key = ($b_arm || $bsd_type) ? 'Features': 'Flags';
8332		@flags = split(/\s+/, $cpu{'flags'}) if $cpu{'flags'};
8333		my $flag = 'N/A';
8334		if (@flags){
8335			@flags = sort @flags;
8336			$flag = join(' ', @flags) if @flags;
8337		}
8338		push(@rows, {
8339		main::key($num++,0,1,$flag_key) => $flag,
8340		},);
8341	}
8342	if ($b_admin){
8343		my @bugs = cpu_bugs_sys();
8344		my $value = '';
8345		if (!@bugs){
8346			if ($cpu{'bugs'}){
8347				my @proc_bugs = split(/\s+/, $cpu{'bugs'});
8348				@proc_bugs = sort @proc_bugs;
8349				$value = join(' ', @proc_bugs);
8350			}
8351			else {
8352				$value = main::row_defaults('cpu-bugs-null');
8353			}
8354		}
8355		push(@rows, {
8356		main::key($num++,1,1,'Vulnerabilities') => $value,
8357		},);
8358		if (@bugs){
8359			$j = $#rows;
8360			foreach my $bug (@bugs){
8361				$rows[$j]->{main::key($num++,1,2,'Type')} = $bug->[0];
8362				$rows[$j]->{main::key($num++,0,3,$bug->[1])} = $bug->[2];
8363				$j++;
8364			}
8365		}
8366	}
8367	eval $end if $b_log;
8368	return @rows;
8369}
8370sub short_output {
8371	eval $start if $b_log;
8372	my ($cpu) = @_;
8373	my @data;
8374	my $num = 0;
8375	$cpu->[1] ||= main::row_defaults('cpu-model-null');
8376	$cpu->[2] ||= 'N/A';
8377	@data = ({
8378	main::key($num++,1,1,'Info') => $cpu->[0] . ' ' . $cpu->[1] . ' [' . $cpu->[2] . ']',
8379	#main::key($num++,0,2,'type') => $cpu->[2],
8380	},);
8381	if ($extra > 0){
8382		$data[0]->{main::key($num++,1,2,'arch')} = $cpu->[7];
8383		if ($cpu->[8]){
8384			$data[0]->{main::key($num++,0,3,'note')} = $cpu->[8];
8385		}
8386	}
8387	$data[0]->{main::key($num++,0,2,$cpu->[3])} = $cpu->[4];
8388	if ($cpu->[6]){
8389		$data[0]->{main::key($num++,0,2,$cpu->[5])} = $cpu->[6];
8390	}
8391	eval $end if $b_log;
8392	return @data;
8393}
8394sub short_data {
8395	eval $start if $b_log;
8396	my ($type) = @_;
8397	my $num = 0;
8398	my (%cpu,@data,%speeds);
8399	my $sys = '/sys/devices/system/cpu/cpufreq/policy0';
8400	# NOTE: : Permission denied, ie, this is not always readable
8401	# /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_cur_freq
8402	if (my $file = $system_files{'proc-cpuinfo'}){
8403		my $sleep = $cpu_sleep * 1000000;
8404		if ($b_hires){
8405			eval 'Time::HiRes::usleep($sleep)';
8406		}
8407		else {
8408			select(undef, undef, undef, $cpu_sleep);
8409		}
8410		%cpu = cpuinfo_data($file,$type);
8411	}
8412	elsif ($bsd_type){
8413		my ($key1,$val1) = ('','');
8414		if ($alerts{'sysctl'}){
8415			if ($alerts{'sysctl'}->{'action'} eq 'use'){
8416# 				$key1 = 'Status';
8417# 				$val1 = main::row_defaults('dev');
8418				%cpu = sysctl_data($type);
8419			}
8420			else {
8421				$key1 = ucfirst($alerts{'sysctl'}->{'action'});
8422				$val1 = $alerts{'sysctl'}->{$alerts{'sysctl'}->{'action'}};
8423				@data = ({main::key($num++,0,1,$key1) => $val1,});
8424				return @data;
8425			}
8426		}
8427	}
8428	# $cpu{'cur-freq'} = $cpu[0]->{'core-id'}[0]{'speed'};
8429	@data = prep_short_data(\%cpu);
8430	eval $end if $b_log;
8431	return @data;
8432}
8433
8434sub prep_short_data {
8435	eval $start if $b_log;
8436	my ($cpu_data) = @_;
8437	my %properties = cpu_properties($cpu_data);
8438	my ($cpu,$speed_key,$speed,$type) = ('','speed',0,'');
8439	$cpu = $cpu_data->{'model_name'} if $cpu_data->{'model_name'};
8440 	$type = $properties{'cpu-type'} if $properties{'cpu-type'};
8441 	$speed_key = $properties{'speed-key'} if $properties{'speed-key'};
8442 	$speed = $properties{'speed'} if $properties{'speed'};
8443 	my @result = (
8444 	$properties{'cpu-layout'},
8445 	$cpu,
8446 	$type,
8447 	$speed_key,
8448 	$speed,
8449 	$properties{'min-max-key'},
8450 	$properties{'min-max'},
8451 	);
8452 	if ($extra > 0){
8453		$cpu_data->{'arch'} ||= 'N/A';
8454		$result[7] = $cpu_data->{'arch'};
8455		$result[8] = $cpu_data->{'arch-note'};
8456 	}
8457	eval $end if $b_log;
8458	return @result;
8459}
8460
8461sub cpuinfo_data {
8462	eval $start if $b_log;
8463	my ($file,$type)=  @_;
8464	my ($arch,@ids,@line,$b_first,$b_proc_int,$note,$starter);
8465	# has to be set above fake cpu section
8466	my %cpu =  set_cpu_data();
8467	$cpu{'type'} = cpu_vendor($cpu_arch) if $cpu_arch =~ /e2k/; # already set to lower
8468	# use --arm flag when testing arm cpus, and --fake-cpu to trigger fake data
8469	if ($fake{'cpu'}){
8470		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-4-core-pinebook-1.txt";
8471		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv6-single-core-1.txt";
8472		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv7-dual-core-1.txt";
8473		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/armv7-new-format-model-name-single-core.txt";
8474		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-2-die-96-core-rk01.txt";
8475		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/16-core-32-mt-ryzen.txt";
8476		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/2-16-core-epyc-abucodonosor.txt";
8477		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/2-core-probook-antix.txt";
8478		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-jean-antix.txt";
8479		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-althlon-mjro.txt";
8480		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-apu-vc-box.txt";
8481		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/amd/4-core-a10-5800k-1.txt";
8482		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-core-ht-atom-bruh.txt";
8483		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/core-2-i3.txt";
8484		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/8-core-i7-damentz64.txt";
8485		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-10-core-xeon-ht.txt";
8486		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-core-xeon-fake-dual-die-zyanya.txt";
8487		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-core-i5-fake-dual-die-hek.txt";
8488		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/2-1-core-xeon-vm-vs2017.txt";
8489		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-1-core-xeon-vps-frodo1.txt";
8490		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/intel/4-6-core-xeon-no-mt-lathander.txt";
8491		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/mips/mips-mainusg-cpuinfo.txt";
8492		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/ppc/ppc-debian-ppc64-cpuinfo.txt";
8493		# $cpu{'type'} = 'elbrus'; # uncomment to test elbrus
8494		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/elbrus-2c3/cpuinfo.txt";
8495		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/1xE1C-8.txt";
8496		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/1xE2CDSP-4.txt";
8497		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/1xE2S4-3-monocub.txt";
8498		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/1xMBE8C-7.txt";
8499		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/4xEL2S4-3.txt";
8500		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/4xE8C-7.txt";
8501		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/4xE2CDSP-4.txt";
8502		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/elbrus/cpuinfo.e8c2.txt";
8503	}
8504	my @cpuinfo = main::reader($file);
8505	my %speeds = set_cpu_speeds_sys();
8506	my @phys_cpus = (0);# start with 1 always
8507	my ($core_count,$die_holder,$die_id,$phys_id,$proc_count,$speed) = (0,0,0,0,0,0,0);
8508	my ($phys_holder) = (undef);
8509	# need to prime for arm cpus, which do not have physical/core ids usually
8510	# level 0 is phys id, level 1 is die id, level 2 is core id
8511	# note, there con be a lot of processors, 32 core HT would have 64, for example.
8512	foreach (@cpuinfo){
8513		next if /^\s*$/;
8514		@line = split(/\s*:\s*/, $_, 2);
8515		next if !$line[0];
8516		$starter = $line[0]; # preserve case for one specific ARM issue
8517		$line[0] = lc($line[0]);
8518		if ($b_arm && !$b_first && $starter eq 'Processor' && $line[1] !~ /^\d+$/){
8519			# print "l1:$line[1]\n";
8520			$cpu{'model_name'} = main::cleaner($line[1]);
8521			$cpu{'model_name'} = cpu_cleaner($cpu{'model_name'});
8522			$cpu{'type'} = 'arm';
8523			# Processor   : AArch64 Processor rev 4 (aarch64)
8524			# Processor : Feroceon 88FR131 rev 1 (v5l)
8525			if ($cpu{'model_name'} && $cpu{'model_name'} =~ /(.*)\srev\s([\S]+)\s(\(([\S]+)\))?/){
8526				$cpu{'model_name'} = $1;
8527				$cpu{'stepping'} = $2;
8528				if ($4){
8529					$cpu{'arch'} = $4;
8530					$cpu{'model_name'} .= ' ' . $cpu{'arch'} if $cpu{'model_name'} !~ /$cpu{'arch'}/i;
8531				}
8532				$cpu{'processors'}->[$proc_count] = 0;
8533				$b_proc_int = 0;
8534				$b_first = 1;
8535				# print "p0:\n";
8536			}
8537		}
8538		elsif ($line[0] eq 'processor'){
8539			# this protects against double processor lines, one int, one string
8540			if ($line[1] =~ /^\d+$/){
8541				$b_proc_int = 1;
8542				$b_first = 1;
8543				$cpu{'processors'}->[$proc_count] = 0;
8544				$proc_count++;
8545				# print "p1: $proc_count\n";
8546			}
8547			else {
8548				if (!$b_proc_int){
8549					$cpu{'processors'}->[$proc_count] = 0;
8550					$proc_count++;
8551					# print "p2a: $proc_count\n";
8552				}
8553				if (!$b_first){
8554					# note: alternate:
8555					# Processor	: AArch64 Processor rev 4 (aarch64)
8556					# but no model name type
8557					if ($b_arm || $line[1] =~ /ARM|AArch/i){
8558						$b_arm = 1;
8559						$cpu{'type'} = 'arm';
8560					}
8561					$cpu{'model_name'} = main::cleaner($line[1]);
8562					$cpu{'model_name'} = cpu_cleaner($cpu{'model'});
8563					# print "p2b:\n";
8564				}
8565				$b_first = 1;
8566			}
8567		}
8568		elsif (!$cpu{'family'} &&
8569		 ($line[0] eq 'architecture' || $line[0] eq 'cpu family' ||
8570		 $line[0] eq 'cpu architecture')){
8571			if ($line[1] =~ /^\d+$/){
8572				# translate integers to hex
8573				$cpu{'family'} = uc(sprintf("%x", $line[1]));
8574			}
8575			elsif ($b_arm){
8576				$cpu{'arch'} = $line[1];
8577			}
8578		}
8579		elsif (!defined $cpu{'stepping'} && ($line[0] eq 'stepping' ||
8580		 $line[0] eq 'cpu revision')){
8581			$cpu{'stepping'} = uc(sprintf("%x", $line[1]));
8582		}
8583		# ppc
8584		elsif (!defined $cpu{'stepping'} && $line[0] eq 'revision'){
8585			$cpu{'stepping'} = $line[1];
8586		}
8587		# this is hex so uc for cpu arch id. raspi 4 has Model rather than Hard
8588		elsif (!$cpu{'model_id'} && (!$b_ppc && !$b_arm && $line[0] eq 'model')){
8589			$cpu{'model_id'} = uc(sprintf("%x", $line[1]));
8590		}
8591		elsif (!$cpu{'model_id'} && $line[0] eq 'cpu variant'){
8592			$cpu{'model_id'} = uc($line[1]);
8593			$cpu{'model_id'} =~ s/^0X//;
8594		}
8595		# cpu can show in arm
8596		elsif (!$cpu{'model_name'} && ($line[0] eq 'model name' ||
8597		 $line[0] eq 'cpu' || $line[0] eq 'cpu model')){
8598			$cpu{'model_name'} = main::cleaner($line[1]);
8599			$cpu{'model_name'} = cpu_cleaner($cpu{'model_name'});
8600			if ($b_arm || $line[1] =~ /ARM|AArch/i){
8601				$b_arm = 1;
8602				$cpu{'type'} = 'arm';
8603				if ($cpu{'model_name'} &&
8604				 $cpu{'model_name'} =~ /(.*)\srev\s([\S]+)\s(\(([\S]+)\))?/){
8605					$cpu{'model_name'} = $1;
8606					$cpu{'stepping'} = $2;
8607					if ($4){
8608						$cpu{'arch'} = $4;
8609						$cpu{'model_name'} .= ' ' . $cpu{'arch'} if $cpu{'model_name'} !~ /$cpu{'arch'}/i;
8610					}
8611					#$cpu{'processors'}->[$proc_count] = 0;
8612				}
8613			}
8614			elsif ($b_mips || $line[1] =~ /mips/i){
8615				$b_mips = 1;
8616				$cpu{'type'} = 'mips';
8617			}
8618		}
8619		elsif ($line[0] eq 'cpu mhz' || $line[0] eq 'clock'){
8620			$speed = speed_cleaner($line[1]);
8621			$cpu{'processors'}->[$proc_count-1] = $speed;
8622			#$ids[$phys_id]->[$die_id] = [$speed];
8623		}
8624		elsif (!$cpu{'siblings'} && $line[0] eq 'siblings'){
8625			$cpu{'siblings'} = $line[1];
8626		}
8627		elsif (!$cpu{'cores'} && $line[0] eq 'cpu cores'){
8628			$cpu{'cores'} = $line[1];
8629		}
8630		# increment by 1 for every new physical id we see. These are in almost all cases
8631		# separate cpus, not separate dies within a single cpu body.
8632		elsif ($line[0] eq 'physical id'){
8633			if (!defined $phys_holder || $phys_holder != $line[1]){
8634				# only increment if not in array counter
8635				push(@phys_cpus, $line[1]) if ! grep {/$line[1]/} @phys_cpus;
8636				$phys_holder = $line[1];
8637				# print "pid: $line[1] ph: $phys_holder did: $die_id\n";
8638				$die_id = 0;
8639				#$die_holder = 0;
8640			}
8641		}
8642		elsif ($line[0] eq 'core id'){
8643			# print "ph: $phys_holder did: $die_id l1: $line[1] s: $speed\n";
8644			# https://www.pcworld.com/article/3214635/components-processors/ryzen-threadripper-review-we-test-amds-monster-cpu.html
8645			if ($line[1] > 0){
8646				$die_holder = $line[1];
8647				$core_count++;
8648			}
8649			# NOTE: this logic won't work for die detections, unforutnately.
8650			# ARM uses a different /sys based method, and ryzen relies on math on the cores
8651			# in process_data
8652			elsif ($line[1] == 0 && $die_holder > 0){
8653				$die_holder = $line[1];
8654				$core_count = 0;
8655				$die_id++ if ($cpu{'type'} ne 'intel' && $cpu{'type'} ne 'amd');
8656			}
8657			$phys_holder = 0 if ! defined $phys_holder;
8658			$ids[$phys_holder]->[$die_id][$line[1]] = $speed;
8659			# print "ph: $phys_holder did: $die_id l1: $line[1] s: $speed\n";
8660		}
8661		if (!$cpu{'type'} && $line[0] eq 'vendor_id'){
8662			$cpu{'type'} = cpu_vendor($line[1]);
8663		}
8664		## this is only for -C full cpu output
8665		if ($type eq 'full'){
8666			if (!$cpu{'l2-cache'} && ($line[0] eq 'cache size' || $line[0] eq 'l2 cache size')){
8667				if ($line[1] =~ /(\d+\s*[KMG])i?B?$/){
8668					$cpu{'l2-cache'} = main::translate_size($1);
8669				}
8670			}
8671			elsif (!$cpu{'l1-cache'} && $line[0] eq 'l1 cache size'){
8672				if ($line[1] =~ /(\d+\s*[KMG])i?B?$/){
8673					$cpu{'l1-cache'} = main::translate_size($1);
8674				}
8675			}
8676			elsif (!$cpu{'l3-cache'} && $line[0] eq 'l3 cache size'){
8677				if ($line[1] =~ /(\d+\s*[KMG])i?B?$/){
8678					$cpu{'l3-cache'} = main::translate_size($1);
8679				}
8680			}
8681			if ($cpu{'type'} eq 'elbrus'){
8682				# note: cache0 is L1i and cache1 L1d, but add both for L1
8683				if (!$cpu{'l0-cache'} && $line[0] eq 'cache0'){
8684					if ($line[1] =~ /size\s*=\s*(\d+)K\s/){
8685						$cpu{'l0-cache'} = $1;
8686					}
8687				}
8688				elsif (!$cpu{'l1-cache'} && $line[0] eq 'cache1'){
8689					if ($line[1] =~ /size\s*=\s*(\d+)K\s/){
8690						$cpu{'l1-cache'} = $1;
8691						$cpu{'l1-cache'} += $cpu{'l0-cache'} if $cpu{'l0-cache'};
8692					}
8693				}
8694				elsif (!$cpu{'l2-cache'} && $line[0] eq 'cache2'){
8695					if ($line[1] =~ /size\s*=\s*(\d+)(K|M)\s/){
8696						$cpu{'l2-cache'} = ($2 eq 'M') ? ($1*1024) : $1;
8697					}
8698				}
8699				elsif (!$cpu{'l3-cache'} && $line[0] eq 'cache3'){
8700					if ($line[1] =~ /size\s*=\s*(\d+)(K|M)\s/){
8701						$cpu{'l3-cache'} = ($2 eq 'M') ? ($1*1024) : $1;
8702					}
8703				}
8704			}
8705			if (!$cpu{'flags'} && ($line[0] eq 'flags' || $line[0] eq 'features')){
8706				$cpu{'flags'} = $line[1];
8707			}
8708			if ($extra > 0 && $line[0] eq 'bogomips'){
8709				# new arm shows bad bogomip value, so don't use it
8710				$cpu{'bogomips'} += $line[1] if $line[1] > 50;
8711			}
8712		}
8713		if ($b_admin){
8714			# note: not used unless maybe /sys data missing?
8715			if (!$cpu{'bugs'} && $line[0] eq 'bugs'){
8716				$cpu{'bugs'} = $line[1];
8717			}
8718			# unlike family and model id, microcode appears to be hex already
8719			if (!$cpu{'microcode'} && $line[0] eq 'microcode'){
8720				if ($line[1] =~ /0x/){
8721					$cpu{'microcode'} = uc($line[1]);
8722					$cpu{'microcode'} =~ s/^0X//;
8723				}
8724				else {
8725					$cpu{'microcode'} = uc(sprintf("%x", $line[1]));
8726				}
8727			}
8728		}
8729	}
8730	$cpu{'phys'} = scalar @phys_cpus;
8731	$cpu{'dies'} = $die_id++; # count starts at 0, all cpus have 1 die at least
8732	if ($b_arm || $b_mips){
8733		if ($cpu{'dies'} <= 1){
8734			my $arm_dies = cpu_dies_sys();
8735			# case were 4 core arm returned 4 sibling lists, obviously wrong
8736			$cpu{'dies'} = $arm_dies if $arm_dies && $proc_count != $arm_dies;
8737		}
8738		$cpu{'type'} = ($b_arm) ? 'arm' : 'mips' if !$cpu{'type'};
8739		if (!$bsd_type){
8740			my %system_cpus = system_cpu_name();
8741			$cpu{'system-cpus'} = \%system_cpus if %system_cpus;
8742		}
8743	}
8744	$cpu{'ids'} = (\@ids);
8745	if ($extra > 0 && !$cpu{'arch'} && $type ne 'short'){
8746		($cpu{'arch'},$cpu{'arch-note'}) = cpu_arch($cpu{'type'},$cpu{'family'},$cpu{'model_id'},$cpu{'stepping'});
8747		# cpu_arch comes from set_os()
8748		$cpu{'arch'} = $cpu_arch if (!$cpu{'arch'} && $cpu_arch && ($b_mips || $b_arm || $b_ppc));
8749		# print "$cpu{'type'},$cpu{'family'},$cpu{'model_id'},$cpu{'arch'}\n";
8750	}
8751	if (!$speeds{'cur-freq'}){
8752		$cpu{'cur-freq'} = $cpu{'processors'}->[0];
8753		$speeds{'min-freq'} = 0;
8754		$speeds{'max-freq'} = 0;
8755	}
8756	else {
8757		$cpu{'cur-freq'} = $speeds{'cur-freq'};
8758		$cpu{'min-freq'} = $speeds{'min-freq'};
8759		$cpu{'max-freq'} = $speeds{'max-freq'};
8760	}
8761	main::log_data('dump','%cpu',\%cpu) if $b_log;
8762	print Data::Dumper::Dumper \%cpu if $dbg[8];
8763	eval $end if $b_log;
8764	return %cpu;
8765}
8766
8767sub sysctl_data {
8768	eval $start if $b_log;
8769	my ($type) = @_;
8770	my %cpu = set_cpu_data();
8771	my (@ids,@line,%speeds,@working);
8772	my ($sep) = ('');
8773	my ($die_holder,$die_id,$phys_holder,$phys_id,$proc_count,$speed) = (0,0,0,0,0,0,0);
8774	@{$sysctl{'cpu'}} = () if !$sysctl{'cpu'}; # don't want error next!
8775	foreach (@{$sysctl{'cpu'}}){
8776		@line = split(/\s*:\s*/, $_);
8777		next if !$line[0];
8778		# darwin shows machine, like MacBook7,1, not cpu
8779		# machdep.cpu.brand_string: Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz
8780		if (($bsd_type ne 'darwin' && $line[0] eq 'hw.model') ||
8781		      $line[0] eq 'machdep.cpu.brand_string'){
8782			# cut L2 cache/cpu max speed out of model string, if available
8783			# openbsd 5.6: AMD Sempron(tm) Processor 3400+ ("AuthenticAMD" 686-class, 256KB L2 cache)
8784			# openbsd 6.x has Lx cache data in dmesg.boot
8785			# freebsd 10: hw.model: AMD Athlon(tm) II X2 245 Processor
8786			$line[1] = main::cleaner($line[1]);
8787			$line[1] = cpu_cleaner($line[1]);
8788			if ($line[1] =~ /([0-9]+)[\s-]*([KM]B)\s+L2 cache/i){
8789				my $multiplier = ($2 eq 'MB') ? 1024: 1;
8790				$cpu{'l2-cache'} = $1 * $multiplier;
8791			}
8792			if ($line[1] =~ /([^0-9\.][0-9\.]+)[\s-]*[MG]Hz/){
8793				$cpu{'max-freq'} = $1;
8794				if ($cpu{'max-freq'} =~ /MHz/i){
8795					$cpu{'max-freq'} =~ s/[\s-]*MHz//;
8796					$cpu{'max-freq'} = speed_cleaner($cpu{'max-freq'},'mhz');
8797				}
8798				elsif ($cpu{'max-freq'} =~ /GHz/){
8799					$cpu{'max-freq'} =~ s/[\s-]*GHz//i;
8800					$cpu{'max-freq'} = $cpu{'max-freq'} / 1000;
8801					$cpu{'max-freq'} = speed_cleaner($cpu{'max-freq'},'mhz');
8802				}
8803			}
8804			if ($line[1] =~ /\)$/){
8805				$line[1] =~ s/\s*\(.*\)$//;
8806			}
8807			$cpu{'model_name'} = $line[1];
8808			$cpu{'type'} = cpu_vendor($line[1]);
8809		}
8810		# NOTE: hw.l1icachesize: hw.l1dcachesize: ; in bytes, apparently
8811		elsif ($line[0] eq 'hw.l1icachesize'){
8812			$cpu{'l1-cache'} = $line[1]/1024;
8813		}
8814		elsif ($line[0] eq 'hw.l2cachesize'){
8815			$cpu{'l2-cache'} = $line[1]/1024;
8816		}
8817		elsif ($line[0] eq 'hw.l3cachesize'){
8818			$cpu{'l3-cache'} = $line[1]/1024;
8819		}
8820		# this is in mghz in samples
8821		elsif (!$cpu{'cur-freq'} &&
8822		 ($line[0] eq 'hw.clockrate' || $line[0] eq 'hw.cpuspeed')){
8823			$cpu{'cur-freq'} = $line[1];
8824		}
8825		# these are in hz: 2400000000
8826		elsif ($line[0] eq 'hw.cpufrequency'){
8827			$cpu{'cur-freq'} = $line[1]/1000000;
8828		}
8829		elsif ($line[0] eq 'hw.busfrequency_min'){
8830			$cpu{'min-freq'} = $line[1]/1000000;
8831		}
8832		elsif ($line[0] eq 'hw.busfrequency_max'){
8833			$cpu{'max-freq'} = $line[1]/1000000;
8834		}
8835		# FB seems to call freq something other than clock speed, unreliable
8836		# eg: 1500 Mhz real shows as 2400 freq, which is wrong
8837		# elsif ($line[0] =~ /^dev\.cpu\.([0-9]+)\.freq$/){
8838		#	 $speed = speed_cleaner($line[1]);
8839		#	 $cpu{'processors'}->[$1] = $speed;
8840		# }
8841		# weird FB thing, freq can be wrong, so just count the cores and call it
8842		# done.
8843		elsif ($line[0] =~ /^dev\.cpu\.([0-9]+)\./ &&
8844			(!$cpu{'processors'} || !defined $cpu{'processors'}->[$1])){
8845			$cpu{'processors'}->[$1] = undef;
8846		}
8847		elsif ($line[0] eq 'machdep.cpu.vendor'){
8848			$cpu{'type'} = cpu_vendor($line[1]);
8849		}
8850		# darwin only?
8851		elsif ($line[0] eq 'machdep.cpu.features'){
8852			$cpu{'flags'} = lc($line[1]);
8853		}
8854		elsif ($line[0] eq 'hw.ncpu'){
8855			$cpu{'cores'} = $line[1];
8856		}
8857		# Freebsd does some voltage hacking to actually run at lowest listed
8858		# frequencies. The cpu does not actually support all the speeds output
8859		# here but works in freebsd. Disabled this, the freq appear to refer to
8860		# something else, not cpu clock. Remove XXX to enable
8861		elsif ($line[0] eq 'dev.cpu.0.freq_levelsXXX'){
8862			$line[1] =~ s/^\s+|\/[0-9]+|\s+$//g;
8863			if ($line[1] =~ /[0-9]+\s+[0-9]+/){
8864				# get rid of -1 in FB: 2400/-1 2200/-1 2000/-1 1800/-1
8865				$line[1] =~ s|/-1||g;
8866				my @temp = split(/\s+/, $line[1]);
8867				$cpu{'max-freq'} = $temp[0];
8868				$cpu{'min-freq'} = $temp[-1];
8869				$cpu{'scalings'} = \@temp;
8870			}
8871		}
8872		# Disabled w/XXX. this is almost certainly bad data, should not be used
8873		elsif (!$cpu{'cur-freq'} && $line[0] eq 'dev.cpu.0.freqXXX'){
8874			$cpu{'cur-freq'} = $line[1];
8875		}
8876		# the following have only been seen in DragonflyBSD data but thumbs up!
8877		elsif ($line[0] eq 'hw.cpu_topology.members'){
8878			my @temp = split(/\s+/, $line[1]);
8879			my $count = scalar @temp;
8880			$count-- if $count > 0;
8881			$cpu{'processors'}->[$count] = 0;
8882			# no way to get per processor speeds yet, so assign 0 to each
8883			foreach (0 .. $count){
8884				$cpu{'processors'}->[$_] = 0;
8885			}
8886		}
8887		elsif ($line[0] eq 'hw.cpu_topology.cpu1.physical_siblings'){
8888			# string, like: cpu0 cpu1
8889			my @temp = split(/\s+/, $line[1]);
8890			$cpu{'siblings'} = scalar @temp;
8891		}
8892		# increment by 1 for every new physical id we see. These are in almost all cases
8893		# separate cpus, not separate dies within a single cpu body.
8894		elsif ($line[0] eq 'hw.cpu_topology.cpu0.physical_id'){
8895			if ($phys_holder != $line[1]){
8896				$phys_id++;
8897				$phys_holder = $line[1];
8898				$ids[$phys_id] = [0];
8899				$ids[$phys_id]->[$die_id] = [0];
8900			}
8901		}
8902		elsif ($line[0] eq 'hw.cpu_topology.cpu0.core_id'){
8903			if ($line[1] > 0){
8904				$die_holder = $line[1];
8905			}
8906			# this handles multi die cpus like 16 core ryzen
8907			elsif ($line[1] == 0 && $die_holder > 0){
8908				$die_id++ ;
8909				$die_holder = $line[1];
8910			}
8911			$ids[$phys_id]->[$die_id][$line[1]] = $speed;
8912			$cpu{'dies'} = $die_id;
8913		}
8914	}
8915	if (!$cpu{'flags'} || !$cpu{'family'}){
8916		my %dmesg_boot = dboot_data();
8917		 # this core count may fix failed MT detection.
8918		$cpu{'cores'} = $dmesg_boot{'cores'} if $dmesg_boot{'cores'};
8919		$cpu{'flags'} = $dmesg_boot{'flags'} if !$cpu{'flags'};
8920		$cpu{'family'} = $dmesg_boot{'family'} if !$cpu{'family'};
8921		$cpu{'l1-cache'} = $dmesg_boot{'l1-cache'} if !$cpu{'l1-cache'};
8922		$cpu{'l2-cache'} = $dmesg_boot{'l2-cache'} if !$cpu{'l2-cache'};
8923		$cpu{'l3-cache'} = $dmesg_boot{'l3-cache'} if !$cpu{'l3-cache'};
8924		$cpu{'microcode'} = $dmesg_boot{'microcode'} if !$cpu{'microcode'};
8925		$cpu{'model_id'} = $dmesg_boot{'model_id'} if !$cpu{'model_id'};
8926		$cpu{'max-freq'} = $dmesg_boot{'max-freq'} if !$cpu{'max-freq'};
8927		$cpu{'min-freq'} = $dmesg_boot{'min-freq'} if !$cpu{'min-freq'};
8928		$cpu{'scalings'} = $dmesg_boot{'scalings'} if !$cpu{'scalings'};
8929		$cpu{'siblings'} = $dmesg_boot{'siblings'} if !$cpu{'siblings'};
8930		$cpu{'stepping'} = $dmesg_boot{'stepping'} if !$cpu{'stepping'};
8931		$cpu{'type'} = $dmesg_boot{'type'} if !$cpu{'type'};
8932	}
8933	if ($extra > 0 && !$cpu{'arch'} && $type ne 'short'){
8934		($cpu{'arch'},$cpu{'arch-note'}) = cpu_arch($cpu{'type'},$cpu{'family'},$cpu{'model_id'},$cpu{'stepping'});
8935		# print "$cpu{'type'},$cpu{'family'},$cpu{'model_id'},$cpu{'arch'}\n";
8936	}
8937	main::log_data('dump','%cpu',\%cpu) if $b_log;
8938	print Data::Dumper::Dumper \%cpu if $dbg[8];
8939	eval $end if $b_log;
8940	return %cpu;
8941}
8942
8943sub dboot_data {
8944	eval $start if $b_log;
8945	my ($max_freq,$min_freq,@scalings,%values);
8946	my ($family,$flags,$microcode,$model,$sep,$stepping,$type) = ('','','','','','','');
8947	my ($cores,$siblings) = (0,0);
8948	my ($l1,$l2,$l3) = (0,0,0);
8949	# this will be null if it was not readable
8950	my $file = $system_files{'dmesg-boot'};
8951	if ($dboot{'cpu'}){
8952		foreach (@{$dboot{'cpu'}}){
8953			# can be ~Features/Features2/AMD Features
8954			if (/Features/ || ($bsd_type eq "openbsd" &&
8955			 /^cpu0:\s*[a-z0-9]{2,3}(\s|,)[a-z0-9]{2,3}(\s|,)/i)){
8956				my @line = split(/:\s*/, lc($_));
8957				# free bsd has to have weird syntax: <....<b23>,<b34>>
8958				# Features2=0x1e98220b<SSE3,PCLMULQDQ,MON,SSSE3,CX16,SSE4.1,SSE4.2,POPCNT,AESNI,XSAVE,OSXSAVE,AVX>
8959				$line[1] =~ s/^[^<]*<|>[^>]*$//g;
8960				# then get rid of <b23> stuff
8961				$line[1] =~ s/<[^>]+>//g;
8962				# handle corner case like ,EL3 32,
8963				$line[1] =~ s/ (32|64)/_$1/g;
8964				# and replace commas with spaces
8965				$line[1] =~ s/,/ /g;
8966				$flags .= $sep . $line[1];
8967				$sep = ' ';
8968			}
8969			# cpu0:AMD E1-1200 APU with Radeon(tm) HD Graphics, 1398.66 MHz, 14-02-00
8970			elsif (/^cpu0:\s*([^,]+),\s+([0-9\.]+\s*MHz),\s+([0-9a-f]+)-([0-9a-f]+)-([0-9a-f]+)/){
8971				$type = cpu_vendor($1);
8972				$family = uc($3);
8973				$model =  uc($4);
8974				$stepping = uc($5);
8975				$family =~ s/^0//;
8976				$model =~ s/^0//;
8977				$stepping =~ s/^0//; # can be 00
8978			}
8979			# note: cpu cache is in KiB MiB even though they call it KB and MB
8980			# cpu31: 32KB 64b/line 8-way I-cache, 32KB 64b/line 8-way D-cache, 512KB 64b/line 8-way L2 cache
8981			# 8-way means 1 per core, 16-way means 1/2 per core
8982			elsif (/^cpu0:\s*[0-9\.]+[KMG]B\s/){
8983				# cpu0: 32KB 64b/line 4-way L1 VIPT I-cache, 32KB 64b/line 4-way L1 D-cache
8984				# cpu0:48KB 64b/line 3-way L1 PIPT I-cache, 32KB 64b/line 2-way L1 D-cache
8985				if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\s(L1 \S+\s)?I[\s-]?cache/){
8986					$l1 = main::translate_size($1);
8987				}
8988				if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sD[\s-]?cache/){
8989					# do nothing, we aren't going to use the D cache
8990				}
8991				if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sL2[\s-]?cache/){
8992					$l2 = main::translate_size($1);
8993				}
8994				if (/\b([0-9\.]+[KMG])i?B\s\S+\s([0-9]+)-way\sL3[\s-]?cache/){
8995					$l3 = main::translate_size($1);
8996				}
8997			}
8998			elsif (/^~Origin:(.+?)[\s,]+(Id|Family|Model|Stepping)/){
8999				$type = cpu_vendor($1);
9000				if (/\bId\s*=\s*(0x)?([0-9a-f]+)\b/){
9001					$microcode = ($1) ? uc($2) : $2;
9002				}
9003				if (/\bFamily\s*=\s*(0x)?([a-f0-9]+)\b/){
9004					$family = ($1) ? uc($2) : $2;
9005				}
9006				if (/\bModel\s*=\s*(0x)?([a-f0-9]+)\b/){
9007					$model = ($1) ? uc($2) : $2;
9008				}
9009				# they don't seem to use hex for steppings, so convert it
9010				if (/\bStepping\s*=\s*(0x)?([0-9a-f]+)\b/){
9011					$stepping = (!$1) ? uc(sprintf("%X", $2)) : $2;
9012				}
9013			}
9014			elsif (/^cpu0:.*?[0-9\.]+\s?MHz:\sspeeds:\s(.*?)\s?MHz/){
9015				@scalings = split(/[,\s]+/,$1);
9016				$min_freq = $scalings[-1];
9017				$max_freq = $scalings[0];
9018			}
9019			# 2 core MT Intel Core/Rzyen similar, use smt 0 as trigger to count:
9020			# cpu2:smt 0, core 1, package 0
9021			# cpu3:smt 1, core 1, package 0
9022			## but: older AMD Athlon 2 core:
9023			# cpu0:smt 0, core 0, package 0
9024			# cpu0:smt 0, core 0, package 1
9025			elsif (/cpu([0-9]+):smt\s([0-9]+),\score\s([0-9]+)(,\spackage\s([0-9]+))?/){
9026				$siblings = $1 + 1;
9027				$cores += 1 if $2 == 0;
9028			}
9029		}
9030		if ($flags){
9031			$flags =~ s/\s+/ /g;
9032			$flags =~ s/^\s+|\s+$//g;
9033		}
9034	}
9035	else {
9036		if ($file && ! -r $file){
9037			$flags = main::row_defaults('dmesg-boot-permissions');
9038		}
9039	}
9040	%values = (
9041	'cores' => $cores,
9042	'family' => $family,
9043	'flags' => $flags,
9044	'l1-cache' => $l1,
9045	'l2-cache' => $l2,
9046	'l3-cache' => $l3,
9047	'max-freq' => $max_freq,
9048	'microcode' => $microcode,
9049	'min-freq' => $min_freq,
9050	'model_id' => $model,
9051	'scalings' => \@scalings,
9052	'siblings' => $siblings,
9053	'stepping' => $stepping,
9054	'type' => $type,
9055	);
9056	print Data::Dumper::Dumper \%values if $dbg[27];
9057	eval $end if $b_log;
9058	return %values;
9059}
9060sub dmidecode_data {
9061	eval $start if $b_log;
9062	return if !@dmi;
9063	my %dmi_data = ('L1' => 0, 'L2' => 0,'L3' => 0, 'ext-clock' => undef, 'socket' => undef,
9064	'speed' => undef, 'max-speed' => undef, 'upgrade' => undef, 'volts' => undef);
9065	my ($id,$amount,$socket,$upgrade);
9066	foreach my $item (@dmi){
9067		next if ref $item ne 'ARRAY';
9068		next if ($item->[0] < 4 || $item->[0] == 5 || $item->[0] == 6);
9069		last if $item->[0] > 7;
9070		if ($item->[0] == 7){
9071			# skip first three rows, we don't need that data
9072			($id,$amount) = ('',0);
9073			foreach my $value (@$item[3 .. $#$item]){
9074				next if $value =~ /~/;
9075				# variants: L3 - Cache; L3 Cache; L3-cache; L2 CACHE; CPU Internal L1
9076				if ($value =~ /^Socket Designation:.*? (L[1-3])\b/){
9077					$id = $1;
9078				}
9079				# some cpus only show Socket Designation: Internal cache
9080				elsif (!$id && $value =~ /^Configuration:.* Level.*?([1-3])\b/){
9081					$id = "L$1";
9082				}
9083				# NOTE: cache is in KiB or MiB but they call it kB or MB
9084				# so we send translate_size k or M which trips KiB/MiB mode
9085				elsif ($id && $value =~ /^Installed Size:\s+(.*?[kKM])i?B$/){
9086					$amount = main::translate_size($1);
9087				}
9088				if ($id && $amount){
9089					$dmi_data{$id} += $amount;
9090					last;
9091				}
9092			}
9093		}
9094		# note: for multi cpu systems, we're hoping that these values are
9095		# the same for each cpu, which in most pc situations they will be,
9096		# and ARM etc won't be using dmi data here anyway.
9097		# Older dmidecode appear to have unreliable Upgrade outputs
9098		elsif ($item->[0] == 4){
9099			# skip first three row,s we don't need that data
9100			($socket,$upgrade) = (undef);
9101			foreach my $value (@$item[3 .. $#$item]){
9102				next if $value =~ /~/;
9103				# note: on single cpu systems, Socket Designation shows socket type,
9104				# but on multi, shows like, CPU1; CPU Socket #2; Socket 0; so check values a bit.
9105				# Socket Designation: Intel(R) Core(TM) i5-3470 CPU @ 3.20GHz
9106				# Sometimes shows as CPU Socket...
9107				if ($value =~ /^Socket Designation:\s*(CPU\s*Socket|Socket)?[\s-]*(.*)$/i){
9108					$upgrade = main::dmi_cleaner($2) if $2 !~ /(cpu|[mg]hz|onboard|socket|@|^#?[0-9]$)/i;
9109					# print "$socket_temp\n";
9110				}
9111				# normally we prefer this value, but sometimes it's garbage
9112				# older systems often show: Upgrade: ZIF Socket which is a generic term, legacy
9113				elsif ($value =~ /^Upgrade:\s*(CPU\s*Socket|Socket)?[\s-]*(.*)$/i){
9114					# print "$2\n";
9115					$socket = main::dmi_cleaner($2) if $2 !~ /(ZIF|\bslot\b)/i;
9116				}
9117				# seen: Voltage: 5.0 V 2.9 V
9118				elsif ($value =~ /^Voltage:\s*([0-9\.]+)\s*(V|Volts)?\b/i){
9119					$dmi_data{'volts'} = main::dmi_cleaner($1);
9120				}
9121				elsif ($value =~ /^Current Speed:\s*([0-9\.]+)\s*([MGK]Hz)?\b/i){
9122					$dmi_data{'speed'} = main::dmi_cleaner($1);
9123				}
9124				elsif ($value =~ /^Max Speed:\s*([0-9\.]+)\s*([MGK]Hz)?\b/i){
9125					$dmi_data{'max-speed'} = main::dmi_cleaner($1);
9126				}
9127				elsif ($value =~ /^External Clock:\s*([0-9\.]+\s*[MGK]Hz)\b/){
9128					$dmi_data{'ext-clock'} = main::dmi_cleaner($1);
9129				}
9130			}
9131		}
9132	}
9133	# Seen older cases where Upgrade: Other value exists
9134	if ($socket || $upgrade){
9135		if ($socket && $upgrade){
9136			$upgrade = undef if $socket eq $upgrade;
9137		}
9138		elsif ($upgrade){
9139			$socket = $upgrade;
9140			$upgrade = undef;
9141		}
9142		$dmi_data{'socket'} = $socket;
9143		$dmi_data{'upgrade'} = $upgrade;
9144	}
9145	main::log_data('dump','%dmi_data',\%dmi_data) if $b_log;
9146	# print Data::Dumper::Dumper \%dmi_data;
9147	eval $end if $b_log;
9148	return %dmi_data;
9149}
9150
9151sub cpu_properties {
9152	my ($cpu) = @_;
9153	my ($b_amd_zen,$b_epyc,$b_ht,$b_elbrus,$b_intel,$b_ryzen,$b_xeon);
9154	my ($cores_x,$cache_check) = (1,'');
9155	if ($cpu->{'type'}){
9156		if ($cpu->{'type'} eq 'intel'){
9157			$b_intel = 1;
9158			$b_xeon = 1 if $cpu->{'model_name'} =~ /Xeon/i;
9159		}
9160		elsif ($cpu->{'type'} eq 'amd'){
9161			if ($cpu->{'family'} && $cpu->{'family'} eq '17'){
9162				$b_amd_zen = 1;
9163				if ($cpu->{'model_name'}){
9164					if ($cpu->{'model_name'} =~ /Ryzen/i){
9165						$b_ryzen = 1;
9166					}
9167					elsif ($cpu->{'model_name'} =~ /EPYC/i){
9168						$b_epyc = 1;
9169					}
9170				}
9171			}
9172		}
9173		elsif ($cpu->{'type'} eq 'elbrus'){
9174			$b_elbrus = 1;
9175		}
9176	}
9177	# my @dies = $phys[0]->[0];
9178	my @phys = @{$cpu->{'ids'}};
9179	my $physical_count = 0;
9180	# my $physical_count = scalar @phys;
9181	my @processors;
9182	my ($speed,$speed_key);
9183	# handle case where cpu reports say, phys id 0, 2, 4, 6 [yes, seen it]
9184	foreach (@phys){
9185		$physical_count++ if $_;
9186	}
9187	# count unique processors ##
9188	# note, this fails for intel cpus at times
9189	@processors = @{$cpu->{'processors'}};
9190	# print ref $cpu->{'processors'}, "\n";
9191	my $processors_count = scalar @processors;
9192	# print "p count:$processors_count\n";
9193	# print Data::Dumper::Dumper \@processors;
9194	# $cpu_cores is per physical cpu
9195	my ($cpu_layout,$cpu_type,$min_max,$min_max_key) = ('','','','');
9196	my ($dmi_max_speed,$dmi_speed,$ext_clock,$socket,$upgrade,$volts) = (undef);
9197	my ($l1_cache,$l2_cache,$l3_cache,$core_count,$cpu_cores,$die_count) = (0,0,0,0,0,0);
9198	# note: elbrus supports turning off cores, so we need to add one for cases where rounds to 0 or 1 less
9199	if ($b_elbrus && $processors_count){
9200		my @elbrus = elbrus_data($cpu->{'family'},$cpu->{'model_id'},$processors_count,$cpu->{'arch'});
9201		$cpu_cores = $elbrus[0];
9202		$physical_count = $elbrus[1];
9203		$cpu->{'arch'} = $elbrus[2];
9204		# print 'model id: ' . $cpu->{'model_id'} . ' arch: ' . $cpu->{'arch'} . " cpc: $cpu_cores phyc: $physical_count proc: $processors_count \n";
9205	}
9206	$physical_count ||= 1; # assume 1 if no id found, as with ARM
9207	foreach my $die_ref (@phys){
9208		next if ref $die_ref ne 'ARRAY';
9209		$core_count = 0;
9210		$die_count = scalar @$die_ref;
9211		#$cpu->{'dies'} = $die_count;
9212		foreach my $core_ref (@$die_ref){
9213			next if ref $core_ref ne 'ARRAY';
9214			$core_count = 0;# reset for each die!!
9215			# NOTE: the counters can be undefined because the index comes from
9216			# core id: which can be 0 skip 1 then 2, which leaves index 1 undefined
9217			# arm cpus do not actually show core id so ignore that counter
9218			foreach my $id (@$core_ref){
9219				$core_count++ if defined $id && !$b_arm;
9220			}
9221			# print 'cores: ' . $core_count, "\n";
9222		}
9223	}
9224	# this covers potentially cases where ARM cpus have > 1 die
9225	$cpu->{'dies'} = ($b_arm && $die_count <= 1 && $cpu->{'dies'} > 1) ? $cpu->{'dies'}: $die_count;
9226	# this is an attempt to fix the amd family 15 bug with reported cores vs actual cores
9227	# NOTE: amd A6-4400M APU 2 core reports: cores: 1 siblings: 2
9228	# NOTE: AMD A10-5800K APU 4 core reports: cores: 2 siblings: 4
9229	if (!$cpu_cores){
9230		if ($cpu->{'cores'} && !$core_count || $cpu->{'cores'} >= $core_count){
9231			$cpu_cores = $cpu->{'cores'};
9232		}
9233		elsif ($core_count > $cpu->{'cores'}){
9234			$cpu_cores = $core_count;
9235		}
9236	}
9237	# print "cpu-c:$cpu_cores\n";
9238	#$cpu_cores = $cpu->{'cores'};
9239	# like, intel core duo
9240	# NOTE: sadly, not all core intel are HT/MT, oh well...
9241	# xeon may show wrong core / physical id count, if it does, fix it. A xeon
9242	# may show a repeated core id : 0 which gives a fake num_of_cores=1
9243	if ($b_intel){
9244		if ($cpu->{'siblings'} && $cpu->{'siblings'} > 1 && $cpu->{'cores'} && $cpu->{'cores'} > 1){
9245			if ($cpu->{'siblings'}/$cpu->{'cores'} == 1){
9246				$b_intel = 0;
9247				$b_ht = 0;
9248			}
9249			else {
9250				$cpu_cores = ($cpu->{'siblings'}/2);
9251				$b_ht = 1;
9252			}
9253		}
9254	}
9255	# ryzen is made out of blocks of 8 core dies
9256	elsif ($b_ryzen){
9257		$cpu_cores = $cpu->{'cores'};
9258		 # note: posix ceil isn't present in Perl for some reason, deprecated?
9259		my $working = $cpu_cores / 8;
9260		my @temp = split('\.', $working);
9261		$cpu->{'dies'} = ($temp[1] && $temp[1] > 0) ? $temp[0]++ : $temp[0];
9262	}
9263	# these always have 4 dies
9264	elsif ($b_epyc){
9265		$cpu_cores = $cpu->{'cores'};
9266		$cpu->{'dies'} = 4;
9267	}
9268# 	elsif ($b_elbrus){
9269# 		$cpu_cores =
9270# 	}
9271	# final check, override the num of cores value if it clearly is wrong
9272	# and use the raw core count and synthesize the total instead of real count
9273	if ($cpu_cores == 0 && ($cpu->{'cores'} * $physical_count > 1)){
9274		$cpu_cores = ($cpu->{'cores'} * $physical_count);
9275	}
9276	# last check, seeing some intel cpus and vms with intel cpus that do not show any
9277	# core id data at all, or siblings.
9278	if ($cpu_cores == 0 && $processors_count > 0){
9279		$cpu_cores = $processors_count;
9280	}
9281	# this happens with BSDs which have very little cpu data available
9282	if ($processors_count == 0 && $cpu_cores > 0){
9283		$processors_count = $cpu_cores;
9284		if ($bsd_type && ($b_ht || $b_amd_zen) && $cpu_cores > 2){
9285			$cpu_cores = $cpu_cores/2;;
9286		}
9287		my $count = $processors_count;
9288		$count-- if $count > 0;
9289		$cpu->{'processors'}[$count] = 0;
9290		# no way to get per processor speeds yet, so assign 0 to each
9291		# must be a numeric value. Could use raw speed from core 0, but
9292		# that would just be a hack.
9293		foreach (0 .. $count){
9294			$cpu->{'processors'}[$_] = 0;
9295		}
9296	}
9297	# so far only OpenBSD has a way to detect MT cpus
9298	if ($bsd_type){
9299		if ($cpu->{'siblings'}){
9300			$cores_x = $cpu_cores if $cpu_cores && $cpu_cores > 1;
9301		}
9302		# if no siblings we couldn't get MT status of cpu so can't trust cache
9303		else {
9304			$cache_check = main::row_defaults('note-check');
9305		}
9306	}
9307	# only elbrus shows L1 / L3 cache data in cpuinfo
9308	else {
9309		$cores_x = $cpu_cores if $cpu_cores && $cpu_cores > 1;
9310	}
9311	# last test to catch some corner cases
9312	# seen a case where a xeon vm in a dual xeon system actually had 2 cores, no MT
9313	# so it reported 4 siblings, 2 cores, but actually only had 1 core per virtual cpu
9314	# print "prc: $processors_count phc: $physical_count coc: $core_count cpc: $cpu_cores\n";
9315	if (!$b_arm && $processors_count == $physical_count*$core_count && $cpu_cores > $core_count){
9316		$b_ht = 0;
9317		#$b_xeon = 0;
9318		$b_intel = 0;
9319		$cpu_cores = 1;
9320		$core_count = 1;
9321		$cpu->{'siblings'} = 1;
9322	}
9323	if ($extra > 1 || ($bsd_type && !$cpu->{'l2-cache'})){
9324		# note: dmidecode has one entry per cpu per cache type, so this already
9325		# has done the arithmetic on > 1 cpus for L1 and L3.
9326		my %cpu_dmi = dmidecode_data();
9327		my $multi = ($bsd_type) ? $cpu_cores: 1;
9328		$l1_cache = $cpu_dmi{'L1'} * $physical_count if $cpu_dmi{'L1'};
9329		# note: bsds often won't have L2 catch data found yet
9330		$cpu->{'l2-cache'} = $cpu_dmi{'L2'} if !$cpu->{'l2-cache'} && $cpu_dmi{'L2'};
9331		$l3_cache = $cpu_dmi{'L3'} * $physical_count if $cpu_dmi{'L3'};
9332		# bsd sysctl can have these values so let's check just in case
9333		$l1_cache = $cpu->{'l1-cache'} * $cores_x * $physical_count if !$l1_cache && $cpu->{'l1-cache'};
9334		# L3 is almost always per physical cpu, not per core
9335		$l3_cache = $cpu->{'l3-cache'} * $physical_count if !$l3_cache && $cpu->{'l3-cache'};
9336		$cache_check = '' if !$l1_cache && !$cpu->{'l2-cache'} && !$l3_cache; # nothing to check!
9337		$dmi_max_speed = $cpu_dmi{'max-speed'} if $cpu_dmi{'max-speed'};
9338		$socket = $cpu_dmi{'socket'} if $cpu_dmi{'socket'};
9339		$upgrade = $cpu_dmi{'upgrade'} if $cpu_dmi{'upgrade'};
9340		$dmi_speed = $cpu_dmi{'speed'} if $cpu_dmi{'speed'};
9341		$ext_clock = $cpu_dmi{'ext-clock'} if $cpu_dmi{'ext-clock'};
9342		$volts = $cpu_dmi{'volts'} if $cpu_dmi{'volts'};
9343	}
9344	# print "pc: $processors_count s: $cpu->{'siblings'} cpuc: $cpu_cores corec: $core_count\n";
9345	# Algorithm:
9346	# if > 1 processor && processor id (physical id) == core id then Multi threaded (MT)
9347	# if siblings > 1 && siblings ==  2 * num_of_cores ($cpu->{'cores'}) then Multi threaded (MT)
9348	# if > 1 processor && processor id (physical id) != core id then Multi-Core Processors (MCP)
9349	# if > 1 processor && processor ids (physical id) > 1 then Symmetric Multi Processing (SMP)
9350	# if = 1 processor then single core/processor Uni-Processor (UP)
9351	if ($processors_count > 1 || ($b_intel && $cpu->{'siblings'} > 0)){
9352		# non-multicore MT
9353		if ($processors_count == ($physical_count * $cpu_cores * 2)){
9354			# print "mt:1\n";
9355			$cpu_type .= 'MT';
9356		}
9357# 		elsif ($b_xeon && $cpu->{'siblings'} > 1){
9358# 			# print "mt:2\n";
9359# 			$cpu_type .= 'MT';
9360# 		}
9361		elsif ($cpu->{'siblings'} > 1 && ($cpu->{'siblings'} == 2 * $cpu_cores)){
9362			# print "mt:3\n";
9363			$cpu_type .= 'MT';
9364		}
9365		# non-MT multi-core or MT multi-core
9366		if (($processors_count == $cpu_cores) || ($physical_count < $cpu_cores)){
9367			my $sep = ($cpu_type) ? ' ' : '' ;
9368			$cpu_type .= $sep . 'MCP';
9369		}
9370		# only solidly known > 1 die cpus will use this, ryzen and arm for now
9371		if ($cpu->{'dies'} > 1){
9372			my $sep = ($cpu_type) ? ' ' : '' ;
9373			$cpu_type .= $sep . 'MCM';
9374		}
9375		# >1 cpu sockets active: Symetric Multi Processing
9376		if ($physical_count > 1){
9377			my $sep = ($cpu_type) ? ' ' : '' ;
9378			$cpu_type .= $sep . 'SMP';
9379		}
9380	}
9381	else {
9382		$cpu_type = 'UP';
9383	}
9384	if ($physical_count > 1){
9385		$cpu_layout = $physical_count . 'x ';
9386	}
9387	$cpu_layout .= count_alpha($cpu_cores) . 'Core';
9388	$cpu_layout .= ' (' . $cpu->{'dies'}. '-Die)' if !$bsd_type && $cpu->{'dies'} > 1;
9389	if (!$cpu->{'l2-cache'}){
9390		# do nothing
9391	}
9392	# the only possible change for bsds is if we can get phys counts in the future
9393	# Looks like Intel on bsd shows L2 per core, not total. Note: Pentium N3540
9394	# uses 2(not 4)xL2 cache size for 4 cores, sigh... you just can't win...
9395	elsif ($bsd_type){
9396		$l2_cache = $cpu->{'l2-cache'} * $cores_x * $physical_count;
9397	}
9398	# AMD SOS chips appear to report full L2 cache per core
9399	elsif ($cpu->{'type'} eq 'amd' && ($cpu->{'family'} eq '14' || $cpu->{'family'} eq '15' || $cpu->{'family'} eq '16')){
9400		$l2_cache = $cpu->{'l2-cache'} * $physical_count;
9401	}
9402	elsif ($cpu->{'type'} ne 'intel'){
9403		$l2_cache = $cpu->{'l2-cache'} * $cpu_cores * $physical_count;
9404	}
9405	## note: this handles how intel reports L2, total instead of per core like AMD does
9406	# note that we need to multiply by number of actual cpus here to get true cache size
9407	else {
9408		$l2_cache = $cpu->{'l2-cache'} * $physical_count;
9409	}
9410	if ($cpu->{'cur-freq'} && $cpu->{'min-freq'} && $cpu->{'max-freq'}){
9411		$min_max = "$cpu->{'min-freq'}/$cpu->{'max-freq'} MHz";
9412		$min_max_key = "min/max";
9413		$speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed';
9414		$speed = "$cpu->{'cur-freq'} MHz";
9415 	}
9416 	elsif ($cpu->{'cur-freq'} && $cpu->{'max-freq'}){
9417		$min_max = "$cpu->{'max-freq'} MHz";
9418		$min_max_key = "max";
9419		$speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed';
9420		$speed = "$cpu->{'cur-freq'} MHz";
9421 	}
9422#  	elsif ($cpu->{'cur-freq'} && $cpu->{'max-freq'} && $cpu->{'cur-freq'} == $cpu->{'max-freq'}){
9423# 		$speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed';
9424# 		$speed = "$cpu->{'cur-freq'} MHz (max)";
9425#  	}
9426 	elsif ($cpu->{'cur-freq'} && $cpu->{'min-freq'}){
9427		$min_max = "$cpu->{'min-freq'} MHz";
9428		$min_max_key = "min";
9429		$speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed';
9430		$speed = "$cpu->{'cur-freq'} MHz";
9431 	}
9432 	elsif ($cpu->{'cur-freq'} && !$cpu->{'max-freq'}){
9433		$speed_key = ($show{'short'} || $show{'cpu-basic'}) ? 'speed' : 'Speed';
9434		$speed = "$cpu->{'cur-freq'} MHz";
9435 	}
9436
9437 	if (!$bits_sys && !$b_arm && $cpu->{'flags'}){
9438		$bits_sys = ($cpu->{'flags'} =~ /\blm\b/) ? 64 : 32;
9439	}
9440	my %cpu_properties = (
9441	'bits-sys' => $bits_sys,
9442	'cache-check' => $cache_check,
9443	'cpu-layout' => $cpu_layout,
9444	'cpu-type' => $cpu_type,
9445	'dmi-max-speed' => $dmi_max_speed,
9446	'dmi-speed' => $dmi_speed,
9447	'ext-clock' => $ext_clock,
9448	'min-max-key' => $min_max_key,
9449	'min-max' => $min_max,
9450	'socket' => $socket,
9451	'speed-key' => $speed_key,
9452	'speed' => $speed,
9453	'upgrade' => $upgrade,
9454	'volts' => $volts,
9455	'l1-cache' => $l1_cache,
9456	'l2-cache' => $l2_cache,
9457	'l3-cache' => $l3_cache,
9458	);
9459	main::log_data('dump','%cpu_properties',\%cpu_properties) if $b_log;
9460	# print Data::Dumper::Dumper $cpu;
9461	# print Data::Dumper::Dumper \%cpu_properties;
9462	# my $dc = scalar @dies;
9463	# print 'phys: ' . $pc . ' dies: ' . $dc, "\n";
9464	eval $end if $b_log;
9465	return %cpu_properties;
9466}
9467
9468sub cpu_bugs_sys {
9469	eval $start if $b_log;
9470	my (@bugs,$type,$value);
9471	return if ! -d '/sys/devices/system/cpu/vulnerabilities/';
9472	my @items = main::globber('/sys/devices/system/cpu/vulnerabilities/*');
9473	if (@items){
9474		foreach (@items){
9475			$value = (-r $_) ? main::reader($_,'',0) : main::row_defaults('root-required');
9476			$type = ($value =~ /^Mitigation:/) ? 'mitigation': 'status';
9477			$_ =~ s/.*\/([^\/]+)$/$1/;
9478			$value =~ s/Mitigation: //;
9479			push(@bugs,[($_,$type,$value)]);
9480		}
9481	}
9482	main::log_data('dump','@bugs',\@bugs) if $b_log;
9483	# print Data::Dumper::Dumper \@bugs;
9484	eval $end if $b_log;
9485	return @bugs;
9486}
9487
9488sub cpu_speeds {
9489	eval $start if $b_log;
9490	my ($processors) = @_;
9491	my (@speeds);
9492	my @files = main::globber('/sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq');
9493	foreach (@files){
9494		my $speed = main::reader($_,'',0);
9495		if (defined $speed){
9496			$speed = sprintf("%.0f", $speed/1000);
9497			push(@speeds, $speed);
9498		}
9499	}
9500	if (!@speeds){
9501		# handle special case, FB, where we use undef for no processor speed found
9502		foreach (@$processors){
9503			if ($_ || (defined $_ && $_ eq '0')){
9504				$_ = sprintf("%.0f", $_);
9505				push(@speeds, $_);
9506			}
9507		}
9508	}
9509	# print join('; ', @speeds), "\n";
9510	eval $end if $b_log;
9511	return @speeds;
9512}
9513sub set_cpu_speeds_sys {
9514	eval $start if $b_log;
9515	my (@max_freq,@min_freq,@policies,%speeds);
9516	my $sys = '/sys/devices/system/cpu/cpufreq/policy0';
9517	my $sys2 = '/sys/devices/system/cpu/cpu0/cpufreq/';
9518	my ($cur,$min,$max) = ('scaling_cur_freq','scaling_min_freq','scaling_max_freq');
9519	if (!-d $sys && -d $sys2){
9520		$sys = $sys2;
9521		($cur,$min,$max) = ('scaling_cur_freq','cpuinfo_min_freq','cpuinfo_max_freq');
9522	}
9523	if (-d $sys){
9524		# corner cases, android, will have the files but they may be unreadable
9525		if (-r "$sys/$cur"){
9526			$speeds{'cur-freq'} = main::reader("$sys/$cur",'',0);
9527			$speeds{'cur-freq'} = speed_cleaner($speeds{'cur-freq'},'khz');
9528		}
9529		if (-r "$sys/$min"){
9530			$speeds{'min-freq'} = main::reader("$sys/$min",'',0);
9531			$speeds{'min-freq'} = speed_cleaner($speeds{'min-freq'},'khz');
9532		}
9533		if (-r "$sys/$max"){
9534			$speeds{'max-freq'} = main::reader("$sys/$max",'',0);
9535			$speeds{'max-freq'} = speed_cleaner($speeds{'max-freq'},'khz');
9536		}
9537		if ($b_arm || $b_mips){
9538			@policies = main::globber('/sys/devices/system/cpu/cpufreq/policy*/');
9539			# there are arm chips with two dies, that run at different min max speeds!!
9540			# see: https://github.com/smxi/inxi/issues/128
9541			# it would be slick to show both die min/max/cur speeds, but this is
9542			# ok for now.
9543			if (scalar @policies > 1){
9544				my ($current,$cur_temp,$max,$max_temp,$min,$min_temp) = (0,0,0,0,0,0);
9545				foreach (@policies){
9546					$_ =~ s/\/$//; # strip off last slash in case globs have them
9547					$max_temp = (-r "$_/cpuinfo_max_freq") ? main::reader("$_/cpuinfo_max_freq",'',0) : 0;
9548					if ($max_temp){
9549						$max_temp = speed_cleaner($max_temp,'khz');
9550						push(@max_freq, $max_temp);
9551					}
9552					$max = $max_temp if ($max_temp > $max);
9553					$min_temp = (-r "$_/cpuinfo_min_freq") ? main::reader("$_/cpuinfo_min_freq",'',0) : 0;
9554					if ($min_temp){
9555						$min_temp = speed_cleaner($min_temp,'khz');
9556						push(@min_freq, $min_temp);
9557					}
9558					$min = $min_temp if ($min_temp < $min || $min == 0);
9559					$cur_temp = (-r "$_/scaling_cur_freq") ? main::reader("$_/scaling_cur_freq",'',0) : 0;
9560					$cur_temp = speed_cleaner($cur_temp,'khz') if $cur_temp;
9561					if ($cur_temp > $current){
9562						$current = $cur_temp;
9563					}
9564				}
9565				if (@max_freq){
9566					main::uniq(\@max_freq);
9567					$max = join(':', @max_freq);
9568				}
9569				if (@min_freq){
9570					main::uniq(\@min_freq);
9571					$min = join(':', @min_freq);
9572				}
9573				$speeds{'cur-freq'} = $current if $current;
9574				$speeds{'max-freq'} = $max if $max;
9575				$speeds{'min-freq'} = $min if $min;
9576			}
9577		}
9578		# policy4/cpuinfo_max_freq:["2000000"] policy0/cpuinfo_max_freq:["1500000"]
9579		# policy4/cpuinfo_min_freq:["200000"]
9580		if ((scalar @max_freq < 2 && scalar @min_freq < 2) &&
9581		 (defined $speeds{'min-freq'} && defined $speeds{'max-freq'}) &&
9582		 ($speeds{'min-freq'} > $speeds{'max-freq'} ||
9583		 $speeds{'min-freq'} == $speeds{'max-freq'})){
9584			$speeds{'min-freq'} = 0;
9585		}
9586	}
9587	main::log_data('dump','%speeds',\%speeds) if $b_log;
9588	eval $end if $b_log;
9589	return %speeds;
9590}
9591
9592# right now only using this for ARM cpus, this is not the same in intel/amd
9593sub cpu_dies_sys {
9594	eval $start if $b_log;
9595	my @data = main::globber('/sys/devices/system/cpu/cpu*/topology/core_siblings_list');
9596	my (@dies);
9597	foreach (@data){
9598		my $siblings = main::reader($_,'',0);
9599		if (! grep {/$siblings/} @dies){
9600			push(@dies, $siblings);
9601		}
9602	}
9603	my $die_count = scalar @dies;
9604	eval $end if $b_log;
9605	return $die_count;
9606}
9607# needed because no physical_id in cpuinfo, but > 1 cpu systems exist
9608# returns: 0 - per cpu cores; 1 - phys cpu count; 2 - override model defaul names
9609sub elbrus_data {
9610	eval $start if $b_log;
9611	my ($family_id,$model_id,$count,$arch) = @_;
9612	# 0: cores
9613	my @return = (0,1,$arch);
9614	my %cores = (
9615	# key=family id + model id
9616	'41' => 1,
9617	'42' => 1,
9618	'43' => 4,
9619	'44' => 2,
9620	'46' => 1,
9621	'47' => 8,
9622	'48' => 1,
9623	'49' => 8,
9624	'59' => 8,
9625	'4A' => 12,
9626	'4B' => 16,
9627	'4C' => 2,
9628	'6A' => 12,
9629	'6B' => 16,
9630	'6C' => 2,
9631	);
9632	$return[0] = $cores{$family_id . $model_id} if $cores{$family_id . $model_id};
9633	if ($return[0]){
9634		$return[1] = ($count % $return[0]) ? int($count/$return[0]) + 1 : $count/$return[0];
9635	}
9636	eval $end if $b_log;
9637	return @return;
9638}
9639
9640# only elbrus ID is actually used live
9641sub cpu_vendor {
9642	eval $start if $b_log;
9643	my ($string) = @_;
9644	my ($vendor) = ('');
9645	$string = lc($string);
9646	if ($string =~ /intel/){
9647		$vendor = "intel"
9648	}
9649	elsif ($string =~ /amd/){
9650		$vendor = "amd"
9651	}
9652	# via
9653	elsif ($string =~ /centaur/){
9654		$vendor = "centaur"
9655	}
9656	elsif ($string =~ /e2k/){
9657		$vendor = "elbrus"
9658	}
9659	eval $end if $b_log;
9660	return $vendor;
9661}
9662sub get_boost_status {
9663	eval $start if $b_log;
9664	my ($boost);
9665	my $path = '/sys/devices/system/cpu/cpufreq/boost';
9666	if (-r $path){
9667		$boost = main::reader($path,'',0);
9668		if (defined $boost && $boost =~ /^[01]$/){
9669			$boost = ($boost) ? 'enabled' : 'disabled';
9670		}
9671	}
9672	eval $end if $b_log;
9673	return $boost;
9674}
9675sub system_cpu_name {
9676	eval $start if $b_log;
9677	my (%cpus,$compat,@working);
9678	if (@working = main::globber('/sys/firmware/devicetree/base/cpus/cpu@*/compatible')){
9679		foreach my $file (@working){
9680			$compat = main::reader($file,'',0);
9681			next if $compat =~ /timer/; # seen on android
9682			# these can have non printing ascii... why? As long as we only have the
9683			# splits for: null 00/start header 01/start text 02/end text 03
9684			$compat = (split(/\x01|\x02|\x03|\x00/, $compat))[0] if $compat;
9685			$compat = (split(/,\s*/, $compat))[-1] if $compat;
9686			$cpus{$compat} = ($cpus{$compat}) ? ++$cpus{$compat}: 1;
9687		}
9688	}
9689	# synthesize it, [4] will be like: cortex-a15-timer; sunxi-timer
9690	# so far all with this directory show soc name, not cpu name for timer
9691	elsif (! -d '/sys/firmware/devicetree/base' && $devices{'timer'}){
9692		foreach my $working (@{$devices{'timer'}}){
9693			next if $working->[0] ne 'timer' || !$working->[4] || $working->[4] =~ /timer-mem$/;
9694			$working->[4] =~ s/(-system)?-timer$//;
9695			$compat = $working->[4];
9696			$cpus{$compat} = ($cpus{$compat}) ? ++$cpus{$compat}: 1;
9697		}
9698	}
9699	main::log_data('dump','%cpus',\%cpus) if $b_log;
9700	eval $end if $b_log;
9701	return %cpus;
9702}
9703
9704sub cpu_arch {
9705	eval $start if $b_log;
9706	my ($type,$family,$model,$stepping) = @_;
9707	$stepping = 0 if !main::is_numeric($stepping);
9708	$family ||= '';
9709	$model ||= '';
9710	my ($arch,$note) = ('','');
9711	my $check = main::row_defaults('note-check');
9712	# See: docs/inxi-resources.txt
9713	# print "type:$type fam:$family model:$model step:$stepping\n";
9714	if ($type eq 'amd'){
9715		if ($family eq '4'){
9716			if ($model =~ /^(3|7|8|9|A)$/){
9717				$arch = 'Am486'}
9718			elsif ($model =~ /^(E|F)$/){
9719				$arch = 'Am5x86'}
9720		}
9721		elsif ($family eq '5'){
9722			if ($model =~ /^(0|1|2|3)$/){
9723				$arch = 'K5'}
9724			elsif ($model =~ /^(6|7)$/){
9725				$arch = 'K6'}
9726			elsif ($model =~ /^(8)$/){
9727				$arch = 'K6-2'}
9728			elsif ($model =~ /^(9|D)$/){
9729				$arch = 'K6-3'}
9730			elsif ($model =~ /^(A)$/){
9731				$arch = 'Geode'}
9732			}
9733		elsif ($family eq '6'){
9734			if ($model =~ /^(1|2)$/){
9735				$arch = 'K7'}
9736			elsif ($model =~ /^(3|4)$/){
9737				$arch = 'K7 Thunderbird'}
9738			elsif ($model =~ /^(6|7|8|A)$/){
9739				$arch = 'K7 Palomino+'}
9740			else {
9741				$arch = 'K7'}
9742		}
9743		elsif ($family eq 'F'){
9744			if ($model =~ /^(4|5|7|8|B|C|E|F|14|15|17|18|1B|1C|1F)$/){
9745				$arch = 'K8'}
9746			elsif ($model =~ /^(21|23|24|25|27|28|2C|2F)$/){
9747				$arch = 'K8 rev.E'}
9748			elsif ($model =~ /^(41|43|48|4B|4C|4F|5D|5F|68|6B|6C|6F|7C|7F|C1)$/){
9749				$arch = 'K8 rev.F+'}
9750			else {
9751				$arch = 'K8'}
9752		}
9753		elsif ($family eq '10'){
9754			if ($model =~ /^(2|4|5|6|8|9|A)$/){
9755				$arch = 'K10'}
9756			else {
9757				$arch = 'K10'}
9758		}
9759		elsif ($family eq '11'){
9760			if ($model =~ /^(3)$/){
9761				$arch = 'Turion X2 Ultra'}
9762		}
9763		# might also need cache handling like 14/16
9764		elsif ($family eq '12'){
9765			if ($model =~ /^(1)$/){
9766				$arch = 'Fusion'}
9767			else {
9768				$arch = 'Fusion'}
9769		}
9770		# SOC, apu
9771		elsif ($family eq '14'){
9772			if ($model =~ /^(1|2)$/){
9773				$arch = 'Bobcat'}
9774			else {
9775				$arch = 'Bobcat'}
9776		}
9777		elsif ($family eq '15'){
9778			if ($model =~ /^(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)$/){
9779				$arch = 'Bulldozer'}
9780			elsif ($model =~ /^(10|11|12|13|14|15|16|17|18|19|1A|1B|1C|1D|1E|1F)$/){
9781				$arch = 'Piledriver'}
9782			elsif ($model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/){
9783				$arch = 'Steamroller'}
9784			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)$/){
9785				$arch = 'Excavator'}
9786			else {
9787				$arch = 'Bulldozer'}
9788		}
9789		# SOC, apu
9790		elsif ($family eq '16'){
9791			if ($model =~ /^(0|1|2|3|4|5|6|7|8|9|A|B|C|D|E|F)$/){
9792				$arch = 'Jaguar'}
9793			elsif ($model =~ /^(30|31|32|33|34|35|36|37|38|39|3A|3B|3C|3D|3E|3F)$/){
9794				$arch = 'Puma'}
9795			else {
9796				$arch = 'Jaguar'}
9797		}
9798		elsif ($family eq '17'){
9799			if ($model =~ /^(1|11|18|20)$/){
9800				$arch = 'Zen'}
9801			# Seen: stepping 1 is Zen+ Ryzen 7 3750H. But stepping 1 Zen is: Ryzen 3 3200U
9802			# Unknown if stepping 0 is Zen or either.
9803			elsif ($model =~ /^(18)$/){
9804				$arch = 'Zen/Zen+';
9805				$note = $check;
9806			}
9807			# shares model 8 with zen, stepping unknown
9808			elsif ($model =~ /^(8)$/){
9809				$arch = 'Zen+'}
9810			# used this but it didn't age well:  ^(2[0123456789ABCDEF]|
9811			elsif ($model =~ /^(31|47|60|68|71|90)$/){
9812				$arch = 'Zen 2'}
9813			else {
9814				$arch = 'Zen';
9815				$note = $check;}
9816		}
9817		elsif ($family eq '18'){
9818			# model 0
9819			$arch = 'Zen (Hygon Dhyana)';
9820		}
9821		elsif ($family eq '19'){
9822			# model: 0 1 21 40 50
9823			$arch = 'Zen 3';
9824		}
9825		# note: family 20 may be Zen 4 but not known for sure yet
9826		# elsif ($family eq '20'){
9827			# model: unknown
9828		#	$arch = 'Zen 4';
9829		# }
9830	}
9831	elsif ($type eq 'arm'){
9832		if ($family ne ''){
9833			$arch="ARMv$family";}
9834		else {
9835			$arch='ARM';}
9836	}
9837# 	elsif ($type eq 'ppc'){
9838# 		$arch='PPC';
9839# 	}
9840	# aka VIA
9841	elsif ($type eq 'centaur'){
9842		if ($family eq '5'){
9843			if ($model =~ /^(4)$/){
9844				$arch = 'WinChip C6'}
9845			elsif ($model =~ /^(8)$/){
9846				$arch = 'WinChip 2'}
9847			elsif ($model =~ /^(9)$/){
9848				$arch = 'WinChip 3'}
9849		}
9850		elsif ($family eq '6'){
9851			if ($model =~ /^(6)$/){
9852				$arch = 'WinChip-based'}
9853			elsif ($model =~ /^(7|8)$/){
9854				$arch = 'C3'}
9855			elsif ($model =~ /^(9)$/){
9856				$arch = 'C3-2'}
9857			elsif ($model =~ /^(A|D)$/){
9858				$arch = 'C7'}
9859			elsif ($model =~ /^(F)$/){
9860				$arch = 'Isaiah'}
9861		}
9862	}
9863	# note, to test uncoment $cpu{'type'} = Elbrus in proc/cpuinfo logic
9864	elsif ($type eq 'elbrus'){
9865		# E8CB
9866		if ($family eq '4'){
9867			if ($model eq '1'){
9868				$arch = 'Elbrus'}
9869			elsif ($model eq '2'){
9870				$arch = 'Elbrus-S'}
9871			elsif ($model eq '3'){
9872				$arch = 'Elbrus-4C'}
9873			elsif ($model eq '4'){
9874				$arch = 'Elbrus-2C+'}
9875			elsif ($model eq '6'){
9876				$arch = 'Elbrus-2CM'}
9877			elsif ($model eq '7'){
9878				if ($stepping >= 2){
9879					$arch = 'Elbrus-8C1';}
9880				else {
9881					$arch = 'Elbrus-8C';}
9882			} # note: stepping > 1 may be 8C1
9883			elsif ($model eq '8'){
9884				$arch = 'Elbrus-1C+'}
9885			# 8C2 morphed out of E8CV, but the two were the same die
9886			elsif ($model eq '9'){
9887				$arch = 'Elbrus-8CV/8C2';
9888				$note = $check;}
9889			elsif ($model eq 'A'){
9890				$arch = 'Elbrus-12C'}
9891			elsif ($model eq 'B'){
9892				$arch = 'Elbrus-16C'}
9893			elsif ($model eq 'C'){
9894				$arch = 'Elbrus-2C3'}
9895			else {
9896				$arch = 'Elbrus-??';
9897				$note = $check;}
9898		}
9899		elsif ($family eq '5'){
9900			if ($model eq '9'){
9901				$arch = 'Elbrus-8C2'}
9902			else {
9903				$arch = 'Elbrus-??';
9904				$note = $check;}
9905		}
9906		elsif ($family eq '6'){
9907			if ($model eq 'A'){
9908				$arch = 'Elbrus-12C'}
9909			elsif ($model eq 'B'){
9910				$arch = 'Elbrus-16C'}
9911			elsif ($model eq 'C'){
9912				$arch = 'Elbrus-2C3'}
9913			else {
9914				$arch = 'Elbrus-??';
9915				$note = $check;}
9916		}
9917		else {
9918			$arch = 'Elbrus-??';
9919			$note = $check;
9920		}
9921	}
9922	elsif ($type eq 'intel'){
9923		if ($family eq '4'){
9924			if ($model =~ /^(0|1|2|3|4|5|6|7|8|9)$/){
9925				$arch = '486'}
9926		}
9927		elsif ($family eq '5'){
9928			if ($model =~ /^(1|2|3|7)$/){
9929				$arch = 'P5'}
9930			elsif ($model =~ /^(4|8)$/){
9931				$arch = 'P5'} # MMX
9932			elsif ($model =~ /^(9|A)$/){
9933				$arch = 'Lakemont'}
9934		}
9935		elsif ($family eq '6'){
9936			if ($model =~ /^(1)$/){
9937				$arch = 'P6 Pro'}
9938			elsif ($model =~ /^(3)$/){
9939				$arch = 'P6 II Klamath'}
9940			elsif ($model =~ /^(5)$/){
9941				$arch = 'P6 II Deschutes'}
9942			elsif ($model =~ /^(6)$/){
9943				$arch = 'P6 II Mendocino'}
9944			elsif ($model =~ /^(7)$/){
9945				$arch = 'P6 III Katmai'}
9946			elsif ($model =~ /^(8)$/){
9947				$arch = 'P6 III Coppermine'}
9948			elsif ($model =~ /^(9)$/){
9949				$arch = 'M Banias'} # pentium M
9950			elsif ($model =~ /^(A)$/){
9951				$arch = 'P6 III Xeon'}
9952			elsif ($model =~ /^(B)$/){
9953				$arch = 'P6 III Tualitin'}
9954			elsif ($model =~ /^(D)$/){
9955				$arch = 'M Dothan'} # Pentium M
9956			elsif ($model =~ /^(E)$/){
9957				$arch = 'M Yonah'}
9958			elsif ($model =~ /^(F|16)$/){
9959				$arch = 'Core Merom'}
9960			elsif ($model =~ /^(15)$/){
9961				$arch = 'M Tolapai'} # pentium M system on chip
9962			elsif ($model =~ /^(17|1D)$/){
9963				$arch = 'Penryn'}
9964			elsif ($model =~ /^(1A|1E|1F|25|2C|2E|2F)$/){
9965				$arch = 'Nehalem'}
9966			elsif ($model =~ /^(1C|26)$/){
9967				$arch = 'Bonnell'} # atom Bonnell? 27?
9968			elsif ($model =~ /^(27|35|36)$/){
9969				$arch = 'Saltwell'}
9970			elsif ($model =~ /^(25|2C|2F)$/){
9971				$arch = 'Westmere'}
9972			elsif ($model =~ /^(2A|2D)$/){
9973				$arch = 'Sandy Bridge'}
9974			elsif ($model =~ /^(37|4A|4D|5A|5D)$/){
9975				$arch = 'Silvermont'}
9976			elsif ($model =~ /^(3A|3E)$/){
9977				$arch = 'Ivy Bridge'}
9978			elsif ($model =~ /^(3C|3F|45|46)$/){
9979				$arch = 'Haswell'}
9980			elsif ($model =~ /^(3D|47|4F|56)$/){
9981				$arch = 'Broadwell'}
9982			elsif ($model =~ /^(4C)$/){
9983				$arch = 'Airmont'}
9984			elsif ($model =~ /^(4E)$/){
9985				$arch = 'Skylake'}
9986			# need to find stepping for these, guessing stepping 4 is last for SL
9987			elsif ($model =~ /^(55)$/){
9988				if ($stepping >= 5 && $stepping <= 7){
9989					$arch = 'Cascade Lake'}
9990				elsif ($stepping >= 8){
9991					$arch = 'Cooper Lake'}
9992				else {
9993					$arch = 'Skylake'} }
9994			elsif ($model =~ /^(57)$/){
9995				$arch = 'Knights Landing'}
9996			elsif ($model =~ /^(5C|5F)$/){
9997				$arch = 'Goldmont'}
9998			elsif ($model =~ /^(5E)$/){
9999				$arch = 'Skylake-S'}
10000			elsif ($model =~ /^(66)$/){
10001				$arch = 'Cannon Lake'}
10002			# 6 are servers, 7 not
10003			elsif ($model =~ /^(6A|6C|7D|7E)$/){
10004				$arch = 'Ice Lake'}
10005			elsif ($model =~ /^(7A)$/){
10006				$arch = 'Goldmont Plus'}
10007			elsif ($model =~ /^(85)$/){
10008				$arch = 'Knights Mill'}
10009			elsif ($model =~ /^(8A|96|9C)$/){
10010				$arch = 'Tremont'}
10011			elsif ($model =~ /^(8C|8D)$/){
10012				$arch = 'Tiger Lake'}
10013			elsif ($model =~ /^(8E)$/){
10014				# can be AmberL or KabyL
10015				if ($stepping == 9){
10016					$arch = 'Amber/Kaby Lake';
10017					$note = $check;}
10018				elsif ($stepping == 10){
10019					$arch = 'Coffee Lake'}
10020				elsif ($stepping == 11){
10021					$arch = 'Whiskey Lake'}
10022				# can be WhiskeyL or CometL
10023				elsif ($stepping == 12){
10024					$arch = 'Comet/Whiskey Lake';
10025					$note = $check;}
10026				# note: had it as > 13, but 0xC seems to be CL
10027				elsif ($stepping >= 13){
10028					$arch = 'Comet Lake'} # guess, have not seen docs yet
10029				# NOTE: not enough info to lock this down
10030				else {
10031					$arch = 'Kaby Lake';
10032					$note = $check;}
10033			}
10034			elsif ($model =~ /^(8F)$/){
10035				$arch = 'Saphire Rapids'} # server
10036			elsif ($model =~ /^(97|9A)$/){
10037				$arch = 'Alder Lake';}
10038			elsif ($model =~ /^(9E)$/){
10039				if ($stepping == 9){
10040					$arch = 'Kaby Lake';}
10041				elsif ($stepping >= 10 && $stepping <= 13){
10042					$arch = 'Coffee Lake'}
10043				else {
10044					$arch = 'Kaby Lake';
10045					$note = $check;}
10046			}
10047			elsif ($model =~ /^(A5)$/){
10048				$arch = 'Comet Lake'} # steppings 0-5
10049			elsif ($model =~ /^(A7)$/){
10050				$arch = 'Rocket Lake'}
10051			# More info: comet: shares family/model, need to find stepping numbers
10052			# Coming: meteor lake; granite rapids; diamond rapids
10053		}
10054		# itanium 1 family 7 all recalled
10055		elsif ($family eq 'B'){
10056			if ($model =~ /^(0)$/){
10057				$arch = 'Knights Ferry'}
10058			if ($model =~ /^(1)$/){
10059				$arch = 'Knights Corner'}
10060		}
10061		elsif ($family eq 'F'){
10062			if ($model =~ /^(0|1)$/){
10063				$arch = 'Netburst Willamette'}
10064			elsif ($model =~ /^(2)$/){
10065				$arch = 'Netburst Northwood'}
10066			elsif ($model =~ /^(3)$/){
10067				$arch = 'Netburst Prescott'} # 6? Nocona
10068			elsif ($model =~ /^(4)$/){
10069				$arch = 'Netburst Smithfield'} # 6? Nocona
10070			elsif ($model =~ /^(6)$/){
10071				$arch = 'Netburst Presler'}
10072			else {
10073				$arch = 'Netburst'}
10074		}
10075		# this is not going to e accurate, WhiskyL or Kaby L can ID as Skylake
10076		# but if it's a new cpu microarch not handled yet, it may give better
10077		# than nothing result. This is intel only
10078		# This is probably the gcc/clang -march/-mtune value, which is not
10079		# necessarily the same as actual microarch, and varies between gcc/clang versions
10080		if (!$model){
10081			my $file = '/sys/devices/cpu/caps/pmu_name';
10082			$model = main::reader($file,'strip',0) if -r $file;
10083			$note = $check if $model;
10084		}
10085	}
10086	eval $end if $b_log;
10087	return ($arch,$note);
10088}
10089
10090sub count_alpha {
10091	my ($count) = @_;
10092	# print "$count\n";
10093	my @alpha = qw(Single Dual Triple Quad);
10094	if ($count > 4){
10095		$count .= '-';
10096	}
10097	else {
10098		$count = $alpha[$count-1] . ' ' if $count > 0;
10099	}
10100	return $count;
10101}
10102sub set_cpu_data {
10103	my %cpu =  (
10104	'arch' => '',
10105	'bogomips' => 0,
10106	'cores' => 0,
10107	'cur-freq' => 0,
10108	'dies' => 0,
10109	'family' => '',
10110	'flags' => '',
10111	'ids' => [],
10112	'l1-cache' => 0, # store in KB
10113	'l2-cache' => 0, # store in KB
10114	'l3-cache' => 0, # store in KB
10115	'max-freq' => 0,
10116	'min-freq' => 0,
10117	'model_id' => undef,
10118	'model_name' => '',
10119	'processors' => [],
10120	'rev' => '',
10121	'scalings' => [],
10122	'siblings' => 0,
10123	'type' => '',
10124	);
10125	return %cpu;
10126}
10127# MHZ - cell cpus
10128sub speed_cleaner {
10129	my ($speed,$opt) = @_;
10130	return if !$speed || $speed eq '0';
10131	$speed =~ s/[GMK]HZ$//gi;
10132	$speed = ($speed/1000) if $opt && $opt eq 'khz';
10133	$speed = sprintf("%.0f", $speed);
10134	return $speed;
10135}
10136sub cpu_cleaner {
10137	my ($cpu) = @_;
10138	return if !$cpu;
10139	my $filters = '@|cpu |cpu deca|([0-9]+|single|dual|two|triple|three|tri|quad|four|';
10140	$filters .= 'penta|five|hepta|six|hexa|seven|octa|eight|multi)[ -]core|';
10141	$filters .= 'ennea|genuine|multi|processor|single|triple|[0-9\.]+ *[MmGg][Hh][Zz]';
10142	$cpu =~ s/$filters//ig;
10143	$cpu =~ s/\s\s+/ /g;
10144	$cpu =~ s/^\s+|\s+$//g;
10145	return $cpu;
10146}
10147sub hex_and_decimal {
10148	my ($data) = @_;
10149	$data = '' if !defined $data;
10150	if ($data =~ /\S/){
10151		$data .= ' (' . hex($data) . ')' if hex($data) ne $data;
10152	}
10153	else {
10154		$data = 'N/A';
10155	}
10156	return $data;
10157}
10158}
10159
10160## DriveItem
10161{
10162package DriveItem;
10163my ($b_hddtemp,$b_nvme,$smartctl_missing);
10164my ($hddtemp,$nvme) = ('','');
10165my (@by_id,@by_path,@vendors);
10166my ($debugger_dir);
10167# main::writer("$debugger_dir/system-repo-data-urpmq.txt",\@data2) if $debugger_dir;
10168sub get {
10169	eval $start if $b_log;
10170	my (@data,@rows,$key1,$val1);
10171	my ($type) = @_;
10172	$type ||= 'standard';
10173	my $num = 0;
10174	@data = drive_data($type);
10175	# NOTE:
10176	if (@data){
10177		if ($type eq 'standard'){
10178			push(@rows,storage_output(\@data));
10179			push(@rows,drive_output(\@data)) if $show{'disk'} && @data;
10180			if ($bsd_type && !$dboot{'disk'} && $type eq 'standard' && $show{'disk'}){
10181				$key1 = 'Drive Report';
10182				my $file = $system_files{'dmesg-boot'};
10183				if ($file && ! -r $file){
10184					$val1 = main::row_defaults('dmesg-boot-permissions');
10185				}
10186				elsif (!$file){
10187					$val1 = main::row_defaults('dmesg-boot-missing');
10188				}
10189				else {
10190					$val1 = main::row_defaults('disk-data-bsd');
10191				}
10192				push(@rows,{main::key($num++,0,1,$key1) => $val1,});
10193			}
10194		}
10195		# used by short form, raw data returned
10196		else {
10197			@rows = @data;
10198			# print Data::Dumper::Dumper \@rows;
10199		}
10200	}
10201	else {
10202		$key1 = 'Message';
10203		$val1 = main::row_defaults('disk-data');
10204		@rows = ({main::key($num++,0,1,$key1) => $val1,});
10205	}
10206	if (!@rows){
10207		$key1 = 'Message';
10208		$val1 = main::row_defaults('disk-data');
10209		@rows = ({main::key($num++,0,1,$key1) => $val1,});
10210	}
10211	# push(@rows,@data);
10212	if ($show{'optical'} || $show{'optical-basic'}){
10213		push(@rows,OpticalItem::get());
10214	}
10215	($b_hddtemp,$b_nvme,$hddtemp,$nvme) = (undef,undef,undef,undef);
10216	(@by_id,@by_path) = (undef,undef);
10217	eval $end if $b_log;
10218	return @rows;
10219}
10220sub storage_output {
10221	eval $start if $b_log;
10222	my ($disks) = @_;
10223	my (@rows);
10224	my ($num,$j) = (0,0);
10225	my ($size,$size_value,$used) = ('','','');
10226	push(@rows, {
10227	main::key($num++,1,1,'Local Storage') => '',
10228	});
10229	# print Data::Dumper::Dumper $disks;
10230	$size = main::get_size($disks->[0]{'size'},'string','N/A');
10231	if ($disks->[0]{'logical-size'}){
10232		$rows[$j]->{main::key($num++,1,2,'total')} = '';
10233		$rows[$j]->{main::key($num++,0,3,'raw')} = $size;
10234		$size = main::get_size($disks->[0]{'logical-size'},'string');
10235		$size_value = $disks->[0]{'logical-size'};
10236		# print Data::Dumper::Dumper $disks;
10237		$rows[$j]->{main::key($num++,1,3,'usable')} = $size;
10238	}
10239	else {
10240		$size_value = $disks->[0]{'size'} if $disks->[0]{'size'};
10241		$rows[$j]->{main::key($num++,0,2,'total')} = $size;
10242	}
10243	$used = main::get_size($disks->[0]{'used'},'string','N/A');
10244	if ($extra > 0 && $disks->[0]{'logical-free'}){
10245		$size = main::get_size($disks->[0]{'logical-free'},'string');
10246		$rows[$j]->{main::key($num++,0,4,'lvm-free')} = $size;
10247	}
10248	if (($size_value && $size_value =~ /^[0-9]/) &&
10249		 ($used && $disks->[0]{'used'} =~ /^[0-9]/)){
10250		$used = $used . ' (' . sprintf("%0.1f", $disks->[0]{'used'}/$size_value*100) . '%)';
10251	}
10252	$rows[$j]->{main::key($num++,0,2,'used')} = $used;
10253	shift @$disks;
10254	eval $end if $b_log;
10255	return @rows;
10256}
10257sub drive_output {
10258	eval $start if $b_log;
10259	my ($disks) = @_;
10260	# print Data::Dumper::Dumper $disks;
10261	my ($b_smart_permissions,@rows,$smart_age,$smart_basic,$smart_fail);
10262	my ($num,$j) = (0,0);
10263	my ($id,$model,$size) = ('','','');
10264	# note: specific smartctl non-missing errors handled inside loop
10265	if ($smartctl_missing){
10266		$j = scalar @rows;
10267		$rows[$j]->{main::key($num++,0,1,'SMART Message')} = $smartctl_missing;
10268	}
10269	elsif ($b_admin){
10270		($smart_age,$smart_basic,$smart_fail) = smartctl_fields();
10271	}
10272	foreach my $row (sort { $a->{'id'} cmp $b->{'id'} } @$disks){
10273		($id,$model,$size) = ('','','');
10274		$num = 1;
10275		$model = ($row->{'model'}) ? $row->{'model'}: 'N/A';
10276		$id =  ($row->{'id'}) ? "/dev/$row->{'id'}":'N/A';
10277		$size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A';
10278		# print Data::Dumper::Dumper $disks;
10279		$j = scalar @rows;
10280		if (!$b_smart_permissions && $row->{'smart-permissions'}){
10281			$b_smart_permissions = 1;
10282			$rows[$j]->{main::key($num++,0,1,'SMART Message')} = $row->{'smart-permissions'};
10283			$j = scalar @rows;
10284		}
10285		push(@rows, {
10286		main::key($num++,1,1,'ID') => $id,
10287		});
10288		if ($b_admin && $row->{'maj-min'}){
10289			$rows[$j]->{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'};
10290		}
10291		if ($row->{'type'}){
10292			$rows[$j]->{main::key($num++,0,2,'type')} = $row->{'type'};
10293		}
10294		if ($row->{'vendor'}){
10295			$rows[$j]->{main::key($num++,0,2,'vendor')} = $row->{'vendor'};
10296		}
10297		$rows[$j]->{main::key($num++,0,2,'model')} = $model;
10298		if ($row->{'drive-vendor'}){
10299			$rows[$j]->{main::key($num++,0,2,'drive vendor')} = $row->{'drive-vendor'};
10300		}
10301		if ($row->{'drive-model'}){
10302			$rows[$j]->{main::key($num++,0,2,'drive model')} = $row->{'drive-model'};
10303		}
10304		if ($row->{'family'}){
10305			$rows[$j]->{main::key($num++,0,2,'family')} = $row->{'family'};
10306		}
10307		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
10308		if ($b_admin && $row->{'block-physical'}){
10309			$rows[$j]->{main::key($num++,1,2,'block-size')} = '';
10310			$rows[$j]->{main::key($num++,0,3,'physical')} = $row->{'block-physical'} . ' B';
10311			$rows[$j]->{main::key($num++,0,3,'logical')} = ($row->{'block-logical'}) ? $row->{'block-logical'} . ' B' : 'N/A';
10312		}
10313		if ($extra > 1 && $row->{'speed'}){
10314			if ($row->{'sata'}){
10315				$rows[$j]->{main::key($num++,0,2,'sata')} = $row->{'sata'};
10316			}
10317			$rows[$j]->{main::key($num++,0,2,'speed')} = $row->{'speed'};
10318			$rows[$j]->{main::key($num++,0,2,'lanes')} = $row->{'lanes'} if $row->{'lanes'};
10319		}
10320		if ($extra > 2){
10321			$row->{'drive-type'} ||= 'N/A';
10322			$rows[$j]->{main::key($num++,0,2,'type')} = $row->{'drive-type'};
10323			if ($row->{'rotation'}){
10324				$rows[$j]->{main::key($num++,0,2,'rpm')} = $row->{'rotation'};
10325			}
10326		}
10327		if ($extra > 1){
10328			if (!$row->{'serial'} && $alerts{'bioctl'} &&
10329			 $alerts{'bioctl'}->{'action'} eq 'permissions'){
10330				$row->{'serial'} = main::row_defaults('root-required');
10331			}
10332			else {
10333				$row->{'serial'} = main::apply_filter($row->{'serial'});
10334			}
10335			$rows[$j]->{main::key($num++,0,2,'serial')} = $row->{'serial'};
10336			if ($row->{'drive-serial'}){
10337				$rows[$j]->{main::key($num++,0,2,'drive serial')} = main::apply_filter($row->{'drive-serial'});
10338			}
10339			if ($row->{'firmware'}){
10340				$rows[$j]->{main::key($num++,0,2,'rev')} = $row->{'firmware'};
10341			}
10342			if ($row->{'drive-firmware'}){
10343				$rows[$j]->{main::key($num++,0,2,'drive rev')} = $row->{'drive-firmware'};
10344			}
10345		}
10346		if ($extra > 0 && $row->{'temp'}){
10347			$rows[$j]->{main::key($num++,0,2,'temp')} = $row->{'temp'} . ' C';
10348		}
10349		if ($extra > 1 && $alerts{'bioctl'}){
10350			if (!$row->{'duid'} && $alerts{'bioctl'}->{'action'} eq 'permissions'){
10351				$rows[$j]->{main::key($num++,0,2,'duid')} = main::row_defaults('root-required');
10352			}
10353			elsif ($row->{'duid'}){
10354				$rows[$j]->{main::key($num++,0,2,'duid')} = main::apply_filter($row->{'duid'});
10355			}
10356		}
10357		# extra level tests already done
10358		if (defined $row->{'partition-table'}){
10359			$rows[$j]->{main::key($num++,0,2,'scheme')} = $row->{'partition-table'};
10360		}
10361
10362		if ($row->{'smart'} || $row->{'smart-error'}){
10363			$j = scalar @rows;
10364			## Basic SMART and drive info ##
10365			smart_output('basic',$smart_basic,$row,$j,\$num,\@rows);
10366			## Old-Age errors ##
10367			smart_output('age',$smart_age,$row,$j,\$num,\@rows);
10368			## Pre-Fail errors ##
10369			smart_output('fail',$smart_fail,$row,$j,\$num,\@rows);
10370		}
10371	}
10372	eval $end if $b_log;
10373	return @rows;
10374}
10375# $num and $rows passed by reference
10376sub smart_output {
10377	eval $start if $b_log;
10378	my ($type,$smart_data,$row,$j,$num,$rows) = @_;
10379	my ($b_found);
10380	my ($l,$m,$p) = ($type eq 'basic') ? (2,3,0) : (3,4,0);
10381	my ($m_h,$p_h) = ($m,$p);
10382	for (my $i = 0; $i < scalar @$smart_data;$i++){
10383		if ($row->{$smart_data->[$i][0]}){
10384			if (!$b_found){
10385				my ($key,$support) = ('','');
10386				if ($type eq 'basic'){
10387					$support = ($row->{'smart'}) ? $row->{'smart'}: $row->{'smart-error'};
10388					$key = $smart_data->[$i][1];
10389				}
10390				elsif ($type eq 'age'){$key = 'Old-Age';}
10391				elsif ($type eq 'fail'){$key = 'Pre-Fail';}
10392				$$rows[$j]->{main::key($$num++,1,$l,$key)} = $support;
10393				$b_found = 1;
10394				next if $type eq 'basic';
10395			}
10396			if ($type ne 'basic'){
10397				if ($smart_data->[$i][0] =~ /-a[vr]?$/){
10398					($p,$m) = (1,$m_h);
10399				}
10400				elsif ($smart_data->[$i][0] =~ /-[ftvw]$/){
10401					($p,$m) = (0,5);
10402				}
10403				else {
10404					($p,$m) = ($p_h,$m_h);
10405				}
10406			}
10407			$$rows[$j]->{main::key($$num++,$p,$m,$smart_data->[$i][1])} = $row->{$smart_data->[$i][0]};
10408		}
10409	}
10410	eval $end if $b_log;
10411}
10412
10413sub drive_data {
10414	eval $start if $b_log;
10415	my ($type) = @_;
10416	my (@rows,@data,@devs);
10417	my $num = 0;
10418	my ($used) = (0);
10419	PartitionItem::set_partitions() if !$loaded{'set-partitions'};
10420	RaidItem::raid_data() if !$loaded{'raid'};
10421	# see docs/inxi-data.txt PARTITION DATA for more on remote/fuse fs
10422	my $fs_skip = PartitionItem::fs_excludes('disk-used');
10423	foreach my $row (@partitions){
10424		# don't count remote/distributed/union type fs towards used
10425		next if ($row->{'fs'} && $row->{'fs'} =~ /^$fs_skip$/);
10426		# don't count non partition swap
10427		next if ($row->{'swap-type'} && $row->{'swap-type'} ne 'partition');
10428		# in some cases, like redhat, mounted cdrom/dvds show up in partition data
10429		next if ($row->{'dev-base'} && $row->{'dev-base'} =~ /^sr[0-9]+$/);
10430		# this is used for specific cases where bind, or incorrect multiple mounts
10431		# to same partitions, or btrfs sub volume mounts, is present. The value is
10432		# searched for an earlier appearance of that partition and if it is present,
10433		# the data is not added into the partition used size.
10434		if ($row->{'dev-base'} !~ /^(\/\/|:\/)/ && !(grep {/$row->{'dev-base'}/} @devs)){
10435			$used += $row->{'used'} if $row->{'used'};
10436			push(@devs, $row->{'dev-base'});
10437		}
10438	}
10439	if (!$bsd_type){
10440		@data = proc_data($used);
10441	}
10442	else {
10443		@data = bsd_data($used);
10444	}
10445	if ($b_admin){
10446		if ($alerts{'smartctl'} && $alerts{'smartctl'}->{'action'} eq 'use'){
10447			@data = smartctl_data(\@data);
10448		}
10449		else {
10450			$smartctl_missing = $alerts{'smartctl'}->{'message'};
10451		}
10452	}
10453	print Data::Dumper::Dumper \@data if $dbg[13];;
10454	main::log_data('data',"used: $used") if $b_log;
10455	eval $end if $b_log;
10456	return @data;
10457}
10458sub proc_data {
10459	eval $start if $b_log;
10460	my ($used) = @_;
10461	my (@data,@drives);
10462	my ($b_hdx,$logical_size,$size) = (0,0,0);
10463	PartitionData::set() if !$bsd_type && !$loaded{'partition-data'};
10464	foreach my $row (@proc_partitions){
10465		if ($row->[-1] =~ /^(fio[a-z]+|[hsv]d[a-z]+|(ada|mmcblk|n[b]?d|nvme[0-9]+n)[0-9]+)$/){
10466			$b_hdx = 1 if $row->[-1] =~ /^hd[a-z]/;
10467			push(@drives, {
10468			'firmware' => '',
10469			'id' => $row->[-1],
10470			'maj-min' => $row->[0] . ':' . $row->[1],
10471			'model' => '',
10472			'serial' => '',
10473			'size' => $row->[2],
10474			'spec' => '',
10475			'speed' => '',
10476			'temp' => '',
10477			'type' => '',
10478			'vendor' => '',
10479			});
10480		}
10481		# See http://lanana.org/docs/device-list/devices-2.6+.txt for major numbers used below
10482		# See https://www.mjmwired.net/kernel/Documentation/devices.txt for kernel 4.x device numbers
10483		# if ($row->[0] =~ /^(3|22|33|8)$/ && $row->[1] % 16 == 0)  {
10484		#	 $size += $row->[2];
10485		# }
10486		# special case from this data: 8     0  156290904 sda
10487		# 43        0   48828124 nbd0
10488		# note: known starters: vm: 252/253/254; grsec: 202; nvme: 259 mmcblk: 179
10489		# Note: with > 1 nvme drives, the minor number no longer passes the modulus tests,
10490		# It appears to just increase randomly from the first 0 minor of the first nvme to
10491		# nvme partitions to next nvme, so it only passes the test for the first nvme drive.
10492		# note: 66       16 9766436864 sdah ; 65      240 9766436864 sdaf[maybe special case when double letters?
10493		# Check /proc/devices for major number matches
10494		if ($row->[0] =~ /^(3|8|22|33|43|6[5-9]|7[12]|12[89]|13[0-5]|179|202|252|253|254|259)$/ &&
10495		 $row->[-1] =~ /(mmcblk[0-9]+|n[b]?d[0-9]+|nvme[0-9]+n[0-9]+|fio[a-z]+|[hsv]d[a-z]+)$/ &&
10496		 ($row->[1] % 16 == 0 || $row->[1] % 16 == 8 || $row->[-1] =~ /(nvme[0-9]+n[0-9]+)$/)){
10497			$size += $row->[2];
10498		}
10499	}
10500	# raw_logical[0] is total of all logical raid/lvm found
10501	# raw_logical[1] is total of all components found. If this totally fails,
10502	# and we end up with raw logical less than used, give up
10503	if (@raw_logical && $raw_logical[0] && (!$used || $raw_logical[0] > $used)){
10504		$logical_size = ($size - $raw_logical[1] + $raw_logical[0]);
10505	}
10506	# print Data::Dumper::Dumper \@drives;
10507	main::log_data('data',"size: $size") if $b_log;
10508	@data = ({
10509	'logical-size' => $logical_size,
10510	'logical-free' => $raw_logical[2],
10511	'size' => $size,
10512	'used' => $used,
10513	});
10514	# print Data::Dumper::Dumper \@data;
10515	if ($show{'disk'}){
10516		unshift(@drives,@data);
10517		# print 'drives:', Data::Dumper::Dumper \@drives;
10518		@data = proc_data_advanced($b_hdx,\@drives);
10519	}
10520	main::log_data('dump','@data',\@data) if $b_log;
10521	print Data::Dumper::Dumper \@data if $dbg[24];
10522	eval $end if $b_log;
10523	return @data;
10524}
10525
10526sub proc_data_advanced {
10527	eval $start if $b_log;
10528	my ($b_hdx,$drives) = @_;
10529	my ($i) = (0);
10530	my (@data,@disk_data,@rows,@scsi,@temp,@working);
10531	my ($pt_cmd) = ('unset');
10532	my ($block_type,$file,$firmware,$model,$path,
10533	$partition_scheme,$serial,$vendor,$working_path);
10534	@by_id = main::globber('/dev/disk/by-id/*');
10535	# these do not contain any useful data, no serial or model name
10536	# wwn-0x50014ee25fb50fc1 and nvme-eui.0025385b71b07e2e
10537	# scsi-SATA_ST980815A_ simply repeats ata-ST980815A_; same with scsi-0ATA_WDC_WD5000L31X
10538	# we also don't need the partition items
10539	my $pattern = '^\/dev\/disk\/by-id\/(md-|lvm-|dm-|wwn-|nvme-eui|raid-|scsi-([0-9]ATA|SATA))|-part[0-9]+$';
10540	@by_id = grep {!/$pattern/} @by_id if @by_id;
10541	# print join("\n", @by_id), "\n";
10542	@by_path = main::globber('/dev/disk/by-path/*');
10543	## check for all ide type drives, non libata, only do it if hdx is in array
10544	## this is now being updated for new /sys type paths, this may handle that ok too
10545	## skip the first rows in the loops since that's the basic size/used data
10546	if ($b_hdx){
10547		for ($i = 1; $i < scalar @$drives; $i++){
10548			$file = "/proc/ide/$drives->[$i]{'id'}/model";
10549			if ($drives->[$i]{'id'} =~ /^hd[a-z]/ && -e $file){
10550				$model = main::reader($file,'strip',0);
10551				$drives->[$i]{'model'} = $model;
10552			}
10553		}
10554	}
10555	# scsi stuff
10556	if ($file = $system_files{'proc-scsi'}){
10557		@scsi = scsi_data($file);
10558	}
10559	# print 'drives:', Data::Dumper::Dumper $drives;
10560	for ($i = 1; $i < scalar @$drives; $i++){
10561		#next if $drives->[$i]{'id'} =~ /^hd[a-z]/;
10562		($block_type,$firmware,$model,$partition_scheme,
10563		$serial,$vendor,$working_path) = ('','','','','','','');
10564		# print "$drives->[$i]{'id'}\n";
10565		@disk_data = disk_data_by_id("/dev/$drives->[$i]{'id'}");
10566		main::log_data('dump','@disk_data', \@disk_data) if $b_log;
10567		if ($drives->[$i]{'id'} =~ /[sv]d[a-z]/){
10568			$block_type = 'sdx';
10569			$working_path = "/sys/block/$drives->[$i]{'id'}/device/";
10570		}
10571		elsif ($drives->[$i]{'id'} =~ /mmcblk/){
10572			$block_type = 'mmc';
10573			$working_path = "/sys/block/$drives->[$i]{'id'}/device/";
10574		}
10575		elsif ($drives->[$i]{'id'} =~ /nvme/){
10576			$block_type = 'nvme';
10577			# this results in:
10578			# /sys/devices/pci0000:00/0000:00:03.2/0000:06:00.0/nvme/nvme0/nvme0n1
10579			# but we want to go one level down so slice off trailing nvme0n1
10580			$working_path = Cwd::abs_path("/sys/block/$drives->[$i]{'id'}");
10581			$working_path =~ s/nvme[^\/]*$//;
10582		}
10583		main::log_data('data',"working path: $working_path") if $b_log;
10584		if ($b_admin && -e "/sys/block/"){
10585			my @working = block_data($drives->[$i]{'id'});
10586			$drives->[$i]{'block-logical'} = $working[0];
10587			$drives->[$i]{'block-physical'} = $working[1];
10588		}
10589		if ($block_type && @scsi && @by_id && ! -e "${working_path}model" && ! -e "${working_path}name"){
10590			## ok, ok, it's incomprehensible, search /dev/disk/by-id for a line that contains the
10591			# discovered disk name AND ends with the correct identifier, sdx
10592			# get rid of whitespace for some drive names and ids, and extra data after - in name
10593			SCSI:
10594			foreach my $row (@scsi){
10595				if ($row->{'model'}){
10596					$row->{'model'} = (split(/\s*-\s*/,$row->{'model'}))[0];
10597					foreach my $id (@by_id){
10598						if ($id =~ /$row->{'model'}/ && "/dev/$drives->[$i]{'id'}" eq Cwd::abs_path($id)){
10599							$drives->[$i]{'firmware'} = $row->{'firmware'};
10600							$drives->[$i]{'model'} = $row->{'model'};
10601							$drives->[$i]{'vendor'} = $row->{'vendor'};
10602							last SCSI;
10603						}
10604					}
10605				}
10606			}
10607		}
10608		# note: an entire class of model names gets truncated by /sys so that should be the last
10609		# in priority re tests.
10610		elsif ((!@disk_data || !$disk_data[0]) && $block_type){
10611			# NOTE: while path ${working_path}vendor exists, it contains junk value, like: ATA
10612			$path = "${working_path}model";
10613			if (-r $path){
10614				$model = main::reader($path,'strip',0);
10615				$drives->[$i]{'model'} = $model if $model;
10616			}
10617			elsif ($block_type eq 'mmc' && -r "${working_path}name"){
10618				$path = "${working_path}name";
10619				$model = main::reader($path,'strip',0);
10620				$drives->[$i]{'model'} = $model if $model;
10621			}
10622		}
10623		if (!$drives->[$i]{'model'} && @disk_data){
10624			$drives->[$i]{'model'} = $disk_data[0] if $disk_data[0];
10625			$drives->[$i]{'vendor'} = $disk_data[1] if $disk_data[1];
10626		}
10627		# maybe rework logic if find good scsi data example, but for now use this
10628		elsif ($drives->[$i]{'model'} && !$drives->[$i]{'vendor'}){
10629			$drives->[$i]{'model'} = main::disk_cleaner($drives->[$i]{'model'});
10630			my @device_data = device_vendor($drives->[$i]{'model'},'');
10631			$drives->[$i]{'model'} = $device_data[1] if $device_data[1];
10632			$drives->[$i]{'vendor'} = $device_data[0] if $device_data[0];
10633		}
10634		if ($working_path){
10635			$path = "${working_path}removable";
10636			$drives->[$i]{'type'} = 'Removable' if -r $path && main::reader($path,'strip',0); # 0/1 value
10637		}
10638		my $peripheral = peripheral_data($drives->[$i]{'id'});
10639		# note: we only want to update type if we found a peripheral, otherwise preserve value
10640		$drives->[$i]{'type'} = $peripheral if $peripheral;
10641		# print "type:$drives->[$i]{'type'}\n";
10642		if ($extra > 0){
10643			$drives->[$i]{'temp'} = hdd_temp("$drives->[$i]{'id'}");
10644			if ($extra > 1){
10645				my @speed_data = device_speed($drives->[$i]{'id'});
10646				$drives->[$i]{'speed'} = $speed_data[0] if $speed_data[0];
10647				$drives->[$i]{'lanes'} = $speed_data[1] if $speed_data[1];
10648				if (@disk_data && $disk_data[2]){
10649					$drives->[$i]{'serial'} = $disk_data[2];
10650				}
10651				else {
10652					$path = "${working_path}serial";
10653					if (-r $path){
10654						$serial = main::reader($path,'strip',0);
10655						$drives->[$i]{'serial'} = $serial if $serial;
10656					}
10657				}
10658				if ($extra > 2 && !$drives->[$i]{'firmware'}){
10659					my @fm = ('rev','fmrev','firmware_rev'); # 0 ~ default; 1 ~ mmc; 2 ~ nvme
10660					foreach my $firmware (@fm){
10661						$path = "${working_path}$firmware";
10662						if (-r $path){
10663							$drives->[$i]{'firmware'} = main::reader($path,'strip',0);
10664							last;
10665						}
10666					}
10667				}
10668			}
10669		}
10670		if ($extra > 2){
10671			@data = disk_data_advanced($pt_cmd,$drives->[$i]{'id'});
10672			$pt_cmd = $data[0];
10673			$drives->[$i]{'partition-table'} = uc($data[1]) if $data[1];
10674			if ($data[2]){
10675				$drives->[$i]{'rotation'} = $data[2];
10676				$drives->[$i]{'drive-type'} = 'HDD';
10677			}
10678			elsif (($block_type && $block_type ne 'sdx') ||
10679			       # note: this case could conceivabley be wrong for a spun down HDD
10680			       (defined $data[2] && $data[2] eq '0') ||
10681			       ($drives->[$i]{'model'} &&
10682			        $drives->[$i]{'model'} =~ /(flash|mmc|msata|\bm[\.-]?2\b|nvme|ssd|solid\s?state)/i)){
10683				$drives->[$i]{'drive-type'} = 'SSD';
10684			}
10685		}
10686	}
10687	main::log_data('dump','$drives',\$drives) if $b_log;
10688	print Data::Dumper::Dumper $drives if $dbg[24];
10689	eval $end if $b_log;
10690	return @$drives;
10691}
10692# camcontrol identify <device> |grep ^serial (this might be (S)ATA specific)
10693# smartcl -i <device> |grep ^Serial
10694# see smartctl; camcontrol devlist; gptid status;
10695sub bsd_data {
10696	eval $start if $b_log;
10697	my ($used) = @_;
10698	my (@data,@drives,@softraid,@temp);
10699	my ($i,$logical_size,$size,$working) = (0,0,0,0);
10700	my $file = $system_files{'dmesg-boot'};
10701	DiskDataBSD::set() if !$loaded{'disk-data-bsd'};
10702	# we don't want non dboot disk data from gpart or disklabel
10703	if ($file && ! -r $file){
10704		$size = main::row_defaults('dmesg-boot-permissions');
10705	}
10706	elsif (!$file){
10707		$size = main::row_defaults('dmesg-boot-missing');
10708	}
10709	elsif (%disks_bsd){
10710		if ($sysctl{'softraid'}){
10711			@softraid = map {$_ =~ s/.*\(([^\)]+)\).*/$1/;$_} @{$sysctl{'softraid'}};
10712		}
10713		foreach my $id (sort keys %disks_bsd){
10714			next if !$disks_bsd{$id} || !$disks_bsd{$id}->{'size'};
10715			$drives[$i]->{'id'} = $id;
10716			$drives[$i]->{'firmware'} = '';
10717			$drives[$i]->{'temp'} = '';
10718			$drives[$i]->{'type'} = '';
10719			$drives[$i]->{'vendor'} = '';
10720			$drives[$i]->{'block-logical'} = $disks_bsd{$id}->{'block-logical'};
10721			$drives[$i]->{'block-physical'} = $disks_bsd{$id}->{'block-physical'};
10722			$drives[$i]->{'partition-table'} = $disks_bsd{$id}->{'scheme'};
10723			$drives[$i]->{'serial'} = $disks_bsd{$id}->{'serial'};
10724			$drives[$i]->{'size'} = $disks_bsd{$id}->{'size'};
10725			# don't count OpenBSD RAID/CRYPTO virtual disks!
10726			if ($drives[$i]->{'size'} && (!@softraid || !(grep {$id eq $_} @softraid))){
10727				$size += $drives[$i]->{'size'} if $drives[$i]->{'size'};
10728			}
10729			$drives[$i]->{'spec'} = $disks_bsd{$id}->{'spec'};
10730			$drives[$i]->{'speed'} = $disks_bsd{$id}->{'speed'};
10731			$drives[$i]->{'type'} = $disks_bsd{$id}->{'type'};
10732			# generate the synthetic model/vendor data
10733			$drives[$i]->{'model'} = $disks_bsd{$id}->{'model'};
10734			if ($drives[$i]->{'model'}){
10735				my @device_data = device_vendor($drives[$i]->{'model'},'');
10736				$drives[$i]->{'vendor'} = $device_data[0] if $device_data[0];
10737				$drives[$i]->{'model'} = $device_data[1] if $device_data[1];
10738			}
10739			if ($disks_bsd{$id}->{'duid'}){
10740				$drives[$i]->{'duid'} = $disks_bsd{$id}->{'duid'};
10741			}
10742			if ($disks_bsd{$id}->{'partition-table'}){
10743				$drives[$i]->{'partition-table'} = $disks_bsd{$id}->{'partition-table'};
10744			}
10745			$i++;
10746		}
10747		# raw_logical[0] is total of all logical raid/lvm found
10748		# raw_logical[1] is total of all components found. If this totally fails,
10749		# and we end up with raw logical less than used, give up
10750		if (@raw_logical && $size && $raw_logical[0] &&
10751		 (!$used || $raw_logical[0] > $used)){
10752			$logical_size = ($size - $raw_logical[1] + $raw_logical[0]);
10753		}
10754		if (!$size){
10755			$size = main::row_defaults('data-bsd');
10756		}
10757	}
10758	@data = ({
10759	'logical-size' => $logical_size,
10760	'logical-free' => $raw_logical[2],
10761	'size' => $size,
10762	'used' => $used,
10763	});
10764	#main::log_data('dump','$data',\@data) if $b_log;
10765	if ($show{'disk'}){
10766		push(@data,@drives);
10767		# print 'data:', Data::Dumper::Dumper \@data;
10768	}
10769	main::log_data('dump','$data',\@data) if $b_log;
10770	print Data::Dumper::Dumper \@data if $dbg[24];
10771	eval $end if $b_log;
10772	return @data;
10773}
10774
10775# return indexes: 0 - age; 1 - basic; 2 - fail
10776# make sure to update if fields added in smartctl_data()
10777sub smartctl_fields {
10778	eval $start if $b_log;
10779	my @data = (
10780	[ # age
10781	['smart-gsense-error-rate-ar','g-sense error rate'],
10782	['smart-media-wearout-a','media wearout'],
10783	['smart-media-wearout-t','threshold'],
10784	['smart-media-wearout-f','alert'],
10785	['smart-multizone-errors-av','write error rate'],
10786	['smart-multizone-errors-t','threshold'],
10787	['smart-udma-crc-errors-ar','UDMA CRC errors'],
10788	['smart-udma-crc-errors-f','alert'],
10789	],
10790	[ # basic
10791	['smart','SMART'],
10792	['smart-error','SMART Message'],
10793	['smart-support','state'],
10794	['smart-status','health'],
10795	['smart-power-on-hours','on'],
10796	['smart-cycles','cycles'],
10797	['smart-units-read','read-units'],
10798	['smart-units-written','written-units'],
10799	['smart-read','read'],
10800	['smart-written','written'],
10801	],
10802	[ # fail
10803	['smart-end-to-end-av','end-to-end'],
10804	['smart-end-to-end-t','threshold'],
10805	['smart-end-to-end-f','alert'],
10806	['smart-raw-read-error-rate-av','read error rate'],
10807	['smart-raw-read-error-rate-t','threshold'],
10808	['smart-raw-read-error-rate-f','alert'],
10809	['smart-reallocated-sectors-av','reallocated sector'],
10810	['smart-reallocated-sectors-t','threshold'],
10811	['smart-reallocated-sectors-f','alert'],
10812	['smart-retired-blocks-av','retired block'],
10813	['smart-retired-blocks-t','threshold'],
10814	['smart-retired-blocks-f','alert'],
10815	['smart-runtime-bad-block-av','runtime bad block'],
10816	['smart-runtime-bad-block-t','threshold'],
10817	['smart-runtime-bad-block-f','alert'],
10818	['smart-seek-error-rate-av', 'seek error rate'],
10819	['smart-seek-error-rate-t', 'threshold'],
10820	['smart-seek-error-rate-f', 'alert'],
10821	['smart-spinup-time-av','spin-up time'],
10822	['smart-spinup-time-t','threshold'],
10823	['smart-spinup-time-f','alert'],
10824	['smart-ssd-life-left-av','life left'],
10825	['smart-ssd-life-left-t','threshold'],
10826	['smart-ssd-life-left-f','alert'],
10827	['smart-unused-reserve-block-av','unused reserve block'],
10828	['smart-unused-reserve-block-t','threshold'],
10829	['smart-unused-reserve-block-f','alert'],
10830	['smart-used-reserve-block-av','used reserve block'],
10831	['smart-used-reserve-block-t','threshold'],
10832	['smart-used-reserve-block-f','alert'],
10833	['smart-unknown-1-a','attribute'],
10834	['smart-unknown-1-v','value'],
10835	['smart-unknown-1-w','worst'],
10836	['smart-unknown-1-t','threshold'],
10837	['smart-unknown-1-f','alert'],
10838	['smart-unknown-2-a','attribute'],
10839	['smart-unknown-2-v','value'],
10840	['smart-unknown-2-w','worst'],
10841	['smart-unknown-2-t','threshold'],
10842	['smart-unknown-2-f','alert'],
10843	['smart-unknown-3-a','attribute'],
10844	['smart-unknown-3-v','value'],
10845	['smart-unknown-3-w','worst'],
10846	['smart-unknown-3-t','threshold'],
10847	['smart-unknown-4-f','alert'],
10848	['smart-unknown-4-a','attribute'],
10849	['smart-unknown-4-v','value'],
10850	['smart-unknown-4-w','worst'],
10851	['smart-unknown-4-t','threshold'],
10852	['smart-unknown-4-f','alert'],
10853	['smart-unknown-5-f','alert'],
10854	['smart-unknown-5-a','attribute'],
10855	['smart-unknown-5-v','value'],
10856	['smart-unknown-5-w','worst'],
10857	['smart-unknown-5-t','threshold'],
10858	['smart-unknown-5-f','alert'],
10859	]
10860	);
10861	eval $end if $b_log;
10862	return @data;
10863}
10864
10865sub smartctl_data {
10866	eval $start if $b_log;
10867	my ($data) = @_;
10868	my ($b_attributes,$b_intel,$b_kingston,$cmd,%holder,$id,@working,@result,@split);
10869	my ($splitter,$num,$a,$f,$r,$t,$v,$w,$y) = (':\s*',0,0,8,1,5,3,4,6); # $y is type, $t threshold, etc
10870	for (my $i = 0; $i < scalar @$data; $i++){
10871		next if !$data->[$i]{'id'};
10872		($b_attributes,$b_intel,$b_kingston,$splitter,$num,$a,$r) = (0,0,0,':\s*',0,0,1);
10873		%holder = ();
10874		# print $data->[$i]{'id'},"\n";
10875		# m2 nvme failed on nvme0n1 drive id:
10876		$id = $data->[$i]{'id'};
10877		$id =~ s/n[0-9]+$// if $id =~ /^nvme/;
10878		# openbsd needs the 'c' partition, which is the entire disk
10879		$id .= 'c' if $bsd_type && $bsd_type eq 'openbsd';
10880		$cmd = $alerts{'smartctl'}->{'path'} . " -AHi /dev/" . $id . ' 2>/dev/null';
10881		@result = main::grabber("$cmd", '', 'strip');
10882		main::log_data('dump','@result', \@result) if $b_log; # log before cleanup
10883		@result = grep {!/^(smartctl|Copyright|==)/} @result;
10884		print 'Drive:/dev/' . $id . ":\n", Data::Dumper::Dumper\@result if $dbg[12];
10885		if (scalar @result < 5){
10886			if (grep {/failed: permission denied/i} @result){
10887				$data->[$i]{'smart-permissions'} = main::row_defaults('tool-permissions','smartctl');
10888			}
10889			elsif (grep {/unknown usb bridge/i} @result){
10890				$data->[$i]{'smart-error'} = main::row_defaults('smartctl-usb');
10891			}
10892			# can come later in output too
10893			elsif (grep {/A mandatory SMART command failed/i} @result){
10894				$data->[$i]{'smart-error'} = main::row_defaults('smartctl-command');
10895			}
10896			elsif (grep {/open device.*Operation not supported by device/i} @result){
10897				$data->[$i]{'smart-error'} = main::row_defaults('smartctl-open');
10898			}
10899			else {
10900				$data->[$i]{'smart-error'} = main::row_defaults('tool-unknown-error','smartctl');
10901			}
10902			next;
10903		}
10904		else {
10905			foreach my $row (@result){
10906				if ($row =~ /^ID#/){
10907					$splitter = '\s+';
10908					$b_attributes = 1;
10909					$a = 1;
10910					$r = 9;
10911					next;
10912				}
10913				@split = split(/$splitter/, $row);
10914				next if !$b_attributes && ! defined $split[$r];
10915				# some cases where drive not in db threshhold will be: ---
10916				# value is usually 0 padded which confuses perl. However this will
10917				# make subsequent tests easier, and will strip off leading 0s
10918				if ($b_attributes){
10919					$split[$t] = (main::is_numeric($split[$t])) ? int($split[$t]) : 0;
10920					$split[$v] = (main::is_numeric($split[$v])) ? int($split[$v]) : 0;
10921				}
10922				# can occur later in output so retest it here
10923				if ($split[$a] =~ /A mandatory SMART command failed/i){
10924					$data->[$i]{'smart-error'} = main::row_defaults('smartctl-command');
10925				}
10926				## DEVICE INFO ##
10927				if ($split[$a] eq 'Device Model'){
10928					$b_intel = 1 if $split[$r] =~/\bintel\b/i;
10929					$b_kingston = 1 if $split[$r] =~/kingston/i;
10930					# usb/firewire/thunderbolt enclosure id method
10931					if ($data->[$i]{'type'}){
10932						@working = device_vendor("$split[$r]");
10933						$data->[$i]{'drive-model'} = $working[1] if $data->[$i]{'model'} && $data->[$i]{'model'} ne $working[1];
10934						$data->[$i]{'drive-vendor'} = $working[0] if $data->[$i]{'vendor'} && $data->[$i]{'vendor'} ne $working[0];
10935					}
10936					# fallback for very corner cases where primary model id failed
10937					if (!$data->[$i]{'model'} && $split[$r]){
10938						@working = device_vendor("$split[$r]");
10939						$data->[$i]{'model'} = $working[1] if $working[1];
10940						$data->[$i]{'vendor'} = $working[0] if $working[0] && !$data->[$i]{'vendor'};
10941					}
10942				}
10943				elsif ($split[$a] eq 'Model Family'){
10944					@working = device_vendor("$split[$r]");
10945					$data->[$i]{'family'} = $working[1];
10946					# $data->[$i]{'family'} =~ s/$data->[$i]{'vendor'}\s*// if $data->[$i]{'vendor'};
10947				}
10948				elsif ($split[$a] eq 'Firmware Version'){
10949					# 01.01A01 vs 1A01
10950					if ($data->[$i]{'firmware'} && $split[$r] !~ /$data->[$i]{'firmware'}/){
10951						$data->[$i]{'drive-firmware'} = $split[$r];
10952					}
10953					elsif (!$data->[$i]{'firmware'}){
10954						$data->[$i]{'firmware'} = $split[$r];
10955					}
10956				}
10957				elsif ($split[$a] eq 'Rotation Rate'){
10958					if ($split[$r] !~ /^Solid/){
10959						$data->[$i]{'rotation'} = $split[$r];
10960						$data->[$i]{'rotation'} =~ s/\s*rpm$//i;
10961						$data->[$i]{'drive-type'} = 'HDD';
10962					}
10963					else {
10964						$data->[$i]{'drive-type'} = 'SSD';
10965					}
10966				}
10967				elsif ($split[$a] eq 'Serial Number'){
10968					if (!$data->[$i]{'serial'}){
10969						$data->[$i]{'serial'} = $split[$r];
10970					}
10971					elsif ($data->[$i]{'type'} && $split[$r] ne $data->[$i]{'serial'}){
10972						$data->[$i]{'drive-serial'} = $split[$r];
10973					}
10974				}
10975				elsif ($split[$a] eq 'SATA Version is'){
10976					if ($split[$r] =~ /SATA ([0-9.]+), ([0-9.]+ [^\s]+)(\(current: ([1-9.]+ [^\s]+)\))?/){
10977						$data->[$i]{'sata'} = $1;
10978						$data->[$i]{'speed'} = $2 if !$data->[$i]{'speed'};
10979					}
10980				}
10981				# seen both Size and Sizes. Linux will usually have both, BSDs not physical
10982				elsif ($split[$a] =~ /^Sector Sizes?$/){
10983					if ($data->[$i]{'type'} || !$data->[$i]{'block-logical'} || !$data->[$i]{'block-physical'}){
10984						if ($split[$r] =~ m|^([0-9]+) bytes logical/physical|){
10985							$data->[$i]{'block-logical'} = $1;
10986							$data->[$i]{'block-physical'} = $1;
10987						}
10988						# 512 bytes logical, 4096 bytes physical
10989						elsif ($split[$r] =~ m|^([0-9]+) bytes logical, ([0-9]+) bytes physical|){
10990							$data->[$i]{'block-logical'} = $1;
10991							$data->[$i]{'block-physical'} = $2;
10992						}
10993					}
10994				}
10995				## SMART STATUS/HEALTH ##
10996				elsif ($split[$a] eq 'SMART support is'){
10997					if ($split[$r] =~ /^(Available|Unavailable) /){
10998						$data->[$i]{'smart'} = $1;
10999						$data->[$i]{'smart'} = ($data->[$i]{'smart'} eq 'Unavailable') ? 'no' : 'yes';
11000					}
11001					elsif ($split[$r] =~ /^(Enabled|Disabled)/){
11002						$data->[$i]{'smart-support'} = lc($1);
11003					}
11004				}
11005				elsif ($split[$a] eq 'SMART overall-health self-assessment test result'){
11006					$data->[$i]{'smart-status'} = $split[$r];
11007					# seen nvme that only report smart health, not smart support
11008					$data->[$i]{'smart'} = 'yes' if !$data->[$i]{'smart'};
11009				}
11010
11011				## DEVICE CONDITION: temp/read/write/power on/cycles ##
11012				# Attributes data fields, sometimes are same syntax as info block:...
11013				elsif ($split[$a] eq 'Power_Cycle_Count' || $split[$a] eq 'Power Cycles'){
11014					$data->[$i]{'smart-cycles'} = $split[$r] if $split[$r];
11015				}
11016				elsif ($split[$a] eq 'Power_On_Hours' || $split[$a] eq 'Power On Hours' ||
11017				 $split[$a] eq 'Power_On_Hours_and_Msec'){
11018					if ($split[$r]){
11019						$split[$r] =~ s/,//;
11020						# trim off: h+0m+00.000s which is useless and at times empty anyway
11021						$split[$r] =~ s/h\+.*$// if $split[$a] eq 'Power_On_Hours_and_Msec';
11022						# $split[$r] = 43;
11023						if ($split[$r] =~ /^([0-9]+)$/){
11024							if ($1 > 9000){
11025								$data->[$i]{'smart-power-on-hours'} = int($1/(24*365)) . 'y ' . int($1/24)%365 . 'd ' . $1%24 . 'h';
11026							}
11027							elsif ($1 > 100){
11028								$data->[$i]{'smart-power-on-hours'} = int($1/24) . 'd ' . $1%24 . 'h';
11029							}
11030							else {
11031								$data->[$i]{'smart-power-on-hours'} = $split[$r] . ' hrs';
11032							}
11033						}
11034						else {
11035							$data->[$i]{'smart-power-on-hours'} = $split[$r];
11036						}
11037					}
11038				}
11039				# 'Airflow_Temperature_Cel' like: 29 (Min/Max 14/43) so can't use -1 index
11040				# Temperature like 29 Celsisu
11041				elsif ($split[$a] eq 'Temperature_Celsius' || $split[$a] eq 'Temperature' ||
11042				  $split[$a] eq 'Airflow_Temperature_Cel'){
11043					if (!$data->[$i]{'temp'} && $split[$r]){
11044						$data->[$i]{'temp'} = $split[$r];
11045					}
11046				}
11047				## DEVICE USE: Reads/Writes ##
11048				elsif ($split[$a] eq 'Data Units Read'){
11049					$data->[$i]{'smart-units-read'} = $split[$r];
11050				}
11051				elsif ($split[$a] eq 'Data Units Written'){
11052					$data->[$i]{'smart-units-written'} = $split[$r];
11053				}
11054				elsif ($split[$a] eq 'Host_Reads_32MiB'){
11055					$split[$r] = $split[$r] * 32 * 1024;
11056					$data->[$i]{'smart-read'} = main::get_size($split[$r],'string');
11057				}
11058				elsif ($split[$a] eq 'Host_Writes_32MiB'){
11059					$split[$r] = $split[$r] * 32 * 1024;
11060					$data->[$i]{'smart-written'} = main::get_size($split[$r],'string');
11061				}
11062				elsif ($split[$a] eq 'Lifetime_Reads_GiB'){
11063					$data->[$i]{'smart-read'} = $split[$r] . ' GiB';
11064				}
11065				elsif ($split[$a] eq 'Lifetime_Writes_GiB'){
11066					$data->[$i]{'smart-written'} = $split[$r] . ' GiB';
11067				}
11068				elsif ($split[$a] eq 'Total_LBAs_Read'){
11069					if (main::is_numeric($split[$r])){
11070						# blocks in bytes, so convert to KiB, the internal unit here
11071						# reports in 32MiB units, sigh
11072						if ($b_intel){
11073							$split[$r] = $split[$r] * 32 * 1024;
11074						}
11075						# reports in 1 GiB units, sigh
11076						elsif ($b_kingston){
11077							$split[$r] = $split[$r] * 1024 * 1024;
11078						}
11079						# rare fringe cases, cygwin run as user, block size will not be found
11080						# this is what it's supposed to refer to
11081						elsif ($data->[$i]{'block-logical'}) {
11082							$split[$r] = int($data->[$i]{'block-logical'} * $split[$r] / 1024);
11083						}
11084						if ($b_intel || $b_kingston || $data->[$i]{'block-logical'}){
11085							$data->[$i]{'smart-read'} = main::get_size($split[$r],'string');
11086						}
11087					}
11088				}
11089				elsif ($split[$a] eq 'Total_LBAs_Written'){
11090					if (main::is_numeric($split[$r]) && $data->[$i]{'block-logical'}){
11091						# blocks in bytes, so convert to KiB, the internal unit here
11092						# reports in 32MiB units, sigh
11093						if ($b_intel){
11094							$split[$r] = $split[$r] * 32 * 1024;
11095						}
11096						# reports in 1 GiB units, sigh
11097						elsif ($b_kingston){
11098							$split[$r] = $split[$r] * 1024 * 1024;
11099						}
11100						# rare fringe cases, cygwin run as user, block size will not be found
11101						# this is what it's supposed to refer to, in byte blocks
11102						elsif ($data->[$i]{'block-logical'}) {
11103							$split[$r] = int($data->[$i]{'block-logical'} * $split[$r] / 1024);
11104						}
11105						if ($b_intel || $b_kingston || $data->[$i]{'block-logical'}){
11106							$data->[$i]{'smart-written'} = main::get_size($split[$r],'string');
11107						}
11108					}
11109				}
11110				## DEVICE OLD AGE ##
11111				# 191 G-Sense_Error_Rate 0x0032 001 001 000 Old_age Always - 291
11112				elsif ($split[$a] eq 'G-Sense_Error_Rate'){
11113					# $data->[$i]{'smart-media-wearout'} = $split[$r];
11114					if ($b_attributes && $split[$r] > 100){
11115						$data->[$i]{'smart-gsense-error-rate-ar'} = $split[$r];
11116					}
11117				}
11118				elsif ($split[$a] eq 'Media_Wearout_Indicator'){
11119					# $data->[$i]{'smart-media-wearout'} = $split[$r];
11120					# seen case where they used hex numbers becaause values
11121					# were in 47 billion range in hex. You can't hand perl an unquoted
11122					# hex number that is > 2^32 without tripping a perl warning
11123					if ($b_attributes && $split[$r] && !main::is_hex("$split[$r]") && $split[$r] > 0){
11124						$data->[$i]{'smart-media-wearout-av'} = $split[$v];
11125						$data->[$i]{'smart-media-wearout-t'} = $split[$t];
11126						$data->[$i]{'smart-media-wearout-f'} = $split[$f] if $split[$f] ne '-';
11127					}
11128				}
11129				elsif ($split[$a] eq 'Multi_Zone_Error_Rate'){
11130					# note: all t values are 0 that I have seen
11131					if (($split[$v] - $split[$t]) < 50){
11132						$data->[$i]{'smart-multizone-errors-av'} = $split[$v];
11133						$data->[$i]{'smart-multizone-errors-t'} = $split[$v];
11134					}
11135
11136				}
11137				elsif ($split[$a] eq 'UDMA_CRC_Error_Count'){
11138					if (main::is_numeric($split[$r]) && $split[$r] > 50){
11139						$data->[$i]{'smart-udma-crc-errors-ar'} = $split[$r];
11140						$data->[$i]{'smart-udma-crc-errors-f'} = main::row_defaults('smartctl-udma-crc') if $split[$r] > 500;
11141					}
11142				}
11143
11144				## DEVICE PRE-FAIL ##
11145				elsif ($split[$a] eq 'Available_Reservd_Space'){
11146					# $data->[$i]{'smart-available-reserved-space'} = $split[$r];
11147					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11148						$data->[$i]{'smart-available-reserved-space-av'} = $split[$v];
11149						$data->[$i]{'smart-available-reserved-space-t'} = $split[$t];
11150						$data->[$i]{'smart-available-reserved-space-f'} = $split[$f] if $split[$f] ne '-';
11151					}
11152				}
11153				## nvme splits these into two field/value sets
11154				elsif ($split[$a] eq 'Available Spare'){
11155					$split[$r] =~ s/%$//;
11156					$holder{'spare'} = int($split[$r]) if main::is_numeric($split[$r]);
11157				}
11158				elsif ($split[$a] eq 'Available Spare Threshold'){
11159					$split[$r] =~ s/%$//;
11160					if ($holder{'spare'} && main::is_numeric($split[$r]) && $split[$r]/$holder{'spare'} > 0.92){
11161						$data->[$i]{'smart-available-reserved-space-ar'} = $holder{'spare'};
11162						$data->[$i]{'smart-available-reserved-space-t'} = int($split[$r]);
11163					}
11164				}
11165				elsif ($split[$a] eq 'End-to-End_Error'){
11166					if ($b_attributes && int($split[$r]) > 0 && $split[$t]){
11167						$data->[$i]{'smart-end-to-end-av'} = $split[$v];
11168						$data->[$i]{'smart-end-to-end-t'} = $split[$t];
11169						$data->[$i]{'smart-end-to-end-f'} = $split[$f] if $split[$f] ne '-';
11170					}
11171				}
11172				# seen raw value: 0/8415644
11173				elsif ($split[$a] eq 'Raw_Read_Error_Rate'){
11174					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11175						$data->[$i]{'smart-raw-read-error-rate-av'} = $split[$v];
11176						$data->[$i]{'smart-raw-read-error-rate-t'} = $split[$t];
11177						$data->[$i]{'smart-raw-read-error-rate-f'} = $split[$f] if $split[$f] ne '-';
11178					}
11179				}
11180				elsif ($split[$a] eq 'Reallocated_Sector_Ct'){
11181					if ($b_attributes && int($split[$r]) > 0 && $split[$t]){
11182						$data->[$i]{'smart-reallocated-sectors-av'} = $split[$v];
11183						$data->[$i]{'smart-reallocated-sectors-t'} = $split[$t];
11184						$data->[$i]{'smart-reallocated-sectors-f'} = $split[$f] if $split[$f] ne '-';
11185					}
11186				}
11187				elsif ($split[$a] eq 'Retired_Block_Count'){
11188					if ($b_attributes && int($split[$r]) > 0 && $split[$t]){
11189						$data->[$i]{'smart-retired-blocks-av'} = $split[$v];
11190						$data->[$i]{'smart-retired-blocks-t'} = $split[$t];
11191						$data->[$i]{'smart-retired-blocks-f'} = $split[$f] if $split[$f] ne '-';
11192					}
11193				}
11194				elsif ($split[$a] eq 'Runtime_Bad_Block'){
11195					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11196						$data->[$i]{'smart-runtime-bad-block-av'} = $split[$v];
11197						$data->[$i]{'smart-runtime-bad-block-t'} = $split[$t];
11198						$data->[$i]{'smart-runtime-bad-block-f'} = $split[$f] if $split[$f] ne '-';
11199					}
11200				}
11201				elsif ($split[$a] eq 'Seek_Error_Rate'){
11202					# value 72; threshold either 000 or 30
11203					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11204						$data->[$i]{'smart-seek-error-rate-av'} = $split[$v];
11205						$data->[$i]{'smart-seek-error-rate-t'} = $split[$t];
11206						$data->[$i]{'smart-seek-error-rate-f'} = $split[$f] if $split[$f] ne '-';
11207					}
11208				}
11209				elsif ($split[$a] eq 'Spin_Up_Time'){
11210					# raw will always be > 0 on spinning disks
11211					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11212						$data->[$i]{'smart-spinup-time-av'} = $split[$v];
11213						$data->[$i]{'smart-spinup-time-t'} = $split[$t];
11214						$data->[$i]{'smart-spinup-time-f'} = $split[$f] if $split[$f] ne '-';
11215					}
11216				}
11217				elsif ($split[$a] eq 'SSD_Life_Left'){
11218					# raw will always be > 0 on spinning disks
11219					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11220						$data->[$i]{'smart-ssd-life-left-av'} = $split[$v];
11221						$data->[$i]{'smart-ssd-life-left-t'} = $split[$t];
11222						$data->[$i]{'smart-ssd-life-left-f'} = $split[$f] if $split[$f] ne '-';
11223					}
11224				}
11225				elsif ($split[$a] eq 'Unused_Rsvd_Blk_Cnt_Tot'){
11226					# raw will always be > 0 on spinning disks
11227					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11228						$data->[$i]{'smart-unused-reserve-block-av'} = $split[$v];
11229						$data->[$i]{'smart-unused-reserve-block-t'} = $split[$t];
11230						$data->[$i]{'smart-unused-reserve-block-f'} = $split[$f] if $split[$f] ne '-';
11231					}
11232				}
11233				elsif ($split[$a] eq 'Used_Rsvd_Blk_Cnt_Tot'){
11234					# raw will always be > 0 on spinning disks
11235					if ($b_attributes && $split[$v] && $split[$t] && $split[$t]/$split[$v] > 0.92){
11236						$data->[$i]{'smart-used-reserve-block-av'} = $split[$v];
11237						$data->[$i]{'smart-used-reserve-block-t'} = $split[$t];
11238						$data->[$i]{'smart-used-reserve-block-f'} = $split[$f] if $split[$f] ne '-';
11239					}
11240				}
11241				elsif ($b_attributes){
11242					if ($split[$y] eq 'Pre-fail' && ($split[$f] ne '-' ||
11243					 ($split[$t] && $split[$v] && $split[$t]/$split[$v] > 0.92))){
11244						$num++;
11245						$data->[$i]{'smart-unknown-' . $num . '-a'} = $split[$a];
11246						$data->[$i]{'smart-unknown-' . $num . '-v'} = $split[$v];
11247						$data->[$i]{'smart-unknown-' . $num . '-w'} = $split[$v];
11248						$data->[$i]{'smart-unknown-' . $num . '-t'} = $split[$t];
11249						$data->[$i]{'smart-unknown-' . $num . '-f'} = $split[$f] if $split[$f] ne '-';
11250					}
11251				}
11252			}
11253		}
11254	}
11255	print Data::Dumper::Dumper $data if $dbg[19];
11256	eval $end if $b_log;
11257	return @$data;
11258}
11259
11260# check for usb/firewire/[and thunderbolt when data found]
11261sub peripheral_data {
11262	eval $start if $b_log;
11263	my ($id) = @_;
11264	my ($type) = ('');
11265	# print "$id here\n";
11266	if (@by_id){
11267		foreach (@by_id){
11268			if ("/dev/$id" eq Cwd::abs_path($_)){
11269				# print "$id here\n";
11270				if (/usb-/i){
11271					$type = 'USB';
11272				}
11273				elsif (/ieee1394-/i){
11274					$type = 'FireWire';
11275				}
11276				last;
11277			}
11278		}
11279	}
11280	# note: sometimes with wwn- numbering usb does not appear in by-id but it does in by-path
11281	if (!$type && @by_path){
11282		foreach (@by_path){
11283			if ("/dev/$id" eq Cwd::abs_path($_)){
11284				if (/usb-/i){
11285					$type = 'USB';
11286				}
11287				elsif (/ieee1394--/i){
11288					$type = 'FireWire';
11289				}
11290				last;
11291			}
11292		}
11293	}
11294	eval $end if $b_log;
11295	return $type;
11296}
11297sub disk_data_advanced {
11298	eval $start if $b_log;
11299	my ($set_cmd,$id) = @_;
11300	my ($cmd,$pt,$program,@data,@return);
11301	if ($set_cmd ne 'unset'){
11302		$return[0] = $set_cmd;
11303	}
11304	else {
11305		# runs as user, but is SLOW: udisksctl info -b /dev/sda
11306		# line: org.freedesktop.UDisks2.PartitionTable:
11307		# Type:               dos
11308		if ($program = main::check_program('udevadm')){
11309			$return[0] = "$program info -q property -n ";
11310		}
11311		elsif ($b_root && -e "/lib/udev/udisks-part-id"){
11312			$return[0] = "/lib/udev/udisks-part-id /dev/";
11313		}
11314		elsif ($b_root && ($program = main::check_program('fdisk'))){
11315			$return[0] = "$program -l /dev/";
11316		}
11317		if (!$return[0]){
11318			$return[0] = 'na'
11319		}
11320	}
11321	if ($return[0] ne 'na'){
11322		$cmd = "$return[0]$id 2>&1";
11323		main::log_data('cmd',$cmd) if $b_log;
11324		@data = main::grabber($cmd);
11325		# for pre ~ 2.30 fdisk did not show gpt, but did show gpt scheme error, so
11326		# if no gpt match, it's dos = mbr
11327		if ($cmd =~ /fdisk/){
11328			foreach (@data){
11329				if (/^WARNING:\s+GPT/){
11330					$return[1] = 'gpt';
11331					last;
11332				}
11333				elsif (/^Disklabel\stype:\s*(.+)/i){
11334					$return[1] = $1;
11335					last;
11336				}
11337			}
11338			$return[1] = 'dos' if !$return[1];
11339		}
11340		else {
11341			foreach (@data){
11342				if (/^(UDISKS_PARTITION_TABLE_SCHEME|ID_PART_TABLE_TYPE)/){
11343					my @working = split('=', $_);
11344					$return[1] = $working[1];
11345				}
11346				elsif (/^ID_ATA_ROTATION_RATE_RPM/){
11347					my @working = split('=', $_);
11348					$return[2] = $working[1];
11349				}
11350				last if defined $return[1] && defined $return[2];
11351			}
11352		}
11353		$return[1] = 'mbr' if $return[1] && lc($return[1]) eq 'dos';
11354	}
11355	eval $end if $b_log;
11356	return @return;
11357}
11358sub scsi_data {
11359	eval $start if $b_log;
11360	my ($file) = @_;
11361	my @temp = main::reader($file);
11362	my (@scsi);
11363	my ($firmware,$model,$vendor) = ('','','');
11364	foreach (@temp){
11365		if (/Vendor:\s*(.*)\s+Model:\s*(.*)\s+Rev:\s*(.*)/i){
11366			$vendor = $1;
11367			$model = $2;
11368			$firmware = $3;
11369		}
11370		if (/Type:/i){
11371			if (/Type:\s*Direct-Access/i){
11372				push(@scsi, {
11373				'vendor' => $vendor,
11374				'model' => $model,
11375				'firmware' => $firmware,
11376				});
11377			}
11378			else {
11379				($firmware,$model,$vendor) = ('','','');
11380			}
11381		}
11382	}
11383	main::log_data('dump','@scsi', \@scsi) if $b_log;
11384	eval $end if $b_log;
11385	return @scsi;
11386}
11387# @b_id has already been cleaned of partitions, wwn-, nvme-eui
11388sub disk_data_by_id {
11389	eval $start if $b_log;
11390	my ($device) = @_;
11391	my ($model,$serial,$vendor) = ('','','');
11392	my (@disk_data);
11393	foreach (@by_id){
11394		if ($device eq Cwd::abs_path($_)){
11395			my @data = split('_', $_);
11396			my @device_data;
11397			last if scalar @data < 2; # scsi-3600508e000000000876995df43efa500
11398			$serial = pop @data if @data;
11399			# usb-PNY_USB_3.0_FD_3715202280-0:0
11400			$serial =~ s/-[0-9]+:[0-9]+$//;
11401			$model = join(' ', @data);
11402			# get rid of the ata-|nvme-|mmc- etc
11403			$model =~ s/^\/dev\/disk\/by-id\/([^-]+-)?//;
11404			$model = main::disk_cleaner($model);
11405			@device_data = device_vendor($model,$serial);
11406			$vendor = $device_data[0] if $device_data[0];
11407			$model = $device_data[1] if $device_data[1];
11408			# print $device, '::', Cwd::abs_path($_),'::', $model, '::', $vendor, '::', $serial, "\n";
11409			(@disk_data) = ($model,$vendor,$serial);
11410			last;
11411		}
11412	}
11413	eval $end if $b_log;
11414	return @disk_data;
11415}
11416# 0 - match pattern; 1 - replace pattern; 2 - vendor print; 3 - serial pattern
11417sub set_vendors {
11418	eval $start if $b_log;
11419	@vendors = (
11420	## MOST LIKELY/COMMON MATCHES ##
11421	['(Crucial|^(FC)?CT|-CT|^M4\b|Gizmo!|^((C300-)?CTF[\s-]?)?DDAC)','Crucial','Crucial',''],
11422	# H10 HBRPEKNX0202A NVMe INTEL 512GB
11423	['(\bINTEL\b|^SSD(PAM|SA2))','\bINTEL\b','Intel',''],
11424	# note: S[AV][1-9][0-9] can trigger false positives
11425	['(KINGSTON|DataTraveler|DT\s?(DUO|Microduo|101)|^RBU|^SMS|^SHS|^SS0|^SUV|^T52|^T[AB]29|^Ultimate CF|HyperX|^S[AV][1234]00|^SKYMEDI|13fe\b)','KINGSTON','Kingston',''], # maybe SHS: SHSS37A SKC SUV
11426	# must come before samsung MU. NOTE: toshiba can have: TOSHIBA_MK6475GSX: mush: MKNSSDCR120GB_
11427	['(^MKN|Mushkin)','Mushkin','Mushkin',''], # MKNS
11428	# MU = Multiple_Flash_Reader too risky: |M[UZ][^L] HD103SI HD start risky
11429	# HM320II HM320II
11430	['(SAMSUNG|^MCG[0-9]+GC|^MCC|^MCBOE|\bEVO\b|^[GS]2 Portable|^DS20|^[DG]3 Station|^DUO\b|^P3|^[BC]GN|^[CD]JN|^BJ[NT]|^[BC]WB|^(HM|SP)[0-9]{2}|^MZMPC|^HD[0-9]{3}[A-Z]{2}$|^G[CD][1-9][QS]|^M[AB]G[0-9][FG]|SV[0-9]|[BE][A-Z][1-9]QT|YP\b)','SAMSUNG','Samsung',''], # maybe ^SM, ^HM
11431	# Android UMS Composite?
11432	['(SanDisk|^SDS[S]?[DQ]|^D[AB]4|^SL([0-9]+)G|^AFGCE|^ABLCD|^SDW[1-9]|^SEM[1-9]|^U3\b|^SU[0-9]|^DX[1-9]|^S[CD][0-9]{2}G|ULTRA\s(FIT|trek)|Clip Sport|Cruzer|^Extreme|iXpand|SSD (Plus|U100) [1-9])','SanDisk','SanDisk',''],
11433	# these are HP/Sandisk cobranded. DX110064A5xnNMRI ids as HP and Sandisc
11434	['(^DX[1-9])','^(HP\b|SANDDISK)','Sandisk/HP',''], # ssd drive, must come before seagate ST test
11435	# real, SSEAGATE Backup+; XP1600HE30002 | 024 HN (spinpoint) ; possible usb: 24AS
11436	# ST[numbers] excludes other ST starting devices
11437	['(^(ATA\s)?ST[0-9]{2}|[S]?SEAGATE|^X[AFP]|^5AS|^BUP|Expansion Desk|^Expansion|FreeAgent|GoFlex|Backup(\+|\s?Plus)\s?(Hub)?|OneTouch|Slim\s? BK)','[S]?SEAGATE','Seagate',''],
11438	['^(WD|WL[0]9]|Western Digital|My (Book|Passport)|\d*LPCX|Elements|easystore|MD0|M000|EARX|EFRX|\d*EAVS|0JD|JP[CV]|[0-9]+(BEV|(00)?AAK|AAV|AZL|EA[CD]S)|3200[AB]|2500[BJ]|EA[A-Z]S|20G2|5000[AB]|6400[AB]|7500[AB]|i HTS|00[ABL][A-Z]{2}|EZRX)','(^WDC|Western\s?Digital)','Western Digital',''],
11439	# rare cases WDC is in middle of string
11440	['(\bWDC\b|1002FAEX)','','Western Digital',''],
11441	## THEN BETTER KNOWN ONESs ##
11442	# A-Data can be in middle of string
11443	['^(.*\bA-?DATA|ASP[0-9]|AX[MN]|CH11|HV[1-9]|IM2|HD[1-9]|HDD\s?CH|IUM)','A-?DATA','A-Data',''],
11444	['^(ASUS|ROG)','^ASUS','ASUS',''], # ROG ESD-S1C
11445	# ATCS05 can be hitachi travelstar but not sure
11446	['^ATP','^ATP\b','ATP',''],
11447	# Force MP500
11448	['^(Corsair|Force\s|(Flash\s*)?(Survivor|Voyager))','^surge Corsair','Corsair',''],
11449	['^(FUJITSU|MJA|MH[TVWYZ][0-9]|MP|MAP[0-9])','^FUJITSU','Fujitsu',''],
11450	# MAB3045SP shows as HP or Fujitsu, probably HP branded fujitsu
11451	['^(MAB[0-9])','^(HP\b|FUJITSU)','Fujitsu/HP',''],
11452	# note: 2012:  wdc bought hgst
11453	['^(HGST|Touro|54[15]0|7250)','^HGST','HGST (Hitachi)',''], # HGST HUA
11454	['^((ATA\s)?Hitachi|HCS|HD[PST]|DK[0-9]|IC|HT|HU|HMS|HDE|0G[0-9])','Hitachi','Hitachi',''],
11455	# vb: VB0250EAVER but clashes with vbox; HP_SSD_S700_120G ;GB0500EAFYL GB starter too generic?
11456	['^(HP\b|[MV]B[0-6]|G[BJ][0-9]|DF[0-9]|F[BK]|0-9]|PSS|XR[0-9]{4}|c350|v[0-9]{3}[bgorw]$|x[0-9]{3}[w]$|VK0)','^HP','HP',''],
11457	['^(Lexar|LSD|JumpDrive|JD\s?Firefly|LX[0-9]|WorkFlow)','^Lexar','Lexar',''], # mmc-LEXAR_0xb016546c; JD Firefly;
11458	# OCZSSD2-2VTXE120G is OCZ-VERTEX2_3.5
11459	['^(OCZ|APOC|D2|DEN|DEN|DRSAK|EC188|FTNC|GFGC|MANG|MMOC|NIMC|NIMR|PSIR|RALLY2|TALOS2|TMSC|TRSAK)','^OCZ[\s-]','OCZ',''],
11460	['^OWC','^OWC\b','OWC',''],
11461	['^(Philips|GoGear)','^Philips','Philips',''],
11462	['^PIONEER','^PIONEER','Pioneer',''],
11463	['^(PNY|Hook\s?Attache|SSD2SC|(SSD7?)?EP7)','^PNY\s','PNY','','^PNY'],
11464	# note: get rid of: M[DGK] becasue mushkin starts with MK
11465	# note: seen: KXG50ZNV512G NVMe TOSHIBA 512GB | THNSN51T02DUK NVMe TOSHIBA 1024GB
11466	['(^[S]?TOS|^THN|TOSHIBA|TransMemory|^M[GKQ][0-9]|KBG4|^HDW|^SA[0-9]{2}G$|^(008|016|032|064|128)G[379E][0-9A]$)','[S]?TOSHIBA','Toshiba',''], # scsi-STOSHIBA_STOR.E_EDITION_
11467	## LAST: THEY ARE SHORT AND COULD LEAD TO FALSE ID, OR ARE UNLIKELY ##
11468	# unknown: AL25744_12345678; ADP may be usb 2.5" adapter; udisk unknown: Z1E6FTKJ 00AAKS
11469	# SSD2SC240G726A10 MRS020A128GTS25C EHSAJM0016GB
11470	['^2[\s-]?Power','^2[\s-]?Power','2-Power',''],
11471	['^(3ware|9650SE)','^3ware','3ware (controller)',''],
11472	['^5ACE','^5ACE','5ACE',''], # could be seagate: ST316021 5ACE
11473	['^(Aarvex|AX[0-9]{2})','^AARVEX','AARVEX',''],
11474	['^(AbonMax|ASU[0-9])','^AbonMax','AbonMax',''],
11475	['^Acasis','^Acasis','Acasis (hub)',''],
11476	['^Acclamator','^Acclamator','Acclamator',''],
11477	['^(Actions|HS USB Flash)','^Actions','Actions',''],
11478	['^Addlink','^Addlink','Addlink',''],
11479	['^(ADplus|SuperVer\b)','^ADplus','ADplus',''],
11480	['^ADTRON','^ADTRON','Adtron',''],
11481	['^(Advantech|SQF)','^Advantech','Advantech',''],
11482	['^AEGO','^AEGO','AEGO',''],
11483	['^AFOX','^AFOX','AFOX',''],
11484	['^(Agile|AGI)','^(AGI|Agile\s?Gear\s?Int[a-z]*)','AGI',''],
11485	['^Aireye','^Aireye','Aireye',''],
11486	['^Alcatel','^Alcatel','Alcatel',''],
11487	['^Alfawise','^Alfawise','Alfawise',''],
11488	['^Android','^Android','Android',''],
11489	['^ANACOMDA','^ANACOMDA','ANACOMDA',''],
11490	['^Apotop','^Apotop','Apotop',''],
11491	# must come before AP|Apacer
11492	['^(APPLE|iPod)','^APPLE','Apple',''],
11493	['^(AP|Apacer)','^Apacer','Apacer',''],
11494	['^(A-?RAM|ARSSD)','^A-?RAM','A-RAM',''],
11495	['^Arch','^Arch(\s*Memory)?','Arch Memory',''],
11496	['^(Asenno|AS[1-9])','^Asenno','Asenno',''],
11497	['^Asgard','^Asgard','Asgard',''],
11498	['^(ASM|2115)','^ASM','ASMedia',''],#asm1153e
11499	['^(AVEXIR|AVSSD)','^AVEXIR','Avexir',''],
11500	['^Axiom','^Axiom','Axiom',''],
11501	['^(Baititon|BT[0-9])','^Baititon','Baititon',''],
11502	['^Bamba','^Bamba','Bamba',''],
11503	['^Bell\b','^Bell','Packard Bell',''],
11504	['^(BelovedkaiAE|GhostPen)','^BelovedkaiAE','BelovedkaiAE',''],
11505	['^BHT','^BHT','BHT',''],
11506	['^(Big\s?Reservoir|B[RG][_\s-])','^Big\s?Reservoir','Big Reservoir',''],
11507	['^BIOSTAR','^BIOSTAR','Biostar',''],
11508	['^BIWIN','^BIWIN','BIWIN',''],
11509	['^Blackpcs','^Blackpcs','Blackpcs',''],
11510	['^Bory','^Bory','Bory',''],
11511	['^Braveeagle','^Braveeagle','BraveEagle',''],
11512	['^(BUFFALO|BSC)','^BUFFALO','Buffalo',''], # usb: BSCR05TU2
11513	['^Bulldozer','^Bulldozer','Bulldozer',''],
11514	['^BUSlink','^BUSlink','BUSlink',''],
11515	['^(STMicro|SMI|CBA)','^(STMicroelectronics|SMI)','SMI (STMicroelectronics)',''],
11516	['^(Canon|MP49)','^Canon','Canon',''],
11517	['^Centerm','^Centerm','Centerm',''],
11518	['^(Centon|DS pro)','^Centon','Centon',''],
11519	['^(CFD|CSSD)','^CFD','CFD',''],
11520	['^(Chipsbank|CHIPSBNK)','^Chipsbank','Chipsbank',''],
11521	['^CHN\b','','Zheino',''],
11522	['^Clover','^Clover','Clover',''],
11523	['^CODi','^CODi','CODi',''],
11524	['^Colorful\b','^Colorful','Colorful',''],
11525	# note: www.cornbuy.com is both a brand and also sells other brands, like newegg
11526	# addlink; colorful; goldenfir; kodkak; maxson; netac; teclast; vaseky
11527	['^Corn','^Corn','Corn',''],
11528	['^CnMemory|Spaceloop','^CnMemory','CnMemory',''],
11529	['^CSD','^CSD','CSD',''],
11530	['^(Dane-?Elec|Z Mate)','^Dane-?Elec','DaneElec',''],
11531	['^DATABAR','^DATABAR','DataBar',''],
11532	# Daplink vfs is an ARM software thing
11533	['^Dataram','^Dataram','Dataram',''],
11534	# DataStation can be Trekstore or I/O gear
11535	['^Dell\b','^Dell','Dell',''],
11536	['^DeLOCK','^Delock(\s?products)?','Delock',''],
11537	['^Derler','^Derler','Derler',''],
11538	['^detech','^detech','DETech',''],
11539	['^DGM','^DGM\b','DGM',''],
11540	['^Digifast','^Digifast','Digifast',''],
11541	['^DIGITAL\s?FILM','DIGITAL\s?FILM','Digital Film',''],
11542	['^Dikom','^Dikom','Dikom',''],
11543	['^Disain','^Disain','Disain',''],
11544	['^(Disney|PIX[\s]?JR)','^Disney','Disney',''],
11545	['^(Doggo|DQ-|Sendisk|Shenchu)','^(doggo|Sendisk(.?Shenchu)?|Shenchu(.?Sendisk)?)','Doggo (SENDISK/Shenchu)',''],
11546	['^(Dogfish|Shark)','^Dogfish(\s*Technology)?','Dogfish Technology',''],
11547	['^DragonDiamond','^DragonDiamond','DragonDiamond',''],
11548	['^DREVO\b','^DREVO','Drevo',''],
11549	['^DREVO\b','^DREVO','Drevo',''],
11550	['^(Dynabook|AE[1-3]00)','^Dynabook','Dynabook',''],
11551	# DX1100 is probably sandisk, but could be HP, or it could be hp branded sandisk
11552	['^(Eaget|V8$)','^Eaget','Eaget',''],
11553	['^EDGE','^EDGE','EDGE Tech',''],
11554	['^Elecom','^Elecom','Elecom',''],
11555	['^Eluktro','^Eluktronics','Eluktronics',''],
11556	['^Emperor','^Emperor','Emperor',''],
11557	['^Emtec','^Emtec','Emtec',''],
11558	['^ENE\b','^ENE','ENE',''],
11559	['^Energy','^Energy','Energy',''],
11560	['^eNova','^eNOVA','eNOVA',''],
11561	['^Epson','^Epson','Epson',''],
11562	['^(Etelcom|SSD051)','^Etelcom','Etelcom',''],
11563	['^EURS','^EURS','EURS',''],
11564	# NOTE: ESA3... may be IBM PCIe SAD card/drives
11565	['^(EXCELSTOR|r technology)','^EXCELSTOR( TECHNO(LOGY)?)?','ExcelStor',''],
11566	['^EZLINK','^EZLINK','EZLINK',''],
11567	['^Fantom','^Fantom( Drive[s]?)?','Fantom Drives',''],
11568	['^Faspeed','^Faspeed','Faspeed',''],
11569	['^FASTDISK','^FASTDISK','FASTDISK',''],
11570	['^Festtive','^Festtive','Festtive',''],
11571	['^FiiO','^FiiO','FiiO',''],
11572	['^Fordisk','^Fordisk','Fordisk',''],
11573	# FK0032CAAZP/FB160C4081 FK or FV can be HP but can be other things
11574	['^FORESEE','^FORESEE','ForeseSU04Ge',''],
11575	['^Founder','^Founder','Founder',''],
11576	['^(FOXLINE|FLD)','^FOXLINE','Foxline',''], # russian vendor?
11577	['^(GALAX\b|Gamer\s?L)','^GALAX','GALAX',''],
11578	['^Galaxy\b','^Galaxy','Galaxy',''],
11579	['^(Garmin|Fenix|Nuvi|Zumo)','^Garmin','Garmin',''],
11580	['^Geil','^Geil','Geil',''],
11581	['^GelL','^GelL','GelL',''], # typo for Geil? GelL ZENITH R3 120GB
11582	['^(Generic|UY[67])','^Generic','Generic',''],
11583	['^Geonix','^Geonix','Geonix',''],
11584	['^Getrich','^Getrich','Getrich',''],
11585	['^Gigabyte','^Gigabyte','Gigabyte',''], # SSD
11586	['^Gigastone','^Gigastone','Gigastone',''],
11587	['^Gigaware','^Gigaware','Gigaware',''],
11588	['^Gloway','^Gloway','Gloway',''],
11589	['^Goldendisk','^Goldendisk','Goldendisk',''],
11590	['^Goldenfir','^Goldenfir','Goldenfir',''],
11591	# Wilk Elektronik SA, poland
11592	['^(Wilk\s*)?(GOODRAM|GOODDRIVE|IR[\s-]?SSD|IRP|SSDPR)','^GOODRAM','GOODRAM',''],
11593	# supertalent also has FM: |FM
11594	['^(G[\.]?SKILL)','^G[\.]?SKILL','G.SKILL',''],
11595	['^G[\s-]*Tech','^G[\s-]*Technology','G-Technology',''],
11596	['^(Hajaan|HS[1-9])','^Haajan','Haajan',''],
11597	['^Haizhide','^Haizhide','Haizhide',''],
11598	['^(Hama|FlashPen\s?Fancy)','^Hama','Hama',''],
11599	['^HDC','^HDC\b','HDC',''],
11600	['^Hectron','^Hectron','Hectron',''],
11601	['^HEMA','^HEMA','HEMA',''],
11602	['^(Hikvision|HKVSN|HS-SSD)','^Hikvision','Hikvision',''],
11603	['^Hoodisk','^Hoodisk','Hoodisk',''],
11604	['^HUAWEI','^HUAWEI','Huawei',''],
11605	['^Hypertec','^Hypertec','Hypertec',''],
11606	['^HyperX','^HyperX','HyperX',''],
11607	['^Hyundai','^Hyundai','Hyundai',''],
11608	['^(IBM|DT|ESA[1-9])','^IBM','IBM',''],
11609	['^IEI Tech','^IEI Tech(\.|nology)?( Corp(\.|oration)?)?','IEI Technology',''],
11610	['^(IGEL|UD Pocket)','^IGEL','IGEL',''],
11611	['^(Imation|Nano\s?Pro|HQT)','^Imation(\sImation)?','Imation',''], # Imation_ImationFlashDrive; TF20 is imation/tdk
11612	['^(Inateck|FE20)','^Inateck','Inateck',''],
11613	['^(Inca\b|Npenterprise)','^Inca','Inca',''],
11614	['^(Indilinx|IND-)','^Indilinx','Indilinx',''],
11615	['^INDMEM','^INDMEM','INDMEM',''],
11616	['^(Infokit)','^Infokit','Infokit',''],
11617	['^(Initio)','^Initio','Initio',''],
11618	['^Inland','^Inland','Inland',''],
11619	['^(InnoDisk|Innolite|SATA\s?Slim)','^InnoDisk( Corp.)?','InnoDisk',''],
11620	['Innostor','Innostor','Innostor',''],
11621	['(^Innovation|Innovation\s?IT)','Innovation(\s*IT)?','Innovation IT',''],
11622	['^Innovera','^Innovera','Innovera',''],
11623	['^Intaiel','^Intaiel','Intaiel',''],
11624	['^(INM|Integral|V\s?Series)','^Integral(\s?Memory)?','Integral Memory',''],
11625	['^(lntenso|Intenso|(Alu|Basic|Business|Micro|c?Mobile|Premium|Rainbow|Slim|Speed|Twister|Ultra) Line|Rainbow)','^Intenso','Intenso',''],
11626	['^(I-?O Data|HDCL)','^I-?O Data','I-O Data',''],
11627	['^(Integrated[\s-]?Technology|IT[0-9]+)','^Integrated[\s-]?Technology','Integrated Technology',''],
11628	['^(Iomega|ZIP\b|Clik!)','^Iomega','Iomega',''],
11629	['^ISOCOM','^ISOCOM','ISOCOM (Shenzhen Longsys Electronics)',''],
11630	['^JingX','^JingX','JingX',''], #JingX 120G SSD - not confirmed, but guessing
11631	['^Jingyi','^Jingyi','Jingyi',''],
11632	# NOTE: ITY2 120GB hard to find
11633	['^JMicron','^JMicron(\s?Tech(nology)?)?','JMicron Tech',''], #JMicron H/W raid
11634	['^KimMIDI','^KimMIDI','KimMIDI',''],
11635	['^Kimtigo','^Kimtigo','Kimtigo',''],
11636	['^Kingbank','^Kingbank','Kingbank',''],
11637	['^Kingchux[\s-]?ing','^Kingchux[\s-]?ing','Kingchuxing',''],
11638	['(KingDian|^NGF)','KingDian','KingDian',''],
11639	['^Kingfast','^Kingfast','Kingfast',''],
11640	['^KingMAX','^KingMAX','KingMAX',''],
11641	['^Kingrich','^Kingrich','KingrSU04Gich',''],
11642	['KING\s?SHA\s?RE','KING\s?SHA\s?RE','KingShare',''],
11643	['^(KingSpec|ACSC|KS[DQ]|N[ET]-[0-9]|P4\b|PA18|T-(3260|64|128))','^KingSpec','KingSpec',''],
11644	['^KingSSD','^KingSSD','KingSSD',''],
11645	# kingwin docking, not actual drive
11646	['^(EZD|EZ-Dock)','','Kingwin Docking Station',''],
11647	['^Kingwin','^Kingwin','Kingwin',''],
11648	['(KIOXIA|^K[BX]G[0-9])','KIOXIA','KIOXIA',''], # company name comes after product ID
11649	['^KLEVV','^KLEVV','KLEVV',''],
11650	['^Kodak','^Kodak','Kodak',''],
11651	['^(KUAIKAI|MSAM)','^KUAIKAI','KuaKai',''],
11652	['(KUIJIA|DAHUA)','^KUIJIA','KUIJIA',''],
11653	['^KUNUP','^KUNUP','KUNUP',''],
11654	['^(Lacie|P92|itsaKey|iamaKey)','^Lacie','LaCie',''],
11655	['^LANBO','^LANBO','LANBO',''],
11656	['^LANTIC','^LANTIC','Lantic',''],
11657	['^(Lazos|L-?ISS)','^Lazos','Lazos',''],
11658	['^LDLC','^LDLC','LDLC',''],
11659	# LENSE30512GMSP34MEAT3TA / UMIS RPITJ256PED2MWX
11660	['^(LEN|UMIS)','^Lenovo','Lenovo',''],
11661	['^RPFT','','Lenovo O.E.M.',''],
11662	# JAJS300M120C JAJM600M256C JAJS600M1024C JAJS600M256C
11663	['^(Leven|JAJ[MS][1-9])','^Leven','Leven',''],
11664	['^LG\b','^LG','LG',''],
11665	['(LITE[-\s]?ON[\s-]?IT)','LITE[-]?ON[\s-]?IT','LITE-ON IT',''], # LITEONIT_LSS-24L6G
11666	['(LITE[-\s]?ON|^PH[1-9])','LITE[-]?ON','LITE-ON',''], # PH6-CE240-L; CL1-3D256-Q11 NVMe LITEON 256GB
11667	['^LONDISK','^LONDISK','LONDISK',''],
11668	['^(LSI|MegaRAID)','^LSI\b','LSI',''],
11669	['^(M-Systems|DiskOnKey)','^M-Systems','M-Systems',''],
11670	['^(Mach\s*Xtreme|MXSSD|MXU|MX[\s-])','^Mach\s*Xtreme','Mach Xtreme',''],
11671	['^Maximus','^Maximus','Maximus',''],
11672	['^Maxone','^Maxone','Maxone',''],
11673	['^(MAXTOR|Atlas|L(250|500)|TM[0-9]{4}|[KL]0[1-9]|Y[0-9]{3}[A-Z]|STM[0-9]|F[0-9]{3}L)','^MAXTOR','Maxtor',''], # note M2 M3 is usually maxtor, but can be samsung
11674	['^(Memorex|TravelDrive|TD\s?Classic)','^Memorex','Memorex',''],
11675	# note: C300/400 can be either micron or crucial, but C400 is M4 from crucial
11676	['(^MT|^M5|^Micron|00-MT|C[34]00)','^Micron','Micron',''],# C400-MTFDDAK128MAM
11677	['^(MARSHAL\b|MAL[0-9])','^MARSHAL','Marshal',''],
11678	['^MARVELL','^MARVELL','Marvell',''],
11679	['^Maxsun','^Maxsun','Maxsun',''],
11680	['^MDT\b','^MDT','MDT (rebuilt WD/Seagate)',''], # mdt rebuilds wd/seagate hdd
11681	# MD1TBLSSHD, careful with this MD starter!!
11682	['^MD[1-9]','^Max\s*Digital','MaxDigital',''],
11683	['^Medion','^Medion','Medion',''],
11684	['^(MEDIAMAX|WL[0-9]{2})','^MEDIAMAX','MediaMax',''],
11685	['^Mengmi','^Mengmi','Mengmi',''],
11686	['^MGTEC','^MGTEC','MGTEC',''],
11687	['^(Microsoft|S31)','^Microsoft','Microsoft',''],
11688	['^MidasForce','^MidasForce','MidasForce',''],
11689	['^(Mimoco|Mimobot)','^Mimoco','Mimoco',''],
11690	['^MINIX','^MINIX','MINIX',''],
11691	['^Miracle','^Miracle','Miracle',''],
11692	['^Moba','^Moba','Moba',''],
11693	# Monster MONSTER DIGITAL
11694	['^(Monster\s)+(Digital)?|OD[\s-]?ADVANCE','^(Monster\s)+(Digital)?','Monster Digital',''],
11695	['^Morebeck','^Morebeck','Morebeck',''],
11696	['^(Moser\s?Bear|MBIL)','^Moser\s?Bear','Moser Bear',''],
11697	['^(Motile|SSM[0-9])','^Motile','Motile',''],
11698	['^(Motorola|XT[0-9]{4})','^Motorola','Motorola',''],
11699	['^Moweek','^Moweek','Moweek',''],
11700	#MRMAD4B128GC9M2C
11701	['^(MRMA|Memoright)','^Memoright','Memoright',''],
11702	['^MSI\b','^MSI\b','MSI',''],
11703	['^MTASE','^MTASE','MTASE',''],
11704	['^MTRON','^MTRON','MTRON',''],
11705	['^(MyDigitalSSD|BP4)','^MyDigitalSSD','MyDigitalSSD',''], # BP4 = BulletProof4
11706	['^(Neo\s*Forza|NFS[0-9])','^Neo\s*Forza','Neo Forza',''],
11707	['^Netac','^Netac','Netac',''],
11708	# NGFF is a type, like msata, sata
11709	['^Nik','^Nikimi','Nikimi',''],
11710	['^NOREL','^NOREL(SYS)?','NorelSys',''],
11711	['^ODYS','^ODYS','ODYS',''],
11712	['^Olympus','^Olympus','Olympus',''],
11713	['^Orico','^Orico','Orico',''],
11714	['^OSC','^OSC\b','OSC',''],
11715	['^(OWC|Aura)','^OWC\b','OWC',''],
11716	['^oyunkey','^oyunkey','Oyunkey',''],
11717	['^PALIT','PALIT','Palit',''], # ssd
11718	['^Panram','^Panram','Panram',''], # ssd
11719	['^(Parker|TP00)','^Parker','Parker',''],
11720	['^(Pasoul|OASD)','^Pasoul','Pasoul',''],
11721	['^(Patriot|PS[8F]|VPN|Viper)','^Patriot([-\s]?Memory)?','Patriot',''],#Viper M.2 VPN100
11722	['^PERC\b','','Dell PowerEdge RAID Card',''], # ssd
11723	['(PHISON[\s-]?|ESR[0-9])','PHISON[\s-]?','Phison',''],# E12-256G-PHISON-SSD-B3-BB1
11724	['^Pioneer','Pioneer','Pioneer',''],
11725	['^(PLEXTOR|PX-)','^PLEXTOR','Plextor',''],
11726	['^(PQI|Intelligent\s?Stick|Cool\s?Drive)','^PQI','PQI',''],
11727	['^(Premiertek|QSSD|Quaroni)','^Premiertek','Premiertek',''],
11728	['^(Pretec|UltimateGuard)','Pretec','Pretec',''],
11729	# PS3109S9 is the result of an error condition with ssd drive
11730	['QEMU','^[0-9]*QEMU( QEMU)?','QEMU',''], # 0QUEMU QEMU HARDDISK
11731	['(^Quantum|Fireball)','^Quantum','Quantum',''],
11732	['^QUMO','^QUMO','Qumo',''],
11733	['^(R[3-9]|AMD\s?(RADEON)?|Radeon)','AMD\s?(RADEON)?','AMD Radeon',''], # ssd
11734	['^(Ramaxel|RT|RM|RPF|RDM)','^Ramaxel','Ramaxel',''],
11735	['^(Ramsta|R[1-9])','^Ramsta','Ramsta',''],
11736	['^(Realtek|RTL)','^Realtek','Realtek',''],
11737	['^RENICE','^RENICE','Renice',''],
11738	['^RevuAhn','^RevuAhn','RevuAhn',''],
11739	['^(Ricoh|R5)','^Ricoh','Ricoh',''],
11740	['^RIM[\s]','^RIM','RIM',''],
11741	 #RTDMA008RAV2BWL comes with lenovo but don't know brand
11742	['^Runcore','^Runcore','Runcore',''],
11743	['^Sabrent','^Sabrent','Sabrent',''],
11744	['^Sage','^Sage(\s?Micro)?','Sage Micro',''],
11745	['^SAMSWEET','^SAMSWEET','Samsweet',''],
11746	['^SandForce','^SandForce','SandForce',''],
11747	['^Sannobel','^Sannobel','Sannobel',''],
11748	# SATADOM can be innodisk or supermirco: dom == disk on module
11749	# SATAFIRM is an ssd failure message
11750	['^(Sea\s?Tech|Transformer)','^Sea\s?Tech','Sea Tech',''],
11751	['^SigmaTel','^SigmaTel','SigmaTel',''],
11752	# DIAMOND_040_GB
11753	['^(SILICON\s?MOTION|SM[0-9])','^SILICON\s?MOTION','Silicon Motion',''],
11754	['(Silicon[\s-]?Power|^SP[CP]C|^Silicon|^Diamond|^HasTopSunlightpeed)','Silicon[\s-]?Power','Silicon Power',''],
11755	['^SINTECHI?','^SINTECHI?','SinTech (adapter)',''],
11756	['^SiS\b','^SiS','SiS',''],
11757	['Smartbuy','\s?Smartbuy','Smartbuy',''], # SSD Smartbuy 60GB; mSata Smartbuy 3
11758	# HFS128G39TND-N210A; seen nvme with name in middle
11759	['(SK\s?HYNIX|^HF[MS]|^H[BC]G)','\s?SK\s?HYNIX','SK Hynix',''],
11760	['(hynix|^HAG[0-9]|h[BC]8aP)','hynix','Hynix',''],# nvme middle of string, must be after sk hynix
11761	['^SH','','Smart Modular Tech.',''],
11762	['^Skill','^Skill','Skill',''],
11763	['^(SMART( Storage Systems)?|TX)','^(SMART( Storage Systems)?)','Smart Storage Systems',''],
11764	['^Sobetter','^Sobetter','Sobetter',''],
11765	['^(S[FR]-|Sony)','^Sony','Sony',''],
11766	['^(SSSTC|CL1-)','^SSSTC','SSSTC',''],
11767	['^STE[CK]','^STE[CK]','sTec',''], # wd bought this one
11768	['^STmagic','^STmagic','STmagic',''],
11769	['^STORFLY','^STORFLY','StorFly',''],
11770	['\dSUN\d','^SUN(\sMicrosystems)?','Sun Microsystems',''],
11771	['^SUNEAST','^SUNEAST','SunEast',''],
11772	['^SuperSSpeed','^SuperSSpeed','SuperSSpeed',''],
11773	# NOTE: F[MNETU] not reliable, g.skill starts with FM too:
11774	# Seagate ST skips STT.
11775	['^(Super\s*Talent|STT|F[HTZ]M[0-9]|PicoDrive|Teranova)','','Super Talent',''],
11776	['^(SF|Swissbit)','^Swissbit','Swissbit',''],
11777	# ['^(SUPERSPEED)','^SUPERSPEED','SuperSpeed',''], # superspeed is a generic term
11778	['^Taisu','^Taisu','Taisu',''],
11779	['^(TakeMS|ColorLine)','^TakeMS','TakeMS',''],
11780	['^Tammuz','^Tammuz','Tammuz',''],
11781	['^TANDBERG','^TANDBERG','Tanberg',''],
11782	['^TC[\s-]*SUNBOW','^TC[\s-]*SUNBOW','TCSunBow',''],
11783	['^(TDK|TF[1-9][0-9])','^TDK','TDK',''],
11784	['^TEAC','^TEAC','TEAC',''],
11785	['^(TEAM|T[\s-]?Create)','^TEAM(\s*Group)?','TeamGroup',''],
11786	['^(Teclast|CoolFlash)','^Teclast','Teclast',''],
11787	['^Teelkoou','^Teelkoou','Teelkoou',''],
11788	['^Tele2','^Tele2','Tele2',''],
11789	['^Teleplan','^Teleplan','Teleplan',''],
11790	['^TEUTONS','^TEUTONS','TEUTONS',''],
11791	['^THU','^THU','THU',''],
11792	['^Tigo','^Tigo','Tigo',''],
11793	['^Timetec','^Timetec','Timetec',''],
11794	['^TKD','^TKD','TKD',''],
11795	['^TopSunligt','^TopSunligt','TopSunligt',''], # is this a typo? hard to know
11796	['^TopSunlight','^TopSunlight','TopSunlight',''],
11797	['^TOROSUS','^TOROSUS','Torosus',''],
11798	['^([F]?TS|Transcend|JetDrive|JetFlash|USDU|EZEX)','^Transcend','Transcend',''],
11799	['^(TrekStor|DS (maxi|pocket)|DataStation)','^TrekStor','TrekStor',''],
11800	['^(TwinMOS|TW[0-9])','^TwinMOS','TwinMOS',''],
11801	# note: udisk means usb disk, it's not a vendor ID
11802	['^UDinfo','^UDinfo','UDinfo',''],
11803	['^USBTech','^USBTech','USBTech',''],
11804	['^(UNIC2)','^UNIC2','UNIC2',''],
11805	['^(UG|Unigen)','^Unigen','Unigen',''],
11806	['^(USBest|UT16)','^USBest','USBest',''],
11807	['^(OOS[1-9]|Utania)','Utania','Utania',''],
11808	['^U-TECH','U-TECH','U-Tech',''],
11809	['^VBOX','','VirtualBox',''],
11810	['^(Verbatim|STORE\s?\'?N\'?\s?(FLIP|GO)|Vi[1-9]|OTG\s?Tiny)','^Verbatim','Verbatim',''],
11811	['^V-GEN','^V-GEN','V-Gen',''],
11812	['^(Victorinox|Swissflash)','^Victorinox','Victorinox',''],
11813	['^(Visipro|SDVP)','^Visipro','Visipro',''],
11814	['^VISIONTEK','^VISIONTEK','VisionTek',''],
11815	['^VMware','^VMware','VMware',''],
11816	['^(Vseky|Vaseky)','^Vaseky','Vaseky',''], # ata-Vseky_V880_350G_
11817	['^(Walgreen|Infinitive)','^Walgreen','Walgreen',''],
11818	['^Walton','^Walton','Walton',''],
11819	['^(Wearable|Air-?Stash)','^Wearable','Wearable',''],
11820	['^Wellcomm','^Wellcomm','Wellcomm',''],
11821	['^Wilk','^Wilk','Wilk',''],
11822	['^WPC','^WPC','WPC',''], # WPC-240GB
11823	['^(Wortmann(\sAG)?|Terra\s?US)','^Wortmann(\sAG)?','Wortmann AG',''],
11824	['^(XinTop|XT-)','^XinTop','XinTop',''],
11825	['^Xintor','^Xintor','Xintor',''],
11826	['^XPG','^XPG','XPG',''],
11827	['^XrayDisk','^XrayDisk','XrayDisk',''],
11828	['^Xstar','^Xstar','Xstar',''],
11829	['^(XUM|HX[0-9])','^XUM','XUM',''],
11830	['^XUNZHE','^XUNZHE','XUNZHE',''],
11831	['^(Yangtze|ZhiTai|PC00[5-9]|SC00[1-9])','^Yangtze(\s*Memory)?','Yangtze Memory',''],
11832	['^(Yeyian|valk)','^Yeyian','Yeyian',''],
11833	['^(YingChu|YGC)','^YingChu','YingChu',''],
11834	['^(YUCUN|R880)','^YUCUN','YUCUN',''],
11835	['^(ZALMAN|ZM\b)','^ZALMAN','Zalman',''],
11836	['^ZEUSLAP','^ZEUSLAP','ZEUSLAP',''],
11837	['^(Zheino|CHN[0-9]|CNM)','^Zheino','Zheino',''],
11838	['^(Zotac|ZTSSD)','^Zotac','Zotac',''],
11839	['^ZSPEED','^ZSPEED','ZSpeed',''],
11840	['^ZTC','^ZTC','ZTC',''],
11841	['^ZTE','^ZTE','ZTE',''],
11842	['^(ASMT|2115)','^ASMT','ASMT (case)',''],
11843	);
11844	eval $end if $b_log;
11845}
11846
11847# receives space separated string that may or may not contain vendor data
11848sub device_vendor {
11849	eval $start if $b_log;
11850	my ($model,$serial) = @_;
11851	my ($vendor) = ('');
11852	my (@data);
11853	return if !$model;
11854	set_vendors() if !@vendors;
11855	# 0 - match pattern; 1 - replace pattern; 2 - vendor print; 3 - serial pattern
11856	# Data URLs: inxi-resources.txt Section: DriveItem device_vendor()
11857	# $model = 'H10 HBRPEKNX0202A NVMe INTEL 512GB';
11858	# $model = 'Patriot Memory';
11859	foreach my $row (@vendors){
11860		if ($model =~ /$row->[0]/i || ($row->[3] && $serial && $serial =~ /$row->[3]/)){
11861			$vendor = $row->[2];
11862			# Usually we want to assign N/A at output phase, maybe do this logic there?
11863			if ($row->[1]){
11864				if ($model !~ m/$row->[1]$/i){
11865					$model =~ s/$row->[1]//i;
11866				}
11867				else {
11868					$model = 'N/A';
11869				}
11870			}
11871			$model =~ s/^[\/\[\s_-]+|[\/\s_-]+$//g;
11872			$model =~ s/\s\s/ /g;
11873			@data = ($vendor,$model);
11874			last;
11875		}
11876	}
11877	eval $end if $b_log;
11878	return @data;
11879}
11880
11881# Normally hddtemp requires root, but you can set user rights in /etc/sudoers.
11882# args: $1 - /dev/<disk> to be tested for
11883sub hdd_temp {
11884	eval $start if $b_log;
11885	my ($device) = @_;
11886	my ($path) = ('');
11887	my (@data,$hdd_temp);
11888	$hdd_temp = hdd_temp_sys($device) if !$force{'hddtemp'} && -e "/sys/block/$device";
11889	if (!$hdd_temp){
11890		$device = "/dev/$device";
11891		if ($device =~ /nvme/i){
11892			if (!$b_nvme){
11893				$b_nvme = 1;
11894				if ($path = main::check_program('nvme')){
11895					$nvme = $path;
11896				}
11897			}
11898			if ($nvme){
11899				$device =~ s/n[0-9]//;
11900				@data = main::grabber("$sudoas$nvme smart-log $device 2>/dev/null");
11901				foreach (@data){
11902					my @row = split(/\s*:\s*/, $_);
11903					next if !$row[0];
11904					# other rows may have: Temperature sensor 1 :
11905					if ($row[0] eq 'temperature'){
11906						$row[1] =~ s/\s*C//;
11907						$hdd_temp = $row[1];
11908						last;
11909					}
11910				}
11911			}
11912		}
11913		else {
11914			if (!$b_hddtemp){
11915				$b_hddtemp = 1;
11916				if ($path = main::check_program('hddtemp')){
11917					$hddtemp = $path;
11918				}
11919			}
11920			if ($hddtemp){
11921				$hdd_temp = (main::grabber("$sudoas$hddtemp -nq -u C $device 2>/dev/null"))[0];
11922			}
11923		}
11924		$hdd_temp =~ s/\s?(Celsius|C)$// if $hdd_temp;
11925	}
11926	eval $end if $b_log;
11927	return $hdd_temp;
11928}
11929sub hdd_temp_sys {
11930	eval $start if $b_log;
11931	my ($device) = @_;
11932	my ($hdd_temp,$hdd_temp_alt,%sensors,@data,@working);
11933	my ($holder,$index) = ('','');
11934	my $path = "/sys/block/$device/device";
11935	my $path_trimmed = Cwd::abs_path("/sys/block/$device");
11936	# slice out the part of path that gives us hwmon in earlier kernel drivetemp
11937	$path_trimmed =~ s%/(block|nvme)/.*$%% if $path_trimmed;
11938	print "device: $device path: $path\n path_trimmed: $path_trimmed\n" if $dbg[21];
11939	return if ! -e $path && (!$path_trimmed || ! -e "$path_trimmed/hwmon");
11940	# first type, trimmed block,nvme (ata and nvme), 5.9 kernel:
11941	# /sys/devices/pci0000:10/0000:10:08.1/0000:16:00.2/ata8/host7/target7:0:0/7:0:0:0/hwmon/hwmon5/
11942	# /sys/devices/pci0000:10/0000:10:01.2/0000:13:00.0/hwmon/hwmon0/ < nvme
11943	# /sys/devices/pci0000:00/0000:00:01.3/0000:01:00.1/ata2/host1/target1:0:0/1:0:0:0/hwmon/hwmon3/
11944	# second type, 5.10+ kernel:
11945	# /sys/devices/pci0000:20/0000:20:03.1/0000:21:00.0/nvme/nvme0/nvme0n1/device/hwmon1
11946	# /sys/devices/pci0000:00/0000:00:08.1/0000:0b:00.2/ata12/host11/target11:0:0/11:0:0:0/block/sdd/device/hwmon/hwmon1
11947	# we don't want these items: crit|max|min|lowest|highest
11948	# original kernel 5.8/9 match for nvme and sd, 5.10+ match for sd
11949	if (-e "$path_trimmed/hwmon/"){
11950		@data = main::globber("$path_trimmed/hwmon/hwmon*/temp*_{input,label}");
11951	}
11952	# this case only happens if path_trimmed case isn't there, but leave in case
11953	elsif (-e "$path/hwmon/"){
11954		@data = main::globber("$path/hwmon/hwmon*/temp*_{input,label}");
11955	}
11956	# current match for nvme, but fails for 5.8/9 kernel nvme
11957	else {
11958		@data = main::globber("$path/hwmon*/temp*_{input,label}");
11959	}
11960	# seeing long lag to read temp input files for some reason
11961	foreach (sort @data){
11962		# print "file: $_\n";
11963		# print(main::reader($_,'',0),"\n");
11964		$path = $_;
11965		# cleanup everything in front of temp, the path
11966		$path =~ s/^.*\///;
11967		@working = split('_', $path);
11968		if ($holder ne $working[0]){
11969			$holder = $working[0];
11970		}
11971		$sensors{$holder}->{$working[1]} = main::reader($_,'strip',0);
11972	}
11973	return if !%sensors;
11974	if (keys %sensors == 1){
11975		if ($sensors{$holder}->{'input'} && main::is_numeric($sensors{$holder}->{'input'})){
11976			$hdd_temp = $sensors{$holder}->{'input'};
11977		}
11978	}
11979	else {
11980		# nvme drives can have > 1 temp types, but composite is the one we want if there
11981		foreach (keys %sensors){
11982			next if !$sensors{$_}->{'input'} || !main::is_numeric($sensors{$_}->{'input'});
11983			if ($sensors{$_}->{'label'} && $sensors{$_}->{'label'} eq 'Composite'){
11984				$hdd_temp = $sensors{$_}->{'input'};
11985				last;
11986			}
11987			else{
11988				$hdd_temp_alt = $sensors{$_}->{'input'};
11989			}
11990		}
11991		$hdd_temp = $hdd_temp_alt if !defined $hdd_temp && defined $hdd_temp_alt;
11992	}
11993	$hdd_temp = sprintf("%.1f", $hdd_temp/1000) if $hdd_temp;
11994	main::log_data('data',"device: $device temp: $hdd_temp") if $b_log;
11995	main::log_data('dump','%sensors',\%sensors) if $b_log;
11996	print Data::Dumper::Dumper \%sensors if $dbg[21];
11997	eval $end if $b_log;
11998	return $hdd_temp;
11999}
12000# args: 1: block id
12001sub block_data {
12002	eval $start if $b_log;
12003	my ($id) = @_;
12004	# 0: logical block size 1: disk physical block size/partition block size;
12005	my @blocks = (0,0);
12006	my ($block_log,$block_size) = (0,0);
12007	# my $path_size = "/sys/block/$id/size";
12008	my $path_log_block = "/sys/block/$id/queue/logical_block_size";
12009	my $path_phy_block = "/sys/block/$id/queue/physical_block_size";
12010	# legacy system path
12011	if (! -e $path_phy_block && -e "/sys/block/$id/queue/hw_sector_size"){
12012		$path_phy_block = "/sys/block/$id/queue/hw_sector_size";
12013	}
12014	$block_log = main::reader($path_log_block,'',0) if  -r $path_log_block;
12015	$block_size = main::reader($path_phy_block,'',0) if -r $path_phy_block;
12016	# print "l-b: $block_log p-b: $block_size raw: $size_raw\n";
12017	@blocks = ($block_log,$block_size);
12018	main::log_data('dump','@blocks',\@blocks) if $b_log;
12019	eval $end if $b_log;
12020	return @blocks;
12021}
12022sub device_speed {
12023	eval $start if $b_log;
12024	my ($device) = @_;
12025	my ($b_nvme,$lanes,$speed,@data);
12026	my $working = Cwd::abs_path("/sys/class/block/$device");
12027	# print "$working\n";
12028	if ($working){
12029		my ($id);
12030		# slice out the ata id:
12031		# /sys/devices/pci0000:00:11.0/ata1/host0/target0:
12032		if ($working =~ /^.*\/ata([0-9]+)\/.*/){
12033			$id = $1;
12034		}
12035		# /sys/devices/pci0000:00/0000:00:05.0/virtio1/block/vda
12036		elsif ($working =~ /^.*\/virtio([0-9]+)\/.*/){
12037			$id = $1;
12038		}
12039		# /sys/devices/pci0000:10/0000:10:01.2/0000:13:00.0/nvme/nvme0/nvme0n1
12040		elsif ($working =~ /^.*\/(nvme[0-9]+)\/.*/){
12041			$id = $1;
12042			$b_nvme = 1;
12043		}
12044		# do host last because the strings above might have host as well as their search item
12045		# 0000:00:1f.2/host3/target3: increment by 1 sine ata starts at 1, but host at 0
12046		elsif ($working =~ /^.*\/host([0-9]+)\/.*/){
12047			$id = $1 + 1 if defined $1;
12048		}
12049		# print "$working $id\n";
12050		if (defined $id){
12051			if ($b_nvme){
12052				$working = "/sys/class/nvme/$id/device/max_link_speed";
12053				$speed = main::reader($working,'',0) if -r $working;
12054				if (defined $speed && $speed =~ /([0-9\.]+)\sGT\/s/){
12055					$speed = $1;
12056					# pcie1: 2.5 GT/s; pcie2: 5.0 GT/s; pci3: 8 GT/s
12057					# NOTE: PCIe 3 stopped using the 8b/10b encoding but a sample pcie3 nvme has
12058					# rated speed of GT/s * .8 anyway. GT/s * (128b/130b)
12059					$speed = ($speed <= 5) ? $speed * .8 : $speed * 128/130;
12060					$speed = sprintf("%.1f",$speed) if $speed;
12061					$working = "/sys/class/nvme/$id/device/max_link_width";
12062					$lanes = main::reader($working,'',0) if -r $working;
12063					$lanes ||= 1;
12064					# https://www.edn.com/electronics-news/4380071/What-does-GT-s-mean-anyway-
12065					# https://www.anandtech.com/show/2412/2
12066					# http://www.tested.com/tech/457440-theoretical-vs-actual-bandwidth-pci-express-and-thunderbolt/
12067					# PCIe 1,2 use “8b/10b” encoding: eight bits are encoded into a 10-bit symbol
12068					# PCIe 3,4,5 use "128b/130b" encoding: 128 bits are encoded into a 130 bit symbol
12069					$speed = ($speed * $lanes) . " Gb/s";
12070				}
12071			}
12072			else {
12073				$working = "/sys/class/ata_link/link$id/sata_spd";
12074				$speed = main::reader($working,'',0) if -r $working;
12075				$speed = main::disk_cleaner($speed) if $speed;
12076				$speed =~ s/Gbps/Gb\/s/ if $speed;
12077			}
12078		}
12079	}
12080	@data = ($speed,$lanes);
12081	# print "$working $speed\n";
12082	eval $end if $b_log;
12083	return @data;
12084}
12085}
12086
12087## GraphicItem
12088{
12089package GraphicItem;
12090my $driver = ''; # we need this as a fallback in case no xorg log found
12091my %graphics;
12092sub get {
12093	eval $start if $b_log;
12094	my (@rows);
12095	my $num = 0;
12096	if (($b_arm || $b_mips) && !$use{'soc-gfx'} && !$use{'pci-tool'}){
12097		my $type = ($b_arm) ? 'arm' : 'mips';
12098		my $key = 'Message';
12099		push(@rows, {
12100		main::key($num++,0,1,$key) => main::row_defaults($type . '-pci',''),
12101		},);
12102	}
12103	else {
12104		push(@rows,device_output());
12105		if (!@rows){
12106			my $key = 'Message';
12107			my $type = 'pci-card-data';
12108			if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){
12109				$type = 'pci-card-data-root';
12110			}
12111			push(@rows, {
12112			main::key($num++,0,1,$key) => main::row_defaults($type,''),
12113			},);
12114		}
12115	}
12116	# note: not perfect, but we need usb gfx to show for all types, soc, pci, etc
12117	push(@rows,usb_output());
12118	push(@rows,display_output());
12119	push(@rows,gl_output());
12120	eval $end if $b_log;
12121	return @rows;
12122}
12123
12124sub device_output {
12125	eval $start if $b_log;
12126	return if !$devices{'graphics'};
12127	my (@rows);
12128	my ($j,$num) = (0,1);
12129	foreach my $row (@{$devices{'graphics'}}){
12130		$num = 1;
12131		# print "$row->[0] $row->[3]\n";
12132		# not using 3D controller yet, needs research: |3D controller |display controller
12133		# note: this is strange, but all of these can be either a separate or the same
12134		# card. However, by comparing bus id, say: 00:02.0 we can determine that the
12135		# cards are  either the same or different. We want only the .0 version as a valid
12136		# card. .1 would be for example: Display Adapter with bus id x:xx.1, not the right one
12137		next if $row->[3] != 0;
12138		# print "$row->[0] $row->[3]\n";
12139		$j = scalar @rows;
12140		$driver = $row->[9];
12141		$driver ||= 'N/A';
12142		my $device = main::trimmer($row->[4]);
12143		$device = ($device) ? main::pci_cleaner($device,'output') : 'N/A';
12144		# have seen absurdly verbose card descriptions, with non related data etc
12145		if (length($device) > 85 || $size{'max'} < 110){
12146			$device = main::pci_long_filter($device);
12147		}
12148		push(@rows, {
12149		main::key($num++,1,1,'Device') => $device,
12150		},);
12151		if ($extra > 0 && $use{'pci-tool'} && $row->[12]){
12152			my $item = main::get_pci_vendor($row->[4],$row->[12]);
12153			$rows[$j]->{main::key($num++,0,2,'vendor')} = $item if $item;
12154		}
12155		$rows[$j]->{main::key($num++,1,2,'driver')} = $driver;
12156		if ($row->[9] && !$bsd_type){
12157			my $version = main::get_module_version($row->[9]);
12158			$version ||= 'N/A';
12159			$rows[$j]->{main::key($num++,0,3,'v')} = $version;
12160		}
12161		if ($b_admin && $row->[10]){
12162			$row->[10] = main::get_driver_modules($row->[9],$row->[10]);
12163			$rows[$j]->{main::key($num++,0,3,'alternate')} = $row->[10] if $row->[10];
12164		}
12165		if ($extra > 0){
12166			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = (!$row->[2] && !$row->[3]) ? 'N/A' : "$row->[2].$row->[3]";
12167		}
12168		if ($extra > 1){
12169			my $chip_id = main::get_chip_id($row->[5],$row->[6]);
12170			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $chip_id;
12171		}
12172		if ($extra > 2 && $row->[1]){
12173			$rows[$j]->{main::key($num++,0,2,'class-ID')} = $row->[1];
12174		}
12175		# print "$row->[0]\n";
12176	}
12177	eval $end if $b_log;
12178	return @rows;
12179}
12180sub usb_output {
12181	eval $start if $b_log;
12182	my (@rows,@ids,$driver,$path_id,$product,@temp2);
12183	my ($j,$num) = (0,1);
12184	return if !$usb{'graphics'};
12185	foreach my $row (@{$usb{'graphics'}}){
12186		# these tests only work for /sys based usb data for now
12187		$num = 1;
12188		$j = scalar @rows;
12189		# make sure to reset, or second device trips last flag
12190		($driver,$path_id,$product) = ('','','');
12191		$product = main::cleaner($row->[13]) if $row->[13];
12192		$driver = $row->[15] if $row->[15];
12193		$path_id = $row->[2] if $row->[2];
12194		$product ||= 'N/A';
12195		# note: for real usb video out, no generic drivers? webcams may have one though
12196		if (!$driver){
12197			if ($row->[14] eq 'Audio-Video'){
12198				$driver = 'N/A';
12199			}
12200			else {
12201				$driver = 'N/A';
12202			}
12203		}
12204		push(@rows, {
12205		main::key($num++,1,1,'Device') => $product,
12206		main::key($num++,0,2,'type') => 'USB',
12207		main::key($num++,0,2,'driver') => $driver,
12208		},);
12209		if ($extra > 0){
12210			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]";
12211		}
12212		if ($extra > 1){
12213			$row->[7] ||= 'N/A';
12214			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $row->[7];
12215		}
12216		if ($extra > 2 && defined $row->[5] && $row->[5] ne ''){
12217			$rows[$j]->{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]";
12218		}
12219		if ($extra > 2 && $row->[16]){
12220			$rows[$j]->{main::key($num++,0,2,'serial')} = main::apply_filter($row->[16]);
12221		}
12222	}
12223	eval $end if $b_log;
12224	return @rows;
12225}
12226sub display_output(){
12227	eval $start if $b_log;
12228	my (@row);
12229	my ($num,$protocol) = (0,'');
12230	# note: these may not always be set, they won't be out of X, for example
12231	$protocol = get_protocol();
12232	# note, since the compositor is the server with wayland, always show it
12233	if ($extra > 1 || $protocol eq 'wayland'){
12234		set_compositor($protocol);
12235	}
12236	if ($b_display){
12237		display_data_x();
12238		# currently barebones, wayland needs a lot more work
12239		if ($protocol && $protocol eq 'wayland' && !$graphics{'screens'}){
12240			display_data_wayland();
12241			# it worked! we got screen data
12242			$graphics{'no-xdpyinfo'} = undef if $graphics{'screens'};
12243		}
12244	}
12245	else {
12246		$graphics{'tty'} = tty_data();
12247	}
12248	# this gives better output than the failure last case, which would only show:
12249	# for example: X.org: 1.9 instead of: X.org: 1.9.0
12250	$graphics{'x-version'} = $graphics{'xorg-version'} if $graphics{'xorg-version'};;
12251	$graphics{'x-version'} = x_version() if !$graphics{'x-version'};
12252	$graphics{'x-version'} = $graphics{'x-version-id'} if !$graphics{'x-version'};
12253	# print Data::Dumper::Dumper \%graphics;
12254	if (%graphics){
12255		my ($driver_missing,$resolution,$server_string) = ('','','');
12256		# print "$graphics{'x-vendor'} $graphics{'x-version'} $graphics{'x-vendor-release'}","\n";
12257		if ($graphics{'x-vendor'}){
12258			my $version = ($graphics{'x-version'}) ? " $graphics{'x-version'}" : '';
12259			# $version = (!$version && $graphics{'x-vendor-release'}) ? " $graphics{'x-vendor-release'}" : '';
12260			$server_string = "$graphics{'x-vendor'}$version";
12261			# print "$server_string\n";
12262		}
12263		elsif ($graphics{'x-version'}){
12264			if ($graphics{'x-version'} =~ /^Xvesa/){
12265				$server_string = $graphics{'x-version'};
12266			}
12267			else {
12268				$server_string = "X.org $graphics{'x-version'}";
12269			}
12270		}
12271		my @drivers = x_drivers();
12272		if (!$protocol && !$server_string && !$graphics{'x-vendor'} && !@drivers){
12273			$server_string = main::row_defaults('display-server');
12274			@row = ({
12275			main::key($num++,1,1,'Display') => '',
12276			main::key($num++,0,2,'server') => $server_string,
12277			});
12278		}
12279		else {
12280			$server_string ||= 'N/A';
12281			@row = ({
12282			main::key($num++,1,1,'Display') => $protocol,
12283			main::key($num++,0,2,'server') => $server_string,
12284			});
12285			if ($graphics{'compositor'}){
12286				$row[0]->{main::key($num++,0,2,'compositor')} = $graphics{'compositor'};
12287				if ($graphics{'compositor-version'}){
12288					$row[0]->{main::key($num++,0,3,'v')} = $graphics{'compositor-version'};
12289				}
12290			}
12291			# note: if no xorg log, and if wayland, there will be no xorg drivers,
12292			# obviously, so we use the last driver found on the card section in that case.
12293			# those come from lscpi kernel drivers so there should be no xorg/wayland issues.
12294			if (!$drivers[0]){
12295				# Fallback: specific case: in Arch/Manjaro gdm run systems, their Xorg.0.log is
12296				# located inside this directory, which is not readable unless you are root
12297				# Normally Arch gdm log is here: ~/.local/share/xorg/Xorg.1.log
12298				# $driver comes from the Device lines, and is just last fallback.
12299				if ($driver && $driver ne 'N/A'){
12300					if (-e '/var/lib/gdm' && !$b_root){
12301						$driver_missing = main::row_defaults('display-driver-na') . ' - ' . main::row_defaults('root-suggested');
12302					}
12303					else {
12304						$driver_missing = main::row_defaults('display-driver-na');
12305					}
12306				}
12307				else {
12308					$driver_missing = main::row_defaults('root-suggested') if -e '/var/lib/gdm' && !$b_root;
12309				}
12310			}
12311			else {
12312				$driver = $drivers[0];
12313			}
12314			$row[0]->{main::key($num++,1,2,'driver')} = '';
12315			$driver ||= 'N/A';
12316			$row[0]->{main::key($num++,1,3,'loaded')} = $driver;
12317			if ($driver_missing){
12318				$row[0]->{main::key($num++,0,4,'note')} = $driver_missing;
12319			}
12320			if ($drivers[1]){
12321				$row[0]->{main::key($num++,0,3,'unloaded')} = $drivers[1];
12322			}
12323			if ($drivers[2]){
12324				$row[0]->{main::key($num++,0,3,'failed')} = $drivers[2];
12325			}
12326			if ($extra > 1 && $drivers[3]){
12327				$row[0]->{main::key($num++,0,3,'alternate')} = $drivers[3];
12328			}
12329		}
12330		if ($b_admin){
12331			if (defined $graphics{'display-id'}){
12332				$row[0]->{main::key($num++,0,2,'display-ID')} = $graphics{'display-id'};
12333			}
12334			if (defined $graphics{'display-screens'}){
12335				$row[0]->{main::key($num++,0,2,'screens')} = $graphics{'display-screens'};
12336			}
12337			if (defined $graphics{'display-default-screen'} &&
12338			 $graphics{'display-screens'} && $graphics{'display-screens'} > 1){
12339				$row[0]->{main::key($num++,0,2,'default screen')} = $graphics{'display-default-screen'};
12340			}
12341		}
12342		if ($graphics{'no-xdpyinfo'}){
12343			$row[0]->{main::key($num++,0,2,'resolution')} = $graphics{'no-xdpyinfo'};
12344		}
12345		elsif ($graphics{'screens'}){
12346			my ($diag,$dpi,$hz,$size);
12347			my ($m_count,$basic_count,$row_key,$screen_count) = (0,0,0,0);
12348			my $s_count = ($graphics{'screens'}) ? scalar @{$graphics{'screens'}}:  0;
12349			foreach my $main (@{$graphics{'screens'}}){
12350				$m_count = scalar @{$main->{'monitors'}} if $main->{'monitors'};
12351				$screen_count++;
12352				($diag,$dpi,$hz,$resolution,$size) = (undef);
12353				$row_key++ if !$show{'graphic-basic'};
12354				if (!$show{'graphic-basic'} || $m_count == 0){
12355					if (!$show{'graphic-basic'} && defined $main->{'screen'}){
12356						$row[$row_key]->{main::key($num++,1,2,'Screen')} = $main->{'screen'};
12357					}
12358					$resolution = $main->{'res-x'} . 'x' . $main->{'res-y'} if $main->{'res-x'} && $main->{'res-y'};
12359					$resolution .= '~' . $main->{'hz'} . 'Hz' if $show{'graphic-basic'} && $main->{'hz'} && $resolution;
12360					$resolution ||= 'N/A';
12361					if ($s_count == 1 || !$show{'graphic-basic'}){
12362						$row[$row_key]->{main::key($num++,0,3,'s-res')} = $resolution;
12363					}
12364					elsif ($show{'graphic-basic'}){
12365						$row[$row_key]->{main::key($num++,0,3,'s-res')} = '' if $screen_count == 1;
12366						$row[$row_key]->{main::key($num++,0,3,$screen_count)} = $resolution;
12367					}
12368					$resolution = '';
12369					if ($main->{'s-dpi'} && (!$show{'graphic-basic'} || $extra > 1)){
12370						$row[$row_key]->{main::key($num++,0,3,'s-dpi')} = $main->{'s-dpi'};
12371					}
12372					if (!$show{'graphic-basic'}){
12373						if ($main->{'size-x'} && $main->{'size-y'}){
12374							$size = $main->{'size-x'} . 'x' . $main->{'size-y'} .
12375							'mm ('. $main->{'size-x-i'} . 'x' . $main->{'size-y-i'} . '")';
12376						}
12377						$size ||= '';
12378						$row[$row_key]->{main::key($num++,0,3,'s-size')} = $size if $size;
12379						if ($main->{'diagonal'}){
12380							$diag = $main->{'diagonal-m'} . 'mm ('. $main->{'diagonal'} . '")';
12381						}
12382						$diag ||= '';
12383						$row[$row_key]->{main::key($num++,0,3,'s-diag')} = $diag if $diag;
12384					}
12385				}
12386				if ($main->{'monitors'}){
12387					# print $basic_count . '::' . $m_count, "\n";
12388					foreach my $monitor (@{$main->{'monitors'}}){
12389						($diag,$dpi,$hz,$resolution,$size) = (undef);
12390						if ($show{'graphic-basic'}){
12391							$basic_count++;
12392							if ($monitor->{'res-x'} && $monitor->{'res-y'}){
12393								$resolution = $monitor->{'res-x'} . 'x' . $monitor->{'res-y'};
12394							}
12395							# using main, noit monitor, dpi because we want xorg dpi, not physical screen dpi
12396							$dpi = $main->{'s-dpi'} if $resolution && $extra > 1 && $main->{'s-dpi'};
12397							$resolution .= '~' . $monitor->{'hz'} . 'Hz' if $monitor->{'hz'} && $resolution;
12398							$resolution ||= 'N/A';
12399							if ($basic_count == 1 && $m_count == 1){
12400								$row[$row_key]->{main::key($num++,0,2,'resolution')} = $resolution;
12401							}
12402							else {
12403								$row[$row_key]->{main::key($num++,1,2,'resolution')} = '' if $basic_count == 1;
12404								$row[$row_key]->{main::key($num++,0,3,$basic_count)} = $resolution;
12405							}
12406							if ($m_count == $basic_count){
12407								$row[$row_key]->{main::key($num++,0,2,'s-dpi')} = $dpi if $dpi;
12408							}
12409							next;
12410						}
12411						$row_key++;
12412						$row[$row_key]->{main::key($num++,0,3,'Monitor')} = $monitor->{'monitor'};
12413						if ($monitor->{'res-x'} && $monitor->{'res-y'}){
12414							$resolution = $monitor->{'res-x'} . 'x' . $monitor->{'res-y'};
12415						}
12416						$resolution ||= 'N/A';
12417						$row[$row_key]->{main::key($num++,0,4,'res')} = $resolution;
12418						$hz = ($monitor->{'hz'}) ? $monitor->{'hz'} : '';
12419						$row[$row_key]->{main::key($num++,0,4,'hz')} = $hz if $hz;
12420						$dpi = ($monitor->{'dpi'}) ? $monitor->{'dpi'} : '';
12421						$row[$row_key]->{main::key($num++,0,4,'dpi')} = $dpi if $dpi;
12422						# print "$dpi :: $main->{'s-dpi'}\n";
12423						if ($monitor->{'size-x'} && $monitor->{'size-y'}){
12424							$size =  $monitor->{'size-x'} . 'x' . $monitor->{'size-y'} .
12425							'mm ('. $monitor->{'size-x-i'} . 'x' . $monitor->{'size-y-i'} . '")';
12426						}
12427						$size ||= '';
12428						$row[$row_key]->{main::key($num++,0,4,'size')} = $size if $size;
12429						if ($monitor->{'diagonal'}){
12430							$diag = $monitor->{'diagonal-m'} . 'mm ('. $monitor->{'diagonal'} . '")';
12431						}
12432						$diag ||= '';
12433						$row[$row_key]->{main::key($num++,0,4,'diag')} = $diag if $diag;
12434					}
12435				}
12436			}
12437		}
12438		else {
12439			$graphics{'tty'} ||= 'N/A';
12440			$row[0]->{main::key($num++,0,2,'tty')} = $graphics{'tty'};
12441		}
12442	}
12443	eval $end if $b_log;
12444	return @row;
12445}
12446
12447sub display_data_x {
12448	eval $start if $b_log;
12449	# X vendor and version detection.
12450	# new method added since radeon and X.org and the disappearance of
12451	# <X server name> version : ...etc. Later on, the normal textual version string
12452	# returned, e.g. like: X.Org version: 6.8.2
12453	# A failover mechanism is in place: if $version empty, release number parsed instead
12454	if (my $program = main::check_program('xdpyinfo')){
12455		my ($diagonal,$diagonal_m,$dpi) = ('','','');
12456		my ($screen_id,$screen,@working);
12457		my ($res_x,$res_x_i,$res_y,$res_y_i,$size_x,$size_x_i,$size_y,$size_y_i);
12458		my @xdpyinfo = main::grabber("$program $display_opt 2>/dev/null","\n",'strip');
12459		# @xdpyinfo = map {s/^\s+//;$_} @xdpyinfo if @xdpyinfo;
12460		# print join("\n",@xdpyinfo), "\n";
12461		foreach (@xdpyinfo){
12462			@working = split(/:\s+/, $_);
12463			next if (($graphics{'screens'} && $working[0] !~ /^(dimensions$|screen\s#)/) || !$working[0]);
12464			# print "$_\n";
12465			if ($working[0] eq 'vendor string'){
12466				$working[1] =~ s/The\s|\sFoundation//g;
12467				# some distros, like fedora, report themselves as the xorg vendor,
12468				# so quick check here to make sure the vendor string includes Xorg in string
12469				if ($working[1] !~ /x/i){
12470					$working[1] .= ' X.org';
12471				}
12472				$graphics{'x-vendor'} = $working[1];
12473			}
12474			elsif ($working[0] eq 'name of display'){
12475				$graphics{'display-id'} = $working[1];
12476			}
12477			elsif ($working[0] eq 'version number'){
12478				$graphics{'x-version-id'} = $working[1];
12479			}
12480			# note used, fix that
12481			elsif ($working[0] eq 'vendor release number'){
12482				$graphics{'x-vendor-release'} = $working[1];
12483			}
12484			elsif ($working[0] eq 'X.Org version'){
12485				$graphics{'xorg-version'} = $working[1];
12486			}
12487			elsif ($working[0] eq 'default screen number'){
12488				$graphics{'display-default-screen'} = $working[1];
12489			}
12490			elsif ($working[0] eq 'number of screens'){
12491				$graphics{'display-screens'} = $working[1];
12492			}
12493			elsif  ($working[0] =~ /^screen #([0-9]+):/){
12494				$screen_id = $1;
12495				$graphics{'screens'} = () if !$graphics{'screens'};
12496			}
12497			elsif ($working[0] eq 'resolution'){
12498				$working[1] =~ s/^([0-9]+)x/$1/;
12499				$graphics{'s-dpi'} = $working[1];
12500			}
12501			elsif ($working[0] eq 'dimensions'){
12502				($dpi,$res_x,$res_y,$size_x,$size_y) = (undef,undef,undef,undef,undef);
12503				if ($working[1] =~ /([0-9]+)\s*x\s*([0-9]+)\s+pixels\s+\(([0-9]+)\s*x\s*([0-9]+)\s*millimeters\)/){
12504					$res_x = $1;
12505					$res_y = $2;
12506					$size_x = $3;
12507					$size_y = $4;
12508					$res_x_i = ($1) ? sprintf("%.1f", ($1/25.4)) : 0;
12509					$res_y_i = ($2) ? sprintf("%.1f", ($2/25.4)) : 0;
12510					$size_x_i = ($3) ? sprintf("%.1f", ($3/25.4)) : 0;
12511					$size_y_i = ($4) ? sprintf("%.1f", ($4/25.4)) : 0;
12512					$dpi = ($res_x && $size_x) ? sprintf("%.0f", ($res_x*25.4/$size_x)) : '';
12513					$diagonal = ($res_x && $size_x) ? sprintf("%.1f", (sqrt($size_x**2 + $size_y**2)/25.4)) : '';
12514					$diagonal += 0 if $diagonal;# trick to get rid of decimal 0
12515					$diagonal_m = ($res_x && $size_x) ? sprintf("%.0f", (sqrt($size_x**2 + $size_y**2))) : '';
12516				}
12517				$screen = {
12518				'screen' => $screen_id,
12519				'res-x' => $res_x,
12520				'res-x-i' => $res_x_i,
12521				'res-y' => $res_y,
12522				'res-y-i' => $res_y_i,
12523				'size-x' => $size_x,
12524				'size-x-i' => $size_x_i,
12525				'size-y' => $size_y,
12526				'size-y-i' => $size_y_i,
12527				's-dpi' => $dpi,
12528				'diagonal' => $diagonal,
12529				'diagonal-m' => $diagonal_m,
12530				};
12531				push(@{$graphics{'screens'}}, $screen);
12532			}
12533		}
12534		# print Data::Dumper::Dumper $graphics{'screens'};
12535		if (my $program = main::check_program('xrandr')){
12536			($diagonal,$diagonal_m,$dpi) = (undef);
12537			($screen_id,$screen,@working) = (undef);
12538			($res_x,$res_x_i,$res_y,$res_y_i,$size_x,$size_x_i,$size_y,$size_y_i) = (undef);
12539			my (@monitors,$monitor_id,$screen,$screen_id,@xrandr_screens);
12540			my @xrandr = main::grabber("$program $display_opt 2>/dev/null",'','strip');
12541			# $graphics{'dimensions'} = (\@dimensions);
12542			# we get a bit more info from xrandr than xdpyinfo, but xrandr fails to handle
12543			# multiple screens from different video cards
12544			foreach (@xrandr){
12545				if (/^Screen ([0-9]+):/){
12546					$screen_id = $1;
12547					push(@xrandr_screens, \@monitors) if @monitors;
12548					@monitors = ();
12549				}
12550				if (/^([^\s]+)\s+connected\s(primary\s)?([0-9]+)\s*x\s*([0-9]+)\+[0-9+]+(\s\([^)]+\))?(\s([0-9]+)mm\sx\s([0-9]+)mm)?/){
12551					$monitor_id = $1;
12552					$res_x = $3;
12553					$res_y = $4;
12554					$size_x = $7;
12555					$size_y = $8;
12556					$res_x_i = ($3) ? sprintf("%.1f", ($3/25.4)) : 0;
12557					$res_y_i = ($4) ? sprintf("%.1f", ($4/25.4)) : 0;
12558					$size_x_i = ($7) ? sprintf("%.1f", ($7/25.4)) : 0;
12559					$size_y_i = ($8) ? sprintf("%.1f", ($8/25.4)) : 0;
12560					$dpi = ($res_x && $size_x) ? sprintf("%.0f", $res_x * 25.4 / $size_x) : '';
12561					$diagonal = ($res_x && $size_x) ? sprintf("%.1f", (sqrt($size_x**2 + $size_y**2)/25.4)) : '';
12562					$diagonal += 0 if $diagonal; # trick to get rid of decimal 0
12563					$diagonal_m = ($res_x && $size_x) ? sprintf("%.0f", (sqrt($size_x**2 + $size_y**2))) : '';
12564					push(@monitors, {
12565					'screen' => $screen_id,
12566					'monitor' => $monitor_id,
12567					'res-x' => $res_x,
12568					'res-x-i' => $res_x_i,
12569					'res-y' => $res_y,
12570					'res-y-i' => $res_y_i,
12571					'size-x' => $size_x,
12572					'size-x-i' => $size_x_i,
12573					'size-y' => $size_y,
12574					'size-y-i' => $size_y_i,
12575					'dpi' => $dpi,
12576					'diagonal' => $diagonal,
12577					'diagonal-m' => $diagonal_m,
12578					});
12579					# print "x:$size_x y:$size_y rx:$res_x ry:$res_y dpi:$dpi\n";
12580					($res_x,$res_x_i,$res_y,$res_y_i,$size_x,$size_x_i,$size_y,$size_y_i) = (0,0,0,0,0,0,0,0);
12581
12582				}
12583				my @working = split(/\s+/,$_);
12584				if ($working[1] =~ /\*/){
12585					$working[1] =~ s/\*|\+//g;
12586					$working[1] = sprintf("%.0f",$working[1]);
12587					$monitors[scalar @monitors - 1]->{'hz'} = $working[1] if @monitors;
12588					($diagonal,$dpi) = ('','');
12589					# print Data::Dumper::Dumper \@monitors;
12590				}
12591			}
12592			push(@xrandr_screens, \@monitors) if @monitors;
12593			# print "xrand: " . Data::Dumper::Dumper \@xrandr_screens;
12594			my ($i) = (0);
12595			foreach my $main (@{$graphics{'screens'}}){
12596				# print "h: " . Data::Dumper::Dumper $main;
12597				# print $main->{'screen'}, "\n";
12598				foreach my $screens (@xrandr_screens){
12599					# print "d: " . Data::Dumper::Dumper $screens;
12600					if ($screens->[0]{'screen'} eq $main->{'screen'}){
12601						$graphics{'screens'}->[$i]{'monitors'} = $screens;
12602						last;
12603					}
12604				}
12605				$i++;
12606			}
12607			if (!$graphics{'screens'}){
12608				$graphics{'tty'} = tty_data();
12609			}
12610		}
12611	}
12612	else {
12613		$graphics{'no-xdpyinfo'} = main::row_defaults('tool-missing-basic','xdpyinfo');
12614	}
12615	print 'last: ', Data::Dumper::Dumper $graphics{'screens'} if $dbg[17];
12616	main::log_data('dump','$graphics{screens}',$graphics{'screens'}) if $b_log;
12617	eval $end if $b_log;
12618}
12619sub display_data_wayland {
12620	eval $start if $b_log;
12621	if ($ENV{'WAYLAND_DISPLAY'}){
12622		$graphics{'display-id'} = $ENV{'WAYLAND_DISPLAY'};
12623		# return as wayland-0 or 0?
12624		$graphics{'display-id'} =~ s/wayland-?//i;
12625	}
12626	# print 'last: ', Data::Dumper::Dumper $graphics{'screens'} if $dbg[17];
12627	# main::log_data('dump','@graphics{screens}',$graphics{'screens'}) if $b_log;
12628	eval $end if $b_log;
12629}
12630sub set_compositor {
12631	eval $start if $b_log;
12632	my ($protocol) = @_;
12633	# initial tests, if wayland, it is certainly a compositor
12634	$protocol = lc($protocol) if $protocol;
12635	$graphics{'compositor'} = display_compositor($protocol);
12636	# gnome-shell is incredibly slow to return version
12637	if (($extra > 2 || $protocol eq 'wayland') && $graphics{'compositor'} &&
12638	 (!$show{'system'} || $graphics{'compositor'} ne 'gnome-shell')){
12639		$graphics{'compositor-version'} = (main::program_data($graphics{'compositor'},$graphics{'compositor'},3))[1];
12640	}
12641	eval $end if $b_log;
12642}
12643sub get_protocol {
12644	eval $start if $b_log;
12645	my ($protocol) = ('');
12646	$protocol = $ENV{'XDG_SESSION_TYPE'} if $ENV{'XDG_SESSION_TYPE'};
12647	$protocol = $ENV{'WAYLAND_DISPLAY'} if (!$protocol && $ENV{'WAYLAND_DISPLAY'});
12648	# can show as wayland-0
12649	$protocol = 'wayland' if $protocol && $protocol =~ /wayland/i;
12650	# yes, I've seen this in 2019 distros, sigh
12651	$protocol = '' if $protocol eq 'tty';
12652	# need to confirm that there's a point to this test, I believe no, fails out of x
12653	# loginctl also results in the session id
12654	if (!$protocol && $b_display && $force{'display'}){
12655		if (my $program = main::check_program('loginctl')){
12656			my $id = '';
12657			# $id = $ENV{'XDG_SESSION_ID'}; # returns tty session in console
12658			my @data = main::grabber("$program --no-pager --no-legend 2>/dev/null",'','strip');
12659			foreach (@data){
12660				next if /tty[v]?[0-6]$/; # freebsd: ttyv3
12661				$id = (split(/\s+/, $_))[0];
12662				last; # multiuser? too bad, we'll go for the first one
12663			}
12664			if ($id){
12665				my $temp = (main::grabber("$program show-session $id -p Type --no-pager --no-legend 2>/dev/null"))[0];
12666				$temp =~ s/Type=// if $temp;
12667				# ssh will not show /dev/ttyx so would have passed the first test
12668				$protocol = $temp if $temp && $temp ne 'tty';
12669			}
12670		}
12671	}
12672	eval $end if $b_log;
12673	return $protocol;
12674}
12675sub gl_output(){
12676	eval $start if $b_log;
12677	my $num = 0;
12678	my (@row,$arg);
12679	# print ("$b_display : $b_root\n");
12680	if ($b_display){
12681		if (my $program = main::check_program('glxinfo')){
12682			# NOTE: glxinfo -B is not always available, unfortunately
12683			my @glxinfo = main::grabber("$program $display_opt 2>/dev/null");
12684			# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/graphics/glxinfo/glxinfo-ssh-centos.txt";
12685			# my @glxinfo = main::reader($file);
12686			if (!@glxinfo){
12687				my $type = 'display-console';
12688				if ($b_root){
12689					$type = 'display-root-x';
12690				}
12691				else {
12692					$type = 'display-null';
12693				}
12694				@row = ({
12695				main::key($num++,0,1,'Message') => main::row_defaults($type),
12696				});
12697				return @row;
12698			}
12699			# print join("\n", @glxinfo),"\n";
12700			my $compat_version = '';
12701			my ($b_compat,$b_nogl,@core_profile_version,@direct_render,@renderer,
12702			@opengl_version,@working);
12703			foreach (@glxinfo){
12704				next if /^\s/;
12705				if (/^opengl renderer/i){
12706					@working = split(/:\s*/, $_, 2);
12707					if ($working[1]){
12708						$working[1] = main::cleaner($working[1]);
12709						# Allow all mesas
12710						# if ($working[1] =~ /mesa/i){
12711						#
12712						# }
12713					}
12714					# note: there are cases where gl drivers are missing and empty
12715					# field value occurs.
12716					else {
12717						$b_nogl = 1;
12718						$working[1] = main::row_defaults('gl-empty');
12719					}
12720					push(@renderer, $working[1]);
12721				}
12722				# dropping all conditions from this test to just show full mesa information
12723				# there is a user case where not f and mesa apply, atom mobo
12724				# /opengl version/ && ( f || $2 !~ /mesa/){
12725				elsif (/^opengl version/i){
12726					@working = split(/:\s*/, $_, 2);
12727					if ($working[1]){
12728						# fglrx started appearing with this extra string, does not appear
12729						# to communicate anything of value
12730						$working[1] =~ s/(Compatibility Profile Context|\(Compatibility Profile\))//;
12731						$working[1] =~ s/\s\s/ /g;
12732						$working[1] =~ s/^\s+|\s+$//;
12733						push(@opengl_version, $working[1]);
12734						# note: this is going to be off if ever multi opengl versions appear,
12735						# never seen one
12736						@working = split(/\s+/, $working[1]);
12737						$compat_version = $working[0];
12738					}
12739					elsif (!$b_nogl){
12740						push(@opengl_version, main::row_defaults('gl-empty'));
12741					}
12742				}
12743				elsif (/^opengl core profile version/i){
12744					@working = split(/:\s*/, $_, 2);
12745					# note: no need to apply empty message here since we don't have the data
12746					# anyway
12747					if ($working[1]){
12748						# fglrx started appearing with this extra string, does not appear
12749						# to communicate anything of value
12750						$working[1] =~ s/(Compatibility Profile Context|\((Compatibility|Core) Profile\))//;
12751						$working[1] =~ s/\s\s/ /g;
12752						$working[1] =~ s/^\s+|\s+$//;
12753						push(@core_profile_version, $working[1]);
12754					}
12755				}
12756				elsif (/direct rendering/){
12757					@working = split(/:\s*/, $_, 2);
12758					push(@direct_render, $working[1]);
12759				}
12760				# if -B was always available, we could skip this, but it is not
12761				elsif (/GLX Visuals/){
12762					last;
12763				}
12764			}
12765			my ($direct_render,$renderer,$version) = ('N/A','N/A','N/A');
12766			$direct_render = join(', ',  @direct_render) if @direct_render;
12767			# non free drivers once filtered and cleaned show the same for core and compat
12768			# but this stopped for some reason at 4.5/4.6 nvidia
12769			if (@core_profile_version && @opengl_version &&
12770			  join('', @core_profile_version) ne join('', @opengl_version) &&
12771			  !(grep {/nvidia/i} @opengl_version)){
12772				@opengl_version = @core_profile_version;
12773				$b_compat = 1;
12774			}
12775			$version = join(', ', @opengl_version) if @opengl_version;
12776			$renderer = join(', ', @renderer) if @renderer;
12777			@row = ({
12778			main::key($num++,1,1,'OpenGL') => '',
12779			main::key($num++,1,2,'renderer') => ($renderer) ? $renderer : 'N/A',
12780			main::key($num++,0,2,'v') => ($version) ? $version : 'N/A',
12781			});
12782			if ($b_compat && $extra > 1 && $compat_version){
12783				$row[0]->{main::key($num++,0,2,'compat-v')} = $compat_version;
12784			}
12785			if ($extra > 0){
12786				$row[0]->{main::key($num++,0,2,'direct render')} = $direct_render;
12787			}
12788		}
12789		else {
12790			@row = ({
12791			main::key($num++,0,1,'Message') => main::row_defaults('glxinfo-missing'),
12792			});
12793		}
12794	}
12795	else {
12796		my $type = 'display-console';
12797		if (!main::check_program('glxinfo')){
12798			$type = 'glxinfo-missing';
12799		}
12800		else {
12801			if ($b_root){
12802				$type = 'display-root';
12803			}
12804			else {
12805				$type = 'display-try';
12806			}
12807		}
12808		@row = ({
12809		main::key($num++,0,1,'Message') => main::row_defaults($type),
12810		});
12811	}
12812	eval $end if $b_log;
12813	return @row;
12814}
12815sub tty_data(){
12816	eval $start if $b_log;
12817	my ($tty);
12818	if ($size{'term-cols'}){
12819		$tty = "$size{'term-cols'}x$size{'term-lines'}";
12820	}
12821	# this is broken
12822	elsif ($b_irc && $client{'console-irc'}){
12823		ShellData::console_irc_tty() if !$loaded{'con-irc-tty'};
12824		my $tty_working = $client{'con-irc-tty'};
12825		if ($tty_working ne '' && (my $program = main::check_program('stty'))){
12826			my $tty_arg = ($bsd_type) ? '-f' : '-F';
12827			# handle vtnr integers, and tty ID with letters etc.
12828			$tty_working = "tty$tty_working" if -e "/dev/tty$tty_working";
12829			$tty = (main::grabber("$program $tty_arg /dev/$tty_working size 2>/dev/null"))[0];
12830			if ($tty){
12831				my @temp = split(/\s+/, $tty);
12832				$tty = "$temp[1]x$temp[0]";
12833			}
12834		}
12835	}
12836	eval $end if $b_log;
12837	return $tty;
12838}
12839sub x_drivers {
12840	eval $start if $b_log;
12841	my ($driver,@driver_data,,%drivers);
12842	my ($alternate,$failed,$loaded,$sep,$unloaded) = ('','','','','');
12843	if (my $log = $system_files{'xorg-log'}){
12844		if ($fake{'xorg-log'}){
12845			# $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/Xorg.0-voyager-serena.log";
12846			# $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/loading-unload-failed-all41-mint.txt";
12847			# $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/loading-unload-failed-phd21-mint.txt";
12848			# $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/Xorg.0-gm10.log";
12849			# $log = "$ENV{HOME}/bin/scripts/inxi/data/xorg-logs/xorg-multi-driver-1.log";
12850		}
12851		my @xorg = main::reader($log);
12852		# list is from sgfxi plus non-free drivers, plus ARM drivers
12853		my $list = join('|', qw(amdgpu apm ark armsoc atimisc ati
12854		chips cirrus cyrix fbdev fbturbo fglrx geode glide glint
12855		i128 i740 i810-dec100 i810e i810 i815 i830 i845 i855 i865 i915 i945
12856		i965 iftv imstt intel ivtv mach64 mesa mga modesetting neomagic newport
12857		nouveau nsc nvidia nv openchrome r128 radeonhd radeon
12858		rendition s3virge s3 savage siliconmotion sisimedia sisusb sis
12859		sunbw2 suncg14 suncg3 suncg6 sunffb sunleo suntcx
12860		tdfx tga trident tseng unichrome v4l vboxvideo vesa vga via vmware vmwgfx
12861		voodoo));
12862		# it's much cheaper to grab the simple pattern match then do the expensive one
12863		# in the main loop.
12864		# @xorg = grep {/Failed|Unload|Loading/} @xorg;
12865		foreach (@xorg){
12866			next if !/Failed|Unload|Loading/;
12867			# print "$_\n";
12868			# note that in file names, driver is always lower case
12869			if (/\sLoading.*($list)_drv.so$/i){
12870				$driver=lc($1);
12871				# we get all the actually loaded drivers first, we will use this to compare the
12872				# failed/unloaded, which have not always actually been truly loaded
12873				$drivers{$driver}='loaded';
12874			}
12875			# openbsd uses UnloadModule:
12876			elsif (/(Unloading\s|UnloadModule).*\"?($list)(_drv.so)?\"?$/i){
12877				$driver=lc($2);
12878				# we get all the actually loaded drivers first, we will use this to compare the
12879				# failed/unloaded, which have not always actually been truly loaded
12880				if (exists $drivers{$driver} && $drivers{$driver} ne 'alternate'){
12881					$drivers{$driver}='unloaded';
12882				}
12883			}
12884			# verify that the driver actually started the desktop, even with false failed messages
12885			# which can occur. This is the driver that is actually driving the display.
12886			# note that xorg will often load several modules, like modesetting,fbdev,nouveau
12887			# NOTE:
12888			# (II) UnloadModule: "nouveau"
12889			# (II) Unloading nouveau
12890			# (II) Failed to load module "nouveau" (already loaded, 0)
12891			# (II) LoadModule: "modesetting"
12892			elsif (/Failed.*($list)\"?.*$/i){
12893				# Set driver to lower case because sometimes it will show as
12894				# RADEON or NVIDIA in the actual x start
12895				$driver=lc($1);
12896				# we need to make sure that the driver has already been truly loaded,
12897				# not just discussed
12898				if (exists $drivers{$driver} && $drivers{$driver} ne 'alternate'){
12899					if ($_ !~ /\(already loaded/){
12900						$drivers{$driver}='failed';
12901					}
12902					# reset the previous line's 'unloaded' to 'loaded' as well
12903					else {
12904						$drivers{$driver}='loaded';
12905					}
12906				}
12907				elsif ($_ =~ /module does not exist/){
12908					$drivers{$driver}='alternate';
12909				}
12910			}
12911		}
12912		my $sep = '';
12913		foreach (sort keys %drivers){
12914			if ($drivers{$_} eq 'loaded'){
12915				$sep = ($loaded) ? ',' : '';
12916				$loaded .= $sep . $_;
12917			}
12918			elsif ($drivers{$_} eq 'unloaded'){
12919				$sep = ($unloaded) ? ',' : '';
12920				$unloaded .= $sep . $_;
12921			}
12922			elsif ($drivers{$_} eq 'failed'){
12923				$sep = ($failed) ? ',' : '';
12924				$failed .= $sep . $_;
12925			}
12926			elsif ($drivers{$_} eq 'alternate'){
12927				$sep = ($alternate) ? ',' : '';
12928				$alternate .= $sep . $_;
12929			}
12930		}
12931		@driver_data = ($loaded,$unloaded,$failed,$alternate);
12932	}
12933	eval $end if $b_log;
12934	return @driver_data;
12935}
12936# fallback if no glx x version data found
12937sub x_version {
12938	eval $start if $b_log;
12939	my ($version,@data,$program);
12940	# load the extra X paths, it's important that these are first, because
12941	# later Xorg versions show error if run in console or ssh if the true path
12942	# is not used.
12943	@paths = (qw(/usr/lib /usr/lib/xorg /usr/lib/xorg-server /usr/libexec /usr/X11R6/bin), @paths);
12944	# IMPORTANT: both commands send version data to stderr!
12945	if ($program = main::check_program('Xorg')){
12946		@data = main::grabber("$program -version 2>&1");
12947	}
12948	elsif ($program = main::check_program('X')){
12949		@data = main::grabber("$program -version 2>&1");
12950	}
12951	elsif ($program = main::check_program('Xvesa')){
12952		@data = main::grabber("$program -version 2>&1");
12953	}
12954	# print join('^ ', @paths), " :: $program\n";
12955	# print Data::Dumper::Dumper \@data;
12956	if (@data){
12957		foreach (@data){
12958			if (/^X.org X server/i){
12959				$version = (split(/\s+/, $_))[3];
12960				last;
12961			}
12962			elsif (/^X Window System Version/i){
12963				$version = (split(/\s+/, $_))[4];
12964				last;
12965			}
12966			elsif (/^Xvesa from/i){
12967				$version = (split(/\s+/, $_))[3];
12968				$version = "Xvesa $version" if $version;
12969				last;
12970			}
12971		}
12972	}
12973	# remove extra X paths
12974	@paths = grep { !/^\/usr\/lib|xorg|X11R6|libexec/ } @paths;
12975	eval $end if $b_log;
12976	return $version;
12977}
12978# $1 - protocol: wayland|x11
12979sub display_compositor {
12980	eval $start if $b_log;
12981	my ($protocol) = @_;
12982	my ($compositor) = ('');
12983	main::set_ps_gui() if !$loaded{'ps-gui'};
12984	if (@ps_gui){
12985		# 1 check program; 2 search; 3 unused version; 4 print
12986		my @compositors = (
12987		['asc','asc','','asc'],
12988		['budgie-wm','budgie-wm','','budgie-wm'],
12989		# owned by: compiz-core in debian
12990		['compiz','compiz','','compiz'],
12991		['compton','compton','','compton'],
12992		# as of version 20 is wayland compositor
12993		['enlightenment','enlightenment','','enlightenment'],
12994		['gnome-shell','gnome-shell','','gnome-shell'],
12995		['kwin_wayland','kwin_wayland','','kwin_wayland'],
12996		['kwin_x11','kwin_x11','','kwin_x11'],
12997		# ['kwin','kwin','','kwin'],
12998		['marco','marco','','marco'],
12999		['muffin','muffin','','muffin'],
13000		['mutter','mutter','','mutter'],
13001		['weston','weston','','weston'],
13002		# these are more obscure, so check for them last
13003		['3dwm','3dwm','','3dwm'],
13004		['dcompmgr','dcompmgr','','dcompmgr'],
13005		['dwc','dwc','','dwc'],
13006		['fireplace','fireplace','','fireplace'],
13007		['grefson','grefson','','grefson'],
13008		['kmscon','kmscon','','kmscon'],
13009		['liri','liri','','liri'],
13010		['metisse','metisse','','metisse'],
13011		['mir','mir','','mir'],
13012		['moblin','moblin','','moblin'],
13013		['motorcar','motorcar','','motorcar'],
13014		['monsterwm','monsterwm','','monsterwm'],
13015		['orbital','orbital','','orbital'],
13016		['papyros','papyros','','papyros'],
13017		['perceptia','perceptia','','perceptia'],
13018		['picom','picom','','picom'],
13019		['rustland','rustland','','rustland'],
13020		['sommelier','sommelier','','sommelier'],
13021		['sway','sway','','sway'],
13022		['swc','swc','','swc'],
13023		['ukwm','ukwm','','ukwm'],
13024		['unagi','unagi','','unagi'],
13025		['unity-system-compositor','unity-system-compositor','','unity-system-compositor'],
13026		['way-cooler','way-cooler','','way-cooler'],
13027		['wavy','wavy','','wavy'],
13028		['wayfire','wayfire','','wayfire'],
13029		['wayhouse','wayhouse','','wayhouse'],
13030		['westford','westford','','westford'],
13031		['xcompmgr','xcompmgr','','xcompmgr'],
13032		['xfwm4','xfwm4','','xfwm4'],
13033		['xfwm5','xfwm5','','xfwm5'],
13034		['xfwm','xfwm','','xfwm'],
13035		);
13036		foreach my $item (@compositors){
13037			# no need to use check program with short list of ps_gui
13038			# if (main::check_program($item[0]) && (grep {/^$item[1]$/} @ps_gui)){
13039			if (grep {/^$item->[1]$/} @ps_gui){
13040				$compositor = $item->[3];
13041				last;
13042			}
13043		}
13044	}
13045	main::log_data('data',"compositor: $compositor") if $b_log;
13046	eval $end if $b_log;
13047	return $compositor;
13048}
13049}
13050
13051## LogicalItem
13052{
13053package LogicalItem;
13054
13055sub get {
13056	eval $start if $b_log;
13057	my (@rows,$key1,$val1);
13058	my $num = 0;
13059	if ($bsd_type){
13060		$key1 = 'Message';
13061		$val1 = main::row_defaults('logical-data-bsd',$uname[0]);
13062		@rows = ({main::key($num++,0,1,$key1) => $val1,});
13063	}
13064 	else {
13065		LsblkData::set() if !$loaded{'lsblk'};
13066		if ($fake{'logical'} || $alerts{'lvs'}->{'action'} eq 'use'){
13067			lvm_data() if !$loaded{'logical-data'};
13068			if (!@lvm){
13069				my $key = 'Message';
13070				# note: arch linux has a bug where lvs returns 0 if non root start
13071				my $message = ($use{'logical-lvm'}) ? main::row_defaults('tool-permissions','lvs') : main::row_defaults('logical-data','');
13072				push(@rows, {
13073				main::key($num++,0,1,$key) => $message,
13074				},);
13075			}
13076			else {
13077				my %processed = process_lvm_data();
13078				@rows = lvm_output(\%processed);
13079			}
13080		}
13081		elsif ($use{'logical-lvm'} && $alerts{'lvs'}->{'action'} eq 'permissions'){
13082			my $key = 'Message';
13083			push(@rows, {
13084			main::key($num++,0,1,$key) => $alerts{'lvs'}->{'message'},
13085			},);
13086		}
13087		elsif (@lsblk && !$use{'logical-lvm'} && ($alerts{'lvs'}->{'action'} eq 'permissions' ||
13088		       $alerts{'lvs'}->{'action'} eq 'missing')){
13089			my $key = 'Message';
13090			push(@rows, {
13091			main::key($num++,0,1,$key) => main::row_defaults('logical-data',''),
13092			},);
13093		}
13094		elsif ($alerts{'lvs'}->{'action'} ne 'use'){
13095			$key1 = $alerts{'lvs'}->{'action'};
13096			$val1 = $alerts{'lvs'}->{'message'};
13097			$key1 = ucfirst($key1);
13098			@rows = ({main::key($num++,0,1,$key1) => $val1,});
13099		}
13100		if ($use{'logical-general'}){
13101			my @general_data = general_data();
13102			push(@rows,general_output(\@general_data)) if @general_data;
13103		}
13104	}
13105	eval $end if $b_log;
13106	return @rows;
13107}
13108sub general_output {
13109	eval $start if $b_log;
13110	my ($general_data) = @_;
13111	my ($size,@rows);
13112	my ($j,$num) = (0,0);
13113	# cryptsetup status luks-a00baac5-44ff-4b48-b303-3bedb1f623ce
13114	foreach my $item (sort {$a->{'type'} cmp $b->{'type'}} @$general_data){
13115		$j = scalar @rows;
13116		$size = ($item->{'size'}) ? main::get_size($item->{'size'}, 'string') : 'N/A';
13117		push(@rows,{
13118		main::key($num++,1,1,'Device') => $item->{'name'},
13119		});
13120		if ($b_admin){
13121			$item->{'name'} ||= 'N/A';
13122			$rows[$j]->{main::key($num++,0,2,'maj-min')} = $item->{'maj-min'};
13123		}
13124		$rows[$j]->{main::key($num++,0,2,'type')} = $item->{'type'};
13125		if ($extra > 0 && $item->{'dm'}){
13126			$rows[$j]->{main::key($num++,0,2,'dm')} = $item->{'dm'};
13127		}
13128		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
13129		my $b_fake;
13130		components_output('general',\$j,\$num,\@rows,\@{$item->{'components'}},\$b_fake);
13131	}
13132	eval $end if $b_log;
13133	return @rows;
13134}
13135sub lvm_output {
13136	eval $start if $b_log;
13137	my ($lvm_data) = @_;
13138	my (@rows);
13139	my ($size);
13140	my ($j,$num) = (0,0);
13141	foreach my $vg (sort keys %$lvm_data){
13142		$j = scalar @rows;
13143		# print Data::Dumper::Dumper $lvm_data->{$vg};
13144		$size = main::get_size($lvm_data->{$vg}{'vg-size'},'string','N/A');
13145		push(@rows,{
13146		main::key($num++,1,1,'Device') => '',
13147		main::key($num++,0,2,'VG') => $vg,
13148		main::key($num++,0,2,'type') => uc($lvm_data->{$vg}{'vg-format'}),
13149		main::key($num++,0,2,'size') => $size,
13150		},);
13151		$size = main::get_size($lvm_data->{$vg}{'vg-free'},'string','N/A');
13152		$rows[$j]->{main::key($num++,0,2,'free')} = $size;
13153		foreach my $lv (sort keys %{$lvm_data->{$vg}{'lvs'}}){
13154			next if $extra < 2 && $lv =~ /^\[/; # it's an internal vg lv, raid meta/image
13155			$j = scalar @rows;
13156			my $b_raid;
13157			$size = main::get_size($lvm_data->{$vg}{'lvs'}{$lv}{'lv-size'},'string','N/A');
13158			$rows[$j]->{main::key($num++,1,2,'LV')} = $lv;
13159			if ($b_admin && $lvm_data->{$vg}{'lvs'}{$lv}{'maj-min'}){
13160				$rows[$j]->{main::key($num++,0,3,'maj-min')} = $lvm_data->{$vg}{'lvs'}{$lv}{'maj-min'};
13161			}
13162			$rows[$j]->{main::key($num++,0,3,'type')} = $lvm_data->{$vg}{'lvs'}{$lv}{'lv-type'};
13163			if ($extra > 0 && $lvm_data->{$vg}{'lvs'}{$lv}{'dm'}){
13164				$rows[$j]->{main::key($num++,0,3,'dm')} = $lvm_data->{$vg}{'lvs'}{$lv}{'dm'};
13165			}
13166			$rows[$j]->{main::key($num++,0,3,'size')} = $size;
13167			if ($extra > 1 && !($show{'raid'} || $show{'raid-basic'}) && $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}){
13168				$j = scalar @rows;
13169				$rows[$j]->{main::key($num++,1,3,'RAID')} = '';
13170				$rows[$j]->{main::key($num++,0,4,'stripes')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'stripes'};
13171				$rows[$j]->{main::key($num++,0,4,'sync')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'sync'};
13172				my $copied = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'copied'};
13173				$copied = (defined $copied) ? ($copied + 0) . '%': 'N/A';
13174				$rows[$j]->{main::key($num++,0,4,'copied')} = $copied;
13175				$rows[$j]->{main::key($num++,0,4,'mismatches')} = $lvm_data->{$vg}{'lvs'}{$lv}{'raid'}{'mismatches'};
13176				$b_raid = 1;
13177			}
13178			components_output('lvm',\$j,\$num,\@rows,\@{$lvm_data->{$vg}{'lvs'}{$lv}{'components'}},\$b_raid);
13179		}
13180	}
13181	eval $end if $b_log;
13182	return @rows;
13183}
13184
13185sub components_output {
13186	my ($type,$j,$num,$rows,$components,$b_raid) = @_;
13187	my ($l1);
13188	$$j = scalar @$rows if $$b_raid || $extra > 1;
13189	$$b_raid = 0;
13190	if ($type eq 'general'){
13191		($l1) = (2);
13192	}
13193	elsif ($type eq 'lvm'){
13194		($l1) = (3);
13195	}
13196	my $status = (!@$components) ? 'N/A': '';
13197	$$rows[$$j]->{main::key($$num++,1,$l1,'Components')} = $status;
13198	components_recursive_output($type,$j,$num,$rows,$components,0,'c','p');
13199}
13200sub components_recursive_output {
13201	my ($type,$j,$num,$rows,$components,$indent,$c,$p) = @_;
13202	my ($l,$m,$size) = (1,1,0);
13203	my ($l2,$l3);
13204	if ($type eq 'general'){
13205		($l2,$l3) = (3+$indent,4+$indent) ;
13206	}
13207	elsif ($type eq 'lvm'){
13208		($l2,$l3) = (4+$indent,5+$indent);
13209	}
13210	# print 'outside: ', scalar @$component, "\n", Data::Dumper::Dumper $component;
13211	foreach my $component (@$components){
13212		# print "inside: -n", Data::Dumper::Dumper $component->[$i];
13213		$$j = scalar @$rows if $b_admin;
13214		my $id;
13215		if ($component->[0] =~ /^(bcache|dm-|md)[0-9]/){
13216			$id = $c .'-' . $m;
13217			$m++;
13218		}
13219		else {
13220			$id = $p . '-' . $l;
13221			$l++;
13222		}
13223		$$rows[$$j]->{main::key($$num++,1,$l2,$id)} = $component->[0];
13224		if ($extra > 1){
13225			if ($b_admin){
13226				$component->[1] ||= 'N/A';
13227				$$rows[$$j]->{main::key($$num++,0,$l3,'maj-min')} = $component->[1];
13228				$$rows[$$j]->{main::key($$num++,0,$l3,'mapped')} = $component->[3] if $component->[3];
13229				$size = main::get_size($component->[2],'string','N/A');
13230				$$rows[$$j]->{main::key($$num++,0,$l3,'size')} = $size;
13231			}
13232			#next if !$component->[$i][4];
13233			for (my $i = 4; $i < scalar @$component; $i++){
13234				components_recursive_output($type,$j,$num,$rows,$component->[$i],$indent+1,$c.'c',$p.'p');
13235			}
13236		}
13237	}
13238}
13239
13240# note: type dm is seen in only one dataset, but it's a start
13241sub general_data {
13242	eval $start if $b_log;
13243	my (@found,@general_data,%parent,$parent_fs);
13244	PartitionData::set('proc') if !$loaded{'partition-data'};
13245	main::set_mapper() if !$loaded{'mapper'};
13246	foreach my $row (@lsblk){
13247		# bcache doesn't have mapped name: !$mapper{$row->{'name'}} ||
13248		next if !$row->{'parent'};
13249		%parent = LsblkData::get($row->{'parent'});
13250		next if !$parent{'fs'};
13251		if ($row->{'type'} && (($row->{'type'} eq 'crypt' ||
13252		    $row->{'type'} eq 'mpath' || $row->{'type'} eq 'multipath')  ||
13253		    ($row->{'type'} eq 'dm' && $row->{'name'} =~ /veracrypt/i) ||
13254		    ($parent{'fs'} eq 'bcache'))){
13255			my (@full_components,$mapped,$type);
13256			$mapped = $mapper{$row->{'name'}} if %mapper;
13257			next if grep(/^$row->{'name'}$/, @found);
13258			push(@found,$row->{'name'});
13259			if ($parent{'fs'} eq 'crypto_LUKS'){
13260				$type = 'LUKS';
13261			}
13262			# note, testing name is random user string, and there is no other
13263			# ID known, the parent FS is '', empty.
13264			elsif ($row->{'type'} eq 'dm' && $row->{'name'} =~ /veracrypt/i){
13265				$type = 'VeraCrypt';
13266			}
13267			elsif ($row->{'type'} eq 'crypt'){
13268				$type = 'Crypto';
13269			}
13270			elsif ($parent{'fs'} eq 'bcache'){
13271				$type = 'bcache';
13272			}
13273			# probably only seen on older Redhat servers, LVM probably replaces
13274			elsif ($row->{'type'} eq 'mpath' || $row->{'type'} eq 'multipath'){
13275				$type = 'MultiPath';
13276			}
13277			elsif ($row->{'type'} eq 'crypt'){
13278				$type = 'Crypt';
13279			}
13280			# my $name = ($use{'filter-uuid'}) ? "luks-$filter_string" : $row->{'name'};
13281			component_data($row->{'maj-min'},\@full_components);
13282			# print "$row->{'name'}\n", Data::Dumper::Dumper \@full_components;
13283			push(@general_data, {
13284			'components' => \@full_components,
13285			'dm' => $mapped,
13286			'maj-min' => $row->{'maj-min'},
13287			'name' => $row->{'name'},
13288			'size' => $row->{'size'},
13289			'type' => $type,
13290			});
13291		}
13292	}
13293	main::log_data('dump','luks @general_data', \@general_data);
13294	print Data::Dumper::Dumper \@general_data if $dbg[23];
13295	eval $end if $b_log;
13296	return @general_data;
13297}
13298
13299# note: called for disk totals, raid, and logical
13300sub lvm_data {
13301	eval $start if $b_log;
13302	$loaded{'logical-data'} = 1;
13303	my (@args,@data,%totals);
13304	@args = qw(vg_name vg_fmt vg_size vg_free lv_name lv_layout lv_size
13305	lv_kernel_major lv_kernel_minor segtype seg_count seg_start_pe seg_size_pe
13306	stripes devices raid_mismatch_count raid_sync_action raid_write_behind
13307	copy_percent);
13308	my $num = 0;
13309	PartitionData::set() if !$loaded{'partition-data'};
13310	main::set_mapper() if !$loaded{'mapper'};
13311	if ($fake{'logical'}){
13312		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lvm/lvs-test-1.txt";
13313		# @data = main::reader($file,'strip');
13314	}
13315	else {
13316		# lv_full_name: ar0-home; lv_dm_path: /dev/mapper/ar0-home
13317		# seg_size: unit location on volume where segement starts
13318		#   2>/dev/null -unit k  ---separator ^:
13319		my $cmd = $alerts{'lvs'}->{'path'};
13320		$cmd .= ' -aPv --unit k --separator "^:" --segments --noheadings -o ';
13321		# $cmd .= ' -o +lv_size,pv_major,pv_minor  2>/dev/null';
13322		$cmd .= join(',', @args);
13323		$cmd .= ' 2>/dev/null';
13324		@data = main::grabber("$cmd",'','strip');
13325		main::log_data('dump','lvm @data', \@data) if $b_log;
13326		print "command: $cmd\n" if $dbg[22];
13327	}
13328	my $j = 0;
13329	foreach (@data){
13330		my @line = split(/\^:/, $_);
13331		next if $_ =~ /^Partial mode/i; # sometimes 2>/dev/null doesn't catch this
13332		for (my $i = 0; $i < scalar @args; $i++){
13333			$line[$i] =~ s/k$// if $args[$i] =~ /_(free|size|used)$/;
13334			$lvm[$j]->{$args[$i]} = $line[$i];
13335		}
13336		if (!$totals{'vgs'}->{$lvm[$j]->{'vg_name'}}){
13337			$totals{'vgs'}->{$lvm[$j]->{'vg_name'}} = $lvm[$j]->{'vg_size'};
13338			$raw_logical[2] += $lvm[$j]->{'vg_free'} if $lvm[$j]->{'vg_free'};
13339		}
13340		$j++;
13341	}
13342	# print Data::Dumper::Dumper \%totals, \@raw_logical;
13343	main::log_data('dump','lvm @lvm', \@lvm) if $b_log;
13344	print Data::Dumper::Dumper \@lvm if $dbg[22];
13345	eval $end if $b_log;
13346}
13347sub process_lvm_data {
13348	eval $start if $b_log;
13349	my (%processed);
13350	foreach my $item (@lvm){
13351		my (@components,@devices,$dm,$dm_tmp,$dm_mm,@full_components,$maj_min,%raid,@temp);
13352		if (!$processed{$item->{'vg_name'}}){
13353			$processed{$item->{'vg_name'}}->{'vg-size'} = $item->{'vg_size'};
13354			$processed{$item->{'vg_name'}}->{'vg-free'} = $item->{'vg_free'};
13355			$processed{$item->{'vg_name'}}->{'vg-format'} = $item->{'vg_fmt'};
13356		}
13357		if (!$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}){
13358			$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'lv-size'} = $item->{'lv_size'};
13359			$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'lv-type'} = $item->{'segtype'};
13360			$maj_min = $item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'};
13361			$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'maj-min'} = $maj_min;
13362			$dm_tmp = $item->{'vg_name'} . '-' . $item->{'lv_name'};
13363			$dm_tmp =~ s/\[|\]$//g;
13364			$dm = $mapper{$dm_tmp} if %mapper;
13365			$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'dm'} = $dm;
13366			if ($item->{'segtype'} && $item->{'segtype'} ne 'linear' && $item->{'segtype'} =~ /^raid/){
13367				$raid{'copied'} = $item->{'copy_percent'};
13368				$raid{'mismatches'} = $item->{'raid_mismatch_count'};
13369				$raid{'stripes'} = $item->{'stripes'};
13370				$raid{'sync'} = $item->{'raid_sync_action'};
13371				$raid{'type'} = $item->{'segtype'};
13372				$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'raid'} = \%raid;
13373			}
13374			component_data($maj_min,\@full_components);
13375			# print "$item->{'lv_name'}\n", Data::Dumper::Dumper \@full_components;
13376			$processed{$item->{'vg_name'}}->{'lvs'}{$item->{'lv_name'}}{'components'} = \@full_components;
13377		}
13378	}
13379	main::log_data('dump','lvm %processed', \%processed) if $b_log;
13380	print Data::Dumper::Dumper \%processed if $dbg[23];
13381	eval $end if $b_log;
13382	return %processed;
13383}
13384sub component_data {
13385	my ($maj_min,$full_components) = @_;
13386	push(@$full_components, component_recursive_data($maj_min));
13387}
13388sub component_recursive_data {
13389	eval $start if $b_log;
13390	my ($maj_min) = @_;
13391	my (@components,@devices);
13392	@devices = main::globber("/sys/dev/block/$maj_min/slaves/*") if -e "/sys/dev/block/$maj_min/slaves";
13393	@devices = map {$_ =~ s|^/.*/||; $_;} @devices if @devices;
13394	# return @devices if !$b_admin;
13395	foreach my $device (@devices){
13396		my ($mapped,$mm2,@part);
13397		@part = PartitionData::get($device) if @proc_partitions;
13398		$mm2 = $part[0] . ':' . $part[1] if @part;
13399		if ($device =~ /^(bcache|dm-|md)[0-9]+$/){
13400			$mapped = $dmmapper{$device};
13401			$raw_logical[1] += $part[2] if $mapped && $mapped =~ /_(cdata|cmeta)$/;
13402			push(@components, [$device,$mm2,$part[2],$mapped,[component_recursive_data($mm2)]]);
13403		}
13404		else {
13405			push(@components,[$device,$mm2,$part[2]]);
13406		}
13407	}
13408	eval $end if $b_log;
13409	return @components;
13410}
13411}
13412
13413## MachineItem
13414{
13415package MachineItem;
13416
13417sub get {
13418	eval $start if $b_log;
13419	my (%soc_machine,%data,@rows,$key1,$val1,$which);
13420	my $num = 0;
13421	if ($bsd_type && $sysctl{'machine'} && !$force{'dmidecode'}){
13422		%data = machine_data_sysctl();
13423		if (%data){
13424			@rows = machine_output(\%data);
13425		}
13426		elsif (!$key1){
13427			$key1 = 'Message';
13428			$val1 = main::row_defaults('machine-data-force-dmidecode','');
13429		}
13430	}
13431	elsif ($bsd_type || $force{'dmidecode'}){
13432		if (!$fake{'dmidecode'} && $alerts{'dmidecode'}->{'action'} ne 'use'){
13433			$key1 = $alerts{'dmidecode'}->{'action'};
13434			$val1 = $alerts{'dmidecode'}->{'message'};
13435			$key1 = ucfirst($key1);
13436		}
13437		else {
13438			%data = machine_data_dmi();
13439			if (%data){
13440				@rows = machine_output(\%data);
13441			}
13442			elsif (!$key1){
13443				$key1 = 'Message';
13444				$val1 = main::row_defaults('machine-data');
13445			}
13446		}
13447	}
13448	elsif (-d '/sys/class/dmi/id/'){
13449		%data = machine_data_sys();
13450		if (%data){
13451			@rows = machine_output(\%data);
13452		}
13453		else {
13454			$key1 = 'Message';
13455			if ($alerts{'dmidecode'}->{'action'} eq 'missing'){
13456				$val1 = main::row_defaults('machine-data-dmidecode');
13457			}
13458			else {
13459				$val1 = main::row_defaults('machine-data');
13460			}
13461		}
13462	}
13463	elsif (!$bsd_type){
13464		# this uses /proc/cpuinfo so only GNU/Linux
13465		if ($b_arm || $b_mips || $b_ppc){
13466			%data = machine_data_soc();
13467			@rows = machine_soc_output(\%data) if %data;
13468		}
13469		if (!%data){
13470			$key1 = 'Message';
13471			$val1 = main::row_defaults('machine-data-force-dmidecode','');
13472		}
13473	}
13474	# if error case, null data, whatever
13475	if ($key1){
13476		push(@rows,{main::key($num++,0,1,$key1) => $val1,});
13477	}
13478	eval $end if $b_log;
13479	return @rows;
13480}
13481## keys for machine data are:
13482# 0-sys_vendor 1-product_name 2-product_version 3-product_serial 4-product_uuid
13483# 5-board_vendor 6-board_name 7-board_version 8-board_serial
13484# 9-bios_vendor 10-bios_version 11-bios_date
13485## with extra data:
13486# 12-chassis_vendor 13-chassis_type 14-chassis_version 15-chassis_serial
13487## unused: 16-bios_rev  17-bios_romsize 18 - firmware type
13488sub machine_output {
13489	eval $start if $b_log;
13490	my ($data) = @_;
13491	my (@rows);
13492	my $firmware = 'BIOS';
13493	my $num = 0;
13494	my $j = 0;
13495	my ($b_chassis,$b_skip_chassis,$b_skip_system);
13496	my ($bios_date,$bios_rev,$bios_romsize,$bios_vendor,$bios_version,$chassis_serial,
13497	$chassis_type,$chassis_vendor,$chassis_version,$mobo_model,$mobo_serial,$mobo_vendor,
13498	$mobo_version,$product_name,$product_serial,$product_version,$system_vendor);
13499# 	foreach my $key (keys %data){
13500# 		print "$key: $data->{$key}\n";
13501# 	}
13502	if (!$data->{'sys_vendor'} ||
13503	    ($data->{'board_vendor'} && $data->{'sys_vendor'} eq $data->{'board_vendor'} &&
13504	     !$data->{'product_name'} && !$data->{'product_version'} && !$data->{'product_serial'})){
13505		$b_skip_system = 1;
13506	}
13507	# The goal here is to not show laptop/mobile devices
13508	# found a case of battery existing but having nothing in it on desktop mobo
13509	# not all laptops show the first. /proc/acpi/battery is deprecated.
13510	elsif (!glob('/proc/acpi/battery/*') && !glob('/sys/class/power_supply/*')){
13511		# ibm / ibm can be true; dell / quantum is false, so in other words, only do this
13512		# in case where the vendor is the same and the version is the same and not null,
13513		# otherwise the version information is going to be different in all cases I think
13514		if (($data->{'sys_vendor'} && $data->{'board_vendor'} &&
13515		      $data->{'sys_vendor'} eq $data->{'board_vendor'}) &&
13516			 (($data->{'product_version'} && $data->{'board_version'} &&
13517			   $data->{'product_version'} eq $data->{'board_version'}) ||
13518			   (!$data->{'product_version'} && $data->{'product_name'} && $data->{'board_name'} &&
13519			     $data->{'product_name'} eq $data->{'board_name'}))){
13520			$b_skip_system = 1;
13521		}
13522	}
13523	$data->{'device'} ||= 'N/A';
13524	$j = scalar @rows;
13525	push(@rows, {
13526	main::key($num++,0,1,'Type') => ucfirst($data->{'device'}),
13527	},);
13528	if (!$b_skip_system){
13529		# this has already been tested for above so we know it's not null
13530		$system_vendor = main::cleaner($data->{'sys_vendor'});
13531		$product_name = ($data->{'product_name'}) ? $data->{'product_name'}:'N/A';
13532		$product_version = ($data->{'product_version'}) ? $data->{'product_version'}:'N/A';
13533		$product_serial = main::apply_filter($data->{'product_serial'});
13534		$rows[$j]->{main::key($num++,1,1,'System')} = $system_vendor;
13535		$rows[$j]->{main::key($num++,0,2,'product')} = $product_name;
13536		$rows[$j]->{main::key($num++,0,2,'v')} = $product_version;
13537		$rows[$j]->{main::key($num++,0,2,'serial')} = $product_serial;
13538		# no point in showing chassis if system isn't there, it's very unlikely that
13539		# would be correct
13540		if ($extra > 1){
13541			if ($data->{'board_version'} && $data->{'chassis_version'} &&
13542			 $data->{'chassis_version'} eq $data->{'board_version'}){
13543				$b_skip_chassis = 1;
13544			}
13545			if (!$b_skip_chassis && $data->{'chassis_vendor'}){
13546				if ($data->{'chassis_vendor'} ne $data->{'sys_vendor'}){
13547					$chassis_vendor = $data->{'chassis_vendor'};
13548				}
13549				# dmidecode can have these be the same
13550				if ($data->{'chassis_type'} && $data->{'device'} ne $data->{'chassis_type'}){
13551					$chassis_type = $data->{'chassis_type'};
13552				}
13553				if ($data->{'chassis_version'}){
13554					$chassis_version = $data->{'chassis_version'};
13555					$chassis_version =~ s/^v([0-9])/$1/i;
13556				}
13557				$chassis_serial = main::apply_filter($data->{'chassis_serial'});
13558				$chassis_vendor ||= '';
13559				$chassis_type ||= '';
13560				$rows[$j]->{main::key($num++,1,1,'Chassis')} = $chassis_vendor;
13561				if ($chassis_type){
13562					$rows[$j]->{main::key($num++,0,2,'type')} = $chassis_type;
13563				}
13564				if ($chassis_version){
13565					$rows[$j]->{main::key($num++,0,2,'v')} = $chassis_version;
13566				}
13567				$rows[$j]->{main::key($num++,0,2,'serial')} = $chassis_serial;
13568			}
13569		}
13570		$j++; # start new row
13571	}
13572	if ($data->{'firmware'}){
13573		$firmware = $data->{'firmware'};
13574	}
13575	$mobo_vendor = ($data->{'board_vendor'}) ? main::cleaner($data->{'board_vendor'}) : 'N/A';
13576	$mobo_model = ($data->{'board_name'}) ? $data->{'board_name'}: 'N/A';
13577	$mobo_version = ($data->{'board_version'})? $data->{'board_version'} : '';
13578	$mobo_serial = main::apply_filter($data->{'board_serial'});
13579	$bios_vendor = ($data->{'bios_vendor'}) ? main::cleaner($data->{'bios_vendor'}) : 'N/A';
13580	if ($data->{'bios_version'}){
13581		$bios_version = $data->{'bios_version'};
13582		$bios_version =~ s/^v([0-9])/$1/i;
13583		if ($data->{'bios_rev'}){
13584			$bios_rev = $data->{'bios_rev'};
13585		}
13586	}
13587	$bios_version ||= 'N/A';
13588	if ($data->{'bios_date'}){
13589		$bios_date = $data->{'bios_date'};
13590	}
13591	$bios_date ||= 'N/A';
13592	if ($extra > 1 && $data->{'bios_romsize'}){
13593		$bios_romsize = $data->{'bios_romsize'};
13594	}
13595	$rows[$j]->{main::key($num++,1,1,'Mobo')} = $mobo_vendor;
13596	$rows[$j]->{main::key($num++,0,2,'model')} = $mobo_model;
13597	if ($mobo_version){
13598		$rows[$j]->{main::key($num++,0,2,'v')} = $mobo_version;
13599	}
13600	$rows[$j]->{main::key($num++,0,2,'serial')} = $mobo_serial;
13601	if ($extra > 2 && $data->{'board_uuid'}){
13602		$rows[$j]->{main::key($num++,0,2,'uuid')} = $data->{'board_uuid'};
13603	}
13604	$rows[$j]->{main::key($num++,1,1,$firmware)} = $bios_vendor;
13605	$rows[$j]->{main::key($num++,0,2,'v')} = $bios_version;
13606	if ($bios_rev){
13607		$rows[$j]->{main::key($num++,0,2,'rev')} = $bios_rev;
13608	}
13609	$rows[$j]->{main::key($num++,0,2,'date')} = $bios_date;
13610	if ($bios_romsize){
13611		$rows[$j]->{main::key($num++,0,2,'rom size')} = $bios_romsize;
13612	}
13613	eval $end if $b_log;
13614	return @rows;
13615}
13616sub machine_soc_output {
13617	my ($soc_machine) = @_;
13618	my ($key,@rows);
13619	my ($cont_sys,$ind_sys,$j,$num) = (1,1,0,0);
13620	# print Data::Dumper::Dumper \%soc_machine;
13621	# this is sketchy, /proc/device-tree/model may be similar to Hardware value from /proc/cpuinfo
13622	# raspi: Hardware	: BCM2835 model: Raspberry Pi Model B Rev 2
13623	if ($soc_machine->{'device'} || $soc_machine->{'model'}){
13624		if ($b_arm){$key = 'ARM Device'}
13625		elsif ($b_mips){$key = 'MIPS Device'}
13626		elsif ($b_ppc){$key = 'PowerPC Device'}
13627		$rows[$j]->{main::key($num++,0,1,'Type')} = $key;
13628		my $system = 'System';
13629		if (defined $soc_machine->{'model'}){
13630			$rows[$j]->{main::key($num++,1,1,'System')} = $soc_machine->{'model'};
13631			$system = 'details';
13632			($cont_sys,$ind_sys) = (0,2);
13633		}
13634		$soc_machine->{'device'} ||= 'N/A';
13635		$rows[$j]->{main::key($num++,$cont_sys,$ind_sys,$system)} = $soc_machine->{'device'};
13636	}
13637	# we're going to print N/A for 0000 values sine the item was there.
13638	if ($soc_machine->{'firmware'}){
13639		# most samples I've seen are like: 0000
13640		$soc_machine->{'firmware'} =~ s/^[0]+$//;
13641		$soc_machine->{'firmware'} ||= 'N/A';
13642		$rows[$j]->{main::key($num++,0,2,'rev')} = $soc_machine->{'firmware'};
13643	}
13644	# sometimes has value like: 0000
13645	if (defined $soc_machine->{'serial'}){
13646		# most samples I've seen are like: 0000
13647		$soc_machine->{'serial'} =~ s/^[0]+$//;
13648		$rows[$j]->{main::key($num++,0,2,'serial')} = main::apply_filter($soc_machine->{'serial'});
13649	}
13650	eval $end if $b_log;
13651	return @rows;
13652}
13653
13654sub machine_data_sys {
13655	eval $start if $b_log;
13656	my (%data,$path,$vm);
13657	my $sys_dir = '/sys/class/dmi/id/';
13658	my $sys_dir_alt = '/sys/devices/virtual/dmi/id/';
13659	my @sys_files = qw(bios_vendor bios_version bios_date
13660	board_name board_serial board_vendor board_version chassis_type
13661	product_name product_serial product_uuid product_version sys_vendor
13662	);
13663	if ($extra > 1){
13664		splice(@sys_files, 0, 0, qw(chassis_serial chassis_vendor chassis_version));
13665	}
13666	$data{'firmware'} = 'BIOS';
13667	# print Data::Dumper::Dumper \@sys_files;
13668	if (!-d $sys_dir){
13669		if (-d $sys_dir_alt){
13670			$sys_dir = $sys_dir_alt;
13671		}
13672		else {
13673			return 0;
13674		}
13675	}
13676	if (-d '/sys/firmware/efi'){
13677		$data{'firmware'} = 'UEFI';
13678	}
13679	elsif (glob('/sys/firmware/acpi/tables/UEFI*')){
13680		$data{'firmware'} = 'UEFI-[Legacy]';
13681	}
13682	foreach (@sys_files){
13683		$path = "$sys_dir$_";
13684		if (-r $path){
13685			$data{$_} = main::reader($path,'',0);
13686			$data{$_} = ($data{$_}) ? main::dmi_cleaner($data{$_}) : '';
13687		}
13688		elsif (!$b_root && -e $path && !-r $path){
13689			$data{$_} = main::row_defaults('root-required');
13690		}
13691		else {
13692			$data{$_} = '';
13693		}
13694	}
13695	if ($data{'chassis_type'}){
13696		if ($data{'chassis_type'} == 1){
13697			$data{'device'} = get_device_vm($data{'sys_vendor'},$data{'product_name'});
13698			$data{'device'} ||= 'other-vm?';
13699		}
13700		else {
13701			$data{'device'} = get_device_sys($data{'chassis_type'});
13702		}
13703	}
13704# 	print "sys:\n";
13705# 	foreach (keys %data){
13706# 		print "$_: $data{$_}\n";
13707# 	}
13708	print Data::Dumper::Dumper \%data if $dbg[28];
13709	main::log_data('dump','%data',\%data) if $b_log;
13710	eval $end if $b_log;
13711	return %data;
13712}
13713# this will create an alternate machine data source
13714# which will be used for alt ARM machine data in cases
13715# where no dmi data present, or by cpu data to guess at
13716# certain actions for arm only.
13717sub machine_data_soc {
13718	eval $end if $b_log;
13719	my (%data,@temp);
13720	if (my $file = $system_files{'proc-cpuinfo'}){
13721		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/cpu/arm/arm-shevaplug-1.2ghz.txt";
13722		my @data = main::reader($file);
13723		foreach (@data){
13724			if (/^(Hardware|machine)\s*:/i){
13725				@temp = split(/\s*:\s*/, $_, 2);
13726				$temp[1] = main::arm_cleaner($temp[1]);
13727				$temp[1] = main::dmi_cleaner($temp[1]);
13728				$data{'device'} = main::cleaner($temp[1]);
13729			}
13730			elsif (/^(system type|model)\s*:/i){
13731				@temp = split(/\s*:\s*/, $_, 2);
13732				$temp[1] = main::dmi_cleaner($temp[1]);
13733				$data{'model'} = main::cleaner($temp[1]);
13734			}
13735			elsif (/^Revision/i){
13736				@temp = split(/\s*:\s*/, $_, 2);
13737				$data{'firmware'} = $temp[1];
13738			}
13739			elsif (/^Serial/i){
13740				@temp = split(/\s*:\s*/, $_, 2);
13741				$data{'serial'} = $temp[1];
13742			}
13743		}
13744	}
13745	if (!$data{'model'} && $b_android){
13746		main::set_build_prop() if !$loaded{'build-prop'};
13747		if ($build_prop{'product-manufacturer'} && $build_prop{'product-model'}){
13748			my $brand = '';
13749			if ($build_prop{'product-brand'} &&
13750			 $build_prop{'product-brand'} ne $build_prop{'product-manufacturer'}){
13751				$brand = $build_prop{'product-brand'} . ' ';
13752			}
13753			$data{'model'} = $brand . $build_prop{'product-manufacturer'} . ' ' . $build_prop{'product-model'};
13754		}
13755		elsif ($build_prop{'product-device'}){
13756			$data{'model'} = $build_prop{'product-device'};
13757		}
13758		elsif ($build_prop{'product-name'}){
13759			$data{'model'} = $build_prop{'product-name'};
13760		}
13761	}
13762	if (!$data{'model'} && -r '/proc/device-tree/model'){
13763		my $model  = main::reader('/proc/device-tree/model','',0);
13764		main::log_data('data',"device-tree-model: $model") if $b_log;
13765		if ($model){
13766			$model = main::dmi_cleaner($model);
13767			$model = (split(/\x01|\x02|\x03|\x00/, $model))[0] if $model;
13768			my $device_temp = main::regex_cleaner($data{'device'});
13769			if (!$data{'device'} || ($model && $model !~ /\Q$device_temp\E/i)){
13770				$model = main::arm_cleaner($model);
13771				$data{'model'} = $model;
13772			}
13773		}
13774	}
13775	if (!$data{'serial'} && -f '/proc/device-tree/serial-number'){
13776		my $serial  = main::reader('/proc/device-tree/serial-number','',0);
13777		$serial = (split(/\x01|\x02|\x03|\x00/, $serial))[0] if $serial;
13778		main::log_data('data',"device-tree-serial: $serial") if $b_log;
13779		$data{'serial'} = $serial if $serial;
13780	}
13781	print Data::Dumper::Dumper \%data if $dbg[28];
13782	main::log_data('dump','%data',\%data) if $b_log;
13783	eval $end if $b_log;
13784	return %data;
13785}
13786
13787# bios_date: 09/07/2010
13788# bios_romsize: dmi only
13789# bios_vendor: American Megatrends Inc.
13790# bios_version: P1.70
13791# bios_rev: 8.14:  dmi only
13792# board_name: A770DE+
13793# board_serial:
13794# board_vendor: ASRock
13795# board_version:
13796# chassis_serial:
13797# chassis_type: 3
13798# chassis_vendor:
13799# chassis_version:
13800# firmware:
13801# product_name:
13802# product_serial:
13803# product_uuid:
13804# product_version:
13805# sys_uuid: dmi/sysctl only
13806# sys_vendor:
13807sub machine_data_dmi {
13808	eval $start if $b_log;
13809	my (%data,$vm);
13810	return if ! @dmi;
13811	$data{'firmware'} = 'BIOS';
13812	# dmi types:
13813	# 0 bios; 1 system info; 2 board|base board info; 3 chassis info;
13814	# 4 processor info, use to check for hypervisor
13815	foreach my $row (@dmi){
13816		# bios/firmware
13817		if ($row->[0] == 0){
13818			# skip first three row, we don't need that data
13819			foreach my $item (@$row[3 .. $#$row]){
13820				if ($item !~ /^~/){ # skip the indented rows
13821					my @value = split(/:\s+/, $item);
13822					if ($value[0] eq 'Release Date'){
13823						$data{'bios_date'} = main::dmi_cleaner($value[1]) }
13824					elsif ($value[0] eq 'Vendor'){
13825						$data{'bios_vendor'} = main::dmi_cleaner($value[1]) }
13826					elsif ($value[0] eq 'Version'){
13827						$data{'bios_version'} = main::dmi_cleaner($value[1]) }
13828					elsif ($value[0] eq 'ROM Size'){
13829						$data{'bios_romsize'} = main::dmi_cleaner($value[1]) }
13830					elsif ($value[0] eq 'BIOS Revision'){
13831						$data{'bios_rev'} = main::dmi_cleaner($value[1]) }
13832				}
13833				else {
13834					if ($item eq '~UEFI is supported'){
13835						$data{'firmware'} = 'UEFI';}
13836				}
13837			}
13838			next;
13839		}
13840		# system information
13841		elsif ($row->[0] == 1){
13842			# skip first three row, we don't need that data
13843			foreach my $item (@$row[3 .. $#$row]){
13844				if ($item !~ /^~/){ # skip the indented rows
13845					my @value = split(/:\s+/, $item);
13846					if ($value[0] eq 'Product Name'){
13847						$data{'product_name'} = main::dmi_cleaner($value[1]) }
13848					elsif ($value[0] eq 'Version'){
13849						$data{'product_version'} = main::dmi_cleaner($value[1]) }
13850					elsif ($value[0] eq 'Serial Number'){
13851						$data{'product_serial'} = main::dmi_cleaner($value[1]) }
13852					elsif ($value[0] eq 'Manufacturer'){
13853						$data{'sys_vendor'} = main::dmi_cleaner($value[1]) }
13854					elsif ($value[0] eq 'UUID'){
13855						$data{'sys_uuid'} = main::dmi_cleaner($value[1]) }
13856				}
13857			}
13858			next;
13859		}
13860		# baseboard information
13861		elsif ($row->[0] == 2){
13862			# skip first three row, we don't need that data
13863			foreach my $item (@$row[3 .. $#$row]){
13864				if ($item !~ /^~/){ # skip the indented rows
13865					my @value = split(/:\s+/, $item);
13866					if ($value[0] eq 'Product Name'){
13867						$data{'board_name'} = main::dmi_cleaner($value[1]) }
13868					elsif ($value[0] eq 'Serial Number'){
13869						$data{'board_serial'} = main::dmi_cleaner($value[1]) }
13870					elsif ($value[0] eq 'Manufacturer'){
13871						$data{'board_vendor'} = main::dmi_cleaner($value[1]) }
13872					elsif ($value[0] eq 'Version'){
13873						$data{'board_version'} = main::dmi_cleaner($value[1]) }
13874				}
13875			}
13876			next;
13877		}
13878		# chassis information
13879		elsif ($row->[0] == 3){
13880			# skip first three row, we don't need that data
13881			foreach my $item (@$row[3 .. $#$row]){
13882				if ($item !~ /^~/){ # skip the indented rows
13883					my @value = split(/:\s+/, $item);
13884					if ($value[0] eq 'Serial Number'){
13885						$data{'chassis_serial'} = main::dmi_cleaner($value[1]) }
13886					elsif ($value[0] eq 'Type'){
13887						$data{'chassis_type'} = main::dmi_cleaner($value[1]) }
13888					elsif ($value[0] eq 'Manufacturer'){
13889						$data{'chassis_vendor'} = main::dmi_cleaner($value[1]) }
13890					elsif ($value[0] eq 'Version'){
13891						$data{'chassis_version'} = main::dmi_cleaner($value[1]) }
13892				}
13893			}
13894			if ($data{'chassis_type'} && $data{'chassis_type'} ne 'Other'){
13895				$data{'device'} = $data{'chassis_type'};
13896			}
13897			next;
13898		}
13899		# this may catch some BSD and fringe Linux cases
13900		# processor information: check for hypervisor
13901		elsif ($row->[0] == 4){
13902			# skip first three row, we don't need that data
13903			if (!$data{'device'}){
13904				if (grep {/hypervisor/i} @$row){
13905					$data{'device'} = 'virtual-machine';
13906				}
13907			}
13908			last;
13909		}
13910		elsif ($row->[0] > 4){
13911			last;
13912		}
13913	}
13914	if (!$data{'device'}){
13915		$data{'device'} = get_device_vm($data{'sys_vendor'},$data{'product_name'});
13916		$data{'device'} ||= 'other-vm?';
13917	}
13918# 	print "dmi:\n";
13919# 	foreach (keys %data){
13920# 		print "$_: $data{$_}\n";
13921# 	}
13922	print Data::Dumper::Dumper \%data if $dbg[28];
13923	main::log_data('dump','%data',\%data) if $b_log;
13924	eval $end if $b_log;
13925	return %data;
13926}
13927# As far as I know, only OpenBSD supports this method.
13928# it uses hw. info from sysctl -a and bios info from dmesg.boot
13929sub machine_data_sysctl {
13930	eval $start if $b_log;
13931	my (%data,$product,$vendor,$vm);
13932	# ^hw\.(vendor|product|version|serialno|uuid)
13933	foreach (@{$sysctl{'machine'}}){
13934		next if !$_;
13935		my @item = split(':', $_);
13936		next if !$item[1];
13937		if ($item[0] eq 'hw.vendor' || $item[0] eq 'machdep.dmi.board-vendor'){
13938			$data{'board_vendor'} = main::dmi_cleaner($item[1]);
13939		}
13940		elsif ($item[0] eq 'hw.product' || $item[0] eq 'machdep.dmi.board-product'){
13941			$data{'board_name'} = main::dmi_cleaner($item[1]);
13942		}
13943		elsif ($item[0] eq 'hw.version' || $item[0] eq 'machdep.dmi.board-version'){
13944			$data{'board_version'} = main::dmi_cleaner($item[1]);
13945		}
13946		elsif ($item[0] eq 'hw.serialno' || $item[0] eq 'machdep.dmi.board-serial'){
13947			$data{'board_serial'} = main::dmi_cleaner($item[1]);
13948		}
13949		elsif ($item[0] eq 'hw.serial'){
13950			$data{'board_serial'} = main::dmi_cleaner($item[1]);
13951		}
13952		elsif ($item[0] eq 'hw.uuid'){
13953			$data{'board_uuid'} = main::dmi_cleaner($item[1]);
13954		}
13955		elsif ($item[0] eq 'machdep.dmi.system-vendor'){
13956			$data{'sys_vendor'} = main::dmi_cleaner($item[1]);
13957		}
13958		elsif ($item[0] eq 'machdep.dmi.system-product'){
13959			$data{'product_name'} = main::dmi_cleaner($item[1]);
13960		}
13961		elsif ($item[0] eq 'machdep.dmi.system-version'){
13962			$data{'product_version'} = main::dmi_cleaner($item[1]);
13963		}
13964		elsif ($item[0] eq 'machdep.dmi.system-serial'){
13965			$data{'product_serial'} = main::dmi_cleaner($item[1]);
13966		}
13967		elsif ($item[0] eq 'machdep.dmi.system-uuid'){
13968			$data{'sys_uuid'} = main::dmi_cleaner($item[1]);
13969		}
13970		# bios0:at mainbus0: AT/286+ BIOS, date 06/30/06, BIOS32 rev. 0 @ 0xf2030, SMBIOS rev. 2.4 @ 0xf0000 (47 entries)
13971		# bios0:vendor Phoenix Technologies, LTD version "3.00" date 06/30/2006
13972		elsif ($item[0] =~ /^bios[0-9]/){
13973			if ($_ =~ /^^bios[0-9]:at\s.*?\srev\.\s([\S]+)\s@.*/){
13974				$data{'bios_rev'} = $1;
13975				$data{'firmware'} = 'BIOS' if $_ =~ /BIOS/;
13976			}
13977			elsif ($item[1] =~ /^vendor\s(.*?)\sversion\s(.*?)\sdate\s([\S]+)/){
13978				$data{'bios_vendor'} = $1;
13979				$data{'bios_version'} = $2;
13980				$data{'bios_date'} = $3;
13981				$data{'bios_version'} =~ s/^v//i if $data{'bios_version'} && $data{'bios_version'} !~ /vi/i;
13982			}
13983		}
13984		elsif ($item[0] eq 'machdep.dmi.bios-vendor'){
13985			$data{'bios_vendor'} = main::dmi_cleaner($item[1]);
13986		}
13987		elsif ($item[0] eq 'machdep.dmi.bios-version'){
13988			$data{'bios_version'} = main::dmi_cleaner($item[1]);
13989		}
13990		elsif ($item[0] eq 'machdep.dmi.bios-date'){
13991			$data{'bios_date'} = main::dmi_cleaner($item[1]);
13992		}
13993	}
13994	if ($data{'board_vendor'} || $data{'sys_vendor'} || $data{'board_name'} || $data{'product_name'}){
13995		$vendor = $data{'sys_vendor'};
13996		$vendor = $data{'board_vendor'} if !$vendor;
13997		$product = $data{'product_name'};
13998		$product = $data{'board_name'} if !$product;
13999	}
14000	# detections can be from other sources.
14001	$data{'device'} = get_device_vm($vendor,$product);
14002	print Data::Dumper::Dumper \%data if $dbg[28];
14003	main::log_data('dump','%data',\%data) if $b_log;
14004	eval $end if $b_log;
14005	return %data;
14006}
14007
14008sub get_device_sys {
14009	eval $start if $b_log;
14010	my ($chasis_id) = @_;
14011	my ($device) = ('');
14012	my @chassis;
14013	# See inxi-resources MACHINE DATA for data sources
14014	$chassis[2] = 'unknown';
14015	$chassis[3] = 'desktop';
14016	$chassis[4] = 'desktop';
14017	# 5 - pizza box was a 1 U desktop enclosure, but some old laptops also id this way
14018	$chassis[5] = 'pizza-box';
14019	$chassis[6] = 'desktop';
14020	$chassis[7] = 'desktop';
14021	$chassis[8] = 'portable';
14022	$chassis[9] = 'laptop';
14023	# note: lenovo T420 shows as 10, notebook,  but it's not a notebook
14024	$chassis[10] = 'laptop';
14025	$chassis[11] = 'portable';
14026	$chassis[12] = 'docking-station';
14027	# note: 13 is all-in-one which we take as a mac type system
14028	$chassis[13] = 'desktop';
14029	$chassis[14] = 'notebook';
14030	$chassis[15] = 'desktop';
14031	$chassis[16] = 'laptop';
14032	$chassis[17] = 'server';
14033	$chassis[18] = 'expansion-chassis';
14034	$chassis[19] = 'sub-chassis';
14035	$chassis[20] = 'bus-expansion';
14036	$chassis[21] = 'peripheral';
14037	$chassis[22] = 'RAID';
14038	$chassis[23] = 'server';
14039	$chassis[24] = 'desktop';
14040	$chassis[25] = 'multimount-chassis'; # blade?
14041	$chassis[26] = 'compact-PCI';
14042	$chassis[27] = 'blade';
14043	$chassis[28] = 'blade';
14044	$chassis[29] = 'blade-enclosure';
14045	$chassis[30] = 'tablet';
14046	$chassis[31] = 'convertible';
14047	$chassis[32] = 'detachable';
14048	$chassis[33] = 'IoT-gateway';
14049	$chassis[34] = 'embedded-pc';
14050	$chassis[35] = 'mini-pc';
14051	$chassis[36] = 'stick-pc';
14052	$device = $chassis[$chasis_id] if $chassis[$chasis_id];
14053	eval $end if $b_log;
14054	return $device;
14055}
14056
14057sub get_device_vm {
14058	eval $start if $b_log;
14059	my ($manufacturer,$product_name) = @_;
14060	$manufacturer ||= '';
14061	$product_name ||= '';
14062	my $vm;
14063	if (my $program = main::check_program('systemd-detect-virt')){
14064		my $vm_test = (main::grabber("$program 2>/dev/null"))[0];
14065		if ($vm_test){
14066			# kvm vbox reports as oracle, usually, unless they change it
14067			if (lc($vm_test) eq 'oracle'){
14068				$vm = 'virtualbox';
14069			}
14070			elsif ($vm_test ne 'none'){
14071				$vm = $vm_test;
14072			}
14073		}
14074	}
14075	if (!$vm || lc($vm) eq 'bochs'){
14076		if (-e '/proc/vz'){$vm = 'openvz'}
14077		elsif (-e '/proc/xen'){$vm = 'xen'}
14078		elsif (-e '/dev/vzfs'){$vm = 'virtuozzo'}
14079		elsif (my $program = main::check_program('lsmod')){
14080			my @vm_data = main::grabber("$program 2>/dev/null");
14081			if (@vm_data){
14082				if (grep {/kqemu/i} @vm_data){$vm = 'kqemu'}
14083				elsif (grep {/kvm|qumranet/i} @vm_data){$vm = 'kvm'}
14084				elsif (grep {/qemu/i} @vm_data){$vm = 'qemu'}
14085			}
14086		}
14087	}
14088	# this will catch many Linux systems and some BSDs
14089	if (!$vm || lc($vm) eq 'bochs'){
14090		# $device_vm is '' if nothing detected
14091		my @vm_data = ($device_vm);
14092		push(@vm_data,@{$dboot{'machine-vm'}}) if $dboot{'machine-vm'};
14093		if (-e '/dev/disk/by-id'){
14094			my @dev = glob('/dev/disk/by-id/*');
14095			push(@vm_data,@dev);
14096		}
14097		if (grep {/innotek|vbox|virtualbox/i} @vm_data){
14098			$vm = 'virtualbox';
14099		}
14100		elsif (grep {/vmware/i} @vm_data){
14101			$vm = 'vmware';
14102		}
14103		# needs to be first, because contains virtio;qumranet, grabber only gets
14104		# first instance then stops, so make sure patterns are right.
14105		elsif (grep {/(openbsd[\s-]vmm)/i} @vm_data){
14106			$vm = 'vmm';
14107		}
14108		elsif (grep {/(\bhvm\b)/i} @vm_data){
14109			$vm = 'hvm';
14110		}
14111		elsif (grep {/(qemu)/i} @vm_data){
14112			$vm = 'qemu';
14113		}
14114		elsif (grep {/(\bkvm\b|qumranet|virtio)/i} @vm_data){
14115			$vm = 'kvm';
14116		}
14117		elsif (grep {/Virtual HD|Microsoft.*Virtual Machine/i} @vm_data){
14118			$vm = 'hyper-v';
14119		}
14120		if (!$vm && (my $file = $system_files{'proc-cpuinfo'})){
14121			my @info = main::reader($file);
14122			$vm = 'virtual-machine' if grep {/^flags.*hypervisor/} @info;
14123		}
14124		# this may be wrong, confirm it
14125		if (!$vm && -e '/dev/vda' || -e '/dev/vdb' || -e '/dev/xvda' || -e '/dev/xvdb'){
14126			$vm = 'virtual-machine';
14127		}
14128	}
14129	if (!$vm && $product_name){
14130		if ($product_name eq 'VMware'){
14131			$vm = 'vmware';
14132		}
14133		elsif ($product_name eq 'VirtualBox'){
14134			$vm = 'virtualbox';
14135		}
14136		elsif ($product_name eq 'KVM'){
14137			$vm = 'kvm';
14138		}
14139		elsif ($product_name eq 'Bochs'){
14140			$vm = 'qemu';
14141		}
14142	}
14143	if (!$vm && $manufacturer && $manufacturer eq 'Xen'){
14144		$vm = 'xen';
14145	}
14146	eval $end if $b_log;
14147	return $vm;
14148}
14149
14150}
14151
14152## NetworkItem
14153{
14154package NetworkItem;
14155my ($b_ip_run,@ifs_found);
14156sub get {
14157	eval $start if $b_log;
14158	my (@rows);
14159	my $num = 0;
14160	if (($b_arm || $b_mips) && !$use{'soc-network'} && !$use{'pci-tool'}){
14161		# do nothing, but keep the test conditions to force
14162		# the non arm case to always run
14163	}
14164	else {
14165		push(@rows,device_output());
14166	}
14167	push(@rows,usb_output());
14168	# note: raspberry pi uses usb networking only
14169	if (!@rows){
14170		if ($b_arm || $b_mips){
14171			my $type = ($b_arm) ? 'arm' : 'mips';
14172			my $key = 'Message';
14173			push(@rows, {
14174			main::key($num++,0,1,$key) => main::row_defaults($type . '-pci',''),
14175			},);
14176		}
14177		else {
14178			my $key = 'Message';
14179			my $type = 'pci-card-data';
14180			if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){
14181				$type = 'pci-card-data-root';
14182			}
14183			push(@rows,{
14184			main::key($num++,0,1,$key) => main::row_defaults($type,''),
14185			},);
14186		}
14187	}
14188	if ($show{'network-advanced'}){
14189		# @ifs_found = ();
14190		# shift @ifs_found;
14191		# pop @ifs_found;
14192		if (!$bsd_type){
14193			push(@rows,advanced_data_sys('check','',0,'','',''));
14194		}
14195		else {
14196			push(@rows,advanced_data_bsd('check'));
14197		}
14198	}
14199	if ($show{'ip'}){
14200		push(@rows,wan_ip());
14201	}
14202	eval $end if $b_log;
14203	return @rows;
14204}
14205
14206sub device_output {
14207	eval $start if $b_log;
14208	return if !$devices{'network'};
14209	my ($b_wifi,@rows,%holder);
14210	my ($j,$num) = (0,1);
14211	foreach my $row (@{$devices{'network'}}){
14212		$num = 1;
14213		# print "$row->[0] $row->[3]\n";
14214		# print "$row->[0] $row->[3]\n";
14215		$j = scalar @rows;
14216		my $driver = $row->[9];
14217		my $chip_id = main::get_chip_id($row->[5],$row->[6]);
14218		# working around a virtuo bug same chip id is used on two nics
14219		if (!defined $holder{$chip_id}){
14220			$holder{$chip_id} = 0;
14221		}
14222		else {
14223			$holder{$chip_id}++;
14224		}
14225		# first check if it's a known wifi id'ed card, if so, no print of duplex/speed
14226		$b_wifi = check_wifi($row->[4]);
14227		my $device = $row->[4];
14228		$device = ($device) ? main::pci_cleaner($device,'output') : 'N/A';
14229		#$device ||= 'N/A';
14230		$driver ||= 'N/A';
14231		push(@rows, {
14232		main::key($num++,1,1,'Device') => $device,
14233		},);
14234		if ($extra > 0 && $use{'pci-tool'} && $row->[12]){
14235			my $item = main::get_pci_vendor($row->[4],$row->[12]);
14236			$rows[$j]->{main::key($num++,0,2,'vendor')} = $item if $item;
14237		}
14238		if ($row->[1] eq '0680'){
14239			$rows[$j]->{main::key($num++,0,2,'type')} = 'network bridge';
14240		}
14241		$rows[$j]->{main::key($num++,1,2,'driver')} = $driver;
14242		my $bus_id = 'N/A';
14243		# note: for arm/mips we want to see the single item bus id, why not?
14244		# note: we can have bus id: 0002 / 0 which is valid, but 0 / 0 is invalid
14245		if (defined $row->[2] && $row->[2] ne '0' && defined $row->[3]){$bus_id = "$row->[2].$row->[3]"}
14246		elsif (defined $row->[2] && $row->[2] ne '0'){$bus_id = $row->[2]}
14247		elsif (defined $row->[3] && $row->[3] ne '0'){$bus_id = $row->[3]}
14248		if ($extra > 0){
14249			if ($row->[9] && !$bsd_type){
14250				my $version = main::get_module_version($row->[9]);
14251				$version ||= 'N/A';
14252				$rows[$j]->{main::key($num++,0,3,'v')} = $version;
14253			}
14254			if ($b_admin && $row->[10]){
14255				$row->[10] = main::get_driver_modules($row->[9],$row->[10]);
14256				$rows[$j]->{main::key($num++,0,3,'modules')} = $row->[10] if $row->[10];
14257			}
14258			$row->[8] ||= 'N/A';
14259			# as far as I know, wifi has no port, but in case it does in future, use it
14260			$rows[$j]->{main::key($num++,0,2,'port')} = $row->[8] if (!$b_wifi || ($b_wifi && $row->[8] ne 'N/A'));
14261			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = $bus_id;
14262		}
14263		if ($extra > 1){
14264			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $chip_id;
14265		}
14266		if ($extra > 2 && $row->[1]){
14267			$rows[$j]->{main::key($num++,0,2,'class-ID')} = $row->[1];
14268		}
14269		if ($show{'network-advanced'}){
14270			my @data;
14271			if (!$bsd_type){
14272				@data = advanced_data_sys($row->[5],$row->[6],$holder{$chip_id},$b_wifi,'',$bus_id);
14273			}
14274			else {
14275				@data = advanced_data_bsd("$row->[9]$row->[11]",$b_wifi) if defined $row->[9] && defined $row->[11];
14276			}
14277			push(@rows,@data) if @data;
14278		}
14279		# print "$row->[0]\n";
14280	}
14281	# @rows = ();
14282	# we want to handle ARM errors in main get
14283	if (!@rows && !$b_arm && !$b_mips){
14284		my $key = 'Message';
14285		my $type = 'pci-card-data';
14286		if ($pci_tool && $alerts{$pci_tool}->{'action'} eq 'permissions'){
14287			$type = 'pci-card-data-root';
14288		}
14289		push(@rows, {
14290		main::key($num++,0,1,$key) => main::row_defaults($type,''),
14291		},);
14292	}
14293	eval $end if $b_log;
14294	return @rows;
14295}
14296sub usb_output {
14297	eval $start if $b_log;
14298	return if !$usb{'network'};
14299	my (@rows,@temp2,$b_wifi,$driver,$path,$path_id,$product,$type);
14300	my ($j,$num) = (0,1);
14301	foreach my $row (@{$usb{'network'}}){
14302		$num = 1;
14303		($driver,$path,$path_id,$product,$type) = ('','','','','');
14304		$product = main::cleaner($row->[13]) if $row->[13];
14305		$driver = $row->[15] if $row->[15];
14306		$path = $row->[3] if $row->[3];
14307		$path_id = $row->[2] if $row->[2];
14308		$type = $row->[14] if $row->[14];
14309		$driver ||= 'N/A';
14310		push(@rows, {
14311		main::key($num++,1,1,'Device') => $product,
14312		main::key($num++,0,2,'type') => 'USB',
14313		main::key($num++,0,2,'driver') => $driver,
14314		},);
14315		$b_wifi = check_wifi($product);
14316		if ($extra > 0){
14317			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = "$path_id:$row->[1]";
14318		}
14319		if ($extra > 1){
14320			$row->[7] ||= 'N/A';
14321			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $row->[7];
14322		}
14323		if ($extra > 2 && defined $row->[5] && $row->[5] ne ''){
14324			$rows[$j]->{main::key($num++,0,2,'class-ID')} = "$row->[4]$row->[5]";
14325		}
14326		if ($extra > 2 && $row->[16]){
14327			$rows[$j]->{main::key($num++,0,2,'serial')} = main::apply_filter($row->[16]);
14328		}
14329		if ($show{'network-advanced'}){
14330			my @data;
14331			if (!$bsd_type){
14332				my (@temp,$vendor,$chip);
14333				@temp = split(':', $row->[7]) if $row->[7];
14334				($vendor,$chip) = ($temp[0],$temp[1]) if @temp;
14335				@data = advanced_data_sys($vendor,$chip,0,$b_wifi,$path,'');
14336			}
14337			# NOTE: we need the driver + driver nu, like wlp0 to get a match,
14338			else {
14339				$driver .= $row->[21] if defined $row->[21];
14340				@data = advanced_data_bsd($driver,$b_wifi);
14341			}
14342			push(@rows,@data) if @data;
14343		}
14344		$j = scalar @rows;
14345	}
14346	eval $end if $b_log;
14347	return @rows;
14348}
14349sub advanced_data_sys {
14350	eval $start if $b_log;
14351	return if ! -d '/sys/class/net';
14352	my ($vendor,$chip,$count,$b_wifi,$path_usb,$bus_id) = @_;
14353	my ($cont_if,$ind_if,$num) = (2,3,0);
14354	my $key = 'IF';
14355	my ($b_check,$b_usb,$if,$path,@paths,@row,@rows);
14356	# ntoe: we've already gotten the base path, now we
14357	# we just need to get the IF path, which is one level in:
14358	# usb1/1-1/1-1:1.0/net/enp0s20f0u1/
14359	if ($path_usb){
14360		$b_usb = 1;
14361		@paths = main::globber("${path_usb}*/net/*");
14362	}
14363	else {
14364		@paths = main::globber('/sys/class/net/*');
14365	}
14366	@paths = grep {!/\/lo$/} @paths;
14367	if ($count > 0 && $count < scalar @paths){
14368		@paths = splice(@paths, $count, scalar @paths);
14369	}
14370	if ($vendor eq 'check'){
14371		$b_check = 1;
14372		$key = 'IF-ID';
14373		($cont_if,$ind_if) = (1,2);
14374	}
14375	# print join('; ', @paths),  $count, "\n";
14376	foreach (@paths){
14377		my ($data1,$data2,$duplex,$mac,$speed,$state);
14378		# for usb, we already know where we are
14379		if (!$b_usb){
14380			# pi mmcnr has pcitool and also these vendor/device paths.
14381			if ((!$b_arm && !$b_ppc) || $use{'pci-tool'}){
14382				$path = "$_/device/vendor";
14383				$data1 = main::reader($path,'',0) if -r $path;
14384				$data1 =~ s/^0x// if $data1;
14385				$path = "$_/device/device";
14386				$data2 = main::reader($path,'',0) if -r $path;
14387				$data2 =~ s/^0x// if $data2;
14388				# this is a fix for a redhat bug in virtio
14389				$data2 = (defined $data2 && $data2 eq '0001' && defined $chip && $chip eq '1000') ? '1000' : $data2;
14390			}
14391			# there are cases where arm devices have a small pci bus
14392			# or, with mmcnr devices, will show device/vendor info in data1/2
14393			# which won't match with the path IDs
14394			if (($b_arm || $b_ppc || $b_mips || $b_sparc) && $chip && Cwd::abs_path($_) =~ /\b$chip\b/){
14395				$data1 = $vendor;
14396				$data2 = $chip;
14397			}
14398		}
14399		# print "d1:$data1 v:$vendor d2:$data2 c:$chip bus_id: $bus_id\n";
14400		# print Cwd::abs_path($_), "\n" if $bus_id;
14401		if ($b_usb || $b_check || ($data1 && $data2 && $data1 eq $vendor && $data2 eq $chip &&
14402		 (($b_arm || $b_mips || $b_ppc || $b_sparc) || check_bus_id($_,$bus_id)))){
14403			$if = $_;
14404			$if =~ s/^\/.+\///;
14405			# print "top: if: $if ifs: @ifs_found\n";
14406			next if ($b_check && grep {/$if/} @ifs_found);
14407			$path = "$_/duplex";
14408			$duplex = main::reader($path,'',0) if -r $path;
14409			$duplex ||= 'N/A';
14410			$path = "$_/address";
14411			$mac = main::reader($path,'',0) if -r $path;
14412			$mac = main::apply_filter($mac);
14413			$path = "$_/speed";
14414			$speed = main::reader($path,'',0) if -r $path;
14415			$speed ||= 'N/A';
14416			$path = "$_/operstate";
14417			$state = main::reader($path,'',0) if -r $path;
14418			$state ||= 'N/A';
14419			# print "$speed \n";
14420			@row = ({
14421			main::key($num++,1,$cont_if,$key) => $if,
14422			main::key($num++,0,$ind_if,'state') => $state,
14423			},);
14424			# my $j = scalar @row - 1;
14425			push(@ifs_found, $if) if (!$b_check && (! grep {/$if/} @ifs_found));
14426			# print "push: if: $if ifs: @ifs_found\n";
14427			# no print out for wifi since it doesn't have duplex/speed data available
14428			# note that some cards show 'unknown' for state, so only testing explicitly
14429			# for 'down' string in that to skip showing speed/duplex
14430			# /sys/class/net/$if/wireless : not always there, but worth a try: wlan/wl/ww/wlp
14431			$b_wifi = 1 if !$b_wifi && (-e "$_$if/wireless" || $if =~ /^(wl|ww)/);
14432			if (!$b_wifi && $state ne 'down' && $state ne 'no'){
14433				# make sure the value is strictly numeric before appending Mbps
14434				$speed = (main::is_int($speed)) ? "$speed Mbps" : $speed;
14435				$row[0]->{main::key($num++,0,$ind_if,'speed')} = $speed;
14436				$row[0]->{main::key($num++,0,$ind_if,'duplex')} = $duplex;
14437			}
14438			$row[0]->{main::key($num++,0,$ind_if,'mac')} = $mac;
14439			if ($b_check){
14440				push(@rows,@row);
14441			}
14442			else {
14443				@rows = @row;
14444			}
14445			if ($show{'ip'}){
14446				@row = if_ip($key,$if);
14447				push(@rows, @row);
14448			}
14449			last if !$b_check;
14450		}
14451	}
14452	eval $end if $b_log;
14453	return @rows;
14454}
14455
14456sub advanced_data_bsd {
14457	eval $start if $b_log;
14458	return if ! @ifs_bsd;
14459	my ($if,$b_wifi) = @_;
14460	my (@data,@row,@rows,$working_if);
14461	my ($b_check,$state,$speed,$duplex,$mac);
14462	my ($cont_if,$ind_if,$num) = (2,3,0);
14463	my $key = 'IF';
14464	my $j = 0;
14465	if ($if eq 'check'){
14466		$b_check = 1;
14467		$key = 'IF-ID';
14468		($cont_if,$ind_if) = (1,2);
14469	}
14470	foreach my $item (@ifs_bsd){
14471		if (ref $item ne 'ARRAY'){
14472			$working_if = $item;
14473			# print "$working_if\n";
14474			next;
14475		}
14476 		else {
14477			@data = @$item;
14478 		}
14479		if ($b_check || $working_if eq $if){
14480			$if = $working_if if $b_check;
14481			# print "top: if: $if ifs: @ifs_found\n";
14482			next if ($b_check && grep {/$if/} @ifs_found);
14483			foreach my $line (@data){
14484				# ($state,$speed,$duplex,$mac)
14485				$duplex = $data[2];
14486				$duplex ||= 'N/A';
14487				$mac = main::apply_filter($data[3]);
14488				$speed = $data[1];
14489				$speed ||= 'N/A';
14490				$state = $data[0];
14491				$state ||= 'N/A';
14492				# print "$speed \n";
14493				@row = ({
14494				main::key($num++,1,$cont_if,$key) => $if,
14495				main::key($num++,0,$ind_if,'state') => $state,
14496				},);
14497				push(@ifs_found, $if) if (!$b_check && (!grep {/$if/} @ifs_found));
14498				# print "push: if: $if ifs: @ifs_found\n";
14499				# no print out for wifi since it doesn't have duplex/speed data available
14500				# note that some cards show 'unknown' for state, so only testing explicitly
14501				# for 'down' string in that to skip showing speed/duplex
14502				if (!$b_wifi && $state ne 'down' && $state ne 'no network'){
14503					# make sure the value is strictly numeric before appending Mbps
14504					$speed = (main::is_int($speed)) ? "$speed Mbps" : $speed;
14505					$row[0]->{main::key($num++,0,$ind_if,'speed')} = $speed;
14506					$row[0]->{main::key($num++,0,$ind_if,'duplex')} = $duplex;
14507				}
14508				$row[0]->{main::key($num++,0,$ind_if,'mac')} = $mac;
14509			}
14510			push(@rows, @row);
14511			if ($show{'ip'} && $if){
14512				@row = if_ip($key,$if);
14513				push(@rows,@row) if @row;
14514			}
14515		}
14516	}
14517	eval $end if $b_log;
14518	return @rows;
14519}
14520## values:
14521# 0 - ipv
14522# 1 - ip
14523# 2 - broadcast, if found
14524# 3 - scope, if found
14525# 4 - scope if, if different from if
14526sub if_ip {
14527	eval $start if $b_log;
14528	my ($type,$if) = @_;
14529	my (@data,@rows,$working_if);
14530	my ($cont_ip,$ind_ip) = (3,4);
14531	my $num = 0;
14532	my $j = 0;
14533	$b_ip_run = 1;
14534	if ($type eq 'IF-ID'){
14535		($cont_ip,$ind_ip) = (2,3);
14536	}
14537	OUTER:
14538	foreach my $item (@ifs){
14539		if (ref $item ne 'ARRAY'){
14540			$working_if = $item;
14541			# print "if:$if wif:$working_if\n";
14542			next;
14543		}
14544 		else {
14545			@data = @$item;
14546			# print "ref:$item\n";
14547 		}
14548		if ($working_if eq $if){
14549			foreach my $data2 (@data){
14550				$j = scalar @rows;
14551				$num = 1;
14552				if ($limit > 0 && $j >= $limit){
14553					push(@rows, {
14554					main::key($num++,0,$cont_ip,'Message') => main::row_defaults('output-limit',scalar @data),
14555					},);
14556					last OUTER;
14557				}
14558				# print "$data2->[0] $data2->[1]\n";
14559				my ($ipv,$ip,$broadcast,$scope,$scope_id);
14560				$ipv = ($data2->[0])? $data2->[0]: 'N/A';
14561				$ip = main::apply_filter($data2->[1]);
14562				$scope = ($data2->[3])? $data2->[3]: 'N/A';
14563				# note: where is this ever set to 'all'? Old test condition?
14564				if ($if ne 'all'){
14565					if (defined $data2->[4] && $working_if ne $data2->[4]){
14566						# scope global temporary deprecated dynamic
14567						# scope global dynamic
14568						# scope global temporary deprecated dynamic
14569						# scope site temporary deprecated dynamic
14570						# scope global dynamic noprefixroute enx403cfc00ac68
14571						# scope global eth0
14572						# scope link
14573						# scope site dynamic
14574						# scope link
14575						# trim off if at end of multi word string if found
14576						$data2->[4] =~ s/\s$if$// if $data2->[4] =~ /[^\s]+\s$if$/;
14577						my $key = ($data2->[4] =~ /deprecated|dynamic|temporary|noprefixroute/) ? 'type' : 'virtual';
14578						push(@rows, {
14579						main::key($num++,1,$cont_ip,"IP v$ipv") => $ip,
14580						main::key($num++,0,$ind_ip,$key) => $data2->[4],
14581						main::key($num++,0,$ind_ip,'scope') => $scope,
14582						},);
14583					}
14584					else {
14585						push(@rows, {
14586						main::key($num++,1,$cont_ip,"IP v$ipv") => $ip,
14587						main::key($num++,0,$ind_ip,'scope') => $scope,
14588						},);
14589					}
14590				}
14591				else {
14592					push(@rows, {
14593					main::key($num++,1,($cont_ip - 1),'IF') => $if,
14594					main::key($num++,1,$cont_ip,"IP v$ipv") => $ip,
14595					main::key($num++,0,$ind_ip,'scope') => $scope,
14596					},);
14597				}
14598				if ($extra > 1 && $data2->[2]){
14599					$broadcast = main::apply_filter($data2->[2]);
14600					$rows[$j]->{main::key($num++,0,$ind_ip,'broadcast')} = $broadcast;
14601				}
14602			}
14603		}
14604	}
14605	eval $end if $b_log;
14606	return @rows;
14607}
14608# get ip using downloader to stdout. This is a clean, text only IP output url,
14609# single line only, ending in the ip address. May have to modify this in the future
14610# to handle ipv4 and ipv6 addresses but should not be necessary.
14611# ip=$(echo  2001:0db8:85a3:0000:0000:8a2e:0370:7334 | gawk  --re-interval '
14612# ip=$(wget -q -O - $WAN_IP_URL | gawk  --re-interval '
14613# this generates a direct dns based ipv4 ip address, but if opendns.com goes down,
14614# the fall backs will still work.
14615# note: consistently slower than domain based:
14616# dig +short +time=1 +tries=1 myip.opendns.com. A @208.67.222.222
14617sub wan_ip {
14618	eval $start if $b_log;
14619	my (@data,$b_dig,$b_html,$ip,$ua);
14620	my $num = 0;
14621	# time: 0.06 - 0.07 seconds
14622	# cisco opendns.com may be terminating supporting this one, sometimes works, sometimes not:
14623	# use -4/6 to force ipv 4 or 6, but generally we want the 'natural' native
14624	# ip returned.
14625	# dig +short +time=1 +tries=1 myip.opendns.com @resolver1.opendns.com
14626	# dig +short @ns1-1.akamaitech.net ANY whoami.akamai.net
14627	# this one can take forever, and sometimes requires explicit -4 or -6
14628	# dig -4 TXT +short o-o.myaddr.l.google.com @ns1.google.com
14629	if (!$force{'no-dig'} && (my $program = main::check_program('dig'))){
14630		$ip = (main::grabber("$program +short +time=1 +tries=1 \@ns1-1.akamaitech.net ANY whoami.akamai.net 2>/dev/null"))[0];
14631		$b_dig = 1;
14632	}
14633	if (!$ip && !$force{'no-html-wan'}){
14634		# note: tests: akamai: 0.055 - 0.065 icanhazip.com: 0.177 0.164
14635		# smxi: 0.525, so almost 10x slower. Dig is fast too
14636		# leaving smxi as last test because I know it will always be up.
14637		# --wan-ip-url replaces values with user supplied arg
14638		# 0.059s: http://whatismyip.akamai.com/
14639		# 0.255s: https://get.geojs.io/v1/ip
14640		# 0.371s: http://icanhazip.com/
14641		# 0.430s: https://smxi.org/opt/ip.php
14642		my @urls = (!$wan_url) ? qw(http://whatismyip.akamai.com/
14643		 http://icanhazip.com/ https://smxi.org/opt/ip.php) : ($wan_url);
14644		foreach (@urls){
14645			$ua = 'ip' if $_ =~ /smxi/;
14646			$ip = main::download_file('stdout',$_,'',$ua);
14647			if ($ip){
14648				# print "$_\n";
14649				chomp($ip);
14650				$ip = (split(/\s+/, $ip))[-1];
14651				last;
14652			}
14653		}
14654		$b_html = 1;
14655	}
14656	if ($ip && $use{'filter'}){
14657		$ip = $filter_string;
14658	}
14659	if (!$ip){
14660		# true case trips
14661		if (!$b_dig){
14662			$ip = main::row_defaults('IP-no-dig', 'WAN IP');
14663		}
14664		elsif ($b_dig && !$b_html){
14665			$ip = main::row_defaults('IP-dig', 'WAN IP');
14666		}
14667		else {
14668			$ip = main::row_defaults('IP', 'WAN IP');
14669		}
14670	}
14671	@data = ({
14672	main::key($num++,0,1,'WAN IP') => $ip,
14673	},);
14674	eval $end if $b_log;
14675	return @data;
14676}
14677sub check_bus_id {
14678	eval $start if $b_log;
14679	my ($path,$bus_id) = @_;
14680	my ($b_valid);
14681	if ($bus_id){
14682		# legacy, not link, but uevent has path:
14683		# PHYSDEVPATH=/devices/pci0000:00/0000:00:0a.1/0000:05:00.0
14684		if (Cwd::abs_path($path) =~ /$bus_id\// ||
14685		 (-r "$path/uevent" && -s "$path/uevent" &&
14686		 (grep {/$bus_id/} main::reader("$path/uevent")))){
14687			$b_valid = 1;
14688		}
14689	}
14690	eval $end if $b_log;
14691	return $b_valid;
14692}
14693sub check_wifi {
14694	my ($item) = @_;
14695	my $b_wifi = ($item =~ /wireless|wi-?fi|wlan|802\.11|centrino/i) ? 1 : 0;
14696	return $b_wifi;
14697}
14698}
14699
14700## OpticalItem
14701{
14702package OpticalItem;
14703sub get {
14704	eval $start if $b_log;
14705	my (%data,@rows,$key1,$val1);
14706	my $num = 0;
14707	if ($bsd_type){
14708		$key1 = 'Optical Report';
14709		$val1 = main::row_defaults('optical-data-bsd');
14710		@rows = ({main::key($num++,0,1,$key1) => $val1,});
14711		if ($dboot{'optical'}){
14712			%data = drive_data_bsd();
14713			@rows = drive_output(\%data) if %data;
14714		}
14715		else{
14716			my $file = $system_files{'dmesg-boot'};
14717			if ($file && ! -r $file){
14718				$val1 = main::row_defaults('dmesg-boot-permissions');
14719			}
14720			elsif (!$file){
14721				$val1 = main::row_defaults('dmesg-boot-missing');
14722			}
14723			else {
14724				$val1 = main::row_defaults('optical-data-bsd');
14725			}
14726			$key1 = 'Optical Report';
14727			@rows = ({main::key($num++,0,1,$key1) => $val1,});
14728		}
14729	}
14730	else {
14731		%data = drive_data_linux();
14732		@rows = drive_output(\%data) if %data;
14733	}
14734	if (!@rows){
14735		$key1 = 'Message';
14736		$val1 = main::row_defaults('optical-data');
14737		@rows = ({main::key($num++,0,1,$key1) => $val1,});
14738	}
14739	eval $end if $b_log;
14740	return @rows;
14741}
14742sub drive_output {
14743	eval $start if $b_log;
14744	my ($drives) = @_;
14745	my (@rows);
14746	my $num = 0;
14747	my $j = 0;
14748	# build floppy if any
14749	foreach my $key (sort keys %$drives){
14750		if ($drives->{$key}{'type'} eq 'floppy'){
14751			push(@rows, {
14752			main::key($num++,0,1,ucfirst($drives->{$key}{'type'})) => "/dev/$key",
14753			});
14754			delete $drives->{$key};
14755		}
14756	}
14757	foreach my $key (sort keys %$drives){
14758		$j = scalar @rows;
14759		$num = 1;
14760		my $vendor = $drives->{$key}{'vendor'};
14761		$vendor ||= 'N/A';
14762		my $model = $drives->{$key}{'model'};
14763		$model ||= 'N/A';
14764		push(@rows, {
14765		main::key($num++,1,1,ucfirst($drives->{$key}{'type'})) => "/dev/$key",
14766		main::key($num++,0,2,'vendor') => $vendor,
14767		main::key($num++,0,2,'model') => $model,
14768		});
14769		if ($extra > 0){
14770			my $rev = $drives->{$key}{'rev'};
14771			$rev ||= 'N/A';
14772			$rows[$j]->{ main::key($num++,0,2,'rev')} = $rev;
14773		}
14774		if ($extra > 1 && $drives->{$key}{'serial'}){
14775			$rows[$j]->{ main::key($num++,0,2,'serial')} = main::apply_filter($drives->{$key}{'serial'});
14776		}
14777		my $links = (@{$drives->{$key}{'links'}}) ? join(',', sort @{$drives->{$key}{'links'}}) : 'N/A' ;
14778		$rows[$j]->{ main::key($num++,0,2,'dev-links')} = $links;
14779		if ($show{'optical'}){
14780			$j = scalar @rows;
14781			my $speed = $drives->{$key}{'speed'};
14782			$speed ||= 'N/A';
14783			my ($audio,$multisession) = ('','');
14784			if (defined $drives->{$key}{'multisession'}){
14785				$multisession = ($drives->{$key}{'multisession'} == 1) ? 'yes' : 'no' ;
14786			}
14787			$multisession ||= 'N/A';
14788			if (defined $drives->{$key}{'audio'}){
14789				$audio = ($drives->{$key}{'audio'} == 1) ? 'yes' : 'no' ;
14790			}
14791			$audio ||= 'N/A';
14792			my $dvd = 'N/A';
14793			my (@rw,$rws);
14794			if (defined $drives->{$key}{'dvd'}){
14795				$dvd = ($drives->{$key}{'dvd'} == 1) ? 'yes' : 'no' ;
14796			}
14797			if ($drives->{$key}{'cdr'}){
14798				push(@rw, 'cd-r');
14799			}
14800			if ($drives->{$key}{'cdrw'}){
14801				push(@rw, 'cd-rw');
14802			}
14803			if ($drives->{$key}{'dvdr'}){
14804				push(@rw, 'dvd-r');
14805			}
14806			if ($drives->{$key}{'dvdram'}){
14807				push(@rw, 'dvd-ram');
14808			}
14809			$rws = (@rw) ? join(',', @rw) : 'none' ;
14810			push(@rows, {
14811			main::key($num++,1,2,'Features') => '',
14812			main::key($num++,0,3,'speed') => $speed,
14813			main::key($num++,0,3,'multisession') => $multisession,
14814			main::key($num++,0,3,'audio') => $audio,
14815			main::key($num++,0,3,'dvd') => $dvd,
14816			main::key($num++,0,3,'rw') => $rws,
14817			});
14818			if ($extra > 0){
14819				my $state = $drives->{$key}{'state'};
14820				$state ||= 'N/A';
14821				$rows[$j]->{ main::key($num++,0,3,'state')} = $state;
14822			}
14823		}
14824	}
14825	# print Data::Dumper::Dumper \%drives;
14826	eval $end if $b_log;
14827	return @rows;
14828}
14829sub drive_data_bsd {
14830	eval $start if $b_log;
14831	my (%drives,@rows,@temp);
14832	my ($count,$i,$working) = (0,0,'');
14833	foreach (@{$dboot{'optical'}}){
14834		$_ =~ s/(cd[0-9]+)\(([^:]+):([0-9]+):([0-9]+)\):/$1:$2-$3.$4,/;
14835		my @row = split(/:\s*/, $_);
14836		next if ! defined $row[1];
14837		if ($working ne $row[0]){
14838			# print "$id_holder $row[0]\n";
14839			$working = $row[0];
14840		}
14841		# no dots, note: ada2: 2861588MB BUT: ada2: 600.000MB/s
14842		if (!exists $drives{$working}){
14843			$drives{$working}->{'links'} = [];
14844			$drives{$working}->{'model'} = '';
14845			$drives{$working}->{'rev'} = '';
14846			$drives{$working}->{'state'} = '';
14847			$drives{$working}->{'vendor'} = '';
14848			$drives{$working}->{'temp'} = '';
14849			$drives{$working}->{'type'} = ($working =~ /^cd/) ? 'optical' : 'unknown';
14850		}
14851		# print "$_\n";
14852		if ($bsd_type !~ /^(net|open)bsd$/){
14853			if ($row[1] && $row[1] =~ /^<([^>]+)>/){
14854				$drives{$working}->{'model'} = $1;
14855				$count = ($drives{$working}->{'model'} =~ tr/ //);
14856				if ($count && $count > 1){
14857					@temp = split(/\s+/, $drives{$working}->{'model'});
14858					$drives{$working}->{'vendor'} = $temp[0];
14859					my $index = ($#temp > 2) ? ($#temp - 1): $#temp;
14860					$drives{$working}->{'model'} = join(' ', @temp[1..$index]);
14861					$drives{$working}->{'rev'} = $temp[-1] if $count > 2;
14862				}
14863				if ($show{'optical'}){
14864					if (/\bDVD\b/){
14865						$drives{$working}->{'dvd'} = 1;
14866					}
14867					if (/\bRW\b/){
14868						$drives{$working}->{'cdrw'} = 1;
14869						$drives{$working}->{'dvdr'} = 1 if $drives{$working}->{'dvd'};
14870					}
14871				}
14872			}
14873			if ($row[1] && $row[1] =~ /^Serial/){
14874				@temp = split(/\s+/,$row[1]);
14875				$drives{$working}->{'serial'} = $temp[-1];
14876			}
14877			if ($show{'optical'}){
14878				if ($row[1] =~ /^([0-9\.]+[MGTP][B]?\/s)/){
14879					$drives{$working}->{'speed'} = $1;
14880					$drives{$working}->{'speed'} =~ s/\.[0-9]+//;
14881				}
14882				if (/\bDVD[-]?RAM\b/){
14883					$drives{$working}->{'cdr'} = 1;
14884					$drives{$working}->{'dvdram'} = 1;
14885				}
14886				if ($row[2] && $row[2] =~ /,\s(.*)$/){
14887					$drives{$working}->{'state'} = $1;
14888					$drives{$working}->{'state'} =~ s/\s+-\s+/, /;
14889				}
14890			}
14891		}
14892		else {
14893			if ($row[2] && $row[2] =~ /<([^>]+)>/){
14894				$drives{$working}->{'model'} = $1;
14895				$count = ($drives{$working}->{'model'} =~ tr/,//);
14896				# print "c: $count $row[2]\n";
14897				if ($count && $count > 1){
14898					@temp = split(/,\s*/, $drives{$working}->{'model'});
14899					$drives{$working}->{'vendor'} = $temp[0];
14900					$drives{$working}->{'model'} = $temp[1];
14901					$drives{$working}->{'rev'} = $temp[2];
14902				}
14903				if ($show{'optical'}){
14904					if (/\bDVD\b/){
14905						$drives{$working}->{'dvd'} = 1;
14906					}
14907					if (/\bRW\b/){
14908						$drives{$working}->{'cdrw'} = 1;
14909						$drives{$working}->{'dvdr'} = 1 if $drives{$working}->{'dvd'};
14910					}
14911					if (/\bDVD[-]?RAM\b/){
14912						$drives{$working}->{'cdr'} = 1;
14913						$drives{$working}->{'dvdram'} = 1;
14914					}
14915				}
14916			}
14917			if ($show{'optical'}){
14918				# print "$row[1]\n";
14919				if (($row[1] =~ tr/,//) > 1){
14920					@temp = split(/,\s*/, $row[1]);
14921					$drives{$working}->{'speed'} = $temp[2];
14922				}
14923
14924			}
14925		}
14926	}
14927
14928	main::log_data('dump','%drives',\%drives) if $b_log;
14929	# print Data::Dumper::Dumper \%drives;
14930	eval $end if $b_log;
14931	return %drives;
14932}
14933sub drive_data_linux {
14934	eval $start if $b_log;
14935	my (@data,%drives,@info,@rows);
14936	@data = main::globber('/dev/dvd* /dev/cdr* /dev/scd* /dev/sr* /dev/fd[0-9]');
14937	# Newer kernel is NOT linking all optical drives. Some, but not all.
14938	# Get the actual disk dev location, first try default which is easier to run,
14939	# need to preserve line breaks
14940	foreach (@data){
14941		my $working = readlink($_);
14942		$working = ($working) ? $working: $_;
14943		next if $working =~ /random/;
14944		# possible fix: puppy has these in /mnt not /dev they say
14945		$working =~ s/\/(dev|media|mnt)\///;
14946		$_ =~ s/\/(dev|media|mnt)\///;
14947		if  (!defined $drives{$working}){
14948			my @temp = ($_ ne $working) ? ($_) : ();
14949			$drives{$working}->{'links'} = \@temp;
14950			$drives{$working}->{'type'} = ($working =~ /^fd/) ? 'floppy' : 'optical' ;
14951		}
14952 		else {
14953 			push(@{$drives{$working}->{'links'}}, $_) if $_ ne $working;
14954 		}
14955		# print "$working\n";
14956	}
14957	if ($show{'optical'} && -e '/proc/sys/dev/cdrom/info'){
14958		@info = main::reader('/proc/sys/dev/cdrom/info','strip');
14959	}
14960	# print join('; ', @data), "\n";
14961	foreach my $key (keys %drives){
14962		next if $drives{$key}->{'type'} eq 'floppy';
14963		my $device = "/sys/block/$key/device";
14964		if (-d $device){
14965			if (-r "$device/vendor"){
14966				$drives{$key}->{'vendor'} = main::reader("$device/vendor",'',0);
14967				$drives{$key}->{'vendor'} = main::cleaner($drives{$key}->{'vendor'});
14968				$drives{$key}->{'state'} = main::reader("$device/state",'',0);
14969				$drives{$key}->{'model'} = main::reader("$device/model",'',0);
14970				$drives{$key}->{'model'} = main::cleaner($drives{$key}->{'model'});
14971				$drives{$key}->{'rev'} = main::reader("$device/rev",'',0);
14972			}
14973		}
14974		elsif (-r "/proc/ide/$key/model"){
14975			$drives{$key}->{'vendor'} = main::reader("/proc/ide/$key/model",'',0);
14976			$drives{$key}->{'vendor'} = main::cleaner($drives{$key}->{'vendor'});
14977		}
14978		if ($show{'optical'} && @info){
14979			my $index = 0;
14980			foreach my $item (@info){
14981				next if $item =~ /^\s*$/;
14982				my @split = split(/\s+/, $item);
14983				if ($item =~ /^drive name:/){
14984					foreach my $id (@split){
14985						last if ($id eq $key);
14986						$index++;
14987					}
14988					last if !$index; # index will be > 0 if it was found
14989				}
14990				elsif ($item =~/^drive speed:/){
14991					$drives{$key}->{'speed'} = $split[$index];
14992				}
14993				elsif ($item =~/^Can read multisession:/){
14994					$drives{$key}->{'multisession'}=$split[$index+1];
14995				}
14996				elsif ($item =~/^Can read MCN:/){
14997					$drives{$key}->{'mcn'}=$split[$index+1];
14998				}
14999				elsif ($item =~/^Can play audio:/){
15000					$drives{$key}->{'audio'}=$split[$index+1];
15001				}
15002				elsif ($item =~/^Can write CD-R:/){
15003					$drives{$key}->{'cdr'}=$split[$index+1];
15004				}
15005				elsif ($item =~/^Can write CD-RW:/){
15006					$drives{$key}->{'cdrw'}=$split[$index+1];
15007				}
15008				elsif ($item =~/^Can read DVD:/){
15009					$drives{$key}->{'dvd'}=$split[$index+1];
15010				}
15011				elsif ($item =~/^Can write DVD-R:/){
15012					$drives{$key}->{'dvdr'}=$split[$index+1];
15013				}
15014				elsif ($item =~/^Can write DVD-RAM:/){
15015					$drives{$key}->{'dvdram'}=$split[$index+1];
15016				}
15017			}
15018		}
15019	}
15020	main::log_data('dump','%drives',\%drives) if $b_log;
15021	# print Data::Dumper::Dumper \%drives;
15022	eval $end if $b_log;
15023	return %drives;
15024}
15025
15026}
15027
15028## PartitionItem
15029{
15030package PartitionItem;
15031
15032sub get {
15033	eval $start if $b_log;
15034	my (@rows,$key1,$val1);
15035	my $num = 0;
15036	set_partitions() if !$loaded{'set-partitions'};
15037 	if (!@partitions){
15038		$key1 = 'Message';
15039		#$val1 = ($bsd_type && $bsd_type eq 'darwin') ?
15040		# main::row_defaults('darwin-feature') : main::row_defaults('partition-data');
15041		$val1 = main::row_defaults('partition-data');
15042		@rows = ({main::key($num++,0,1,$key1) => $val1,});
15043 	}
15044 	else {
15045		@rows = create_output();
15046 	}
15047	eval $end if $b_log;
15048	return @rows;
15049}
15050sub create_output {
15051	eval $start if $b_log;
15052	my $num = 0;
15053	my $j = 0;
15054	my (@rows,$dev,$dev_type,$fs,$percent,$raw_size,$size,$used);
15055	# alpha sort for non numerics
15056	if ($show{'partition-sort'} !~ /^(percent-used|size|used)$/){
15057		@partitions = sort { $a->{$show{'partition-sort'}} cmp $b->{$show{'partition-sort'}} } @partitions;
15058	}
15059	else {
15060		@partitions = sort { $a->{$show{'partition-sort'}} <=> $b->{$show{'partition-sort'}} } @partitions;
15061	}
15062	my $fs_skip = fs_excludes('label-uuid');
15063	foreach my $row (@partitions){
15064		$num = 1;
15065		next if $row->{'type'} eq 'secondary' && $show{'partition'};
15066		next if $show{'swap'} && $row->{'fs'} && $row->{'fs'} eq 'swap';
15067		next if $row->{'swap-type'} && $row->{'swap-type'} ne 'partition';
15068		if (!$row->{'hidden'}){
15069			$size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A';
15070			$used = main::get_size($row->{'used'},'string','N/A'); # used can be 0
15071			$percent = (defined $row->{'percent-used'}) ? ' (' . $row->{'percent-used'} . '%)' : '';
15072		}
15073		else {
15074			$percent = '';
15075			$used = $size = (!$b_root) ? main::row_defaults('root-required') : main::row_defaults('partition-hidden');
15076		}
15077		$fs = ($row->{'fs'}) ? lc($row->{'fs'}): 'N/A';
15078		$dev_type = ($row->{'dev-type'}) ? $row->{'dev-type'} : 'dev';
15079		$row->{'dev-base'} = '/dev/' . $row->{'dev-base'} if $dev_type eq 'dev' && $row->{'dev-base'};
15080		$dev = ($row->{'dev-base'}) ? $row->{'dev-base'} : 'N/A';
15081		$row->{'id'} =~ s|/home/[^/]+/(.*)|/home/$filter_string/$1| if $use{'filter'};
15082		$j = scalar @rows;
15083		push(@rows, {
15084		main::key($num++,1,1,'ID') => $row->{'id'},
15085		});
15086		if (($b_admin || $row->{'hidden'}) && $row->{'raw-size'}){
15087			# It's an error! permissions or missing tool
15088			$raw_size = ($row->{'raw-size'}) ? main::get_size($row->{'raw-size'},'string') : 'N/A';
15089			$rows[$j]->{main::key($num++,0,2,'raw-size')} = $raw_size;
15090		}
15091		if ($b_admin && $row->{'raw-available'} && $size ne 'N/A'){
15092			$size .=  ' (' . $row->{'raw-available'} . '%)';
15093		}
15094		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
15095		$rows[$j]->{main::key($num++,0,2,'used')} = $used . $percent;
15096		$rows[$j]->{main::key($num++,0,2,'fs')} = $fs;
15097		if ($b_admin && $fs eq 'swap' && defined $row->{'swappiness'}){
15098			$rows[$j]->{main::key($num++,0,2,'swappiness')} = $row->{'swappiness'};
15099		}
15100		if ($b_admin && $fs eq 'swap' && defined $row->{'cache-pressure'}){
15101			$rows[$j]->{main::key($num++,0,2,'cache-pressure')} = $row->{'cache-pressure'};
15102		}
15103		if ($extra > 1 && $fs eq 'swap' && defined $row->{'priority'}){
15104			$rows[$j]->{main::key($num++,0,2,'priority')} = $row->{'priority'};
15105		}
15106		if ($b_admin && $row->{'block-size'}){
15107			$rows[$j]->{main::key($num++,0,2,'block-size')} = $row->{'block-size'} . ' B';;
15108			#$rows[$j]->{main::key($num++,0,2,'physical')} = $row->{'block-size'} . ' B';
15109			#$rows[$j]->{main::key($num++,0,2,'logical')} = $row->{'block-logical'} . ' B';
15110		}
15111		$rows[$j]->{main::key($num++,1,2,$dev_type)} = $dev;
15112		if ($b_admin && $row->{'maj-min'}){
15113			$rows[$j]->{main::key($num++,0,3,'maj-min')} = $row->{'maj-min'};
15114		}
15115		if ($extra > 0 && $row->{'dev-mapped'}){
15116			$rows[$j]->{main::key($num++,0,3,'mapped')} = $row->{'dev-mapped'};
15117		}
15118		# add fs known to not use label/uuid here
15119		if (($show{'label'} || $show{'uuid'}) && $dev_type eq 'dev' &&
15120		 $fs !~ /^$fs_skip$/){
15121			if ($show{'label'}){
15122				if ($use{'filter-label'}){
15123					$row->{'label'} = main::apply_partition_filter('part', $row->{'label'}, '');
15124				}
15125				$row->{'label'} ||= 'N/A';
15126				$rows[$j]->{main::key($num++,0,2,'label')} = $row->{'label'};
15127			}
15128			if ($show{'uuid'}){
15129				if ($use{'filter-uuid'}){
15130					$row->{'uuid'} = main::apply_partition_filter('part', $row->{'uuid'}, '');
15131				}
15132				$row->{'uuid'} ||= 'N/A';
15133				$rows[$j]->{main::key($num++,0,2,'uuid')} = $row->{'uuid'};
15134			}
15135		}
15136	}
15137	eval $end if $b_log;
15138	return @rows;
15139}
15140
15141sub set_partitions {
15142	eval $start if $b_log;
15143	#return if $bsd_type && $bsd_type eq 'darwin'; # darwin has muated output, of course
15144	my (@data,@rows,@mount,@partitions_working,%part,@working);
15145	my ($back_size,$back_used,$b_fs,$cols,$roots) = (4,3,1,6,0);
15146	my ($b_fake_map,$b_load,$b_logical,$b_space,);
15147	my ($block_size,$blockdev,$dev_base,$dev_mapped,$dev_type,$fs,$id,$label,
15148	$maj_min,$percent_used,$raw_size,$replace,$size_available,$size,$test,
15149	$type,$uuid,$used);
15150	$loaded{'set-partitions'} = 1;
15151	if ($b_admin){
15152		# for partition block size
15153		$blockdev = $alerts{'blockdev'}->{'path'} if $alerts{'blockdev'}->{'path'};
15154	}
15155	# for raw partition sizes, maj_min
15156	PartitionData::set() if !$bsd_type && !$loaded{'partition-data'};
15157	DiskDataBSD::set() if $bsd_type && !$loaded{'disk-data-bsd'};
15158	LsblkData::set() if !$bsd_type && !$loaded{'lsblk'};
15159	# set @labels, @uuid
15160	if (!$bsd_type){
15161		set_label_uuid() if !$loaded{'label-uuid'};
15162	}
15163	# most current OS support -T and -k, but -P means different things
15164	# in freebsd. However since most use is from linux, we make that default
15165	# android 7 no -T support
15166	if (!$fake{'partitions'}){
15167		if (!$bsd_type){
15168			@partitions_working = main::grabber("df -P -T -k 2>/dev/null");
15169			main::set_mapper() if !$loaded{'mapper'};
15170		}
15171		else {
15172			# this is missing the file system data
15173			if ($bsd_type ne 'darwin'){
15174				@partitions_working = main::grabber("df -T -k 2>/dev/null");
15175			}
15176			#Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
15177			else {
15178				$cols = 8;
15179				($back_size,$back_used) = (7,6);
15180			}
15181			# turns out freebsd uses this junk too
15182			$b_fake_map = 1;
15183		}
15184		# busybox only supports -k and -P, openbsd, darwin
15185		if (!@partitions_working){
15186			@partitions_working = main::grabber("df -k 2>/dev/null");
15187			$b_fs = 0;
15188			$cols = 5 if !$bsd_type || $bsd_type ne 'darwin';
15189			if (my $path = main::check_program('mount')){
15190				@mount = main::grabber("$path 2>/dev/null");
15191			}
15192		}
15193	}
15194	else {
15195		# my $file;
15196		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/df/df-kTP-cygwin-1.txt";
15197		# @partitions_working = main::reader($file);
15198	}
15199	# print Data::Dumper::Dumper \@partitions_working;
15200	# determine positions
15201	my $row1 = shift @partitions_working;
15202	# new kernels/df have rootfs and / repeated, creating two entries for the same partition
15203	# so check for two string endings of / then slice out the rootfs one, I could check for it
15204	# before slicing it out, but doing that would require the same action twice re code execution
15205	foreach (@partitions_working){
15206		$roots++ if /\s\/$/;
15207	}
15208	@partitions_working = grep {!/^rootfs/} @partitions_working if $roots > 1;
15209	my $filters = partition_filters();
15210	# push @partitions_working, '//mafreebox.freebox.fr/Disque dur cifs         239216096  206434016  20607496      91% /freebox/Disque dur';
15211	# push @partitions_working, '//mafreebox.freebox.fr/AllPG      cifs         436616192  316339304 120276888      73% /freebox/AllPG';
15212	foreach (@partitions_working){
15213		($dev_base,$dev_mapped,$dev_type,$fs,$id,$label,
15214		$maj_min,$type,$uuid) = ('','','','','','','','','');
15215		($b_load,$b_space,$block_size,$percent_used,$raw_size,$size_available,
15216		$size,$used) = (0,0,0,0,0,0,0,0);
15217		undef %part;
15218		# apple crap, maybe also freebsd?
15219		$_ =~ s/^map\s+([\S]+)/map:\/$1/ if $b_fake_map;
15220		# handle spaces in remote filesystem names
15221		# busybox df shows KM, sigh; note: GoogleDrive Hogne: fuse.rclone  15728640
15222		if (/^(.*)(\s[\S]+)\s+[a-z][a-z0-9\.]+\s+[0-9]+/){
15223			$replace = $test = "$1$2";
15224			if ($test =~ /\s/){ # paranoid test, but better safe than sorry
15225				$b_space = 1;
15226				$replace =~ s/\s/^^/g;
15227				# print ":$replace:\n";
15228				$_ =~ s/^$test/$replace/;
15229				# print "$_\n";
15230			}
15231		}
15232		my @row = split(/\s+/, $_);
15233		$row[0] =~ s/\^\^/ /g if $b_space; # reset spaces in > 1 word fs name
15234		# autofs is a bsd thing, has size 0
15235		if ($row[0] =~ /^($filters)$/ || $row[0] =~ /^ROOT/i ||
15236		 ($b_fs && ($row[2] == 0 || $row[1] =~ /^(autofs|devtmpfs|iso9660|tmpfs)$/))){
15237			next;
15238		}
15239		# NOTE: using -P for linux fixes line wraps, and for bsds, assuming they don't use such long file names
15240		# cygwin C:\cygwin passes this test so has to be handled later
15241		if ($row[0] =~ /^\/dev\/|:\/|\/\//){
15242			# this could point to by-label or by-uuid so get that first. In theory, abs_path should
15243			# drill down to get the real path, but it isn't always working.
15244			if ($row[0] eq '/dev/root'){
15245				$row[0] = get_root();
15246			}
15247			# sometimes paths are set using /dev/disk/by-[label|uuid] so we need to get the /dev/xxx path
15248			if ($row[0] =~ /by-label|by-uuid/){
15249				$row[0] = Cwd::abs_path($row[0]);
15250			}
15251			elsif ($row[0] =~ /mapper\// && %mapper){
15252				$dev_mapped = $row[0];
15253				$dev_mapped =~ s|^/.*/||;
15254				$row[0] = $mapper{$dev_mapped} if $mapper{$dev_mapped};
15255			}
15256			elsif ($row[0] =~ /\/dm-[0-9]+$/ && %dmmapper){
15257				my $temp = $row[0];
15258				$temp =~ s|^/.*/||;
15259				$dev_mapped = $dmmapper{$temp};
15260			}
15261			$dev_base = $row[0];
15262			$dev_base =~ s|^/.*/||;
15263			%part = LsblkData::get($dev_base) if @lsblk;
15264			$maj_min = get_maj_min($dev_base) if @proc_partitions;
15265		}
15266		# this handles zfs type devices/partitions, which do not start with / but contain /
15267		# note: Main/jails/transmission_1 path can be > 1 deep
15268		# Main zfs 3678031340 8156 3678023184 0% /mnt/Main
15269		if (!$dev_base && ($row[0] =~ /^([^\/]+\/)(.+)/ ||
15270		 ($row[0] =~ /^[^\/]+$/ && $row[1] =~ /^(btrfs|hammer[2-9]?|zfs)$/))){
15271			$dev_base = $row[0];
15272			$dev_type = 'logical';
15273		}
15274		# this handles yet another fredforfaen special case where a mounted drive
15275		# has the search string in its name, includes / (|
15276		if ($row[-1] =~ m%^/(|boot|boot/efi|home|opt|tmp|usr|usr/home|var|var/log|var/tmp)$% ||
15277		 ($b_android && $row[-1] =~ /^\/(cache|data|firmware|system)$/)){
15278			$b_load = 1;
15279			# note, older df in bsd do not have file system column
15280			$type = 'main';
15281		}
15282		# $cols in case where mount point has space in name, we only care about the first part
15283		elsif ($row[$cols] !~ m%^\/(|boot|boot/efi|home|opt|tmp|usr|usr/home|var|var/log|var/tmp)$% &&
15284		 $row[$cols] !~ /^filesystem/ &&
15285		 !($b_android && $row[$cols] =~ /^\/(cache|data|firmware|system)$/)){
15286			$b_load = 1;
15287			$type = 'secondary';
15288		}
15289		if ($b_load){
15290			if (!$bsd_type){
15291				if ($b_fs){
15292					$fs = ($part{'fs'}) ? $part{'fs'} : $row[1];
15293				}
15294				else {
15295					$fs = get_mounts_fs($row[0],\@mount);
15296				}
15297				if ($show{'label'}){
15298					if ($part{'label'}){
15299						$label = $part{'label'};
15300					}
15301					elsif (@labels){
15302						$label = get_label($row[0]);
15303					}
15304				}
15305				if ($show{'uuid'}){
15306					if ($part{'uuid'}){
15307						$uuid = $part{'uuid'};
15308					}
15309					elsif (@uuids){
15310						$uuid = get_uuid($row[0]);
15311					}
15312				}
15313			}
15314			else {
15315				$fs = ($b_fs) ? $row[1]: get_mounts_fs($row[0],\@mount);
15316			}
15317			# assuming that all null/nullfs are parts of a logical fs
15318			$b_logical = 1 if $fs && $fs =~ /^(btrfs|hammer|null|zfs)/;
15319			$id = join(' ', @row[$cols .. $#row]);
15320			$size = $row[$cols - $back_size];
15321			if ($b_admin && -e "/sys/block/"){
15322				@working = admin_data($blockdev,$dev_base,$size);
15323				$raw_size = $working[0];
15324				$size_available = $working[1];
15325				$block_size = $working[2];
15326			}
15327			if (!$dev_type){
15328				# C:/cygwin64, D:
15329				if ($b_cygwin && $row[0] =~ /^[A-Z]+:/){
15330					$dev_type = 'windows';
15331					$dev_base = $row[0] if !$dev_base;
15332					# looks weird if D:, yes, I know, windows uses \, but cygwin doesn't
15333					$dev_base .= '/' if $dev_base =~ /:$/;
15334				}
15335				# need data set, this could maybe be converted to use
15336				# dev-mapped and abspath but not without testing
15337				elsif ($dev_base =~ /^map:\/(.*)/){
15338					$dev_type = 'mapped';
15339					$dev_base = $1;
15340				}
15341				# note: possible: sshfs path: beta:data/; remote: fuse.rclone
15342				elsif ($dev_base =~ /^\/\/|:\// || ($fs && $fs =~ /(rclone)/)){
15343					$dev_type = 'remote';
15344					$dev_base = $row[0] if !$dev_base; # only trips in fs test case
15345				}
15346				# a slice bsd system, zfs can't be detected this easily
15347				elsif ($b_logical && $fs && $fs =~ /^(null(fs)?)$/){
15348					$dev_type = 'logical';
15349					$dev_base = $row[0] if !$dev_base;
15350				}
15351				# an error has occurred almost for sure
15352				elsif (!$dev_base){
15353					$dev_type = 'source';
15354					$dev_base = main::row_defaults('unknown-dev');
15355				}
15356				else {
15357					$dev_type = 'dev';
15358				}
15359			}
15360			if ($bsd_type && $dev_type eq 'dev' && $row[0] &&
15361			 ($b_admin || $show{'label'} || $show{'uuid'})){
15362				my %temp = DiskDataBSD::get($row[0]);
15363				$block_size = $temp{'logical-block-size'};
15364				$label = $temp{'label'};
15365				$uuid = $temp{'uuid'};
15366			}
15367			$used = $row[$cols - $back_used];
15368			$percent_used = sprintf("%.1f", ($used/$size)*100) if ($size && main::is_numeric($size));
15369			push(@partitions,{
15370			'block-size' => $block_size,
15371			'dev-base' => $dev_base,
15372			'dev-mapped' => $dev_mapped,
15373			'dev-type' => $dev_type,
15374			'fs' => $fs,
15375			'id' => $id,
15376			'label' => $label,
15377			'maj-min' => $maj_min,
15378			'percent-used' => $percent_used,
15379			'raw-available' => $size_available,
15380			'raw-size' => $raw_size,
15381			'size' => $size,
15382			'type' => $type,
15383			'used' => $used,
15384			'uuid' => $uuid,
15385			});
15386		}
15387	}
15388	swap_data() if !$loaded{'set-swap'};
15389	push(@partitions,@swaps);
15390	print Data::Dumper::Dumper \@partitions if $dbg[16];
15391	if (!$bsd_type && @lsblk){
15392		check_partition_data();# updates @partitions
15393	}
15394	main::log_data('dump','@partitions',\@partitions) if $b_log;
15395	print Data::Dumper::Dumper \@partitions if $dbg[16];
15396	eval $end if $b_log;
15397}
15398sub swap_data {
15399	eval $start if $b_log;
15400	$loaded{'set-swap'} = 1;
15401	my (@data,@working);
15402	my ($block_size,$cache_pressure,$dev_base,$dev_mapped,$dev_type,$label,
15403	$maj_min,$mount,$path,$pattern1,$pattern2,$percent_used,$priority,
15404	$size,$swap_type,$swappiness,$used,$uuid);
15405	my ($s,$j,$size_id,$used_id) = (1,0,2,3);
15406	if (!$bsd_type){
15407		# faster, avoid subshell, same as swapon -s
15408		if (-r '/proc/swaps'){
15409			@working = main::reader("/proc/swaps");
15410		}
15411		elsif ($path = main::check_program('swapon')){
15412			# note: while -s is deprecated, --show --bytes is not supported
15413			# on older systems
15414			@working = main::grabber("$path -s 2>/dev/null");
15415		}
15416		if ($b_admin){
15417			@data = swap_advanced_data();
15418			$swappiness = $data[0];
15419			$cache_pressure = $data[1];
15420		}
15421		if (($show{'label'} || $show{'uuid'}) && !$loaded{'label-uuid'}){
15422			set_label_uuid();
15423		}
15424		$pattern1 = 'partition|file|ram';
15425		$pattern2 = '[^\s].*[^\s]';
15426	}
15427	else {
15428		if ($path = main::check_program('swapctl')){
15429			# output in in KB blocks
15430			@working = main::grabber("$path -l -k 2>/dev/null");
15431		}
15432		($size_id,$used_id) = (1,2);
15433		$pattern1 = '[0-9]+';
15434		$pattern2 = '[^\s]+';
15435	}
15436	# now add the swap partition data, don't want to show swap files, just partitions,
15437	# though this can include /dev/ramzswap0. Note: you can also use /proc/swaps for this
15438	# data, it's the same exact output as swapon -s
15439	foreach (@working){
15440		#next if ! /^\/dev/ || /^\/dev\/(ramzwap|zram)/;
15441		next if /^(Device|Filename|no swap)/;
15442		($block_size,$dev_base,$dev_mapped,$dev_type,$label,$maj_min,$mount,$priority,
15443		$swap_type,$uuid) = ('','','','','','','',undef,'partition','');
15444		@data = split(/\s+/, $_);
15445		if (/^\/dev\/(block\/)?(compcache|ramzwap|zram)/i){
15446			$swap_type = 'zram';
15447			$dev_type = 'dev';
15448		}
15449		elsif ($data[1] && $data[1] eq 'ram'){
15450			$swap_type = 'ram';
15451		}
15452		elsif (m|^/dev|){
15453			$swap_type = 'partition';
15454			$dev_base = $data[0];
15455			$dev_base =~ s|^/dev/||;
15456			if (!$bsd_type){
15457				if ($dev_base =~ /^dm-/ && %dmmapper){
15458					$dev_mapped = $dmmapper{$dev_base};
15459				}
15460				if ($show{'label'} && @labels){
15461					$label = get_label($data[0]);
15462				}
15463				if ($show{'uuid'} && @uuids){
15464					$uuid = get_uuid($data[0]);
15465				}
15466			}
15467			else {
15468				if ($show{'label'} || $show{'uuid'}){
15469					my %temp = DiskDataBSD::get($data[0]);
15470					$block_size = $temp{'logical-block-size'};
15471					$label = $temp{'label'};
15472					$uuid = $temp{'uuid'};
15473				}
15474			}
15475			$dev_type = 'dev';
15476			$maj_min = get_maj_min($dev_base) if @proc_partitions;
15477		}
15478		elsif ($data[1] && $data[1] eq 'file' || m|^/|){
15479			$swap_type = 'file';
15480		}
15481		$priority = $data[-1] if !$bsd_type;
15482		# swpaon -s: /dev/sdb1 partition 16383996 109608  -2
15483		# swapctl -l -k: /dev/label/swap0.eli     524284     154092
15484		# users could have space in swapfile name
15485		if (/^($pattern2)\s+($pattern1)\s+/){
15486			$mount = main::trimmer($1);
15487		}
15488		$size = $data[$size_id];
15489		$used = $data[$used_id];
15490		$percent_used = sprintf("%.1f", ($used/$size)*100);
15491		push(@swaps, {
15492		'block-size' => $block_size,
15493		'cache-pressure' => $cache_pressure,
15494		'dev-base' => $dev_base,
15495		'dev-mapped' => $dev_mapped,
15496		'dev-type' => $dev_type,
15497		'fs' => 'swap',
15498		'id' => "swap-$s",
15499		'label' => $label,
15500		'maj-min' => $maj_min,
15501		'mount' => $mount,
15502		'percent-used' => $percent_used,
15503		'priority' => $priority,
15504		'size' => $size,
15505		'swappiness' => $swappiness,
15506		'type' => 'main',
15507		'swap-type' => $swap_type,
15508		'used' => $used,
15509		'uuid' => $uuid,
15510		});
15511		$s++;
15512	}
15513	main::log_data('dump','@swaps',\@swaps) if $b_log;
15514	print Data::Dumper::Dumper \@swaps if $dbg[15];;
15515	eval $end if $b_log;
15516}
15517sub swap_advanced_data {
15518	eval $start if $b_log;
15519	my ($swappiness,$cache_pressure) = (undef,undef);
15520	if (-r '/proc/sys/vm/swappiness'){
15521		$swappiness = main::reader('/proc/sys/vm/swappiness','',0);
15522		if (defined $swappiness){
15523			$swappiness .= ($swappiness == 60) ? ' (default)' : ' (default 60)' ;
15524		}
15525	}
15526	if (-r '/proc/sys/vm/vfs_cache_pressure'){
15527		$cache_pressure = main::reader('/proc/sys/vm/vfs_cache_pressure','',0);
15528		if (defined $cache_pressure){
15529			$cache_pressure .= ($cache_pressure == 100) ? ' (default)' : ' (default 100)' ;
15530		}
15531	}
15532	eval $end if $b_log;
15533	return ($swappiness,$cache_pressure);
15534}
15535# handle cases of hidden file systems
15536sub check_partition_data {
15537	eval $start if $b_log;
15538	my ($b_found,$dev_mapped,$temp);
15539	my $filters = partition_filters();
15540	foreach my $row (@lsblk){
15541		$b_found = 0;
15542		$dev_mapped = '';
15543		if (!$row->{'name'} || !$row->{'mount'} || !$row->{'type'} ||
15544		 ($row->{'fs'} && $row->{'fs'} =~ /^($filters)$/) ||
15545		 ($row->{'type'} =~ /^(disk|loop|rom)$/)){
15546			next;
15547		}
15548		# unmap so we can match name to dev-base
15549		if (%mapper && $mapper{$row->{'name'}}){
15550			$dev_mapped = $row->{'name'};
15551			$row->{'name'} = $mapper{$row->{'name'}};
15552		}
15553		# print "$row->{'name'} $row->{'mount'}\n";
15554		foreach my $row2 (@partitions){
15555			# print "1: n:$row->{'name'} m:$row->{'mount'} db:$row2->{'dev-base'} id:$row2->{'id'}\n";
15556			next if !$row2->{'id'};
15557			# note: for swap mount point is [SWAP] in @lsblk, but swap-x in @partitions
15558			if ($row->{'mount'} eq $row2->{'id'} || $row->{'name'} eq $row2->{'dev-base'}){
15559				$b_found = 1;
15560				last;
15561			}
15562			# print "m:$row->{'mount'} id:$row2->{'id'}\n";
15563		}
15564		if (!$b_found){
15565			# print "found: n:$row->{'name'} m:$row->{'mount'}\n";
15566			$temp = {
15567			'block-logical' => $row->{'block-logical'},
15568			'dev-base' => $row->{'name'},
15569			'dev-mapped' => $dev_mapped,
15570			'fs' => $row->{'fs'},
15571			'id' => $row->{'mount'},
15572			'hidden' => 1,
15573			'label' => $row->{'label'},
15574			'maj-min' => $row->{'maj-min'},
15575			'percent-used' => 0,
15576			'raw-size' => $row->{'size'},
15577			'size' => 0,
15578			'type' => 'secondary',
15579			'used' => 0,
15580			'uuid' => $row->{'uuid'},
15581			};
15582			push(@partitions,$temp);
15583			main::log_data('dump','lsblk check: @temp',$temp) if $b_log;
15584		}
15585	}
15586	eval $end if $b_log;
15587}
15588# NOTE: Was forgetting to update one or the other so put them
15589# all here for: subs partitiion_data(), check_partition_data()
15590# note: p_d filters 'filesystem', and c_p_d filters against fs
15591sub partition_filters {
15592	# snap mounts with squashfs; appimage/flatpak mount?
15593	# swap is set in swap_data(); cgmfs is in ram, like devfs, sysfs;
15594	# union fs types: aufs, overlayfs, unionfs, mergerfs
15595	my $filters = 'aufs|cgroup.*|cgmfs|configfs|debugfs|\/dev|dev|\/dev\/loop[0-9]*|';
15596	$filters .= 'devfs|devtmpfs|efivarfs|fdescfs|hugetlbfs|iso9660|kernfs|';
15597	$filters .= 'linprocfs|linsysfs|none|overla(id|y)(fs)?|procfs|ptyfs|';
15598	$filters .= '/run(\/.*)?|run|securityfs|shm|squashfs|swap|sys|\/sys\/.*|sysfs|';
15599	$filters .= 'tmpfs|tracefs|type|udev|unionfs|vartmp';
15600	return $filters
15601}
15602# used to exclude disk used, partition/unmounted/swap label/uuid, unmounted label/uuid
15603# see docs/inxi-data.txt PARTITION DATA for more on remote/fuse fs
15604sub fs_excludes {
15605	my ($source) = @_;
15606	# panfs is parallel NAS volume manager, need more data
15607	# null is hammer fs slice; nfs/nfs3/nfs4; some can be fuse mounts: fuse.sshfs
15608	# afs aufs avfs cifs ffs gfs\d{0,2} hdfs ipfs k(osmos)?fs .*lafs mhddfs
15609	# mergerfs nfs\d{0,2} null ocfs\d{0,2} openafs orangefs overla(id|y)(fs)?
15610	# panfs pvfs\d{0,2} s3fs squashfs sshfs smbfs unionfs vmfs
15611	my $excludes = '(fuse(blk)?[\._-]?)?(';
15612	$excludes .= 'f|' if $source eq 'label-uuid'; # ffs not remote, but no u/l
15613	$excludes .= 'a|archivemount|au|av|ceph|ci|g|gluster|gmail|hd|ip|';
15614	$excludes .= 'iso9660|k(osmos)?|.*la|mhdd|merger|moose|n|null|oc|opena|';
15615	$excludes .= 'orange|overla(id|y)|pan|pv|s3|rclone|sheepdog|squash|ssh|';
15616	$excludes .= 'smb|union|vm';
15617	$excludes .= ')(fs)?(\d{0,2})?';
15618	return $excludes;
15619}
15620sub get_mounts_fs {
15621	eval $start if $b_log;
15622	my ($item,$mount) = @_;
15623	$item =~ s/map:\/(\S+)/map $1/ if $bsd_type && $bsd_type eq 'darwin';
15624	return 'N/A' if ! @$mount;
15625	my ($fs) = ('');
15626	# linux: /dev/sdb6 on /var/www/m type ext4 (rw,relatime,data=ordered)
15627	# /dev/sda3 on /root.dev/ugw type ext3 (rw,relatime,errors=continue,user_xattr,acl,barrier=1,data=journal)
15628	# bsd: /dev/ada0s1a on / (ufs, local, soft-updates)
15629	# bsd 2: /dev/wd0g on /home type ffs (local, nodev, nosuid)
15630	foreach (@$mount){
15631		if ($_ =~ /^$item\s+on.*?\s+type\s+([\S]+)\s+\([^\)]+\)/){
15632			$fs = $1;
15633			last;
15634		}
15635		elsif ($_ =~ /^$item\s+on.*?\s+\(([^,\s\)]+?)[,\s]*.*\)/){
15636			$fs = $1;
15637			last;
15638		}
15639	}
15640	eval $end if $b_log;
15641	main::log_data('data',"fs: $fs") if $b_log;
15642	return $fs;
15643}
15644sub set_label_uuid {
15645	eval $start if $b_log;
15646	$loaded{'label-uuid'} = 1;
15647	if ($show{'unmounted'} || $show{'label'} || $show{'swap'} || $show{'uuid'}){
15648		if (-d '/dev/disk/by-label'){
15649			@labels = main::globber('/dev/disk/by-label/*');
15650		}
15651		if (-d '/dev/disk/by-uuid'){
15652			@uuids = main::globber('/dev/disk/by-uuid/*');
15653		}
15654		main::log_data('dump', '@labels', \@labels) if $b_log;
15655		main::log_data('dump', '@uuids', \@uuids) if $b_log;
15656	}
15657	eval $end if $b_log;
15658}
15659
15660# args: 1: blockdev full path (part only); 2: block id; 3: size (part only)
15661sub admin_data {
15662	eval $start if $b_log;
15663	my ($blockdev,$id,$size) = @_;
15664	# 0: calc block 1: available percent 2: disk physical block size/partition block size;
15665	my @sizes = (0,0,0);
15666	my ($block_size,$percent,$size_raw) = (0,0,0);
15667	foreach my $row (@proc_partitions){
15668		if ($row->[-1] eq $id){
15669			$size_raw = $row->[2];
15670			last;
15671		}
15672	}
15673	# get the fs block size
15674	$block_size = (main::grabber("$blockdev --getbsz /dev/$id 2>/dev/null"))[0] if $blockdev;
15675	if (!$size_raw){
15676		$size_raw = 'N/A';
15677	}
15678	else {
15679		$percent = sprintf("%.2f", ($size/$size_raw) * 100) if $size && $size_raw;
15680	}
15681	# print "$id size: $size %: $percent p-b: $block_size raw: $size_raw\n";
15682	@sizes = ($size_raw,$percent,$block_size);
15683	main::log_data('dump','@sizes',\@sizes) if $b_log;
15684	eval $end if $b_log;
15685	return @sizes;
15686}
15687sub get_maj_min {
15688	eval $start if $b_log;
15689	my ($id) = @_;
15690	my ($maj_min,@working);
15691	foreach my $row (@proc_partitions){
15692		if ($id eq $row->[-1]){
15693			$maj_min = $row->[0] . ':' . $row->[1];
15694			last;
15695		}
15696	}
15697	eval $end if $b_log;
15698	return $maj_min;
15699}
15700sub get_label {
15701	eval $start if $b_log;
15702	my ($item) = @_;
15703	my $label = '';
15704	foreach (@labels){
15705		if ($item eq Cwd::abs_path($_)){
15706			$label = $_;
15707			$label =~ s/\/dev\/disk\/by-label\///;
15708			$label =~ s/\\x20/ /g;
15709			$label =~ s%\\x2f%/%g;
15710			last;
15711		}
15712	}
15713	$label ||= 'N/A';
15714	eval $end if $b_log;
15715	return $label;
15716}
15717sub get_root {
15718	eval $start if $b_log;
15719	my ($path) = ('/dev/root');
15720	# note: the path may be a symbolic link to by-label/by-uuid but not
15721	# sure how far in abs_path resolves the path.
15722	my $temp = Cwd::abs_path($path);
15723	$path = $temp if $temp;
15724	# note: it's a kernel config option to have /dev/root be a sym link
15725	# or not, if it isn't, path will remain /dev/root, if so, then try mount
15726	if ($path eq '/dev/root' && (my $program = main::check_program('mount'))){
15727		my @data = main::grabber("$program 2>/dev/null");
15728		# /dev/sda2 on / type ext4 (rw,noatime,data=ordered)
15729		foreach (@data){
15730			if (/^([\S]+)\son\s\/\s/){
15731				$path = $1;
15732				# note: we'll be handing off any uuid/label paths to the next
15733				# check tools after get_root() above, so don't trim those.
15734				$path =~ s/.*\/// if $path !~ /by-uuid|by-label/;
15735				last;
15736			}
15737		}
15738	}
15739	eval $end if $b_log;
15740	return $path;
15741}
15742
15743sub get_uuid {
15744	eval $start if $b_log;
15745	my ($item) = @_;
15746	my $uuid = '';
15747	foreach (@uuids){
15748		if ($item eq Cwd::abs_path($_)){
15749			$uuid = $_;
15750			$uuid =~ s/\/dev\/disk\/by-uuid\///;
15751			last;
15752		}
15753	}
15754	$uuid ||= 'N/A';
15755	eval $end if $b_log;
15756	return $uuid;
15757}
15758}
15759
15760## ProcessItem
15761{
15762package ProcessItem;
15763sub get {
15764	eval $start if $b_log;
15765	my $num = 0;
15766	my (@rows);
15767	if (@ps_aux){
15768		if ($show{'ps-cpu'}){
15769			push(@rows,cpu_processes());
15770		}
15771		if ($show{'ps-mem'}){
15772			push(@rows,mem_processes());
15773		}
15774	}
15775	else {
15776		my $key = 'Message';
15777		push(@rows, ({
15778		main::key($num++,0,1,$key) => main::row_defaults('ps-data-null',''),
15779		},));
15780	}
15781	eval $end if $b_log;
15782	return @rows;
15783}
15784sub cpu_processes {
15785	eval $start if $b_log;
15786	my ($j,$num,$cpu,$cpu_mem,$mem,$pid) = (0,0,'','','','');
15787	my ($pid_col,@processes,@rows);
15788	my $count = ($b_irc)? 5: $ps_count;
15789	if ($ps_cols >= 10){
15790		@rows = sort {
15791		my @a = split(/\s+/, $a);
15792		my @b = split(/\s+/, $b);
15793		$b[2] <=> $a[2] } @ps_aux;
15794		$pid_col = 1;
15795	}
15796	else {
15797		@rows = @ps_aux;
15798		$pid_col = 0 if $ps_cols == 2;
15799	}
15800	# if there's a count limit, for irc, etc, only use that much of the data
15801	@rows = splice(@rows,0,$count);
15802
15803	$j = scalar @rows;
15804	# $cpu_mem = ' - Memory: MiB / % used' if $extra > 0;
15805	my $throttled = throttled($ps_count,$count,$j);
15806	# my $header = "CPU  % used - Command - pid$cpu_mem - top";
15807	# my $header = "Top $count by CPU";
15808	my @data = ({
15809	main::key($num++,1,1,'CPU top') => "$count$throttled" . ' of ' . scalar @ps_aux,
15810	},);
15811	push(@processes,@data);
15812	my $i = 1;
15813	foreach (@rows){
15814		$num = 1;
15815		$j = scalar @processes;
15816		my @row = split(/\s+/, $_);
15817		my @command = process_starter(scalar @row, $row[$ps_cols],$row[$ps_cols + 1]);
15818		$cpu = ($ps_cols >= 10) ? $row[2] . '%': 'N/A';
15819		@data = ({
15820		main::key($num++,1,2,$i++) => '',
15821		main::key($num++,0,3,'cpu') => $cpu,
15822		main::key($num++,1,3,'command') => $command[0],
15823		},);
15824		push(@processes,@data);
15825		if ($command[1]){
15826			$processes[$j]->{main::key($num++,0,4,'started by')} = $command[1];
15827		}
15828		$pid = (defined $pid_col)? $row[$pid_col] : 'N/A';
15829		$processes[$j]->{main::key($num++,0,3,'pid')} = $pid;
15830		if ($extra > 0 && $ps_cols >= 10){
15831			my $decimals = ($row[5]/1024 > 10) ? 1 : 2;
15832			$mem = (defined $row[5]) ? sprintf("%.${decimals}f", $row[5]/1024) . ' MiB' : 'N/A';
15833			$mem .= ' (' . $row[3] . '%)';
15834			$processes[$j]->{main::key($num++,0,3,'mem')} = $mem;
15835		}
15836		# print Data::Dumper::Dumper \@processes, "i: $i; j: $j ";
15837	}
15838	eval $end if $b_log;
15839	return @processes;
15840}
15841sub mem_processes {
15842	eval $start if $b_log;
15843	my ($j,$num,$cpu,$cpu_mem,$mem,$pid) = (0,0,'','','','');
15844	my (@data,$pid_col,@processes,$memory,@rows);
15845	my $count = ($b_irc)? 5: $ps_count;
15846	if ($ps_cols >= 10){
15847		@rows = sort {
15848		my @a = split(/\s+/, $a);
15849		my @b = split(/\s+/, $b);
15850		$b[5] <=> $a[5] } @ps_aux; # 5
15851		#$a[1] <=> $b[1] } @ps_aux; # 5
15852		$pid_col = 1;
15853	}
15854	else {
15855		@rows = @ps_aux;
15856		$pid_col = 0 if $ps_cols == 2;
15857	}
15858	@rows = splice(@rows,0,$count);
15859	# print Data::Dumper::Dumper \@rows;
15860	@processes = main::MemoryData::full('process') if !$loaded{'memory'};
15861	$j = scalar @rows;
15862	my $throttled = throttled($ps_count,$count,$j);
15863	#$cpu_mem = ' - CPU: % used' if $extra > 0;
15864	# my $header = "Memory MiB/% used - Command - pid$cpu_mem - top";
15865	# my $header = "Top $count by Memory";
15866	@data = ({
15867	main::key($num++,1,1,'Memory top') => "$count$throttled" . ' of ' . scalar @ps_aux,
15868	},);
15869	push(@processes,@data);
15870	my $i = 1;
15871	foreach (@rows){
15872		$num = 1;
15873		$j = scalar @processes;
15874		my @row = split(/\s+/, $_);
15875		if ($ps_cols >= 10){
15876			my $decimals = ($row[5]/1024 > 10) ? 1 : 2;
15877			$mem = (main::is_int($row[5])) ? sprintf("%.${decimals}f", $row[5]/1024) . ' MiB' : 'N/A';
15878			$mem .= " (" . $row[3] . "%)";
15879		}
15880		else {
15881			$mem = 'N/A';
15882		}
15883		my @command = process_starter(scalar @row, $row[$ps_cols],$row[$ps_cols + 1]);
15884		@data = ({
15885		main::key($num++,1,2,$i++) => '',
15886		main::key($num++,0,3,'mem') => $mem,
15887		main::key($num++,1,3,'command') => $command[0],
15888		},);
15889		push(@processes,@data);
15890		if ($command[1]){
15891			$processes[$j]->{main::key($num++,0,4,'started by')} = $command[1];
15892		}
15893		$pid = (defined $pid_col)? $row[$pid_col] : 'N/A';
15894		$processes[$j]->{main::key($num++,0,3,'pid')} = $pid;
15895		if ($extra > 0 && $ps_cols >= 10){
15896			$cpu = $row[2] . '%';
15897			$processes[$j]->{main::key($num++,0,3,'cpu')} = $cpu;
15898		}
15899		# print Data::Dumper::Dumper \@processes, "i: $i; j: $j ";
15900	}
15901	eval $end if $b_log;
15902	return @processes;
15903}
15904sub process_starter {
15905	my ($count, $row10, $row11) = @_;
15906	my (@return);
15907	# note: [migration/0] would clear with a simple basename
15908	if ($count > ($ps_cols + 1) && $row11 =~ /^\// && $row11 !~ /^\/(tmp|temp)/){
15909		$row11 =~ s/^\/.*\///;
15910		$return[0] = $row11;
15911		$row10 =~ s/^\/.*\///;
15912		$return[1] = $row10;
15913	}
15914	else {
15915		$row10 =~ s/^\/.*\///;
15916		$return[0] = $row10;
15917		$return[1] = '';
15918	}
15919	return @return;
15920}
15921sub throttled {
15922	my ($ps_count,$count,$j) = @_;
15923	my $throttled = '';
15924	if ($count > $j){
15925		$throttled = " ( $j processes)"; # space to avoid emoji in irc
15926	}
15927	elsif ($count < $ps_count){
15928		$throttled = " (throttled from $ps_count)";
15929	}
15930	return $throttled;
15931}
15932}
15933
15934## RaidItem
15935{
15936package RaidItem;
15937
15938sub get {
15939	eval $start if $b_log;
15940	my (@hardware_raid,@rows,$key1,$val1);
15941	my $num = 0;
15942	@hardware_raid = hw_data() if $use{'hardware-raid'} || $fake{'raid-hw'};
15943	raid_data() if !$loaded{'raid'};
15944	# print 'get btrfs: ', Data::Dumper::Dumper \@btrfs_raid;
15945	# print 'get lvm: ', Data::Dumper::Dumper \@lvm_raid;
15946	# print 'get md: ', Data::Dumper::Dumper \@md_raid;
15947	# print 'get zfs: ', Data::Dumper::Dumper \@zfs_raid;
15948	if (!@btrfs_raid && !@lvm_raid && !@md_raid && !@zfs_raid && !@soft_raid &&
15949	 !@hardware_raid){
15950		if ($show{'raid-forced'}){
15951			$key1 = 'Message';
15952			$val1 = main::row_defaults('raid-data');
15953		}
15954	}
15955	else {
15956		if (@hardware_raid){
15957			push(@rows,hw_output(\@hardware_raid));
15958		}
15959		if (@btrfs_raid){
15960			push(@rows,btrfs_output());
15961		}
15962		if (@lvm_raid){
15963			push(@rows,lvm_output());
15964		}
15965		if (@md_raid){
15966			push(@rows,md_output());
15967		}
15968		if (@soft_raid){
15969			push(@rows,soft_output());
15970		}
15971		if (@zfs_raid){
15972			push(@rows,zfs_output());
15973		}
15974	}
15975	if (!@rows && $key1){
15976		@rows = ({main::key($num++,0,1,$key1) => $val1,});
15977	}
15978	eval $end if $b_log;
15979	return @rows;
15980}
15981sub hw_output {
15982	eval $start if $b_log;
15983	my ($hardware_raid) = @_;
15984	my (@rows);
15985	my ($j,$num) = (0,0);
15986	foreach my $row (@$hardware_raid){
15987		$num = 1;
15988		my $device = ($row->{'device'}) ? $row->{'device'}: 'N/A';
15989		my $driver = ($row->{'driver'}) ? $row->{'driver'}: 'N/A';
15990		push(@rows, {
15991		main::key($num++,1,1,'Hardware') => $device,
15992		});
15993		$j = scalar @rows - 1;
15994		$rows[$j]->{main::key($num++,0,2,'vendor')} = $row->{'vendor'} if $row->{'vendor'};
15995		$rows[$j]->{main::key($num++,1,2,'driver')} = $driver;
15996		if ($extra > 0){
15997			$row->{'driver-version'} ||= 'N/A';
15998			$rows[$j]->{main::key($num++,0,3,'v')} = $row->{'driver-version'};
15999			if ($extra > 2){
16000				my $port= ($row->{'port'}) ? $row->{'port'}: 'N/A' ;
16001				$rows[$j]->{main::key($num++,0,2,'port')} = $port;
16002			}
16003			my $bus_id = (defined $row->{'bus-id'} && defined $row->{'sub-id'}) ? "$row->{'bus-id'}.$row->{'sub-id'}": 'N/A' ;
16004			$rows[$j]->{main::key($num++,0,2,'bus-ID')} = $bus_id;
16005		}
16006		if ($extra > 1){
16007			my $chip_id = main::get_chip_id($row->{'vendor-id'},$row->{'chip-id'});
16008			$rows[$j]->{main::key($num++,0,2,'chip-ID')} = $chip_id;
16009		}
16010		if ($extra > 2){
16011			$row->{'rev'} = 'N/A' if !defined $row->{'rev'}; # could be 0
16012			$rows[$j]->{main::key($num++,0,2,'rev')} = $row->{'rev'};
16013			$rows[$j]->{main::key($num++,0,2,'class-ID')} = $row->{'class-id'} if $row->{'class-id'};
16014		}
16015	}
16016	eval $end if $b_log;
16017	# print Data::Dumper::Dumper \@rows;
16018	return @rows;
16019}
16020sub btrfs_output {
16021	eval $start if $b_log;
16022	my (@components,@good,@rows);
16023	my ($size);
16024	my ($j,$num) = (0,0);
16025	foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @btrfs_raid){
16026
16027		$j = scalar @rows;
16028		$rows[$j]->{main::key($num++,1,2,'Components')} = '';
16029		my $b_bump;
16030		components_output('lvm','Online',\@rows,\@good,\$j,\$num,\$b_bump);
16031		components_output('lvm','Meta',\@rows,\@components,\$j,\$num,\$b_bump);
16032	}
16033	eval $end if $b_log;
16034	# print Data::Dumper::Dumper \@rows;
16035	return @rows;
16036}
16037sub lvm_output {
16038	eval $start if $b_log;
16039	my (@components,@good,@components_meta,@rows);
16040	my ($size);
16041	my ($j,$num) = (0,0);
16042	foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @lvm_raid){
16043		$j = scalar @rows;
16044		push(@rows, {
16045		main::key($num++,1,1,'Device') => $row->{'id'},
16046		});
16047		if ($b_admin && $row->{'maj-min'}){
16048			$rows[$j]->{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'};
16049		}
16050		$rows[$j]->{main::key($num++,0,2,'type')} = $row->{'type'};
16051		$rows[$j]->{main::key($num++,0,2,'level')} = $row->{'level'};
16052		$size = ($row->{'size'}) ? main::get_size($row->{'size'},'string'): 'N/A';
16053		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
16054		if ($row->{'raid-sync'}){
16055			$rows[$j]->{main::key($num++,0,2,'sync')} = $row->{'raid-sync'};
16056		}
16057
16058		if ($extra > 0){
16059			$j = scalar @rows;
16060			$num = 1;
16061			$rows[$j]->{main::key($num++,1,2,'Info')} = '';
16062			if (defined $row->{'stripes'}){
16063				$rows[$j]->{main::key($num++,0,3,'stripes')} = $row->{'stripes'};
16064			}
16065			if (defined $row->{'raid-mismatches'} && ($extra > 1 || $row->{'raid-mismatches'} > 0)){
16066				$rows[$j]->{main::key($num++,0,3,'mismatches')} = $row->{'raid-mismatches'};
16067			}
16068			if (defined $row->{'copy-percent'} && ($extra > 1 || $row->{'copy-percent'} < 100)){
16069				$rows[$j]->{main::key($num++,0,3,'copied')} = ($row->{'copy-percent'} + 0) . '%';
16070			}
16071			if ($row->{'vg'}){
16072				$rows[$j]->{main::key($num++,1,3,'v-group')} = $row->{'vg'};
16073			}
16074			$size = ($row->{'vg-size'}) ? main::get_size($row->{'vg-size'},'string') : 'N/A';
16075			$rows[$j]->{main::key($num++,0,4,'vg-size')} = $size;
16076			$size = ($row->{'vg-free'}) ? main::get_size($row->{'vg-free'},'string') : 'N/A';
16077			$rows[$j]->{main::key($num++,0,4,'vg-free')} = $size;
16078		}
16079		@components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : ();
16080		@good = ();
16081		@components_meta = ();
16082		foreach my $item (sort { $a->[0] cmp $b->[0]} @components){
16083			if ($item->[4] =~ /_rmeta/){
16084				push(@components_meta, $item);
16085			}
16086			else {
16087				push(@good, $item);
16088			}
16089		}
16090		$j = scalar @rows;
16091		$rows[$j]->{main::key($num++,1,2,'Components')} = '';
16092		my $b_bump;
16093		components_output('lvm','Online',\@rows,\@good,\$j,\$num,\$b_bump);
16094		components_output('lvm','Meta',\@rows,\@components_meta,\$j,\$num,\$b_bump);
16095	}
16096	eval $end if $b_log;
16097	# print Data::Dumper::Dumper \@rows;
16098	return @rows;
16099}
16100sub md_output {
16101	eval $start if $b_log;
16102	my (@components,@good,@failed,@inactive,@rows,@spare,@temp);
16103	my ($blocks,$chunk,$level,$report,$size,$status);
16104	my ($j,$num) = (0,0);
16105	# print Data::Dumper::Dumper \@md_raid;
16106	if ($extra > 2 && $md_raid[0]->{'supported-levels'}){
16107		push(@rows, {
16108		main::key($num++,0,1,'Supported mdraid levels') => $md_raid[0]->{'supported-levels'},
16109		});
16110	}
16111	foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @md_raid){
16112		$j = scalar @rows;
16113		next if !%$row;
16114		$num = 1;
16115		$level = (defined $row->{'level'}) ? $row->{'level'} : 'linear';
16116		push(@rows, {
16117		main::key($num++,1,1,'Device') => $row->{'id'},
16118		});
16119		if ($b_admin && $row->{'maj-min'}){
16120			$rows[$j]->{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'};
16121		}
16122		$rows[$j]->{main::key($num++,0,2,'type')} = $row->{'type'};
16123		$rows[$j]->{main::key($num++,0,2,'level')} = $level;
16124		$rows[$j]->{main::key($num++,0,2,'status')} = $row->{'status'};
16125		if ($row->{'details'}{'state'}){
16126			$rows[$j]->{main::key($num++,0,2,'state')} = $row->{'details'}{'state'};
16127		}
16128		if ($row->{'size'}){
16129			$size = main::get_size($row->{'size'},'string');
16130		}
16131		else {
16132			$size = (!$b_root && !@lsblk) ? main::row_defaults('root-required'): 'N/A';
16133		}
16134		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
16135		$report = ($row->{'report'}) ? $row->{'report'}: '';
16136		$report .= " $row->{'u-data'}" if $report;
16137		$report ||= 'N/A';
16138		if ($extra == 0){
16139			# print "here 0\n";
16140			$rows[$j]->{main::key($num++,0,2,'report')} = $report;
16141		}
16142		if ($extra > 0){
16143			$j = scalar @rows;
16144			$num = 1;
16145			$rows[$j]->{main::key($num++,1,2,'Info')} = '';
16146			#$rows[$j]->{main::key($num++,0,3,'raid')} = $raid;
16147			$rows[$j]->{main::key($num++,0,3,'report')} = $report;
16148			$blocks = ($row->{'blocks'}) ? $row->{'blocks'} : 'N/A';
16149			$rows[$j]->{main::key($num++,0,3,'blocks')} = $blocks;
16150			$chunk = ($row->{'chunk-size'}) ? $row->{'chunk-size'} : 'N/A';
16151			$rows[$j]->{main::key($num++,0,3,'chunk-size')} = $chunk;
16152			if ($extra > 1){
16153				if ($row->{'bitmap'}){
16154					$rows[$j]->{main::key($num++,0,3,'bitmap')} = $row->{'bitmap'};
16155				}
16156				if ($row->{'super-block'}){
16157					$rows[$j]->{main::key($num++,0,3,'super-blocks')} = $row->{'super-block'};
16158				}
16159				if ($row->{'algorithm'}){
16160					$rows[$j]->{main::key($num++,0,3,'algorithm')} = $row->{'algorithm'};
16161				}
16162			}
16163		}
16164		@components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : ();
16165		@good = ();
16166		@failed = ();
16167		@inactive = ();
16168		@spare = ();
16169		# @spare = split(/\s+/, $row->{'unused'}) if $row->{'unused'};
16170		# print Data::Dumper::Dumper \@components;
16171		foreach my $item (sort { $a->[1] <=> $b->[1]} @components){
16172			if (defined $item->[2] && $item->[2] =~ /^(F)$/){
16173				push(@failed,$item);
16174			}
16175			elsif (defined $item->[2] && $item->[2] =~ /(S)$/){
16176				push(@spare,$item);
16177			}
16178			elsif ($row->{'status'} && $row->{'status'} eq 'inactive'){
16179				push(@inactive,$item);
16180			}
16181			else {
16182				push(@good,$item);
16183			}
16184		}
16185		$j = scalar @rows;
16186		$rows[$j]->{main::key($num++,1,2,'Components')} = '';
16187		my $b_bump;
16188		components_output('mdraid','Online',\@rows,\@good,\$j,\$num,\$b_bump);
16189		components_output('mdraid','Failed',\@rows,\@failed,\$j,\$num,\$b_bump);
16190		components_output('mdraid','Inactive',\@rows,\@inactive,\$j,\$num,\$b_bump);
16191		components_output('mdraid','Spare',\@rows,\@spare,\$j,\$num,\$b_bump);
16192		if ($row->{'recovery-percent'}){
16193			$j = scalar @rows;
16194			$num = 1;
16195			my $percent = $row->{'recovery-percent'};
16196			if ($extra > 1 && $row->{'progress-bar'}){
16197				$percent .= " $row->{'progress-bar'}"
16198			}
16199			$rows[$j]->{main::key($num++,1,2,'Recovering')} = $percent;
16200			my $finish = ($row->{'recovery-finish'})?$row->{'recovery-finish'} : 'N/A';
16201			$rows[$j]->{main::key($num++,0,3,'time-remaining')} = $finish;
16202			if ($extra > 0){
16203				if ($row->{'sectors-recovered'}){
16204					$rows[$j]->{main::key($num++,0,3,'sectors')} = $row->{'sectors-recovered'};
16205				}
16206			}
16207			if ($extra > 1 && $row->{'recovery-speed'}){
16208				$rows[$j]->{main::key($num++,0,3,'speed')} = $row->{'recovery-speed'};
16209			}
16210		}
16211	}
16212	eval $end if $b_log;
16213	# print Data::Dumper::Dumper \@rows;
16214	return @rows;
16215}
16216
16217sub soft_output {
16218	eval $start if $b_log;
16219	my (@components,@good,@failed,@rows,@offline,@rebuild,@temp);
16220	my ($size);
16221	my ($j,$num) = (0,0);
16222	if (@soft_raid && $alerts{'bioctl'}->{'action'} eq 'permissions'){
16223		push(@rows,{
16224		main::key($num++,1,1,'Message') => main::row_defaults('root-item-incomplete','softraid'),
16225		});
16226	}
16227	# print Data::Dumper::Dumper \@soft_raid;
16228	foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @soft_raid){
16229		$j = scalar @rows;
16230		next if !%$row;
16231		$num = 1;
16232		push(@rows, {
16233		main::key($num++,1,1,'Device') => $row->{'id'},
16234		});
16235		$row->{'level'} ||= 'N/A';
16236		$rows[$j]->{main::key($num++,0,2,'type')} = $row->{'type'};
16237		$rows[$j]->{main::key($num++,0,2,'level')} = $row->{'level'};
16238		$rows[$j]->{main::key($num++,0,2,'status')} = $row->{'status'};
16239		if ($row->{'state'}){
16240			$rows[$j]->{main::key($num++,0,2,'state')} = $row->{'state'};
16241		}
16242		if ($row->{'size'}){
16243			$size = main::get_size($row->{'size'},'string');
16244		}
16245		$size ||= 'N/A';
16246		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
16247		@components = (ref $row->{'components'} eq 'ARRAY') ? @{$row->{'components'}} : ();
16248		@good = ();
16249		@failed = ();
16250		@offline = ();
16251		@rebuild = ();
16252		foreach my $item (sort { $a->[1] <=> $b->[1]} @components){
16253			if (defined $item->[2] && $item->[2] eq 'failed'){
16254				push(@failed,$item);
16255			}
16256			elsif (defined $item->[2] && $item->[2] eq 'offline'){
16257				push(@offline,$item);
16258			}
16259			elsif (defined $item->[2] && $item->[2] eq 'rebuild'){
16260				push(@rebuild,$item);
16261			}
16262			else {
16263				push(@good,$item);
16264			}
16265		}
16266		$j = scalar @rows;
16267		$rows[$j]->{main::key($num++,1,2,'Components')} = '';
16268		my $b_bump;
16269		components_output('softraid','Online',\@rows,\@good,\$j,\$num,\$b_bump);
16270		components_output('softraid','Failed',\@rows,\@failed,\$j,\$num,\$b_bump);
16271		components_output('softraid','Rebuild',\@rows,\@rebuild,\$j,\$num,\$b_bump);
16272		components_output('softraid','Offline',\@rows,\@offline,\$j,\$num,\$b_bump);
16273	}
16274	eval $end if $b_log;
16275	# print Data::Dumper::Dumper \@rows;
16276	return @rows;
16277}
16278
16279sub zfs_output {
16280	eval $start if $b_log;
16281	my (@arrays,@arrays_holder,@components,@good,@failed,@rows,@spare);
16282	my ($allocated,$available,$level,$size,$status);
16283	my ($b_row_1_sizes);
16284	my ($j,$num) = (0,0);
16285	# print Data::Dumper::Dumper \@zfs_raid;
16286	foreach my $row (sort {$a->{'id'} cmp $b->{'id'}} @zfs_raid){
16287		$j = scalar @rows;
16288		$b_row_1_sizes = 0;
16289		next if !%$row;
16290		$num = 1;
16291		push(@rows, {
16292		main::key($num++,1,1,'Device') => $row->{'id'},
16293		main::key($num++,0,2,'type') => $row->{'type'},
16294		main::key($num++,0,2,'status') => $row->{'status'},
16295		});
16296		$size = ($row->{'raw-size'}) ? main::get_size($row->{'raw-size'},'string') : '';
16297		$available = main::get_size($row->{'raw-free'},'string',''); # could be zero free
16298		if ($extra > 2){
16299			$allocated = ($row->{'raw-allocated'}) ? main::get_size($row->{'raw-allocated'},'string') : '';
16300		}
16301		@arrays = @{$row->{'arrays'}};
16302		@arrays = grep {defined $_} @arrays;
16303		@arrays_holder = @arrays;
16304		my $count = scalar @arrays;
16305		if (!defined $arrays[0]->{'level'}){
16306			$level = 'linear';
16307			$rows[$j]->{main::key($num++,0,2,'level')} = $level;
16308		}
16309		elsif ($count < 2 && $arrays[0]->{'level'}){
16310			$rows[$j]->{main::key($num++,0,2,'level')} = $arrays[0]->{'level'};
16311		}
16312		if ($size || $available || $allocated){
16313			$rows[$j]->{main::key($num++,1,2,'raw')} = '';
16314			if ($size){
16315				# print "here 0\n";
16316				$rows[$j]->{main::key($num++,0,3,'size')} = $size;
16317				$size = '';
16318				$b_row_1_sizes = 1;
16319			}
16320			if ($available){
16321				$rows[$j]->{main::key($num++,0,3,'free')} = $available;
16322				$available = '';
16323				$b_row_1_sizes = 1;
16324			}
16325			if ($allocated){
16326				$rows[$j]->{main::key($num++,0,3,'allocated')} = $allocated;
16327				$allocated = '';
16328			}
16329		}
16330		if ($row->{'zfs-size'}){
16331			$rows[$j]->{main::key($num++,1,2,'zfs-fs')} = '';
16332			$rows[$j]->{main::key($num++,0,3,'size')} = main::get_size($row->{'zfs-size'},'string');
16333			$rows[$j]->{main::key($num++,0,3,'free')} = main::get_size($row->{'zfs-free'},'string');
16334		}
16335		foreach my $row2 (@arrays){
16336			if ($count > 1){
16337				$j = scalar @rows;
16338				$num = 1;
16339				$size = ($row2->{'raw-size'}) ? main::get_size($row2->{'raw-size'},'string') : 'N/A';
16340				$available = ($row2->{'raw-free'}) ? main::get_size($row2->{'raw-free'},'string') : 'N/A';
16341				$level = (defined $row2->{'level'}) ? $row2->{'level'}: 'linear';
16342				$status = ($row2->{'status'}) ? $row2->{'status'}: 'N/A';
16343				push(@rows, {
16344				main::key($num++,1,2,'Array') => $level,
16345				main::key($num++,0,3,'status') => $status,
16346				main::key($num++,1,3,'raw') => '',
16347				main::key($num++,0,4,'size') => $size,
16348				main::key($num++,0,4,'free') => $available,
16349				});
16350			}
16351			# items like cache may have one component, with a size on that component
16352			elsif (!$b_row_1_sizes){
16353				# print "here $count\n";
16354				$size = ($row2->{'raw-size'}) ? main::get_size($row2->{'raw-size'},'string') : 'N/A';
16355				$available = ($row2->{'raw-free'}) ? main::get_size($row2->{'raw-free'},'string') : 'N/A';
16356				$rows[$j]->{main::key($num++,1,2,'raw')} = '';
16357				$rows[$j]->{main::key($num++,0,3,'size')} = $size;
16358				$rows[$j]->{main::key($num++,0,3,'free')} = $available;
16359				if ($extra > 2){
16360					$allocated = ($row->{'raw-allocated'}) ? main::get_size($row2->{'allocated'},'string') : '';
16361					if ($allocated){
16362						$rows[$j]->{main::key($num++,0,3,'allocated')} = $allocated;
16363					}
16364				}
16365			}
16366			@components = (ref $row2->{'components'} eq 'ARRAY') ? @{$row2->{'components'}} : ();
16367			@failed = ();
16368			@spare = ();
16369			@good = ();
16370			# @spare = split(/\s+/, $row->{'unused'}) if $row->{'unused'};
16371			foreach my $item (sort { $a->[0] cmp $b->[0]} @components){
16372				if (defined $item->[3] && $item->[3] =~ /^(DEGRADED|FAULTED|UNAVAIL)$/){
16373					push(@failed, $item);
16374				}
16375				elsif (defined $item->[3] && $item->[3] =~ /(AVAIL|OFFLINE|REMOVED)$/){
16376					push(@spare, $item);
16377				}
16378				# note: spares in use show: INUSE but technically it's still a spare,
16379				# but since it's in use, consider it online.
16380				else {
16381					push(@good, $item);
16382				}
16383			}
16384			$j = scalar @rows;
16385			$rows[$j]->{main::key($num++,1,3,'Components')} = '';
16386			my $b_bump;
16387			components_output('zfs','Online',\@rows,\@good,\$j,\$num,\$b_bump);
16388			components_output('zfs','Failed',\@rows,\@failed,\$j,\$num,\$b_bump);
16389			components_output('zfs','Available',\@rows,\@spare,\$j,\$num,\$b_bump);
16390		}
16391	}
16392	eval $end if $b_log;
16393	# print Data::Dumper::Dumper \@rows;
16394	return @rows;
16395}
16396
16397## Most key stuff passed by ref, and is changed on the fly
16398sub components_output {
16399	eval $start if $b_log;
16400	my ($type,$item,$rows_ref,$array_ref,$j_ref,$num_ref,$b_bump_ref) = @_;
16401	return if !@$array_ref && $item ne 'Online';
16402	my ($extra1,$extra2,$f1,$f2,$f3,$f4,$f5,$k,$k1,$key1,$l1,$l2,$l3);
16403	if ($type eq 'btrfs'){
16404
16405	}
16406	elsif ($type eq 'lvm'){
16407		($f1,$f2,$f3,$f4,$f5,$l1,$l2,$l3) = (1,2,3,4,5,3,4,5);
16408		$k = 1;
16409		$extra1 = 'mapped';
16410		$extra2 = 'dev';
16411	}
16412	elsif ($type eq 'mdraid'){
16413		($f1,$f2,$f3,$f4,$k1,$l1,$l2,$l3) = (3,4,5,6,1,3,4,5);
16414		$extra1 = 'mapped';
16415		$k = 1 if $item eq 'Inactive';
16416	}
16417	elsif ($type eq 'softraid'){
16418		($f1,$f2,$f3,$f4,$k1,$l1,$l2,$l3) = (1,10,10,3,5,3,4,5);
16419		$extra1 = 'device';
16420		$k = 1;
16421	}
16422	elsif ($type eq 'zfs'){
16423		($f1,$f2,$f3,$l1,$l2,$l3) = (1,2,3,4,5,6);
16424		$k = 1;
16425	}
16426	# print "item: $item\n";
16427	$$j_ref++ if $$b_bump_ref;
16428	$$b_bump_ref = 0;
16429	my $good = ($item eq 'Online' && !@$array_ref) ? 'N/A' : '';
16430	$$rows_ref[$$j_ref]->{main::key($$num_ref++,1,$l1,$item)} = $good;
16431	#$$j_ref++ if $b_admin;
16432	# print Data::Dumper::Dumper $array_ref;
16433	foreach my $device (@$array_ref){
16434		next if ref $device ne 'ARRAY';
16435		# if ($b_admin && $device->[$f1] && $device->[$f2]){
16436		if ($b_admin){
16437			$$j_ref++;
16438			$$b_bump_ref = 1;
16439			$$num_ref = 1;
16440		}
16441		$key1 = (defined $k1 && defined $device->[$k1]) ? $device->[$k1] : $k++;
16442		$$rows_ref[$$j_ref]->{main::key($$num_ref++,1,$l2,$key1)} = $device->[0];
16443		if ($b_admin && $device->[$f2]){
16444			$$rows_ref[$$j_ref]{main::key($$num_ref++,0,$l3,'maj-min')} = $device->[$f2];
16445		}
16446		if ($b_admin && $device->[$f1]){
16447			my $size = main::get_size($device->[$f1],'string');
16448			$$rows_ref[$$j_ref]->{main::key($$num_ref++,0,$l3,'size')} = $size;
16449		}
16450		if ($b_admin && $device->[$f3]){
16451			$$rows_ref[$$j_ref]->{main::key($$num_ref++,0,$l3,'state')} = $device->[$f3];
16452		}
16453		if ($b_admin && $extra1 && $device->[$f4]){
16454			$$rows_ref[$$j_ref]->{main::key($$num_ref++,0,$l3,$extra1)} = $device->[$f4];
16455		}
16456		if ($b_admin && $extra2 && $device->[$f5]){
16457			$$rows_ref[$$j_ref]->{main::key($$num_ref++,0,$l3,$extra2)} = $device->[$f5];
16458		}
16459	}
16460	eval $end if $b_log;
16461}
16462
16463sub raid_data {
16464	eval $start if $b_log;
16465	LsblkData::set() if !$bsd_type && !$loaded{'lsblk'};
16466	main::set_mapper() if !$bsd_type && !$loaded{'mapper'};
16467	PartitionData::set() if !$bsd_type && !$loaded{'partition-data'};
16468	my (@data);
16469	$loaded{'raid'} = 1;
16470	if ($fake{'raid-btrfs'} ||
16471	 ($alerts{'btrfs'}->{'action'} && $alerts{'btrfs'}->{'action'} eq 'use')){
16472		@btrfs_raid = btrfs_data();
16473	}
16474	if ($fake{'raid-lvm'} ||
16475	 ($alerts{'lvs'}->{'action'} && $alerts{'lvs'}->{'action'} eq 'use')){
16476		@lvm_raid = lvm_data();
16477	}
16478	if ($fake{'raid-md'} || (my $file = $system_files{'proc-mdstat'})){
16479		@md_raid = md_data($file);
16480	}
16481	if ($fake{'raid-soft'} || $sysctl{'softraid'}){
16482		DiskDataBSD::set() if !$loaded{'disk-data-bsd'};
16483		@soft_raid = soft_data();
16484	}
16485	if ($fake{'raid-zfs'} || (my $path = main::check_program('zpool'))){
16486		DiskDataBSD::set() if $bsd_type && !$loaded{'disk-data-bsd'};
16487		@zfs_raid = zfs_data($path);
16488	}
16489	eval $end if $b_log;
16490}
16491# 0 type
16492# 1 type_id
16493# 2 bus_id
16494# 3 sub_id
16495# 4 device
16496# 5 vendor_id
16497# 6 chip_id
16498# 7 rev
16499# 8 port
16500# 9 driver
16501# 10 modules
16502sub hw_data {
16503	eval $start if $b_log;
16504	return if !$devices{'hwraid'};
16505	my ($driver,$vendor,@hardware_raid);
16506	foreach my $working (@{$devices{'hwraid'}}){
16507		$driver = ($working->[9]) ? lc($working->[9]): '';
16508		$driver =~ s/-/_/g if $driver;
16509		my $driver_version = ($driver) ? main::get_module_version($driver): '';
16510		if ($extra > 2 && $use{'pci-tool'} && $working->[11]){
16511			$vendor = main::get_pci_vendor($working->[4],$working->[11]);
16512		}
16513		push(@hardware_raid, {
16514		'class-id' => $working->[1],
16515		'bus-id' => $working->[2],
16516		'chip-id' => $working->[6],
16517		'device' => $working->[4],
16518		'driver' => $driver,
16519		'driver-version' => $driver_version,
16520		'port' => $working->[8],
16521		'rev' => $working->[7],
16522		'sub-id' => $working->[3],
16523		'vendor-id' => $working->[5],
16524		'vendor' => $vendor,
16525		});
16526	}
16527	# print Data::Dumper::Dumper \@hardware_raid;
16528	main::log_data('dump','@hardware_raid',\@hardware_raid) if $b_log;
16529	eval $end if $b_log;
16530	return @hardware_raid;
16531}
16532sub btrfs_data {
16533	eval $start if $b_log;
16534	my (@btraid,@working);
16535	if ($fake{'raid-btrfs'}){
16536
16537	}
16538	else {
16539
16540	}
16541	print Data::Dumper::Dumper \@working if $dbg[37];
16542
16543	print Data::Dumper::Dumper \@btraid if $dbg[37];
16544	main::log_data('dump','@lvraid',\@btraid) if $b_log;
16545	eval $end if $b_log;
16546	return @btraid;
16547}
16548sub lvm_data {
16549	eval $start if $b_log;
16550	LogicalItem::lvm_data() if !$loaded{'logical-data'};
16551	return if !@lvm;
16552	my (@lvraid,$maj_min,$vg_used,@working);
16553	foreach my $item (@lvm){
16554		next if $item->{'segtype'} && $item->{'segtype'} !~ /^raid/;
16555		my (@components,$dev,$maj_min,$vg_used);
16556		# print Data::Dumper::Dumper $item;
16557		if ($item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'}){
16558			$maj_min = $item->{'lv_kernel_major'} . ':' . $item->{'lv_kernel_minor'};
16559		}
16560		if (defined $item->{'vg_free'} && defined $item->{'vg_size'}){
16561			$vg_used = ($item->{'vg_size'} - $item->{'vg_free'});
16562		}
16563		$raw_logical[0] += $item->{'lv_size'} if $item->{'lv_size'};
16564		@working = main::globber("/sys/dev/block/$maj_min/slaves/*") if $maj_min;
16565		@working = map {$_ =~ s|^/.*/||; $_;} @working if @working;
16566		foreach my $part (@working){
16567			my (@data,$dev,$maj_min,$mapped,$size);
16568			if (@proc_partitions){
16569				@data = PartitionData::get($part);
16570				$maj_min = $data[0] . ':' . $data[1] if defined $data[1];
16571				$size = $data[2];
16572				$raw_logical[1] += $size if $part =~ /^dm-/ && $size;
16573				@data = main::globber("/sys/dev/block/$maj_min/slaves/*") if $maj_min;
16574				@data = map {$_ =~ s|^/.*/||; $_;} @data if @data;
16575				$dev = join(',', @data) if @data;
16576			}
16577			$mapped = $dmmapper{$part} if %dmmapper;
16578			push(@components, [$part,$size,$maj_min,undef,$mapped,$dev],);
16579		}
16580		if ($item->{'segtype'}){
16581			if ($item->{'segtype'} eq 'raid1'){$item->{'segtype'} = 'mirror';}
16582			else {$item->{'segtype'} =~ s/^raid([0-9]+)/raid-$1/; }
16583		}
16584		push(@lvraid, {
16585		'components' => \@components,
16586		'copy-percent' => $item->{'copy_percent'},
16587		'id' => $item->{'lv_name'},
16588		'level' => $item->{'segtype'},
16589		'maj-min' => $maj_min,
16590		'raid-mismatches' =>  $item->{'raid_mismatch_count'},
16591		'raid-sync' =>  $item->{'raid_sync_action'},
16592		'size' => $item->{'lv_size'},
16593		'stripes' => $item->{'stripes'},
16594		'type' => $item->{'vg_fmt'},
16595		'vg' => $item->{'vg_name'},
16596		'vg-free' => $item->{'vg_free'},
16597		'vg-size' => $item->{'vg_size'},
16598		'vg-used' => $vg_used,
16599		});
16600	}
16601	print Data::Dumper::Dumper \@lvraid if $dbg[37];
16602	main::log_data('dump','@lvraid',\@lvraid) if $b_log;
16603	eval $end if $b_log;
16604	return @lvraid;
16605}
16606sub md_data {
16607	eval $start if $b_log;
16608	my ($mdstat) = @_;
16609	my $j = 0;
16610	if ($fake{'raid-md'}){
16611		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-4-device-1.txt";
16612		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-rebuild-1.txt";
16613		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-2-mirror-fserver2-1.txt";
16614		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-2-raid10-abucodonosor.txt";
16615		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-2-raid10-ant.txt";
16616		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-inactive-weird-syntax.txt";
16617		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-inactive-active-syntax.txt";
16618		#$mdstat = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/md-inactive-active-spare-syntax.txt";
16619	}
16620	my @working = main::reader($mdstat,'strip');
16621	# print Data::Dumper::Dumper \@working;
16622	my (@mdraid,@temp,$b_found,$system,$unused);
16623	# NOTE: a system with empty mdstat will not show these values
16624	if ($working[0] && $working[0] =~ /^Personalities/){
16625		$system = (split(/:\s*/, $working[0]))[1];
16626		$system =~ s/\[|\]//g if $system;
16627		shift @working;
16628	}
16629	if ($working[-1] && $working[-1] =~ /^unused\sdevices/){
16630		$unused = (split(/:\s*/, $working[-1]))[1];
16631		$unused =~ s/<|>|none//g if $unused;
16632		pop @working;
16633	}
16634	foreach (@working){
16635		$_ =~ s/\s*:\s*/:/;
16636		# print "$_\n";
16637		# md0 : active raid1 sdb1[2] sda1[0]
16638		# md126 : active (auto-read-only) raid1 sdq1[0]
16639		# md127 : inactive sda0
16640		# md1 : inactive sda1[0] sdd1[3] sdc1[2] sdb1[1]
16641		# if (/^(md[0-9]+)\s*:\s*([^\s]+)(\s\([^)]+\))?\s([^\s]+)\s(.*)/){
16642		if (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?/){
16643			my ($component_string,$id,$level,$maj_min,@part,$size,$status);
16644			my (@components,%details,%device);
16645			$id = $1;
16646			$status = $2;
16647			if (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?\s((faulty|linear|multipath|raid)[\S]*)\s(.*)/){
16648				$level = $4;
16649				$component_string = $6;
16650				$level =~ s/^raid1$/mirror/;
16651				$level =~ s/^raid/raid-/;
16652				$level = 'mirror' if $level eq '1';
16653			}
16654			elsif (/^(md[0-9]+)\s*:\s*([\S]+)(\s\([^)]+\))?\s(.*)/){
16655				$component_string = $4;
16656				$level = 'N/A';
16657			}
16658			@temp = ();
16659			# cascade of tests, light to cpu intense
16660			if ((!$maj_min || !$size) && @proc_partitions){
16661				@part = PartitionData::get($id);
16662				if (@part){
16663					$maj_min = $part[0] . ':' . $part[1];
16664					$size = $part[2];
16665				}
16666			}
16667			if ((!$maj_min || !$size) && @lsblk){
16668				%device = LsblkData::get($id) if @lsblk;
16669				$maj_min = $device{'maj-min'} if $device{'maj-min'};
16670				$size = $device{'size'} if $device{'size'};
16671			}
16672			if ((!$size || $b_admin) && $alerts{'mdadm'}->{'action'} eq 'use'){
16673				%details = md_details($id);
16674				$size = $details{'size'} if $details{'size'};
16675			}
16676			$raw_logical[0] += $size if $size;
16677			# remember, these include the [x] id, so remove that for disk/unmounted
16678			foreach my $component (split(/\s+/, $component_string)){
16679				my (%data,$maj_min,$name,$number,$info,$mapped,$part_size,$state);
16680				if ($component =~ /([\S]+)\[([0-9]+)\]\(?([SF])?\)?/){
16681					($name,$number,$info) = ($1,$2,$3);
16682				}
16683				elsif ($component =~ /([\S]+)/){
16684					$name = $1;
16685				}
16686				next if !$name;
16687				if ($details{'devices'} && ref $details{'devices'} eq 'HASH'){
16688					$maj_min = $details{'devices'}->{$name}{'maj-min'};
16689					$state = $details{'devices'}->{$name}{'state'};
16690				}
16691				if ((!$maj_min || !$part_size) && @proc_partitions){
16692					@part = PartitionData::get($name);
16693					if (@part){
16694						$maj_min = $part[0] . ':' . $part[1] if !$maj_min;
16695						$part_size = $part[2] if !$part_size;
16696					}
16697				}
16698				if ((!$maj_min || !$part_size) && @lsblk){
16699					%data= LsblkData::get($name);
16700					$maj_min = $data{'maj-min'} if !$maj_min;
16701					$part_size = $data{'size'}if !$part_size;
16702				}
16703				$mapped = $dmmapper{$name} if %dmmapper;
16704				$raw_logical[1] += $part_size if $part_size;
16705				$state = $info if !$state && $info;
16706				push(@components,[$name,$number,$info,$part_size,$maj_min,$state,$mapped]);
16707			}
16708			# print "$component_string\n";
16709			$j = scalar @mdraid;
16710			push(@mdraid, {
16711			'chunk-size' => $details{'chunk-size'}, # if we got it, great, if not, further down
16712			'components' => \@components,
16713			'details' => \%details,
16714			'id' => $id,
16715			'level' => $level,
16716			'maj-min' => $maj_min,
16717			'size' => $size,
16718			'status' => $status,
16719			'type' => 'mdraid',
16720			});
16721		}
16722		# print "$_\n";
16723		if ($_ =~ /^([0-9]+)\sblocks/){
16724			$mdraid[$j]->{'blocks'} = $1;
16725		}
16726		if ($_ =~ /super\s([0-9\.]+)\s/){
16727			$mdraid[$j]->{'super-block'} = $1;
16728		}
16729		if ($_ =~ /algorithm\s([0-9\.]+)\s/){
16730			$mdraid[$j]->{'algorithm'} = $1;
16731		}
16732		if ($_ =~ /\[([0-9]+\/[0-9]+)\]\s\[([U_]+)\]/){
16733			$mdraid[$j]->{'report'} = $1;
16734			$mdraid[$j]->{'u-data'} = $2;
16735		}
16736		if ($_ =~ /resync=([\S]+)/){
16737			$mdraid[$j]->{'resync'} = $1;
16738		}
16739		if ($_ =~ /([0-9]+[km])\schunk/i){
16740			$mdraid[$j]->{'chunk-size'} = $1;
16741		}
16742		if ($_ =~ /(\[[=]*>[\.]*\]).*(resync|recovery)\s*=\s*([0-9\.]+%)?(\s\(([0-9\/]+)\))?/){
16743			$mdraid[$j]->{'progress-bar'} = $1;
16744			$mdraid[$j]->{'recovery-percent'} = $3 if $3;
16745			$mdraid[$j]->{'sectors-recovered'} = $5 if $5;
16746		}
16747		if ($_ =~ /finish\s*=\s*([\S]+)\s+speed\s*=\s*([\S]+)/){
16748			$mdraid[$j]->{'recovery-finish'} = $1;
16749			$mdraid[$j]->{'recovery-speed'} = $2;
16750		}
16751		# print 'mdraid loop: ', Data::Dumper::Dumper \@mdraid;
16752	}
16753	if (@mdraid){
16754		$mdraid[0]->{'supported-levels'} = $system if $system;
16755		$mdraid[0]->{'unused'} = $unused if $unused;
16756	}
16757	print Data::Dumper::Dumper \@mdraid if $dbg[37];
16758	eval $end if $b_log;
16759	return @mdraid;
16760}
16761sub md_details {
16762	eval $start if $b_log;
16763	my ($id) = @_;
16764	my (%details,@working);
16765	my $cmd = $alerts{'mdadm'}->{'path'} . " --detail /dev/$id 2>/dev/null";
16766	my @data = main::grabber($cmd,'','strip');
16767	main::log_data('dump',"$id raw: \@data",\@data) if $b_log;
16768	foreach (@data){
16769		@working = split(/\s*:\s*/, $_, 2);
16770		if (scalar @working == 2){
16771			if ($working[0] eq 'Array Size' && $working[1] =~ /^([0-9]+)\s\(/){
16772				$details{'size'} = $1;
16773			}
16774			elsif ($working[0] eq 'Active Devices'){
16775				$details{'c-active'} = $working[1];
16776			}
16777			elsif ($working[0] eq 'Chunk Size'){
16778				$details{'chunk-size'} = $working[1];
16779			}
16780			elsif ($working[0] eq 'Failed Devices'){
16781				$details{'c-failed'} = $working[1];
16782			}
16783			elsif ($working[0] eq 'Raid Devices'){
16784				$details{'c-raid'} = $working[1];
16785			}
16786			elsif ($working[0] eq 'Spare Devices'){
16787				$details{'c-spare'} = $working[1];
16788			}
16789			elsif ($working[0] eq 'State'){
16790				$details{'state'} = $working[1];
16791			}
16792			elsif ($working[0] eq 'Total Devices'){
16793				$details{'c-total'} = $working[1];
16794			}
16795			elsif ($working[0] eq 'Used Dev Size' && $working[1] =~ /^([0-9]+)\s\(/){
16796				$details{'dev-size'} = $1;
16797			}
16798			elsif ($working[0] eq 'UUID'){
16799				$details{'uuid'} = $working[1];
16800			}
16801			elsif ($working[0] eq 'Working Devices'){
16802				$details{'c-working'} = $working[1];
16803			}
16804		}
16805		# end component data lines
16806		else {
16807			@working = split(/\s+/,$_);
16808			# 0       8       80        0      active sync   /dev/sdf
16809			# 2       8      128        -      spare   /dev/sdi
16810			next if !@working || $working[0] eq 'Number' || scalar @working < 6;
16811			$working[-1] =~ s|^/dev/(mapper/)?||;
16812			$details{'devices'}->{$working[-1]} = {
16813			'maj-min' => $working[1] . ':' . $working[2],
16814			'number' => $working[0],
16815			'raid-device' => $working[3],
16816			'state' => join(' ', @working[4..($#working - 1)]),
16817			};
16818		}
16819	}
16820	# print Data::Dumper::Dumper \%details;
16821	main::log_data('dump',"$id: %details",\%details) if $b_log;
16822	eval $end if $b_log;
16823	return %details;
16824}
16825
16826sub soft_data {
16827	eval $start if $b_log;
16828	my ($cmd,$id,$state,$status,@data,@softraid,@working);
16829	# already been set in DiskDataBSD but we know the device exists
16830	foreach my $device (@{$sysctl{'softraid'}}){
16831		if ($device =~ /\.drive[\d]+:([\S]+)\s\(([a-z0-9]+)\)[,\s]+(\S+)/){
16832			my ($level,$size,@components);
16833			$id = $2;
16834			$status = $1;
16835			$state = $3;
16836			if ($alerts{'bioctl'}->{'action'} eq 'use'){
16837				$cmd = $alerts{'bioctl'}->{'path'} . " $id 2>/dev/null";
16838				@data = main::grabber($cmd,'','strip');
16839				main::log_data('dump','softraid @data',\@data) if $b_log;
16840				shift @data if @data; # get rid of headers
16841				foreach my $row (@data){
16842					@working = split(/\s+/,$row);
16843					next if !defined $working[0];
16844					if ($working[0] =~ /^softraid/){
16845						if ($working[3] && main::is_numeric($working[3])){
16846							$size = $working[3]/1024;# it's in bytes
16847							$raw_logical[0] += $size;
16848						}
16849						$status = lc($working[2]) if $working[2];
16850						$state = lc(join(' ', @working[6..$#working])) if $working[6];
16851						$level = lc($working[5]) if $working[5];
16852					}
16853					elsif ($working[0] =~ /^[\d]{1,2}$/){
16854						my ($c_id,$c_device,$c_size,$c_status);
16855						if ($working[2] && main::is_numeric($working[2])){
16856							$c_size = $working[2]/1024;# it's in bytes
16857							$raw_logical[1] += $c_size;
16858						}
16859						$c_status = lc($working[1]) if $working[1];
16860						if ($working[3] && $working[3] =~ /^([\d:\.]+)$/){
16861							$c_device = $1;
16862						}
16863						if ($working[5] && $working[5] =~ /<([^>]+)>/){
16864							$c_id = $1;
16865						}
16866						# when offline, there will be no $c_id, but we want to show device
16867						if (!$c_id && $c_device){
16868							$c_id = $c_device;
16869						}
16870						push(@components,[$c_id,$c_size,$c_status,$c_device]) if $c_id;
16871					}
16872				}
16873			}
16874			push(@softraid, {
16875			'components' => \@components,
16876			'id' => $id,
16877			'level' => $level,
16878			'size' => $size,
16879			'state' => $state,
16880			'status' => $status,
16881			'type' => 'softraid',
16882			});
16883		}
16884	}
16885	print Data::Dumper::Dumper \@softraid if $dbg[37];
16886	main::log_data('dump','@softraid',\@softraid) if $b_log;
16887	eval $end if $b_log;
16888	return @softraid;
16889}
16890
16891sub zfs_data {
16892	eval $start if $b_log;
16893	my ($zpool) = @_;
16894	my (@components,@zfs);
16895	my ($allocated,$free,$size,$size_holder,$status,$zfs_used,$zfs_avail,
16896	$zfs_size,@working);
16897	my $b_v = 1;
16898	my ($i,$j,$k) = (0,0,0);
16899	if ($fake{'raid-zfs'}){
16900		# my $file;
16901		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-1-mirror-main-solestar.txt";
16902		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-2-mirror-main-solestar.txt";
16903		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-v-tank-1.txt";
16904		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-list-v-gojev-1.txt";
16905		#@working = main::reader($file);$zpool = '';
16906	}
16907	else {
16908		@working = main::grabber("$zpool list -v 2>/dev/null");
16909	}
16910	# bsd sed does not support inserting a true \n so use this trick
16911	# some zfs does not have -v
16912	if (!@working){
16913		@working = main::grabber("$zpool list 2>/dev/null");
16914		$b_v = 0;
16915	}
16916	my $zfs_path = main::check_program('zfs');
16917	# print Data::Dumper::Dumper \@working;
16918	main::log_data('dump','@working',\@working) if $b_log;
16919	if (!@working){
16920		main::log_data('data','no zpool list data') if $b_log;
16921		eval $end if $b_log;
16922		return ();
16923	}
16924	my ($status_i) = (0);
16925	# NAME   SIZE  ALLOC   FREE  EXPANDSZ   FRAG    CAP  DEDUP  HEALTH  ALTROOT
16926	my $test = shift @working; # get rid of first header line
16927	if ($test){
16928		foreach (split(/\s+/, $test)){
16929			last if $_ eq 'HEALTH';
16930			$status_i++;
16931		}
16932	}
16933	foreach (@working){
16934		my @row = split(/\s+/, $_);
16935		if (/^[\S]+/){
16936			@components = ();
16937			$i = 0;
16938			$size = ($row[1] && $row[1] ne '-') ? main::translate_size($row[1]): '';
16939			$allocated = ($row[2] && $row[2] ne '-')? main::translate_size($row[2]): '';
16940			$free = ($row[3] && $row[3] ne '-')? main::translate_size($row[3]): '';
16941			($zfs_used,$zfs_avail) = zfs_fs_sizes($zfs_path,$row[0]) if $zfs_path;
16942			if (defined $zfs_used && defined $zfs_avail){
16943				$zfs_size = $zfs_used + $zfs_avail;
16944				$raw_logical[0] += $zfs_size;
16945			}
16946			else {
16947				# must be BEFORE '$size_holder =' because only used if hits a new device
16948				# AND unassigned via raid/mirror arrays. Corner case for > 1 device systems.
16949				$raw_logical[0] += $size_holder if $size_holder;
16950				$size_holder = $size;
16951			}
16952			$status = (defined $row[$status_i] && $row[$status_i] ne '') ? $row[$status_i]: 'no-status';
16953			$j = scalar @zfs;
16954			push(@zfs, {
16955			'id' => $row[0],
16956			'arrays' => ([],),
16957			'raw-allocated' => $allocated,
16958			'raw-free' => $free,
16959			'raw-size' => $size,
16960			'zfs-free' => $zfs_avail,
16961			'zfs-size' => $zfs_size,
16962			'status' => $status,
16963			'type' => 'zfs',
16964			});
16965		}
16966		# print Data::Dumper::Dumper \@zfs;
16967		# raid level is the second item in the output, unless it is not, sometimes it is absent
16968		elsif ($row[1] =~ /raid|mirror/){
16969			$row[1] =~ s/^raid1/mirror/;
16970			#$row[1] =~ s/^raid/raid-/; # need to match in zpool status <device>
16971			$k = scalar @{$zfs[$j]->{'arrays'}};
16972			$zfs[$j]->{'arrays'}[$k]{'level'} = $row[1];
16973			$i = 0;
16974			$size = ($row[2] && $row[2] ne '-') ? main::translate_size($row[2]) : '';
16975			if (!defined $zfs_used || !defined $zfs_avail){
16976				$size_holder = 0;
16977				$raw_logical[0] += $size if $size;
16978			}
16979			$zfs[$j]->{'arrays'}[$k]{'raw-allocated'} = ($row[3] && $row[3] ne '-') ? main::translate_size($row[3]) : '';
16980			$zfs[$j]->{'arrays'}[$k]{'raw-free'} = ($row[4] && $row[4] ne '-') ? main::translate_size($row[4]) : '';
16981			$zfs[$j]->{'arrays'}[$k]{'raw-size'} = $size;
16982		}
16983		# https://blogs.oracle.com/eschrock/entry/zfs_hot_spares
16984		elsif ($row[1] =~ /spares/){
16985			next;
16986		}
16987		# the first is a member of a raid array
16988		#    ada2        -      -      -         -      -      -
16989		# this second is a single device not in an array
16990		#  ada0s2    25.9G  14.6G  11.3G         -     0%    56%
16991		#    gptid/3838f796-5c46-11e6-a931-d05099ac4dc2      -      -      -         -      -      -
16992		# third is using /dev/disk/by-id
16993		#     ata-VBOX_HARDDISK_VB5b6350cd-06618d58      -      -      -        -         -      -      -      -  ONLINE
16994		elsif ($row[1] =~ /^(sd[a-z]+|[a-z0-9]+[0-9]+|([\S]+)\/.*|(ata|mmc|nvme|pci|scsi|wwn)-\S+)$/ &&
16995		 ($row[2] eq '-' || $row[2] =~ /^[0-9\.]+[MGTPE]$/)){
16996			#print "r1:$row[1]",' :: ', Cwd::abs_path('/dev/disk/by-id/'.$row[1]), "\n";
16997			$row[1] =~ /^(sd[a-z]+|[a-z0-9]+[0-9]+|([\S]+)\/.*|(ata|mmc|nvme|pci|scsi|wwn)-\S+)\s.*?(DEGRADED|FAULTED|OFFLINE)?$/;
16998			#my $working = '';
16999			my $working = ($1) ? $1 : ''; # note: the negative case can never happen
17000			my $state = ($4) ? $4 : '';
17001			my ($maj_min,$real,$part_size);
17002			if ($bsd_type && $working =~ /[\S]+\//){
17003				$working = GlabelData::get($working);
17004			}
17005			elsif (!$bsd_type && $row[1] =~ /^(ata|mmc|nvme|scsi|wwn)-/ &&
17006			 -e "/dev/disk/by-id/$row[1]" && ($real = Cwd::abs_path('/dev/disk/by-id/'.$row[1]))){
17007				$real =~ s|/dev/||;
17008				$working = $real;
17009			}
17010			elsif (!$bsd_type && $row[1] =~ /^(pci)-/ &&
17011			 -e "/dev/disk/by-path/$row[1]" && ($real = Cwd::abs_path('/dev/disk/by-path/'.$row[1]))){
17012				$real =~ s|/dev/||;
17013				$working = $real;
17014			}
17015			# kind of a hack, things like cache may not show size/free
17016			# data since they have no array row, but they might show it in
17017			# component row:
17018			#   ada0s2    25.9G  19.6G  6.25G         -     0%    75%
17019			if (!$zfs[$j]->{'size'} && $row[2] && $row[2] ne '-'){
17020				$size = ($row[2])? main::translate_size($row[2]): '';
17021				$size_holder = 0;
17022				$zfs[$j]->{'arrays'}[$k]{'size'} = $size;
17023				$raw_logical[0] += $size if $size;
17024			}
17025			if (!$zfs[$j]->{'allocated'} && $row[3] && $row[3] ne '-'){
17026				$allocated = ($row[3])? main::translate_size($row[3]): '';
17027				$zfs[$j]->{'arrays'}[$k]{'allocated'} = $allocated;
17028			}
17029			if (!$zfs[$j]->{'free'} && $row[4] && $row[4] ne '-'){
17030				$free = ($row[4])? main::translate_size($row[4]): '';
17031				$zfs[$j]->{'arrays'}[$k]{'free'} = $free;
17032			}
17033			if ((!$maj_min || !$part_size) && @proc_partitions){
17034				my @part = PartitionData::get($working);
17035				if (@part){
17036					$maj_min = $part[0] . ':' . $part[1];
17037					$part_size = $part[2];
17038				}
17039			}
17040			if ((!$maj_min || !$part_size) && @lsblk){
17041				my %data= LsblkData::get($working);
17042				$maj_min = $data{'maj-min'};
17043				$part_size = $data{'size'};
17044			}
17045			if (!$part_size && $bsd_type){
17046				my %temp = DiskDataBSD::get($working);
17047				$part_size = $temp{'size'} if $temp{'size'};
17048			}
17049			$raw_logical[1] += $part_size if $part_size;
17050			$zfs[$j]->{'arrays'}[$k]{'components'}[$i] = [$working,$part_size,$maj_min,$state];
17051			$i++;
17052		}
17053	}
17054	$raw_logical[0] += $size_holder if $size_holder;
17055	# print Data::Dumper::Dumper \@zfs;
17056	# clear out undefined arrrays values
17057	$j = 0;
17058	foreach my $row (@zfs){
17059		my @arrays = (ref $row->{'arrays'} eq 'ARRAY') ? @{$row->{'arrays'}} : ();
17060		@arrays = grep {defined $_} @arrays;
17061		$zfs[$j]->{'arrays'} = \@arrays;
17062		$j++;
17063	}
17064	@zfs = zfs_status($zpool,\@zfs);
17065	print Data::Dumper::Dumper \@zfs if $dbg[37];
17066	eval $end if $b_log;
17067	return @zfs;
17068}
17069sub zfs_fs_sizes {
17070	my ($path,$id) = @_;
17071	eval $start if $b_log;
17072	my @result = main::grabber("$path list -pH $id 2>/dev/null",'','strip');
17073	main::log_data('dump','zfs list @result',\@result) if $b_log;
17074	print Data::Dumper::Dumper \@result if $dbg[37];
17075	my @working = split(/\s+/,$result[0]);
17076	eval $end if $b_log;
17077	$working[1] = $working[1]/1024 if $working[1];
17078	$working[2] = $working[2]/1024 if $working[2];
17079	return ($working[1],$working[2]);
17080}
17081sub zfs_status {
17082	eval $start if $b_log;
17083	my ($zpool,$zfs) = @_;
17084	my ($cmd,$level,$status,@pool_status,@temp);
17085	my ($i,$j,$k,$l) = (0,0,0,0);
17086	foreach my $row (@$zfs){
17087		$i = 0;
17088		$k = 0;
17089		if ($fake{'raid-zfs'}){
17090			my $file;
17091			# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-1-mirror-main-solestar.txt";
17092			# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-2-mirror-main-solestar.txt";
17093			# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/raid/zpool-status-tank-1.txt";
17094			#@pool_status = main::reader($file,'strip');
17095		}
17096		else {
17097			$cmd = "$zpool status $row->{'id'} 2>/dev/null";
17098			@pool_status = main::grabber($cmd,"\n",'strip');
17099		}
17100		main::log_data('cmd',$cmd) if $b_log;
17101		# @arrays = (ref $row->{'arrays'} eq 'ARRAY') ? @{$row->{'arrays'}} : ();
17102		# print "$row->{'id'} rs:$row->{'status'}\n";
17103		$status = ($row->{'status'} && $row->{'status'} eq 'no-status') ? check_zfs_status($row->{'id'},\@pool_status): $row->{'status'};
17104		$zfs->[$j]{'status'} = $status if $status;
17105		#@arrays = grep {defined $_} @arrays;
17106		# print "$row->{id} $#arrays\n";
17107		# print Data::Dumper::Dumper \@arrays;
17108		foreach my $array (@{$row->{'arrays'}}){
17109			# print 'ref: ', ref $array, "\n";
17110			#next if ref $array ne 'HASH';
17111			my @components = (ref $array->{'components'} eq 'ARRAY') ? @{$array->{'components'}} : ();
17112			$l = 0;
17113			# zpool status: mirror-0  ONLINE       2     0     0
17114			$level = ($array->{'level'}) ? "$array->{'level'}-$i": $array->{'level'};
17115			$status = ($level) ? check_zfs_status($level,\@pool_status): '';
17116			$zfs->[$j]{'arrays'}[$k]{'status'} = $status;
17117			# print "$level i:$i j:$j k:$k $status\n";
17118			foreach my $component (@components){
17119				my @temp = split('~', $component);
17120				$status = ($temp[0]) ? check_zfs_status($temp[0],\@pool_status): '';
17121				$zfs->[$j]{'arrays'}[$k]{'components'}[$l] .= $status if $status;
17122				$l++;
17123			}
17124			$k++;
17125			# haven't seen a raid5/6 type array yet, zfs uses z1,z2,and z3
17126			$i++ if $array->{'level'}; # && $array->{'level'} eq 'mirror';
17127		}
17128		$j++;
17129	}
17130	eval $end if $b_log;
17131	return @$zfs;
17132}
17133sub check_zfs_status {
17134	eval $start if $b_log;
17135	my ($item,$pool_status) = @_;
17136	my ($status) = ('');
17137	foreach (@$pool_status){
17138		my @temp = split(/\s+/, $_);
17139		if ($temp[0] eq $item){
17140			last if !$temp[1];
17141			$status = $temp[1];
17142			last;
17143		}
17144	}
17145	eval $end if $b_log;
17146	return $status;
17147}
17148}
17149
17150## RamItem
17151{
17152package RamItem;
17153my (@vendors,%vendor_ids);
17154sub get {
17155	my (@data,@rows,$key1,@ram,$val1);
17156	my $num = 0;
17157	@rows = MemoryData::full('ram') if !$loaded{'memory'};
17158	if ($bsd_type && !$force{'dmidecode'} && ($dboot{'ram'} || $fake{'dboot'})){
17159		@ram = dboot_data();
17160		if (@ram){
17161			@data = ram_output(\@ram,'dboot');
17162		}
17163		else {
17164			$key1 = 'message';
17165			$val1 = main::row_defaults('ram-data-dmidecode');
17166			@data = ({
17167			main::key($num++,1,1,'RAM Report') => '',
17168			main::key($num++,0,2,$key1) => $val1,
17169			});
17170		}
17171	}
17172	elsif ($fake{'dmidecode'} || $alerts{'dmidecode'}->{'action'} eq 'use'){
17173		@ram = dmidecode_data();
17174		if (@ram){
17175			@data = ram_output(\@ram,'dmidecode');
17176		}
17177		else {
17178			$key1 = 'message';
17179			$val1 = main::row_defaults('ram-data');
17180			@data = ({
17181			main::key($num++,1,1,'RAM Report') => '',
17182			main::key($num++,0,2,$key1) => $val1,
17183			});
17184		}
17185	}
17186	else {
17187		$key1 = $alerts{'dmidecode'}->{'action'};
17188		$val1 = $alerts{'dmidecode'}->{'message'};
17189		push(@rows, {
17190		main::key($num++,1,1,'RAM Report') => '',
17191		main::key($num++,0,2,$key1) => $val1,
17192		});
17193	}
17194	push(@rows,@data);
17195	eval $end if $b_log;
17196	return @rows;
17197}
17198
17199sub ram_output {
17200	eval $start if $b_log;
17201	my ($ram,$source) = @_;
17202	return if !@$ram;
17203	my $num = 0;
17204	my $j = 0;
17205	my (@rows,$b_non_system);
17206	my ($arrays,$modules,$slots,$type_holder) = (0,0,0,'');
17207	if ($source eq 'dboot'){
17208		push(@rows, {
17209		main::key($num++,0,1,'Message') => main::row_defaults('ram-data-complete'),
17210		});
17211	}
17212	foreach my $item (@$ram){
17213		$j = scalar @rows;
17214		if (!$show{'ram-short'}){
17215			$b_non_system = ($item->{'use'} && lc($item->{'use'}) ne 'system memory') ? 1:0 ;
17216			$num = 1;
17217			push(@rows, {
17218			main::key($num++,1,1,'Array') => '',
17219			main::key($num++,1,2,'capacity') => process_size($item->{'capacity'}),
17220			});
17221			if ($item->{'cap-qualifier'}){
17222				$rows[$j]->{main::key($num++,0,3,'note')} = $item->{'cap-qualifier'};
17223			}
17224			$rows[$j]->{main::key($num++,0,2,'use')} = $item->{'use'} if $b_non_system;
17225			$rows[$j]->{main::key($num++,1,2,'slots')} = $item->{'slots'};
17226			if ($item->{'slots-qualifier'}){
17227				$rows[$j]->{main::key($num++,0,3,'note')} = $item->{'slots-qualifier'};
17228			}
17229			$item->{'eec'} ||= 'N/A';
17230			$rows[$j]->{main::key($num++,0,2,'EC')} = $item->{'eec'};
17231			if ($extra > 0 && (!$b_non_system ||
17232				(main::is_numeric($item->{'max-module-size'}) && $item->{'max-module-size'} > 10))){
17233				$rows[$j]->{main::key($num++,1,2,'max-module-size')} = process_size($item->{'max-module-size'});
17234				if ($item->{'mod-qualifier'}){
17235					$rows[$j]->{main::key($num++,0,3,'note')} = $item->{'mod-qualifier'};
17236				}
17237			}
17238			if ($extra > 2 && $item->{'voltage'}){
17239				$rows[$j]->{main::key($num++,0,2,'voltage')} = $item->{'voltage'};
17240			}
17241		}
17242		else {
17243			$slots += $item->{'slots'} if $item->{'slots'};
17244			$arrays++;
17245		}
17246		foreach my $entry ($item->{'modules'}){
17247			next if ref $entry ne 'ARRAY';
17248			# print Data::Dumper::Dumper $entry;
17249			foreach my $mod (@$entry){
17250				$num = 1;
17251				$j = scalar @rows;
17252				# multi array setups will start index at next from previous array
17253				next if ref $mod ne 'HASH';
17254				if ($show{'ram-short'}){
17255					$modules++ if ($mod->{'size'} =~ /^\d/);
17256					$type_holder = $mod->{'device-type'} if $mod->{'device-type'};
17257					next;
17258				}
17259				next if ($show{'ram-modules'} && $mod->{'size'} =~ /\D/);
17260				$mod->{'locator'} ||= 'N/A';
17261				push(@rows, {
17262				main::key($num++,1,2,'Device') => $mod->{'locator'},
17263				main::key($num++,0,3,'size') => process_size($mod->{'size'}),
17264				});
17265				next if ($mod->{'size'} =~ /\D/);
17266				if ($extra > 1 && $mod->{'type'}){
17267					$rows[$j]->{main::key($num++,0,3,'info')} = $mod->{'type'};
17268				}
17269				if ($mod->{'speed'} && $mod->{'configured-clock-speed'} &&
17270				    $mod->{'speed'} ne $mod->{'configured-clock-speed'}){
17271					$rows[$j]->{main::key($num++,1,3,'speed')} = '';
17272					$rows[$j]->{main::key($num++,0,4,'spec')} = $mod->{'speed'};
17273					$rows[$j]->{main::key($num++,0,4,'note')} = $mod->{'speed-note'} if $mod->{'speed-note'};
17274					$rows[$j]->{main::key($num++,0,4,'actual')} = $mod->{'configured-clock-speed'};
17275					$rows[$j]->{main::key($num++,0,5,'note')} = $mod->{'configured-note'} if $mod->{'configured-note'};
17276				}
17277				else {
17278					if (!$mod->{'speed'} && $mod->{'configured-clock-speed'}){
17279						if ($mod->{'configured-clock-speed'}){
17280							$mod->{'speed'} = $mod->{'configured-clock-speed'};
17281							$mod->{'speed-note'} = $mod->{'configured-note'} if $mod->{'configured-note'} ;
17282						}
17283					}
17284					# rare instances, dmi type 6, no speed, dboot also no speed
17285					$mod->{'speed'} ||= 'N/A';
17286					$rows[$j]->{main::key($num++,1,3,'speed')} = $mod->{'speed'};
17287					$rows[$j]->{main::key($num++,0,4,'note')} = $mod->{'speed-note'} if $mod->{'speed-note'};
17288				}
17289				if ($extra > 0){
17290					$mod->{'device-type'} ||= 'N/A';
17291					$rows[$j]->{main::key($num++,0,3,'type')} = $mod->{'device-type'};
17292					if ($extra > 2 && $mod->{'device-type'} ne 'N/A'){
17293						$mod->{'device-type-detail'} ||= 'N/A';
17294						$rows[$j]->{main::key($num++,0,3,'detail')} = $mod->{'device-type-detail'};
17295					}
17296				}
17297				if ($source ne 'dboot' && $extra > 2){
17298					$mod->{'data-width'} ||= 'N/A';
17299					$rows[$j]->{main::key($num++,0,3,'bus-width')} = $mod->{'data-width'};
17300					$mod->{'total-width'} ||= 'N/A';
17301					$rows[$j]->{main::key($num++,0,3,'total')} = $mod->{'total-width'};
17302				}
17303				if ($source ne 'dboot' && $extra > 1){
17304					$mod->{'manufacturer'} ||= 'N/A';
17305					$rows[$j]->{main::key($num++,0,3,'manufacturer')} = $mod->{'manufacturer'};
17306					$mod->{'part-number'} ||= 'N/A';
17307					$rows[$j]->{main::key($num++,0,3,'part-no')} = $mod->{'part-number'};
17308				}
17309				if ($source ne 'dboot' && $extra > 2){
17310					$mod->{'serial'} = main::apply_filter($mod->{'serial'});
17311					$rows[$j]->{main::key($num++,0,3,'serial')} = $mod->{'serial'};
17312				}
17313			}
17314		}
17315	}
17316	if ($show{'ram-short'}){
17317		$num = 1;
17318		$type_holder ||= 'N/A';
17319		push(@rows, {
17320		main::key($num++,1,1,'Report') => '',
17321		main::key($num++,0,2,'arrays') => $arrays,
17322		main::key($num++,0,2,'slots') => $slots,
17323		main::key($num++,0,2,'modules') => $modules,
17324		main::key($num++,0,2,'type') => $type_holder,
17325		});
17326	}
17327	eval $end if $b_log;
17328	return @rows;
17329}
17330
17331sub dmidecode_data {
17332	eval $start if $b_log;
17333	my ($b_5,$handle,@ram,@temp);
17334	my ($derived_module_size,$max_cap_5,$max_cap_16,$max_module_size) = (0,0,0,0);
17335	my ($i,$j,$k) = (0,0,0);
17336	my $check = main::row_defaults('note-check');
17337	# print Data::Dumper::Dumper \@dmi;
17338	foreach my $entry (@dmi){
17339		## NOTE: do NOT reset these values, that causes failures
17340		# ($derived_module_size,$max_cap_5,$max_cap_16,$max_module_size) = (0,0,0,0);
17341		if ($entry->[0] == 5){
17342			foreach my $item (@$entry){
17343				@temp = split(/:\s*/, $item, 2);
17344				next if !$temp[1];
17345				if ($temp[0] eq 'Maximum Memory Module Size'){
17346					$max_module_size = calculate_size($temp[1],$max_module_size);
17347					$ram[$k]->{'max-module-size'} = $max_module_size;
17348				}
17349				elsif ($temp[0] eq 'Maximum Total Memory Size'){
17350					$max_cap_5 = calculate_size($temp[1],$max_cap_5);
17351					$ram[$k]->{'max-capacity-5'} = $max_cap_5;
17352				}
17353				elsif ($temp[0] eq 'Memory Module Voltage'){
17354					$temp[1] =~ s/\s*V.*$//;
17355					$ram[$k]->{'voltage'} = $temp[1];
17356				}
17357				elsif ($temp[0] eq 'Associated Memory Slots'){
17358					$ram[$k]->{'slots-5'} = $temp[1];
17359				}
17360				elsif ($temp[0] eq 'Error Detecting Method'){
17361					$temp[1] ||= 'None';
17362					$ram[$k]->{'eec'} = $temp[1];
17363				}
17364			}
17365			$ram[$k]->{'modules'} = [];
17366			# print Data::Dumper::Dumper \@ram;
17367			$b_5 = 1;
17368		}
17369		elsif ($entry->[0] == 6){
17370			my ($size,$speed,$type) = (0,0,0);
17371			my ($bank_locator,$device_type,$locator,$main_locator) = ('','','','');
17372			foreach my $item (@$entry){
17373				@temp = split(/:\s*/, $item, 2);
17374				next if !$temp[1];
17375				if ($temp[0] eq 'Installed Size'){
17376					# get module size
17377					$size = calculate_size($temp[1],0);
17378					# using this causes issues, really only works for 16
17379# 					if ($size =~ /^[0-9][0-9]+$/){
17380# 						$ram[$k]->{'device-count-found'}++;
17381# 						$ram[$k]->{'used-capacity'} += $size;
17382# 					}
17383					# get data after module size
17384					$temp[1] =~ s/ Connection\)?//;
17385					$temp[1] =~ s/^[0-9]+\s*[KkMGTP]B\s*\(?//;
17386					$type = lc($temp[1]);
17387				}
17388				elsif ($temp[0] eq 'Current Speed'){
17389					$speed = main::dmi_cleaner($temp[1]);
17390				}
17391				elsif ($temp[0] eq 'Locator' || $temp[0] eq 'Socket Designation'){
17392					$temp[1] =~ s/D?RAM slot #?/Slot/i; # can be with or without #
17393					$locator = $temp[1];
17394				}
17395				elsif ($temp[0] eq 'Bank Locator'){
17396					$bank_locator = $temp[1];
17397				}
17398				elsif ($temp[0] eq 'Type'){
17399					$device_type = $temp[1];
17400				}
17401			}
17402			# because of the wide range of bank/slot type data, we will just use
17403			# the one that seems most likely to be right. Some have: Bank: SO DIMM 0 slot: J6A
17404			# so we dump the useless data and use the one most likely to be visibly correct
17405			if ($bank_locator =~ /DIMM/){
17406				$main_locator = $bank_locator;
17407			}
17408			else {
17409				$main_locator = $locator;
17410			}
17411			$ram[$k]->{'modules'}[$j] = {
17412			'device-type' => $device_type,
17413			'locator' => $main_locator,
17414			'size' => $size,
17415			'speed' => $speed,
17416			'type' => $type,
17417			};
17418			# print Data::Dumper::Dumper \@ram;
17419			$j++;
17420		}
17421		elsif ($entry->[0] == 16){
17422			$handle = $entry->[1];
17423			$ram[$handle] = $ram[$k] if $ram[$k];
17424			$ram[$k] = undef;
17425			# ($derived_module_size,$max_cap_16) = (0,0);
17426			foreach my $item (@$entry){
17427				@temp = split(/:\s*/, $item, 2);
17428				next if !$temp[1];
17429				if ($temp[0] eq 'Maximum Capacity'){
17430					$max_cap_16 = calculate_size($temp[1],$max_cap_16);
17431					$ram[$handle]->{'max-capacity-16'} = $max_cap_16;
17432				}
17433				# note: these 3 have cleaned data in DmiData, so replace stuff manually
17434				elsif ($temp[0] eq 'Location'){
17435					$temp[1] =~ s/\sOr\sMotherboard//;
17436					$temp[1] ||= 'System Board';
17437					$ram[$handle]->{'location'} = $temp[1];
17438				}
17439				elsif ($temp[0] eq 'Use'){
17440					$temp[1] ||= 'System Memory';
17441					$ram[$handle]->{'use'} = $temp[1];
17442				}
17443				elsif ($temp[0] eq 'Error Correction Type'){
17444					$temp[1] ||= 'None';
17445					$ram[$handle]->{'eec'} = $temp[1];
17446				}
17447				elsif ($temp[0] eq 'Number Of Devices'){
17448					$ram[$handle]->{'slots-16'} = $temp[1];
17449				}
17450				# print "0: $temp[0]\n";
17451			}
17452			$ram[$handle]->{'derived-module-size'} = 0;
17453			$ram[$handle]->{'device-count-found'} = 0;
17454			$ram[$handle]->{'used-capacity'} = 0;
17455			# print "s16: $ram[$handle]->{'slots-16'}\n";
17456		}
17457		elsif ($entry->[0] == 17){
17458			my ($bank_locator,$configured_speed,$configured_note,
17459			$data_width) = ('','','','');
17460			my ($device_type,$device_type_detail,$form_factor,$locator,
17461			$main_locator) = ('','','','','');
17462			my ($manufacturer,$vendor_id,$part_number,$serial,$speed,$speed_note,
17463			$total_width) = ('','','','','','','');
17464			my ($device_size,$i_data,$i_total,$working_size) = (0,0,0,0);
17465			foreach my $item (@$entry){
17466				@temp = split(/:\s*/, $item, 2);
17467				next if !$temp[1];
17468				if ($temp[0] eq 'Array Handle'){
17469					$handle = hex($temp[1]);
17470				}
17471				elsif ($temp[0] eq 'Data Width'){
17472					$data_width = $temp[1];
17473				}
17474				elsif ($temp[0] eq 'Total Width'){
17475					$total_width = $temp[1];
17476				}
17477				# do not try to guess from installed modules, only use this to correct type 5 data
17478				elsif ($temp[0] eq 'Size'){
17479					# we want any non real size data to be preserved
17480					if ($temp[1] =~ /^[0-9]+\s*[KkMTPG]i?B/){
17481						$derived_module_size = calculate_size($temp[1],$derived_module_size);
17482						$working_size = calculate_size($temp[1],0);
17483						$device_size = $working_size;
17484					}
17485					else {
17486						$device_size = $temp[1];
17487					}
17488				}
17489				elsif ($temp[0] eq 'Locator'){
17490					$temp[1] =~ s/D?RAM slot #?/Slot/i;
17491					$locator = $temp[1];
17492				}
17493				elsif ($temp[0] eq 'Bank Locator'){
17494					$bank_locator = $temp[1];
17495				}
17496				elsif ($temp[0] eq 'Form Factor'){
17497					$form_factor = $temp[1];
17498				}
17499				elsif ($temp[0] eq 'Type'){
17500					$device_type = $temp[1];
17501				}
17502				elsif ($temp[0] eq 'Type Detail'){
17503					$device_type_detail = $temp[1];
17504				}
17505				elsif ($temp[0] eq 'Speed'){
17506					($speed,$speed_note) = process_speed($temp[1],$device_type,$check);
17507				}
17508				# this is the actual speed the system booted at, speed is hardcoded
17509				# clock speed means MHz, memory speed MT/S
17510				elsif ($temp[0] eq 'Configured Clock Speed' || $temp[0] eq 'Configured Memory Speed'){
17511					($configured_speed,$configured_note) = process_speed($temp[1],$device_type,$check);
17512				}
17513				elsif ($temp[0] eq 'Manufacturer'){
17514					$temp[1] = main::dmi_cleaner($temp[1]);
17515					$manufacturer = $temp[1];
17516				}
17517				elsif ($temp[0] eq 'Part Number'){
17518					$temp[1] =~ s/(^[0]+$||.*Module.*|Undefined.*|PartNum.*|\[Empty\]|^To be filled.*)//g;
17519					$part_number = $temp[1];
17520				}
17521				elsif ($temp[0] eq 'Serial Number'){
17522					$temp[1] =~ s/(^[0]+$|Undefined.*|SerNum.*|\[Empty\]|^To be filled.*)//g;
17523					$serial = $temp[1];
17524				}
17525			}
17526			# because of the wide range of bank/slot type data, we will just use
17527			# the one that seems most likely to be right. Some have: Bank: SO DIMM 0 slot: J6A
17528			# so we dump the useless data and use the one most likely to be visibly correct
17529			if ($bank_locator =~ /DIMM/){
17530				$main_locator = $bank_locator;
17531			}
17532			else {
17533				$main_locator = $locator;
17534			}
17535			if ($working_size =~ /^[0-9][0-9]+$/){
17536				$ram[$handle]->{'device-count-found'}++;
17537				# build up actual capacity found for override tests
17538				$ram[$handle]->{'used-capacity'} += $working_size;
17539			}
17540			# sometimes the data is just wrong, they reverse total/data. data I believe is
17541			# used for the actual memory bus width, total is some synthetic thing, sometimes missing.
17542			# note that we do not want a regular string comparison, because 128 bit memory buses are
17543			# in our future, and 128 bits < 64 bits with string compare
17544			$data_width =~ /(^[0-9]+).*/;
17545			$i_data = $1;
17546			$total_width =~ /(^[0-9]+).*/;
17547			$i_total = $1;
17548			if ($i_data && $i_total && $i_data > $i_total){
17549				my $temp_width = $data_width;
17550				$data_width = $total_width;
17551				$total_width = $temp_width;
17552			}
17553			if ($manufacturer && $manufacturer =~ /^([a-f0-9]{4})$/i){
17554				$vendor_id = lc($1) if $1;
17555			}
17556			if ((!$manufacturer || $vendor_id)  && $part_number){
17557				my @result = ram_vendor($part_number);
17558				$manufacturer = $result[0] if $result[0];
17559				$part_number = $result[1] if $result[1];
17560			}
17561			if ($vendor_id && !$manufacturer){
17562				set_vendor_ids() if !%vendor_ids;
17563				if ($vendor_ids{$vendor_id}){
17564					$manufacturer = $vendor_ids{$vendor_id};
17565				}
17566			}
17567			$ram[$handle]->{'derived-module-size'} = $derived_module_size;
17568			$ram[$handle]->{'modules'}[$i]{'configured-clock-speed'} = $configured_speed;
17569			$ram[$handle]->{'modules'}[$i]{'configured-note'} = $configured_note if $configured_note;
17570			$ram[$handle]->{'modules'}[$i]{'data-width'} = $data_width;
17571			$ram[$handle]->{'modules'}[$i]{'size'} = $device_size;
17572			$ram[$handle]->{'modules'}[$i]{'device-type'} = $device_type;
17573			$ram[$handle]->{'modules'}[$i]{'device-type-detail'} = lc($device_type_detail);
17574			$ram[$handle]->{'modules'}[$i]{'form-factor'} = $form_factor;
17575			$ram[$handle]->{'modules'}[$i]{'locator'} = $main_locator;
17576			$ram[$handle]->{'modules'}[$i]{'manufacturer'} = $manufacturer;
17577			$ram[$handle]->{'modules'}[$i]{'vendor-id'} = $vendor_id;
17578			$ram[$handle]->{'modules'}[$i]{'part-number'} = $part_number;
17579			$ram[$handle]->{'modules'}[$i]{'serial'} = $serial;
17580			$ram[$handle]->{'modules'}[$i]{'speed'} = $speed;
17581			$ram[$handle]->{'modules'}[$i]{'speed-note'} = $speed_note if $speed_note;
17582			$ram[$handle]->{'modules'}[$i]{'total-width'} = $total_width;
17583			$i++
17584		}
17585		elsif ($entry->[0] < 17){
17586			next;
17587		}
17588		elsif ($entry->[0] > 17){
17589			last;
17590		}
17591	}
17592	print Data::Dumper::Dumper \@ram if $dbg[36];
17593	main::log_data('dump','@ram',\@ram) if $b_log;
17594	@ram = process_data(\@ram) if @ram;
17595	main::log_data('dump','@ram',\@ram) if $b_log;
17596	print Data::Dumper::Dumper \@ram if $dbg[36];
17597	eval $end if $b_log;
17598	return @ram;
17599}
17600sub dboot_data {
17601	eval $start if $b_log;
17602	my (@ram);
17603	my $est = main::row_defaults('note-est');
17604	my ($arr,$derived_module_size,$subtract) = (0,0,0);
17605	my ($holder);
17606	foreach (@{$dboot{'ram'}}){
17607		my ($addr,$detail,$device_detail,$ecc,$iic,$locator,$size,$speed,$type);
17608		# note: seen one netbsd with multiline spdmem0/1 etc but not consistent so don't use
17609		if (/^(spdmem([\d]+)):at iic([\d]+)(\saddr 0x([0-9a-f]+))?/){
17610			$iic = $3;
17611			$locator = $1;
17612			$holder = $iic if !defined $holder; # prime for first use
17613			# note: seen iic2 as only device
17614			if ($iic != $holder){
17615				if ($ram[$arr] && $ram[$arr]->{'slots-16'}){
17616					$subtract += $ram[$arr]->{'slots-16'};
17617				}
17618				$holder = $iic;
17619				# then since we are on a new iic device, assume new ram array.
17620				# this needs more data to confirm this guess.
17621				$arr++;
17622			}
17623			if ($5){
17624				$addr = hex($5);
17625			}
17626			if (/(non?[\s-]parity)/i){
17627				$device_detail = $1;
17628				$ecc = 'None';
17629			}
17630			elsif (/EEC/i){
17631				$device_detail = 'EEC';
17632				$ecc = 'EEC';
17633			}
17634			if (/\b(PC[0-9]+-\S+)\b/){
17635				$speed = $1;
17636				my $temp = speed_mapper($speed);
17637				if ($temp ne $speed){
17638					$detail = $speed;
17639					$speed = $temp;
17640				}
17641			}
17642			# we want to avoid netbsd trying to complete @ram without real data
17643			if (/:(\d+[MGT])B?\s(DDR[0-9]*)\b/){
17644				$size = main::translate_size($1)/1024;
17645				$type = $2;
17646				if ($addr){
17647					$ram[$arr]->{'slots-16'} = $addr - 80 + 1 - $subtract;
17648					$locator = 'Slot-' . $ram[$arr]->{'slots-16'};
17649				}
17650				$ram[$arr]->{'device-count-found'}++;
17651				# build up actual capacity found for override tests
17652				$ram[$arr]->{'max-capacity-16'} += $size;
17653				$ram[$arr]->{'max-cap-qualifier'} = $est;
17654				$ram[$arr]->{'slots-16'}++ if !$addr;
17655				$derived_module_size = $size if $size > $derived_module_size;
17656				$ram[$arr]->{'slots-qualifier'} = $est;
17657				$ram[$arr]->{'eec'} = $ecc;
17658				$ram[$arr]->{'derived-module-size'} = $derived_module_size;
17659				push(@{$ram[$arr]->{'modules'}},{
17660				'device-type'  => $type,
17661				'device-type-detail'  => $detail,
17662				'locator' => $locator,
17663				'size' => $size,
17664				'speed' => $speed,
17665				});
17666			}
17667		}
17668	}
17669	for (my $i = 0; $i++ ;scalar @ram){
17670		next if ref $ram[$i] ne 'HASH';
17671		# 1 slot is possible, but 3 is very unlikely due to dual channel ddr
17672		if ($ram[$i]->{'slots'} && $ram[$i]->{'slots'} > 2 && $ram[$i]->{'slots'} % 2 == 1){
17673			$ram[$i]->{'slots'}++;
17674		}
17675	}
17676	print Data::Dumper::Dumper \@ram if $dbg[36];
17677	main::log_data('dump','@ram',\@ram) if $b_log;
17678	@ram = process_data(\@ram) if @ram;
17679	main::log_data('dump','@ram',\@ram) if $b_log;
17680	print Data::Dumper::Dumper \@ram if $dbg[36];
17681	eval $end if $b_log;
17682	return @ram;
17683}
17684sub process_data {
17685	eval $start if $b_log;
17686	my ($ram) = @_;
17687	my $b_debug = 0;
17688	my (@return);
17689	my $check = main::row_defaults('note-check');
17690	my $est = main::row_defaults('note-est');
17691	foreach my $item (@$ram){
17692		# because we use the actual array handle as the index,
17693		# there will be many undefined keys
17694		next if ! defined $item;
17695		my ($max_cap,$max_mod_size) = (0,0);
17696		my ($alt_cap,$est_cap,$est_mod,$est_slots,$unit) = (0,'','','','');
17697		$max_cap = $item->{'max-capacity-16'};
17698		$max_cap ||= 0;
17699		# make sure they are integers not string if empty
17700		$item->{'slots-5'} ||= 0;
17701		$item->{'slots-16'} ||= 0;
17702		$item->{'device-count-found'} ||= 0;
17703		$item->{'max-capacity-5'} ||= 0;
17704		$item->{'max-module-size'} ||= 0;
17705		$item->{'used-capacity'} ||= 0;
17706		#$item->{'max-module-size'} = 0;# debugger
17707		# 1: if max cap 1 is null, and max cap 2 not null, use 2
17708		if ($b_debug){
17709			print "1: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} :mc: $max_cap :uc: $item->{'used-capacity'}\n";
17710			print "1a: s5: $item->{'slots-5'} s16: $item->{'slots-16'}\n";
17711		}
17712		if (!$max_cap && $item->{'max-capacity-5'}){
17713			$max_cap = $item->{'max-capacity-5'};
17714		}
17715		if ($b_debug){
17716			print "2: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} :mc: $max_cap :uc: $item->{'used-capacity'}\n";
17717		}
17718		# 2: now check to see if actually found module sizes are > than listed max module, replace if >
17719		if ($item->{'max-module-size'} && $item->{'derived-module-size'} &&
17720		     $item->{'derived-module-size'} > $item->{'max-module-size'}){
17721			$item->{'max-module-size'} = $item->{'derived-module-size'};
17722			$est_mod = $est;
17723		}
17724		if ($b_debug){
17725			print "3: dcf: $item->{'device-count-found'} :dms: $item->{'derived-module-size'} :mc: $max_cap :uc: $item->{'used-capacity'}\n";
17726		}
17727		# note: some cases memory capacity == max module size, so one stick will fill it
17728		# but I think only with cases of 2 slots does this happen, so if > 2, use the count of slots.
17729		if ($max_cap && ($item->{'device-count-found'} || $item->{'slots-16'})){
17730			# first check that actual memory found is not greater than listed max cap, or
17731			# checking to see module count * max mod size is not > used capacity
17732			if ($item->{'used-capacity'} && $item->{'max-capacity-16'}){
17733				if ($item->{'used-capacity'} > $max_cap){
17734					if ($item->{'max-module-size'} &&
17735					  $item->{'used-capacity'} < ($item->{'slots-16'} * $item->{'max-module-size'})){
17736						$max_cap = $item->{'slots-16'} * $item->{'max-module-size'};
17737						$est_cap = $est;
17738						print "A\n" if $b_debug;
17739					}
17740					elsif ($item->{'derived-module-size'} &&
17741					  $item->{'used-capacity'} < ($item->{'slots-16'} * $item->{'derived-module-size'})){
17742						$max_cap = $item->{'slots-16'} * $item->{'derived-module-size'};
17743						$est_cap = $est;
17744						print "B\n" if $b_debug;
17745					}
17746					else {
17747						$max_cap = $item->{'used-capacity'};
17748						$est_cap = $est;
17749						print "C\n" if $b_debug;
17750					}
17751				}
17752			}
17753			# note that second case will never really activate except on virtual machines and maybe
17754			# mobile devices
17755			if (!$est_cap){
17756				# do not do this for only single modules found, max mod size can be equal to the array size
17757				if ($item->{'slots-16'} > 1 && $item->{'device-count-found'} > 1 &&
17758				  $max_cap < ($item->{'derived-module-size'} * $item->{'slots-16'})){
17759					$max_cap = $item->{'derived-module-size'} * $item->{'slots-16'};
17760					$est_cap = $est;
17761					print "D\n" if $b_debug;
17762				}
17763				elsif ($item->{'device-count-found'} > 0 && $max_cap < ($item->{'derived-module-size'} * $item->{'device-count-found'})){
17764					$max_cap = $item->{'derived-module-size'} * $item->{'device-count-found'};
17765					$est_cap = $est;
17766					print "E\n" if $b_debug;
17767				}
17768				## handle cases where we have type 5 data: mms x device count equals type 5 max cap
17769				# however do not use it if cap / devices equals the derived module size
17770				elsif ($item->{'max-module-size'} > 0 &&
17771				  ($item->{'max-module-size'} * $item->{'slots-16'}) == $item->{'max-capacity-5'} &&
17772				  $item->{'max-capacity-5'} != $item->{'max-capacity-16'} &&
17773				  $item->{'derived-module-size'} != ($item->{'max-capacity-16'}/$item->{'slots-16'})){
17774					$max_cap = $item->{'max-capacity-5'};
17775					$est_cap = $est;
17776					print "F\n" if $b_debug;
17777				}
17778
17779			}
17780			if ($b_debug){
17781				print "4: mms: $item->{'max-module-size'} :dms: $item->{'derived-module-size'} :mc: $max_cap :uc: $item->{'used-capacity'}\n";
17782			}
17783			# some cases of type 5 have too big module max size, just dump the data then since
17784			# we cannot know if it is valid or not, and a guess can be wrong easily
17785			if ($item->{'max-module-size'} && $max_cap && $item->{'max-module-size'} > $max_cap){
17786				$item->{'max-module-size'} = 0;
17787			}
17788			if ($b_debug){
17789				print "5: dms: $item->{'derived-module-size'} :s16: $item->{'slots-16'} :mc: $max_cap\n";
17790			}
17791			# now prep for rebuilding the ram array data
17792			if (!$item->{'max-module-size'}){
17793				# ie: 2x4gB
17794				if (!$est_cap && $item->{'derived-module-size'} > 0 && $max_cap > ($item->{'derived-module-size'} * $item->{'slots-16'} * 4)){
17795					$est_cap = $check;
17796					print "G\n" if $b_debug;
17797				}
17798				if ($max_cap && ($item->{'slots-16'} || $item->{'slots-5'})){
17799					my $slots = 0;
17800					if ($item->{'slots-16'} && $item->{'slots-16'} >= $item->{'slots-5'}){
17801						$slots = $item->{'slots-16'};
17802					}
17803					elsif ($item->{'slots-5'} && $item->{'slots-5'} > $item->{'slots-16'}){
17804						$slots = $item->{'slots-5'};
17805					}
17806					# print "slots: $slots\n" if $b_debug;
17807					if ($item->{'derived-module-size'} * $slots > $max_cap){
17808						$item->{'max-module-size'} = $item->{'derived-module-size'};
17809						print "H\n" if $b_debug;
17810					}
17811					else {
17812						$item->{'max-module-size'} = sprintf("%.f",$max_cap/$slots);
17813						print "J\n" if $b_debug;
17814					}
17815					$est_mod = $est;
17816				}
17817			}
17818			# case where listed max cap is too big for actual slots x max cap, eg:
17819			# listed max cap, 8gb, max mod 2gb, slots 2
17820			else {
17821				if (!$est_cap && $item->{'max-module-size'} > 0){
17822					if ($max_cap > ($item->{'max-module-size'} * $item->{'slots-16'})){
17823						$est_cap = $check;
17824						print "K\n" if $b_debug;
17825					}
17826				}
17827			}
17828		}
17829		# no slots found due to legacy dmi probably. Note, too many logic errors
17830		# happen if we just set a general slots above, so safest to do it here
17831		$item->{'slots-16'} = $item->{'slots-5'} if $item->{'slots-5'} && !$item->{'slots-16'};
17832		if (!$item->{'slots-16'} && $item->{'modules'} && ref $item->{'modules'} eq 'ARRAY'){
17833			$est_slots = $check;
17834			$item->{'slots-16'} = scalar @{$item->{'modules'}};
17835			print "L\n" if $b_debug;
17836		}
17837		# only bsds using dmesg data
17838		elsif ($item->{'slots-qualifier'}){
17839			$est_slots = $item->{'slots-qualifier'};
17840			$est_cap = $est;
17841		}
17842		push(@return, {
17843		'capacity' => $max_cap,
17844		'cap-qualifier' => $est_cap,
17845		'eec' => $item->{'eec'},
17846		'location' => $item->{'location'},
17847		'max-module-size' => $item->{'max-module-size'},
17848		'mod-qualifier' => $est_mod,
17849		'modules' => $item->{'modules'},
17850		'slots' => $item->{'slots-16'},
17851		'slots-qualifier' => $est_slots,
17852		'use' => $item->{'use'},
17853		'voltage' => $item->{'voltage'},
17854		});
17855	}
17856	eval $end if $b_log;
17857	return @return;
17858}
17859sub process_speed {
17860	my ($speed,$device_type,$check) = @_;
17861	my $speed_note;
17862	$speed = main::dmi_cleaner($speed) if $speed;
17863	if ($device_type && $device_type =~ /ddr/i && $speed && $speed =~ /^([0-9]+)\s*MHz/){
17864		$speed = ($1 * 2) . " MT/s ($speed)";
17865	}
17866	# seen cases of 1 MT/s, 61690 MT/s, not sure why, bug
17867	# crucial is shipping 5100 MT/s now, and 6666 has been hit, so speeds can hit 10k
17868	if ($speed && $speed =~ /^([0-9]+)\s*M/){
17869		$speed_note = $check if $1 < 50 || $1 > 20000 ;
17870	}
17871	return ($speed,$speed_note);
17872}
17873# this should be fixed, but for now, size in RAM is in MiB, not
17874# KiB like the rest of inxi.
17875sub process_size {
17876	my ($size) = @_;
17877	my ($b_trim,$unit) = (0,'');
17878	# print "size0: $size\n";
17879	return 'N/A' if !$size;
17880	#return $size if $size =~ /\D/;
17881	return $size if !main::is_numeric($size);
17882	# print "size: $size\n";
17883	# we only want a max 2 decimal places, and only when it's
17884	# a unit > 1 GiB
17885	$b_trim = 1 if $size > 1024;
17886	# switch it back to KiB for tool
17887	($size,$unit) = main::get_size($size * 1024);
17888	$size = sprintf("%.2f",$size) if $b_trim;
17889	$size =~ s/\.[0]+$//;
17890	$size = "$size $unit";
17891	return $size;
17892}
17893# note that even though MB should be 1000^x it's actually
17894# MiB etc. As with process_size, this uses MiB not KiB
17895sub calculate_size {
17896	my ($data, $size) = @_;
17897	# technically k is KiB, K is KB but can't trust that
17898	if ($data =~ /^([0-9]+\s*[kKGMTP])i?B/){
17899		my $working = $1;
17900		# this converts it to KiB
17901		my $working_size = main::translate_size($working);
17902		# but we want it back in MiB for RAM, that should get fixed
17903		$working_size = $working_size/1024 if $working_size;
17904		# print "ws-a: $working_size s-1: $size\n";
17905		if (main::is_numeric($working_size) && $working_size > $size){
17906			$size = $working_size;
17907		}
17908		# print "ws-b: $working_size s-2: $size\n";
17909	}
17910	else {
17911		$size = 0;
17912	}
17913	# print "d-2: $data s-3: $size\n";
17914	return $size;
17915}
17916sub speed_mapper {
17917	my ($type) = @_;
17918	my %speeds = (
17919	# DDR
17920	'PC-1600' => 200,
17921	'PC-2100' => 266,
17922	'PC-2400' => 300,
17923	'PC-2700' => 333,
17924	'PC-3200' => 400,
17925	# DDR2
17926	'PC2-3200' => 400,
17927	'PC2-4200' => 533,
17928	'PC2-5300' => 667,
17929	'PC2-6400' => 800,
17930	'PC2-8000' => 1000,
17931	# DDR3
17932	'PC3-6400' => 800,
17933	'PC3-8500' => 1066,
17934	'PC3-10600' => 1333,
17935	'PC3-12800' => 1600,
17936	# DDR4
17937	'PC4-19200' => 2400,
17938	'PC4-21300' => 2666,
17939	'PC4-23400' => 2933,
17940	'PC4-24000' => 3000,
17941	'PC4-25600' => 3200,
17942	'PC4-28800' => 3600,
17943	'PC4-32000' => 4000,
17944	'PC4-35200' => 4400,
17945	# DDR5
17946	'PC5-38400' => 4800,
17947	'PC5-51200' => 6400,
17948	# DDR6, coming...
17949	);
17950	return ($speeds{$type}) ?  $speeds{$type} . ' MT/s' : $type;
17951}
17952sub set_vendors {
17953	@vendors = (
17954	# A-Data xpg: AX4U; AX\d{4} for axiom
17955	['^(A[DX]\dU|AVD|A[\s-]?Data)','A[\s-]?Data','A-Data',''],
17956	['^(A[\s-]?Tech)','A[\s-]?Tech','A-Tech',''], # don't know part nu
17957	['^(AX[\d]{4}|Axiom)','Axiom','Axiom',''],
17958	['^(BD\d|Black[s-]?Diamond)','Black[s-]?Diamond','Black Diamond',''],
17959	['^(-BN$|Brute[s-]?Networks)','Brute[s-]?Networks','Brute Networks',''],
17960	['^(CM|Corsair)','Corsair','Corsair',''],
17961	['^(CT\d|BL|Crucial)','Crucial','Crucial',''],
17962	['^(CY|Cypress)','Cypress','Cypress',''],
17963	['^(SNP|Dell)','Dell','Dell',''],
17964	['^(PE[\d]{4}|Edge)','Edge','Edge',''],
17965	['^(Elpida|EB)','^Elpida','Elpida',''],
17966	['^(GVT|Galvantech)','Galvantech','Galvantech',''],
17967	# if we get more G starters, make rules tighter
17968	['^(G[A-Z]|Geil)','Geil','Geil',''],
17969	# Note: FA- but make loose FA
17970	['^(F4|G[\s\.-]?Skill)','G[\s\.-]?Skill','G.Skill',''],
17971	['^(HP)','','HP',''], # no IDs found
17972	['^(HX|HyperX)','HyperX','HyperX',''],
17973	# qimonda spun out of infineon, same ids
17974	# ['^(HYS]|Qimonda)','Qimonda','Qimonda',''],
17975	['^(HY|Infineon)','Infineon','Infineon',''],#HY[A-Z]\d
17976	['^(KSM|KVR|Kingston)','Kingston','Kingston',''],
17977	['^(MT|Micron)','Micron','Micron',''],
17978	# seen: 992069 991434 997110S
17979	['^(M[BLERS][A-Z][1-7]|99[0-9]{3}|Mushkin)','Mushkin','Mushkin',''],
17980	['^(OCZ)','^OCZ\b','OCZ',''],
17981	['^([MN]D\d|OLOy)','OLOy','OLOy',''],
17982	['^(M[ERS]\d|Nemix)','Nemix','Nemix',''],
17983	# before patriot just in case
17984	['^(MN\d|PNY)','PNY\s','PNY',''],
17985	['^(P[A-Z]|Patriot)','Patriot','Patriot',''],
17986	['^(K[1-6][ABT]|K[1-6][\d]{3}|M[\d]{3}[A-Z]|Samsung)','Samsung','Samsung',''],
17987	['^(SP|Silicon[\s-]?Power)','Silicon[\s-]?Power','Silicon Power',''],
17988	['^(STK|Simtek)','Simtek','Simtek',''],
17989	['^(HM[ACT]|SK[\s-]?Hynix)','SK[\s-]?Hynix','SK-Hynix',''],
17990	# TED TTZD TLRD TDZAD TF4D4 TPD4 TXKD4 seen: HMT but could by skh
17991	#['^(T(ED|D[PZ]|F\d|LZ|P[DR]T[CZ]|XK)|Team[\s-]?Group)','Team[\s-]?Group','TeamGroup',''],
17992	['^(T[^\dR]|Team[\s-]?Group)','Team[\s-]?Group','TeamGroup',''],
17993	['^(TR\d|JM\d|Transcend)','Transcend','Transcend',''],
17994	['^(VK\d|Vaseky)','Vaseky','Vaseky',''],
17995	['^(Yangtze|Zhitai)','Yangtze(\s*Memory)?','Yangtze Memory',''],
17996	);
17997}
17998# note: many of these are pci ids, not confirmed valid for ram
17999sub set_vendor_ids {
18000	%vendor_ids = {
18001	'01f4' => 'Transcend',# confirmed
18002	'02fe' => 'Elpida',# confirmed
18003	'0314' => 'Mushkin',# confirmed
18004	'1014' => 'IBM',
18005	'1099' => 'Samsung',
18006	'10c3' => 'Samsung',
18007	'11e2' => 'Samsung',
18008	'1249' => 'Samsung',
18009	'144d' => 'Samsung',
18010	'15d1' => 'Infineon',
18011	'167d' => 'Samsung',
18012	'196e' => 'PNY',
18013	'1b1c' => 'Corsair',
18014	'1b85' => 'OCZ',
18015	'1c5c' => 'SK-Hynix',
18016	'1cc1' => 'A-Data',
18017	'0215' => 'Corsair',# confirmed
18018	'2646' => 'Kingston',
18019	'2c00' => 'Micron',# confirmed
18020	'5105' => 'Qimonda',# confirmed
18021	'802c' => 'Micron',# confirmed
18022	'80ad' => 'SK-Hynix',# confirmed
18023	'80ce' => 'Samsung',# confirmed
18024	'8551' => 'Qimonda',# confirmed
18025	'8564' => 'Transcend',
18026	'ad00' => 'SK-Hynix',# confirmed
18027	'c0a9' => 'Crucial',
18028	'ce00' => 'Samsung',# confirmed
18029	# '' => '',
18030	}
18031}
18032sub ram_vendor {
18033	eval $end if $b_log;
18034	my ($id) = $_[0];
18035	set_vendors() if !@vendors;
18036	my ($vendor,@data);
18037	foreach my $row (@vendors){
18038		if ($id =~ /$row->[0]/i){
18039			$vendor = $row->[2];
18040			# Usually we want to assign N/A at output phase, maybe do this logic there?
18041			if ($row->[1]){
18042				if ($id !~ m/$row->[1]$/i){
18043					$id =~ s/$row->[1]//i;
18044				}
18045				else {
18046					$id = 'N/A';
18047				}
18048			}
18049			$id =~ s/^[\/\[\s_-]+|[\/\s_-]+$//g;
18050			$id =~ s/\s\s/ /g;
18051			@data = ($vendor,$id);
18052			last;
18053		}
18054	}
18055	eval $end if $b_log;
18056	return @data;
18057}
18058}
18059
18060## RepoItem
18061{
18062package RepoItem;
18063
18064# easier to keep these package global, but undef after done
18065my (@dbg_files,$debugger_dir);
18066my $num = 0;
18067sub get {
18068	eval $start if $b_log;
18069	($debugger_dir) = @_;
18070	my (@data,@rows,@rows_p,@rows_r);
18071	if ($extra > 0 && !$loaded{'package-data'}){
18072		my %packages = PackageData::get('main',\$num);
18073		my @data;
18074		for (keys %packages){
18075			$rows_p[0]->{$_} = $packages{$_};
18076		}
18077	}
18078	$num = 0;
18079	if ($bsd_type){
18080		@rows_r = get_repos_bsd();
18081	}
18082	else {
18083		@rows_r = get_repos_linux();
18084	}
18085	if ($debugger_dir){
18086		@rows = @dbg_files;
18087		undef @dbg_files;
18088		undef $debugger_dir;
18089	}
18090	else {
18091		if (!@rows_r){
18092			my $pm_missing;
18093			if ($bsd_type){
18094				$pm_missing = main::row_defaults('repo-data-bsd',$uname[0]);
18095			}
18096			else {
18097				$pm_missing = main::row_defaults('repo-data');
18098			}
18099			@data = ({main::key($num++,0,1,'Alert') => $pm_missing});
18100		}
18101		@rows = (@rows_p,@rows_r,@data);
18102	}
18103	eval $end if $b_log;
18104	return @rows;
18105}
18106sub get_repos_linux {
18107	eval $start if $b_log;
18108	my (@content,@data,@data2,@data3,@files,$repo,@repos,@rows);
18109	my ($key,$path);
18110	my $apk = '/etc/apk/repositories';
18111	my $apt = '/etc/apt/sources.list';
18112	my $apt_termux = '/data/data/com.termux/files/usr' . $apt;
18113	$apt = $apt_termux if -e $apt_termux; # for android termux
18114	my $cards = '/etc/cards.conf';
18115	my $dnf_conf = '/etc/dnf/dnf.conf';
18116	my $dnf_repo_dir = '/etc/dnf.repos.d/';
18117	my $eopkg_dir = '/var/lib/eopkg/';
18118	my $nix = '/etc/nix/nix.conf';
18119	my $pacman = '/etc/pacman.conf';
18120	my $pacman_g2 = '/etc/pacman-g2.conf';
18121	my $pisi_dir = '/etc/pisi/';
18122	my $portage_dir = '/etc/portage/repos.conf/';
18123	my $portage_gentoo_dir = '/etc/portage-gentoo/repos.conf/';
18124	my $scratchpkg = '/etc/scratchpkg.repo';
18125	my $slackpkg = '/etc/slackpkg/mirrors';
18126	my $slackpkg_plus = '/etc/slackpkg/slackpkgplus.conf';
18127	my $slapt_get = '/etc/slapt-get/';
18128	my $tce_app = '/usr/bin/tce';
18129	my $tce_file = '/opt/tcemirror';
18130	my $tce_file2 = '/opt/localmirrors';
18131	my $yum_conf = '/etc/yum.conf';
18132	my $yum_repo_dir = '/etc/yum.repos.d/';
18133	my $xbps_dir_1 = '/etc/xbps.d/';
18134	my $xbps_dir_2 = '/usr/share/xbps.d/';
18135	my $zypp_repo_dir = '/etc/zypp/repos.d/';
18136	my $b_test = 0;
18137	# apt - debian, buntus, also sometimes some yum/rpm repos may create
18138	# apt repos here as well
18139	if (-f $apt || -d "$apt.d"){
18140		my ($apt_arch,$apt_comp,$apt_suites,$apt_types,@apt_urls,@apt_working,
18141		$b_apt_enabled,$file,$string);
18142		my $counter = 0;
18143		@files = main::globber("$apt.d/*.list");
18144		push(@files, $apt);
18145		main::log_data('data',"apt repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log;
18146		foreach (sort @files){
18147			# altlinux/pclinuxos use rpms in apt files
18148			@data = repo_builder($_,'apt','^\s*(deb|rpm)') if -r $_;
18149			push(@rows,@data);
18150		}
18151		#@files = main::globber("$ENV{'HOME'}/bin/scripts/inxi/data/repo/apt/*.sources");
18152		@files = main::globber("$apt.d/*.sources");
18153		main::log_data('data',"apt deb822 repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log;
18154		foreach $file (@files){
18155			# critical: whitespace is the separator, no logical ordering of
18156			# field names exists within each entry.
18157			@data2 = main::reader($file);
18158			# print Data::Dumper::Dumper \@data2;
18159			if (@data2){
18160				@data2 = map {s/^\s*$/~/;$_} @data2;
18161				push(@data2, '~');
18162			}
18163			push(@dbg_files, $file) if $debugger_dir;
18164			# print "$file\n";
18165			@apt_urls = ();
18166			@apt_working = ();
18167			$b_apt_enabled = 1;
18168			foreach my $row (@data2){
18169				# NOTE: the syntax of deb822 must be considered a bug, it's sloppy beyond belief.
18170				# deb822 supports line folding which starts with space
18171				# BUT: you can start a URIs: block of urls with a space, sigh.
18172				next if $row =~ /^\s+/ && $row !~ /^\s+[^#]+:\//;
18173				# strip out line space starters now that it's safe
18174				$row =~ s/^\s+//;
18175				# print "$row\n";
18176				if ($row eq '~'){
18177					if (@apt_working && $b_apt_enabled){
18178						# print "1: url builder\n";
18179						foreach $repo (@apt_working){
18180							$string = $apt_types;
18181							$string .= ' [arch=' . $apt_arch . ']' if $apt_arch;
18182							$string .= ' ' . $repo;
18183							$string .= ' ' . $apt_suites if $apt_suites ;
18184							$string .= ' ' . $apt_comp if $apt_comp;
18185							# print "s1:$string\n";
18186							push(@data3, $string);
18187						}
18188						# print join("\n",@data3),"\n";
18189						push(@apt_urls,@data3);
18190					}
18191					@data3 = ();
18192					@apt_working = ();
18193					$apt_arch = '';
18194					$apt_comp = '';
18195					$apt_suites = '';
18196					$apt_types = '';
18197					$b_apt_enabled = 1;
18198				}
18199				# print "row:$row\n";
18200				elsif ($row =~ /^Types:\s*(.*)/i){
18201					# print "ath:$type_holder\n";
18202					$apt_types = $1;
18203				}
18204				elsif ($row =~ /^Enabled:\s*(.*)/i){
18205					my $status = $1;
18206					$b_apt_enabled = ($status =~ /\b(disable|false|off|no|without)\b/i) ? 0: 1;
18207				}
18208				elsif ($row =~ /^[^#]+:\//){
18209					my $url = $row;
18210					$url =~ s/^URIs:\s*//i;
18211					push(@apt_working, $url) if $url;
18212				}
18213				elsif ($row =~ /^Suites:\s*(.*)/i){
18214					$apt_suites = $1;
18215				}
18216				elsif ($row =~ /^Components:\s*(.*)/i){
18217					$apt_comp = $1;
18218				}
18219				elsif ($row =~ /^Architectures:\s*(.*)/i){
18220					$apt_arch = $1;
18221				}
18222			}
18223			if (@apt_urls){
18224				$key = repo_data('active','apt');
18225				url_cleaner(\@apt_urls);
18226			}
18227			else {
18228				$key = repo_data('missing','apt');
18229			}
18230			push(@rows,
18231			{main::key($num++,1,1,$key) => $file},
18232			[@apt_urls],
18233			);
18234		}
18235		@files = ();
18236	}
18237	# pacman: Arch and derived
18238	if (-f $pacman || -f $pacman_g2){
18239		$repo = 'pacman';
18240		if (-f $pacman_g2){
18241			$pacman = $pacman_g2;
18242			$repo = 'pacman-g2';
18243		}
18244		@files = main::reader($pacman,'strip');
18245		if (@files){
18246			@repos = grep {/^\s*Server/i} @files;
18247			@files = grep {/^\s*Include/i} @files;
18248		}
18249		if (@files){
18250			@files = map {
18251				my @working = split(/\s+=\s+/, $_);
18252				$working[1];
18253			} @files;
18254		}
18255		@files = sort @files;
18256		main::uniq(\@files);
18257		unshift(@files, $pacman) if @repos;
18258		foreach (@files){
18259			if (-f $_){
18260				@data = repo_builder($_,$repo,'^\s*Server','\s*=\s*',1);
18261				push(@rows,@data);
18262			}
18263			else {
18264				# set it so the debugger knows the file wasn't there
18265				push(@dbg_files, $_) if $debugger_dir;
18266				push(@rows,
18267				{main::key($num++,1,1,'File listed in') => $pacman},
18268				[("$_ does not seem to exist.")],
18269				);
18270			}
18271		}
18272		if (!@rows){
18273			push(@rows,
18274			{main::key($num++,0,1,repo_data('missing','files')) => $pacman },
18275			);
18276		}
18277	}
18278	# slackware
18279	if (-f $slackpkg || -f $slackpkg_plus || -d $slapt_get){
18280		#$slackpkg = "$ENV{HOME}/bin/scripts/inxi/data/repo/slackware/slackpkg-2.conf";
18281		if (-f $slackpkg){
18282			@data = repo_builder($slackpkg,'slackpkg','^[[:space:]]*[^#]+');
18283			push(@rows,@data);
18284		}
18285		if (-d $slapt_get){
18286			@data2 = main::globber("${slapt_get}*");
18287			foreach my $file (@data2){
18288				@data = repo_builder($file,'slaptget','^\s*SOURCE','\s*=\s*',1);
18289				push(@rows,@data);
18290			}
18291		}
18292		if (-f $slackpkg_plus){
18293			push(@dbg_files, $slackpkg_plus) if $debugger_dir;
18294			@data =  main::reader($slackpkg_plus,'strip');
18295			my (@repoplus_list,$active_repos);
18296			foreach my $row (@data){
18297				@data2 = split(/\s*=\s*/, $row);
18298				@data2 = map { $_ =~ s/^\s+|\s+$//g ; $_ } @data2;
18299				last if $data2[0] =~ /^SLACKPKGPLUS/i && $data2[1] eq 'off';
18300				# REPOPLUS=(slackpkgplus restricted alienbob ktown multilib slacky)
18301				if ($data2[0] =~ /^REPOPLUS/i){
18302					@repoplus_list = split(/\s+/, $data2[1]);
18303					@repoplus_list = map {s/\(|\)//g; $_} @repoplus_list;
18304					$active_repos = join('|',@repoplus_list);
18305
18306				}
18307				# MIRRORPLUS['multilib']=http://taper.alienbase.nl/mirrors/people/alien/multilib/14.1/
18308				if ($active_repos && $data2[0] =~ /^MIRRORPLUS/i){
18309					$data2[0] =~ s/MIRRORPLUS\[\'|\'\]//ig;
18310					if ($data2[0] =~ /$active_repos/){
18311						push(@content,"$data2[0] ~ $data2[1]");
18312					}
18313				}
18314			}
18315			if (! @content){
18316				$key = repo_data('missing','slackpkg+');
18317			}
18318			else {
18319				url_cleaner(\@content);
18320				$key = repo_data('active','slackpkg+');
18321			}
18322			@data = (
18323			{main::key($num++,1,1,$key) => $slackpkg_plus},
18324			[@content],
18325			);
18326			url_cleaner(\@data);
18327			push(@rows,@data);
18328			@content = ();
18329		}
18330	}
18331	# redhat/suse
18332	if (-f $dnf_conf  ||-d $dnf_repo_dir|| -d $yum_repo_dir || -f $yum_conf ||
18333	 -d $zypp_repo_dir){
18334		@files = ();
18335		push(@files, $dnf_conf) if -f $dnf_conf;
18336		push(@files, main::globber("$dnf_repo_dir*.repo")) if -d $dnf_repo_dir;
18337		push(@files, $yum_conf) if -f $yum_conf;
18338		push(@files, main::globber("$yum_repo_dir*.repo")) if -d $yum_repo_dir;
18339		if (-d $zypp_repo_dir){
18340			push(@files, main::globber("$zypp_repo_dir*.repo"));
18341			main::log_data('data',"zypp repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log;
18342		}
18343 		# push(@files, "$ENV{'HOME'}/bin/scripts/inxi/data/repo/yum/rpmfusion-nonfree-1.repo");
18344		if (@files){
18345			foreach (sort @files){
18346				@data2 = main::reader($_);
18347				push(@dbg_files, $_) if $debugger_dir;
18348				if (/yum/){
18349					$repo = 'yum';
18350				}
18351				elsif (/dnf/){
18352					$repo = 'dnf';
18353				}
18354				elsif(/zypp/){
18355					$repo = 'zypp';
18356				}
18357				my ($enabled,$url,$title) = (undef,'','');
18358				foreach my $line (@data2){
18359					# this is a hack, assuming that each item has these fields listed, we collect the 3
18360					# items one by one, then when the url/enabled fields are set, we print it out and
18361					# reset the data. Not elegant but it works. Note that if enabled was not present
18362					# we assume it is enabled then, and print the line, reset the variables. This will
18363					# miss the last item, so it is printed if found in END
18364					if ($line =~ /^\[(.+)\]/){
18365						my $temp = $1;
18366						if ($url && $title && defined $enabled){
18367							if ($enabled > 0){
18368								push(@content, "$title ~ $url");
18369							}
18370							($enabled,$url,$title) = (undef,'','');
18371						}
18372						$title = $temp;
18373					}
18374					# Note: it looks like enabled comes before url
18375					elsif ($line =~ /^(metalink|mirrorlist|baseurl)\s*=\s*(.*)/i){
18376						$url = $2;
18377					}
18378					# note: enabled = 1. enabled = 0 means disabled
18379					elsif ($line =~ /^enabled\s*=\s*(0|1|No|Yes|True|False)/i){
18380						$enabled = $1;
18381						$enabled =~ s/(No|False)/0/i;
18382						$enabled =~ s/(Yes|True)/1/i;
18383					}
18384					# print out the line if all 3 values are found, otherwise if a new
18385					# repoTitle is hit above, it will print out the line there instead
18386					if ($url && $title && defined $enabled){
18387						if ($enabled > 0){
18388 							push(@content, "$title ~ $url");
18389 						}
18390 						($enabled,$url,$title) = (0,'','');
18391					}
18392				}
18393				# print the last one if there is data for it
18394				if ($url && $title && $enabled){
18395					push(@content, "$title ~ $url");
18396				}
18397
18398				if (! @content){
18399					$key = repo_data('missing',$repo);
18400				}
18401				else {
18402					url_cleaner(\@content);
18403					$key = repo_data('active',$repo);
18404				}
18405				push(@rows,
18406				{main::key($num++,1,1,$key) => $_},
18407				[@content],
18408				);
18409				@content = ();
18410			}
18411		}
18412		# print Data::Dumper::Dumper \@rows;
18413	}
18414	# gentoo
18415	if ((-d $portage_dir || -d $portage_gentoo_dir) && main::check_program('emerge')){
18416		@files = (main::globber("$portage_dir*.conf"),main::globber("$portage_gentoo_dir*.conf"));
18417		$repo = 'portage';
18418		if (@files){
18419			foreach (sort @files){
18420				@data2 = main::reader($_);
18421				push(@dbg_files, $_) if $debugger_dir;
18422				my ($enabled,$url,$title) = (undef,'','');
18423				foreach my $line (@data2){
18424					# this is a hack, assuming that each item has these fields listed, we collect the 3
18425					# items one by one, then when the url/enabled fields are set, we print it out and
18426					# reset the data. Not elegant but it works. Note that if enabled was not present
18427					# we assume it is enabled then, and print the line, reset the variables. This will
18428					# miss the last item, so it is printed if found in END
18429					if ($line =~ /^\[(.+)\]/){
18430						my $temp = $1;
18431						if ($url && $title && defined $enabled){
18432							if ($enabled > 0){
18433								push(@content, "$title ~ $url");
18434							}
18435							($enabled,$url,$title) = (undef,'','');
18436						}
18437						$title = $temp;
18438					}
18439					elsif ($line =~ /^(sync-uri)\s*=\s*(.*)/i){
18440						$url = $2;
18441					}
18442					# note: enabled = 1. enabled = 0 means disabled
18443					elsif ($line =~ /^auto-sync\s*=\s*(0|1|No|Yes|True|False)/i){
18444						$enabled = $1;
18445						$enabled =~ s/(No|False)/0/i;
18446						$enabled =~ s/(Yes|True)/1/i;
18447					}
18448					# print out the line if all 3 values are found, otherwise if a new
18449					# repoTitle is hit above, it will print out the line there instead
18450					if ($url && $title && defined $enabled){
18451						if ($enabled > 0){
18452 							push(@content, "$title ~ $url");
18453 						}
18454 						($enabled,$url,$title) = (undef,'','');
18455					}
18456				}
18457				# print the last one if there is data for it
18458				if ($url && $title && $enabled){
18459					push(@content, "$title ~ $url");
18460				}
18461				if (! @content){
18462					$key = repo_data('missing','portage');
18463				}
18464				else {
18465					url_cleaner(\@content);
18466					$key = repo_data('active','portage');
18467				}
18468				push(@rows,
18469				{main::key($num++,1,1,$key) => $_},
18470				[@content],
18471				);
18472				@content = ();
18473			}
18474		}
18475	}
18476	# Alpine linux
18477	if (-f $apk){
18478		@data = repo_builder($apk,'apk','^\s*[^#]+');
18479		push(@rows,@data);
18480	}
18481	# Venom
18482	if (-f $scratchpkg){
18483		@data = repo_builder($scratchpkg,'scratchpkg','^[[:space:]]*[^#]+');
18484		push(@rows,@data);
18485	}
18486	# cards/nutyx
18487	if (-f $cards){
18488		@data3 = main::reader($cards,'clean');
18489		push(@dbg_files, $cards) if $debugger_dir;
18490		foreach (@data3){
18491			if ($_ =~ /^dir\s+\/[^\|]+\/([^\/\|]+)\s*(\|\s*((http|ftp).*))?/){
18492				my $type = ($3) ? $3: 'local';
18493				push(@content, "$1 ~ $type");
18494			}
18495		}
18496		if (! @content){
18497			$key = repo_data('missing','cards');
18498		}
18499		else {
18500			url_cleaner(\@content);
18501			$key = repo_data('active','cards');
18502		}
18503		push(@rows,
18504		{main::key($num++,1,1,$key) => $cards},
18505		[@content],
18506		);
18507		@content = ();
18508	}
18509	# TinyCore
18510	if (-e $tce_app || -f $tce_file || -f $tce_file2){
18511		@data = repo_builder($tce_file,'tce','^\s*[^#]+');
18512		push(@rows,@data);
18513		if (-f $tce_file2){
18514			@data = repo_builder($tce_file2,'tce','^\s*[^#]+');
18515			push(@rows,@data);
18516		}
18517	}
18518	# Void
18519	if (-d $xbps_dir_1 || -d $xbps_dir_2){
18520		@files = main::globber("$xbps_dir_1*.conf");
18521		push(@files,main::globber("$xbps_dir_2*.conf")) if -d $xbps_dir_2;
18522		main::log_data('data',"xbps repo files:\n" . main::joiner(\@files, "\n", 'unset')) if $b_log;
18523		foreach (sort @files){
18524			@data = repo_builder($_,'xbps','^\s*repository\s*=','\s*=\s*',1) if -r $_;
18525			push(@rows,@data);
18526		}
18527	}
18528	# Mandriva/Mageia using: urpmq
18529	if ($path = main::check_program('urpmq')){
18530		@data2 = main::grabber("$path --list-media active --list-url","\n",'strip');
18531		main::writer("$debugger_dir/system-repo-data-urpmq.txt",\@data2) if $debugger_dir;
18532		# now we need to create the structure: repo info: repo path
18533		# we do that by looping through the lines of the output and then
18534		# putting it back into the <data>:<url> format print repos expects to see
18535		# note this structure in the data, so store first line and make start of line
18536		# then when it's an http line, add it, and create the full line collection.
18537		# Contrib ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/contrib/release
18538		# Contrib Updates ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/contrib/updates
18539		# Non-free ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/non-free/release
18540		# Non-free Updates ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/2011/x86_64/media/non-free/updates
18541		# Nonfree Updates (Local19) /mnt/data/mirrors/mageia/distrib/cauldron/x86_64/media/nonfree/updates
18542		foreach (@data2){
18543			# need to dump leading/trailing spaces and clear out color codes for irc output
18544			$_ =~ s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g;
18545			$_ =~ s/\e\[([0-9];)?[0-9]+m//g;
18546			# urpmq output is the same each line, repo name space repo url, can be:
18547			# rsync://, ftp://, file://, http:// OR repo is locally mounted on FS in some cases
18548			if (/(.+)\s([\S]+:\/\/.+)/){
18549				# pack the repo url
18550				push(@content, $1);
18551				url_cleaner(\@content);
18552				# get the repo
18553				$repo = $2;
18554				push(@rows,
18555				{main::key($num++,1,1,'urpmq repo') => $repo},
18556				[@content],
18557				);
18558				@content = ();
18559			}
18560		}
18561	}
18562	# Pardus/Solus
18563	if ((-d $pisi_dir && ($path = main::check_program('pisi'))) ||
18564	 (-d $eopkg_dir && ($path = main::check_program('eopkg')))){
18565		#$path = 'eopkg';
18566		my $which = ($path =~ /pisi$/) ? 'pisi': 'eopkg';
18567		my $cmd = ($which eq 'pisi') ? "$path list-repo": "$path lr";
18568		# my $file = "$ENV{HOME}/bin/scripts/inxi/data/repo/solus/eopkg-2.txt";
18569		# @data2 = main::reader($file,'strip');
18570		@data2 = main::grabber("$cmd 2>/dev/null","\n",'strip');
18571		main::writer("$debugger_dir/system-repo-data-$which.txt",\@data2) if $debugger_dir;
18572		# now we need to create the structure: repo info: repo path
18573		# we do that by looping through the lines of the output and then
18574		# putting it back into the <data>:<url> format print repos expects to see
18575		# note this structure in the data, so store first line and make start of line
18576		# then when it's an http line, add it, and create the full line collection.
18577		# Pardus-2009.1 [Aktiv]
18578		# 	http://packages.pardus.org.tr/pardus-2009.1/pisi-index.xml.bz2
18579		# Contrib [Aktiv]
18580		# 	http://packages.pardus.org.tr/contrib-2009/pisi-index.xml.bz2
18581		# Solus [inactive]
18582		# 	https://packages.solus-project.com/shannon/eopkg-index.xml.xz
18583		foreach (@data2){
18584			next if /^\s*$/;
18585			# need to dump leading/trailing spaces and clear out color codes for irc output
18586			$_ =~ s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g;
18587			$_ =~ s/\e\[([0-9];)?[0-9]+m//g;
18588			if (/^\/|:\/\//){
18589				push(@content, $_) if $repo;
18590			}
18591			# Local [inactive] Unstable [active]
18592			elsif (/^(.*)\s\[([\S]+)\]/){
18593				$repo = $1;
18594				$repo = ($2 =~ /^activ/i) ? $repo : '';
18595			}
18596			if ($repo && @content){
18597				url_cleaner(\@content);
18598				$key = repo_data('active',$which);
18599				push(@rows,
18600				{main::key($num++,1,1,$key) => $repo},
18601				[@content],
18602				);
18603				$repo = '';
18604				@content = ();
18605			}
18606		}
18607		# last one if present
18608		if ($repo && @content){
18609			url_cleaner(\@content);
18610			$key = repo_data('active',$which);
18611			push(@rows,
18612			{main::key($num++,1,1,$key) => $repo},
18613			[@content],
18614			);
18615		}
18616	}
18617	if (-f $nix && ($path = main::check_program('nix-channel'))){
18618		@content = main::grabber("$path --list 2>/dev/null","\n",'strip');
18619		main::writer("$debugger_dir/system-repo-data-nix.txt",\@content) if $debugger_dir;
18620		if (!@content){
18621			$key = repo_data('missing','nix');
18622		}
18623		else {
18624			url_cleaner(\@content);
18625			$key = repo_data('active','nix');
18626		}
18627		my $user = ($ENV{'USER'}) ? $ENV{'USER'}: 'N/A';
18628		push(@rows,
18629		{main::key($num++,1,1,$key) => $user},
18630		[@content],
18631		);
18632		@content = ();
18633
18634	}
18635	# print Dumper \@rows;
18636	eval $end if $b_log;
18637	return @rows;
18638}
18639sub get_repos_bsd {
18640	eval $start if $b_log;
18641	my (@content,@data,@data2,@data3,@files,@rows);
18642	my ($key);
18643	my $bsd_pkg = '/usr/local/etc/pkg/repos/';
18644	my $freebsd = '/etc/freebsd-update.conf';
18645	my $freebsd_pkg = '/etc/pkg/FreeBSD.conf';
18646	my $ghostbsd_pkg = '/etc/pkg/GhostBSD.conf';
18647	my $hardenedbsd_pkg = '/etc/pkg/HardenedBSD.conf';
18648	my $mports = '/usr/mports/Makefile';
18649	my $netbsd = '/usr/pkg/etc/pkgin/repositories.conf';
18650	my $openbsd = '/etc/pkg.conf';
18651	my $openbsd2 = '/etc/installurl';
18652	my $portsnap =  '/etc/portsnap.conf';
18653	if (-f $portsnap || -f $freebsd || -d $bsd_pkg ||
18654	 -f $ghostbsd_pkg || -f $hardenedbsd_pkg){
18655		if (-f $portsnap){
18656			@data = repo_builder($portsnap,'portsnap','^\s*SERVERNAME','\s*=\s*',1);
18657			push(@rows,@data);
18658		}
18659		if (-f $freebsd){
18660			@data = repo_builder($freebsd,'freebsd','^\s*ServerName','\s+',1);
18661			push(@rows,@data);
18662		}
18663		if (-d $bsd_pkg || -f $freebsd_pkg || -f $ghostbsd_pkg || -f $hardenedbsd_pkg){
18664			@files = main::globber('/usr/local/etc/pkg/repos/*.conf');
18665			push(@files, $freebsd_pkg) if -f $freebsd_pkg;
18666			push(@files, $ghostbsd_pkg) if -f $ghostbsd_pkg;
18667			push(@files, $hardenedbsd_pkg) if -f $hardenedbsd_pkg;
18668			if (@files){
18669				my ($url);
18670				foreach (@files){
18671					push(@dbg_files, $_) if $debugger_dir;
18672					# these will be result sets separated by an empty line
18673					# first dump all lines that start with #
18674					@content =  main::reader($_,'strip');
18675					# then do some clean up on the lines
18676					@content = map { $_ =~ s/{|}|,|\*//g; $_; } @content if @content;
18677					# get all rows not starting with a # and starting with a non space character
18678					my $url = '';
18679					foreach my $line (@content){
18680						if ($line !~ /^\s*$/){
18681							my @data2 = split(/\s*:\s*/, $line);
18682							@data2 = map { $_ =~ s/^\s+|\s+$//g; $_; } @data2;
18683							if ($data2[0] eq 'url'){
18684								$url = "$data2[1]:$data2[2]";
18685								$url =~ s/"|,//g;
18686							}
18687							# print "url:$url\n" if $url;
18688							if ($data2[0] eq 'enabled'){
18689								if ($url && $data2[1] =~ /^(1|true|yes)$/i){
18690									push(@data3, "$url");
18691								}
18692								$url = '';
18693							}
18694						}
18695					}
18696					if (!@data3){
18697						$key = repo_data('missing','bsd-package');
18698					}
18699					else {
18700						url_cleaner(\@data3);
18701						$key = repo_data('active','bsd-package');
18702					}
18703					push(@rows,
18704					{main::key($num++,1,1,$key) => $_},
18705					[@data3],
18706					);
18707					@data3 = ();
18708				}
18709			}
18710		}
18711	}
18712	if (-f $openbsd || -f $openbsd2){
18713		if (-f $openbsd){
18714			@data = repo_builder($openbsd,'openbsd','^installpath','\s*=\s*',1);
18715			push(@rows,@data);
18716		}
18717		if (-f $openbsd2){
18718			@data = repo_builder($openbsd2,'openbsd','^(http|ftp)','',1);
18719			push(@rows,@data);
18720		}
18721	}
18722	if (-f $netbsd){
18723		# not an empty row, and not a row starting with #
18724		@data = repo_builder($netbsd,'netbsd','^\s*[^#]+$');
18725		push(@rows,@data);
18726	}
18727	# I don't think this is right, have to find out, for midnightbsd
18728# 	if (-f $mports){
18729# 		@data = main::reader($mports,'strip');
18730# 		main::writer("$debugger_dir/system-repo-data-mports.txt",\@data) if $debugger_dir;
18731# 		for (@data){
18732# 			if (!/^MASTER_SITE_INDEX/){
18733# 				next;
18734# 			}
18735# 			else {
18736# 				push(@data3,(split(/=\s*/,$_))[1]);
18737# 			}
18738# 			last if /^INDEX/;
18739# 		}
18740# 		if (!@data3){
18741# 			$key = repo_data('missing','mports');
18742# 		}
18743# 		else {
18744# 			url_cleaner(\@data3);
18745# 			$key = repo_data('active','mports');
18746# 		}
18747# 		push(@rows,
18748# 		{main::key($num++,1,1,$key) => $mports},
18749# 		[@data3],
18750# 		);
18751# 		@data3 = ();
18752# 	}
18753	# BSDs do not default always to having repo files, so show correct error
18754	# mesage in that case
18755	if (!@rows){
18756		if ($bsd_type eq 'freebsd'){
18757			$key = repo_data('missing','freebsd-files');
18758		}
18759		elsif ($bsd_type eq 'openbsd'){
18760			$key = repo_data('missing','openbsd-files');
18761		}
18762		elsif ($bsd_type eq 'netbsd'){
18763			$key = repo_data('missing','netbsd-files');
18764		}
18765		else {
18766			$key = repo_data('missing','bsd-files');
18767		}
18768		push(@rows,
18769		{main::key($num++,0,1,'Message') => $key},
18770		[()],
18771		);
18772	}
18773	eval $start if $b_log;
18774	return @rows;
18775}
18776sub repo_data {
18777	eval $start if $b_log;
18778	my ($status,$type) = @_;
18779	my %keys = (
18780	'apk-active' => 'APK repo',
18781	'apk-missing' => 'No active APK repos in',
18782	'apt-active' => 'Active apt repos in',
18783	'apt-missing' => 'No active apt repos in',
18784	'bsd-files-missing' => 'No pkg server files found',
18785	'bsd-package-active' => 'Enabled pkg servers in',
18786	'bsd-package-missing' => 'No enabled BSD pkg servers in',
18787	'cards-active' => 'Active CARDS collections in',
18788	'cards-missing' => 'No active CARDS collections in',
18789	'dnf-active' => 'Active dnf repos in',
18790	'dnf-missing' => 'No active dnf repos in',
18791	'eopkg-active' => 'Active eopkg repo',
18792	'eopkg-missing' => 'No active eopkg repos found',
18793	'files-missing' => 'No repo files found in',
18794	'freebsd-active' => 'FreeBSD update server',
18795	'freebsd-files-missing' => 'No FreeBSD update server files found',
18796	'freebsd-missing' => 'No FreeBSD update servers in',
18797	'freebsd-pkg-active' => 'FreeBSD default pkg server',
18798	'freebsd-pkg-missing' => 'No FreeBSD default pkg server in',
18799	'mports-active' => 'mports servers',
18800	'mports-missing' => 'No mports servers found',
18801	'netbsd-active' => 'NetBSD pkg servers',
18802	'netbsd-files-missing' => 'No NetBSD pkg server files found',
18803	'netbsd-missing' => 'No NetBSD pkg servers in',
18804	'nix-active' => 'Active nix channels for user',
18805	'nix-missing' => 'No nix channels found for user',
18806	'openbsd-active' => 'OpenBSD pkg mirror',
18807	'openbsd-files-missing' => 'No OpenBSD pkg mirror files found',
18808	'openbsd-missing' => 'No OpenBSD pkg mirrors in',
18809	'pacman-active' => 'Active pacman repo servers in',
18810	'pacman-missing' => 'No active pacman repos in',
18811	'pacman-g2-active' => 'Active pacman-g2 repo servers in',
18812	'pacman-g2-missing' => 'No active pacman-g2 repos in',
18813	'pisi-active' => 'Active pisi repo',
18814	'pisi-missing' => 'No active pisi repos found',
18815	'portage-active' => 'Enabled portage sources in',
18816	'portage-missing' => 'No enabled portage sources in',
18817	'portsnap-active' => 'Ports server',
18818	'portsnap-missing' => 'No ports servers in',
18819	'scratchpkg-active' => 'scratchpkg repos in',
18820	'scratchpkg-missing' => 'No active scratchpkg repos in',
18821	'slackpkg-active' => 'slackpkg repos in',
18822	'slackpkg-missing' => 'No active slackpkg repos in',
18823	'slackpkg+-active' => 'slackpkg+ repos in',
18824	'slackpkg+-missing' => 'No active slackpkg+ repos in',
18825	'slaptget-active' => 'slapt-get repos in',
18826	'slaptget-missing' => 'No active slapt-get repos in',
18827	'tce-active' => 'tce mirrors in',
18828	'tce-missing' => 'No tce mirrors in',
18829	'xbps-active' => 'Active xbps repos in',
18830	'xbps-missing' => 'No active xbps repos in',
18831	'yum-active' => 'Active yum repos in',
18832	'yum-missing' => 'No active yum repos in',
18833	'zypp-active' => 'Active zypp repos in',
18834	'zypp-missing' => 'No active zypp repos in',
18835	);
18836	eval $end if $b_log;
18837	return $keys{$type . '-' . $status};
18838}
18839sub repo_builder {
18840	eval $start if $b_log;
18841	my ($file,$type,$search,$split,$count) = @_;
18842	my (@content,@data,$key);
18843	push(@dbg_files, $file) if $debugger_dir;
18844	if (-r $file){
18845		@content =  main::reader($file);
18846		@content = grep {/$search/i && !/^\s*$/} @content if @content;
18847		data_cleaner(\@content) if @content;
18848	}
18849	if ($split && @content){
18850		@content = map {
18851		my @inner = split(/$split/, $_);
18852		$inner[$count];
18853		} @content;
18854	}
18855	if (!@content){
18856		$key = repo_data('missing',$type);
18857	}
18858	else {
18859		$key = repo_data('active',$type);
18860		url_cleaner(\@content);
18861	}
18862	@data = (
18863	{main::key($num++,1,1,$key) => $file},
18864	[@content],
18865	);
18866	eval $end if $b_log;
18867	return @data;
18868}
18869sub data_cleaner {
18870	# basics: trim white space, get rid of double spaces
18871	@{$_[0]} = map {$_ =~ s/^\s+|\s+$//g; $_ =~ s/\s\s+/ /g; $_} @{$_[0]};
18872}
18873# clean if irc
18874sub url_cleaner {
18875	@{$_[0]} = map {$_ =~ s/:\//: \//; $_} @{$_[0]} if $b_irc;
18876}
18877sub file_path {
18878	my ($filename,$dir) = @_;
18879	my ($working);
18880	$working = $filename;
18881	$working =~ s/^\///;
18882	$working =~ s/\//-/g;
18883	$working = "$dir/file-repo-$working.txt";
18884	return $working;
18885}
18886}
18887
18888## SensorItem
18889{
18890package SensorItem;
18891sub get {
18892	eval $start if $b_log;
18893	my ($key1,$program,$val1,@data,@rows,%sensors);
18894	my $num = 0;
18895	my $source = 'sensors';
18896	# we're allowing 1 or 2 ipmi tools, first the gnu one, then the
18897	# almost certain to be present in BSDs
18898	if ($fake{'ipmi'} || (main::globber('/dev/ipmi**') &&
18899	    (($program = main::check_program('ipmi-sensors')) ||
18900	     ($program = main::check_program('ipmitool'))))){
18901		if ($fake{'ipmi'} || $b_root){
18902			%sensors = ipmi_data($program);
18903			@data = sensors_output('ipmi',\%sensors);
18904			if (!@data){
18905				$key1 = 'Message';
18906				$val1 = main::row_defaults('sensors-data-ipmi');
18907				# $val1 = main::row_defaults('dev');
18908				@data = ({main::key($num++,0,1,$key1) => $val1,});
18909			}
18910			push(@rows,@data);
18911			$source = 'lm-sensors'; # trips per sensor type output
18912		}
18913		else {
18914			$key1 = 'Permissions';
18915			$val1 = main::row_defaults('sensors-ipmi-root');
18916			@data = ({main::key($num++,0,1,$key1) => $val1,});
18917			push(@rows,@data);
18918		}
18919	}
18920	if ($sysctl{'sensor'}){
18921		%sensors = sysctl_data();
18922		@data = sensors_output('sysctl-sensors',\%sensors);
18923		if (!@data){
18924			$key1 = 'Message';
18925			$val1 = main::row_defaults('sensors-data-bsd',$uname[0]);
18926			@data = ({main::key($num++,0,1,$key1) => $val1,});
18927		}
18928		push(@rows,@data);
18929	}
18930	else {
18931		if (!$fake{'sensors'} && $alerts{'sensors'}->{'action'} ne 'use'){
18932			# print "here 1\n";
18933			if ($bsd_type && $bsd_type =~ /^(free|open)bsd/){
18934				$key1 = 'Message';
18935				$val1 = main::row_defaults('sensors-data-bsd-ok');
18936			}
18937			else {
18938				$key1 = $alerts{'sensors'}->{'action'};
18939				$val1 = $alerts{'sensors'}->{'message'};
18940				$key1 = ucfirst($key1);
18941			}
18942			@data = ({main::key($num++,0,1,$key1) => $val1,});
18943			push(@rows,@data);
18944		}
18945		else {
18946			%sensors = lm_sensors_data();
18947			@data = sensors_output($source,\%sensors);
18948			# print "here 2\n";
18949			if (!@data){
18950				$key1 = 'Message';
18951				$val1 = main::row_defaults('sensors-data-linux');
18952				@data = ({main::key($num++,0,1,$key1) => $val1,});
18953			}
18954			push(@rows,@data);
18955		}
18956	}
18957	eval $end if $b_log;
18958	return @rows;
18959}
18960sub sensors_output {
18961	eval $start if $b_log;
18962	my ($source,$sensors) = @_;
18963	# note: might revisit this, since gpu sensors data might be present
18964	return if ! %$sensors;
18965	my (@gpu,@rows,@fan_default,@fan_main);
18966	my ($data_source) = ('');
18967	my $fan_number = 0;
18968	my $num = 0;
18969	my $j = 0;
18970	@gpu = gpu_data() if ($source eq 'sensors' || $source eq 'lm-sensors');
18971	my $temp_unit  = (defined $sensors->{'temp-unit'}) ? " $sensors->{'temp-unit'}": '';
18972	my $cpu_temp = (defined $sensors->{'cpu-temp'}) ? $sensors->{'cpu-temp'} . $temp_unit: 'N/A';
18973	my $mobo_temp = (defined $sensors->{'mobo-temp'}) ? $sensors->{'mobo-temp'} . $temp_unit: 'N/A';
18974	my $cpu1_key = ($sensors->{'cpu2-temp'}) ? 'cpu-1': 'cpu' ;
18975	$data_source = $source if ($source eq 'ipmi' || $source eq 'lm-sensors');
18976	push(@rows, {
18977	main::key($num++,1,1,'System Temperatures') => $data_source,
18978	main::key($num++,0,2,$cpu1_key) => $cpu_temp,
18979	});
18980	if ($sensors->{'cpu2-temp'}){
18981		$rows[$j]->{main::key($num++,0,2,'cpu-2')} = $sensors->{'cpu2-temp'} . $temp_unit;
18982	}
18983	if ($sensors->{'cpu3-temp'}){
18984		$rows[$j]->{main::key($num++,0,2,'cpu-3')} = $sensors->{'cpu3-temp'} . $temp_unit;
18985	}
18986	if ($sensors->{'cpu4-temp'}){
18987		$rows[$j]->{main::key($num++,0,2,'cpu-4')} = $sensors->{'cpu4-temp'} . $temp_unit;
18988	}
18989	$rows[$j]->{main::key($num++,0,2,'mobo')} = $mobo_temp;
18990	if (defined $sensors->{'sodimm-temp'}){
18991		my $sodimm_temp = $sensors->{'sodimm-temp'} . $temp_unit;
18992		$rows[$j]->{main::key($num++,0,2,'sodimm')} = $sodimm_temp;
18993	}
18994	if (defined $sensors->{'psu-temp'}){
18995		my $psu_temp = $sensors->{'psu-temp'} . $temp_unit;
18996		$rows[$j]->{main::key($num++,0,2,'psu')} = $psu_temp;
18997	}
18998	if (defined $sensors->{'ambient-temp'}){
18999		my $ambient_temp = $sensors->{'ambient-temp'} . $temp_unit;
19000		$rows[$j]->{main::key($num++,0,2,'ambient')} = $ambient_temp;
19001	}
19002	if (scalar @gpu == 1 && defined $gpu[0]->{'temp'}){
19003		my $gpu_temp = $gpu[0]->{'temp'};
19004		my $gpu_type = $gpu[0]->{'type'};
19005		my $gpu_unit = (defined $gpu[0]{'temp-unit'} && $gpu_temp) ? " $gpu[0]->{'temp-unit'}" : ' C';
19006		$rows[$j]->{main::key($num++,1,2,'gpu')} = $gpu_type;
19007		$rows[$j]->{main::key($num++,0,3,'temp')} = $gpu_temp . $gpu_unit;
19008		if ($extra > 1 && $gpu[0]->{'temp-mem'}){
19009			$rows[$j]->{main::key($num++,0,3,'mem')} = $gpu[0]->{'temp-mem'} . $gpu_unit;
19010		}
19011	}
19012	$j = scalar @rows;
19013	@fan_main = @{$sensors->{'fan-main'}} if @{$sensors->{'fan-main'}};
19014	@fan_default = @{$sensors->{'fan-default'}} if @{$sensors->{'fan-default'}};
19015	my $fan_def = ($data_source) ? $data_source : '';
19016	if (!@fan_main && !@fan_default){
19017		$fan_def = ($fan_def) ? "$data_source N/A" : 'N/A';
19018	}
19019	$rows[$j]->{main::key($num++,1,1,'Fan Speeds (RPM)')} = $fan_def;
19020	my $b_cpu = 0;
19021	for (my $i = 0; $i < scalar @fan_main; $i++){
19022		next if $i == 0;# starts at 1, not 0
19023		if (defined $fan_main[$i]){
19024			if ($i == 1 || ($i == 2 && !$b_cpu)){
19025				$rows[$j]->{main::key($num++,0,2,'cpu')} = $fan_main[$i];
19026				$b_cpu = 1;
19027			}
19028			elsif ($i == 2 && $b_cpu){
19029				$rows[$j]->{main::key($num++,0,2,'mobo')} = $fan_main[$i];
19030			}
19031			elsif ($i == 3){
19032				$rows[$j]->{main::key($num++,0,2,'psu')} = $fan_main[$i];
19033			}
19034			elsif ($i == 4){
19035				$rows[$j]->{main::key($num++,0,2,'sodimm')} = $fan_main[$i];
19036			}
19037			elsif ($i > 4){
19038				$fan_number = $i - 4;
19039				$rows[$j]->{main::key($num++,0,2,"case-$fan_number")} = $fan_main[$i];
19040			}
19041		}
19042	}
19043	for (my $i = 0; $i < scalar @fan_default; $i++){
19044		next if $i == 0;# starts at 1, not 0
19045		if (defined $fan_default[$i]){
19046			$rows[$j]->{main::key($num++,0,2,"fan-$i")} = $fan_default[$i];
19047		}
19048	}
19049	$rows[$j]->{main::key($num++,0,2,'psu')} = $sensors->{'fan-psu'} if defined $sensors->{'fan-psu'};
19050	$rows[$j]->{main::key($num++,0,2,'psu-1')} = $sensors->{'fan-psu1'} if defined $sensors->{'fan-psu1'};
19051	$rows[$j]->{main::key($num++,0,2,'psu-2')} = $sensors->{'fan-psu2'} if defined $sensors->{'fan-psu2'};
19052	# note: so far, only nvidia-settings returns speed, and that's in percent
19053	if (scalar @gpu == 1 && defined $gpu[0]->{'fan-speed'}){
19054		my $gpu_fan = $gpu[0]->{'fan-speed'} . $gpu[0]{'speed-unit'};
19055		my $gpu_type = $gpu[0]->{'type'};
19056		$rows[$j]->{main::key($num++,1,2,'gpu')} = $gpu_type;
19057		$rows[$j]->{main::key($num++,0,3,'fan')} = $gpu_fan;
19058	}
19059	if (scalar @gpu > 1){
19060		$j = scalar @rows;
19061		$rows[$j]->{main::key($num++,1,1,'GPU')} = '';
19062		my $gpu_unit = (defined $gpu[0]->{'temp-unit'}) ? " $gpu[0]->{'temp-unit'}" : ' C';
19063		foreach my $info (@gpu){
19064			# speed unit is either '' or %
19065			my $gpu_fan = (defined $info->{'fan-speed'}) ? $info->{'fan-speed'} . $info->{'speed-unit'}: undef ;
19066			my $gpu_type = $info->{'type'};
19067			my $gpu_temp = (defined $info->{'temp'}) ? $info->{'temp'} . $gpu_unit: 'N/A';
19068			$rows[$j]->{main::key($num++,1,2,'device')} = $gpu_type;
19069			if (defined $info->{'screen'}){
19070				$rows[$j]->{main::key($num++,0,3,'screen')} = $info->{'screen'};
19071			}
19072			$rows[$j]->{main::key($num++,0,3,'temp')} = $gpu_temp;
19073			if ($extra > 1 && $info->{'temp-mem'}){
19074				$rows[$j]->{main::key($num++,0,3,'mem')} = $info->{'temp-mem'} . $gpu_unit;
19075			}
19076			if (defined $gpu_fan){
19077				$rows[$j]->{main::key($num++,0,3,'fan')} = $gpu_fan;
19078			}
19079			if ($extra > 2 && $info->{'watts'}){
19080				$rows[$j]->{main::key($num++,0,3,'watts')} = $info->{'watts'};
19081			}
19082			if ($extra > 2 && $info->{'mvolts'}){
19083				$rows[$j]->{main::key($num++,0,3,'mV')} = $info->{'mvolts'};
19084			}
19085		}
19086	}
19087	if ($extra > 0 && ($source eq 'ipmi' ||
19088	   ($sensors->{'volts-12'} || $sensors->{'volts-5'} || $sensors->{'volts-3.3'} || $sensors->{'volts-vbat'}))){
19089		$j = scalar @rows;
19090		$sensors->{'volts-12'} ||= 'N/A';
19091		$sensors->{'volts-5'} ||= 'N/A';
19092		$sensors->{'volts-3.3'} ||= 'N/A';
19093		$sensors->{'volts-vbat'} ||= 'N/A';
19094		$rows[$j]->{main::key($num++,1,1,'Power')} = $data_source;
19095		$rows[$j]->{main::key($num++,0,2,'12v')} = $sensors->{'volts-12'};
19096		$rows[$j]->{main::key($num++,0,2,'5v')} = $sensors->{'volts-5'};
19097		$rows[$j]->{main::key($num++,0,2,'3.3v')} = $sensors->{'volts-3.3'};
19098		$rows[$j]->{main::key($num++,0,2,'vbat')} = $sensors->{'volts-vbat'};
19099		if ($extra > 1 && $source eq 'ipmi'){
19100			$sensors->{'volts-dimm-p1'} ||= 'N/A';
19101			$sensors->{'volts-dimm-p2'} ||= 'N/A';
19102			$rows[$j]->{main::key($num++,0,2,'dimm-p1')} = $sensors->{'volts-dimm-p1'} if $sensors->{'volts-dimm-p1'};
19103			$rows[$j]->{main::key($num++,0,2,'dimm-p2')} = $sensors->{'volts-dimm-p2'} if $sensors->{'volts-dimm-p2'};
19104			$rows[$j]->{main::key($num++,0,2,'soc-p1')} = $sensors->{'volts-soc-p1'} if $sensors->{'volts-soc-p1'};
19105			$rows[$j]->{main::key($num++,0,2,'soc-p2')} = $sensors->{'volts-soc-p2'} if $sensors->{'volts-soc-p2'};
19106		}
19107		if (scalar @gpu == 1 && $extra > 2 && ($gpu[0]->{'watts'} || $gpu[0]->{'mvolts'})){
19108			$rows[$j]->{main::key($num++,1,2,'gpu')} = $gpu[0]->{'type'};
19109			$rows[$j]->{main::key($num++,0,3,'watts')} = $gpu[0]->{'watts'} if $gpu[0]->{'watts'}  ;
19110			$rows[$j]->{main::key($num++,0,3,'mV')} = $gpu[0]->{'mvolts'} if $gpu[0]->{'mvolts'};
19111		}
19112	}
19113	eval $end if $b_log;
19114	return @rows;
19115}
19116sub ipmi_data {
19117	eval $start if $b_log;
19118	my ($program) = @_;
19119	my ($b_cpu_0,$cmd,$file,@data,$fan_working,%sensors,@row,$sys_fan_nu,
19120	$temp_working,$working_unit);
19121	my ($b_ipmitool,$i_key,$i_value,$i_unit);
19122	if ($fake{'ipmi'}){
19123		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-archerseven-1.txt";$program='ipmitool';
19124		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-crazy-epyc-1.txt";$program='ipmitool';
19125		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-RK016013.txt";$program='ipmitool';
19126		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmitool-sensors-freebsd-offsite-backup.txt";
19127		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-crazy-epyc-1.txt";
19128		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-lathander.txt";
19129		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-zwerg.txt";
19130		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ipmitool/ipmi-sensors-arm-server-1.txt";
19131		# @data = main::reader($file);
19132		#($b_ipmitool,$i_key,$i_value,$i_unit) = (0,1,3,4); # ipmi-sensors
19133		#($b_ipmitool,$i_key,$i_value,$i_unit) = (1,0,1,2); # ipmitool sensors
19134	}
19135	else {
19136		if ($program =~ /ipmi-sensors$/){
19137			$cmd = $program;
19138			($b_ipmitool,$i_key,$i_value,$i_unit) = (0,1,3,4);
19139		}
19140		else { # ipmitool
19141			$cmd = "$program sensors";
19142			($b_ipmitool,$i_key,$i_value,$i_unit) = (1,0,1,2);
19143		}
19144		@data = main::grabber("$cmd 2>/dev/null");
19145	}
19146	# print join("\n", @data), "\n";
19147	# shouldn't need to log, but saw a case with debugger ipmi data, but none here apparently
19148	main::log_data('dump','ipmi @data',\@data) if $b_log;
19149	return if !@data;
19150	foreach (@data){
19151		next if /^\s*$/;
19152		# print "$_\n";
19153		@row = split(/\s*\|\s*/, $_);
19154		# print "$row[$i_value]\n";
19155		next if !main::is_numeric($row[$i_value]);
19156		# print "$row[$i_key] - $row[$i_value]\n";
19157		if (!$sensors{'mobo-temp'} && $row[$i_key] =~ /^(MB_TEMP[0-9]|System[\s_]Temp|System[\s_]?Board([\s_]Temp)?)$/i){
19158			$sensors{'mobo-temp'} = int($row[$i_value]);
19159			$working_unit = $row[$i_unit];
19160			$working_unit =~ s/degrees\s// if $b_ipmitool;
19161			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19162		}
19163		elsif ($row[$i_key] =~ /^(Ambient)([\s_]Temp)?$/i){
19164			$sensors{'ambient-temp'} = int($row[$i_value]);
19165			$working_unit = $row[$i_unit];
19166			$working_unit =~ s/degrees\s// if $b_ipmitool;
19167			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19168		}
19169		# Platform Control Hub (PCH), it is the X370 chip on the Crosshair VI Hero.
19170		# VRM: voltage regulator module
19171		# NOTE: CPU0_TEMP CPU1_TEMP is possible, unfortunately; CPU Temp Interf
19172		elsif (!$sensors{'cpu-temp'} && $row[$i_key] =~ /^CPU([01])?([\s_]Temp)?$/i){
19173			$b_cpu_0 = 1 if defined $1 && $1 == 0;
19174			$sensors{'cpu-temp'} = int($row[$i_value]);
19175			$working_unit = $row[$i_unit];
19176			$working_unit =~ s/degrees\s// if $b_ipmitool;
19177			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19178		}
19179		elsif ($row[$i_key] =~ /^CPU([1-4])([\s_]Temp)?$/i){
19180			$temp_working = $1;
19181			$temp_working++ if $b_cpu_0;
19182			$sensors{"cpu${temp_working}-temp"} = int($row[$i_value]);
19183			$working_unit = $row[$i_unit];
19184			$working_unit =~ s/degrees\s// if $b_ipmitool;
19185			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19186		}
19187		# for temp1/2 only use temp1/2 if they are null or greater than the last ones
19188		elsif ($row[$i_key] =~ /^(MB[_]?TEMP1|Temp[\s_]1)$/i){
19189			$temp_working = int($row[$i_value]);
19190			$working_unit = $row[$i_unit];
19191			$working_unit =~ s/degrees\s// if $b_ipmitool;
19192			if (!$sensors{'temp1'} || (defined $temp_working && $temp_working > 0)){
19193				$sensors{'temp1'} = $temp_working;
19194			}
19195			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19196		}
19197		elsif ($row[$i_key] =~ /^(MB[_]?TEMP2|Temp[\s_]2)$/i){
19198			$temp_working = int($row[$i_value]);
19199			$working_unit = $row[$i_unit];
19200			$working_unit =~ s/degrees\s// if $b_ipmitool;
19201			if (!$sensors{'temp2'} || (defined $temp_working && $temp_working > 0)){
19202				$sensors{'temp2'} = $temp_working;
19203			}
19204			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19205		}
19206		# temp3 is only used as an absolute override for systems with all 3 present
19207		elsif ($row[$i_key] =~ /^(MB[_]?TEMP3|Temp[\s_]3)$/i){
19208			$temp_working = int($row[$i_value]);
19209			$working_unit = $row[$i_unit];
19210			$working_unit =~ s/degrees\s// if $b_ipmitool;
19211			if (!$sensors{'temp3'} || (defined $temp_working && $temp_working > 0)){
19212				$sensors{'temp3'} = $temp_working;
19213			}
19214			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19215		}
19216		elsif (!$sensors{'sodimm-temp'} && $row[$i_key] =~ /^(DIMM[-_]([A-Z][0-9][-_])?[A-Z]?[0-9][A-Z]?)$/i){
19217			$sensors{'sodimm-temp'} = int($row[$i_value]);
19218			$working_unit = $row[$i_unit];
19219			$working_unit =~ s/degrees\s// if $b_ipmitool;
19220			$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19221		}
19222		# note: can be cpu fan:, cpu fan speed:, etc.
19223		elsif ($row[$i_key] =~ /^(CPU|Processor)[\s_]Fan/i){
19224			$sensors{'fan-main'}->[1] = int($row[$i_value]);
19225		}
19226		# note that the counters are dynamically set for fan numbers here
19227		# otherwise you could overwrite eg aux fan2 with case fan2 in theory
19228		# note: cpu/mobo/ps are 1/2/3
19229		elsif ($row[$i_key] =~ /^(SYS[\s_])?FAN[\s_]?([0-9A-F]+)/i){
19230			$sys_fan_nu = hex($2);
19231			$fan_working = int($row[$i_value]);
19232			$sensors{'fan-default'} = () if !$sensors{'fan-default'};
19233			if ($sys_fan_nu =~ /^([0-9]+)$/){
19234				# add to array if array index does not exist OR if number is > existing number
19235				if (defined $sensors{'fan-default'}->[$sys_fan_nu]){
19236					if ($fan_working >= $sensors{'fan-default'}->[$sys_fan_nu]){
19237						$sensors{'fan-default'}->[$sys_fan_nu] = $fan_working;
19238					}
19239				}
19240				else {
19241					$sensors{'fan-default'}->[$sys_fan_nu] = $fan_working;
19242				}
19243			}
19244		}
19245		elsif ($row[$i_key] =~ /^(FAN PSU|PSU FAN)$/i){
19246			$sensors{'fan-psu'} = int($row[$i_value]);
19247		}
19248		elsif ($row[$i_key] =~ /^(FAN PSU1|PSU1 FAN)$/i){
19249			$sensors{'fan-psu-1'} = int($row[$i_value]);
19250		}
19251		elsif ($row[$i_key] =~ /^(FAN PSU2|PSU2 FAN)$/i){
19252			$sensors{'fan-psu-2'} = int($row[$i_value]);
19253		}
19254		if ($extra > 0){
19255			if ($row[$i_key] =~ /^((MAIN\s|P[_]?)?12V|PSU[12]_VOUT)$/i){
19256				$sensors{'volts-12'} = $row[$i_value];
19257			}
19258			elsif ($row[$i_key] =~ /^(MAIN\s5V|P5V|5VCC|5V PG|5V_SB)$/i){
19259				$sensors{'volts-5'} = $row[$i_value];
19260			}
19261			elsif ($row[$i_key] =~ /^(MAIN\s3\.3V|P3V3|3\.3VCC|3\.3V PG|3V3_SB)$/i){
19262				$sensors{'volts-3.3'} = $row[$i_value];
19263			}
19264			elsif ($row[$i_key] =~ /^((P_)?VBAT|CMOS Battery|BATT 3.0V)$/i){
19265				$sensors{'volts-vbat'} = $row[$i_value];
19266			}
19267			# NOTE: VDimmP1ABC VDimmP1DEF
19268			elsif (!$sensors{'volts-dimm-p1'} && $row[$i_key] =~ /^(P1_VMEM|VDimmP1|MEM RSR A PG|DIMM_VR1_VOLT)/i){
19269				$sensors{'volts-dimm-p1'} = $row[$i_value];
19270			}
19271			elsif (!$sensors{'volts-dimm-p2'} && $row[$i_key] =~ /^(P2_VMEM|VDimmP2|MEM RSR B PG|DIMM_VR2_VOLT)/i){
19272				$sensors{'volts-dimm-p2'} = $row[$i_value];
19273			}
19274			elsif (!$sensors{'volts-soc-p1'} && $row[$i_key] =~ /^(P1_SOC_RUN$)/i){
19275				$sensors{'volts-soc-p1'} = $row[$i_value];
19276			}
19277			elsif (!$sensors{'volts-soc-p2'} && $row[$i_key] =~ /^(P2_SOC_RUN$)/i){
19278				$sensors{'volts-soc-p2'} = $row[$i_value];
19279			}
19280		}
19281	}
19282	print Data::Dumper::Dumper \%sensors if $dbg[31];
19283	%sensors = process_data(%sensors) if %sensors;
19284	main::log_data('dump','ipmi: %sensors',\%sensors) if $b_log;
19285	eval $end if $b_log;
19286	print Data::Dumper::Dumper \%sensors if $dbg[31];
19287	return %sensors;
19288}
19289sub lm_sensors_data {
19290	eval $start if $b_log;
19291	my (%sensors);
19292	my ($sys_fan_nu)  = (0);
19293	my ($adapter,$fan_working,$temp_working,$working_unit)  = ('','','','','');
19294	process_lm_sensors() if !$loaded{'lm-sensors'};
19295	foreach $adapter (keys %{$sensors_raw{'main'}}){
19296		next if !$adapter || ref $sensors_raw{'main'}->{$adapter} ne 'ARRAY';
19297		# not sure why hwmon is excluded, forgot to add info in comments
19298		if ((@sensors_use && !(grep {/$adapter/} @sensors_use)) ||
19299		 (@sensors_exclude && (grep {/$adapter/} @sensors_exclude))){
19300			next;
19301		}
19302		foreach (@{$sensors_raw{'main'}->{$adapter}}){
19303			my @working = split(':', $_);
19304			next if !$working[0];
19305			# print "$working[0]:$working[1]\n";
19306			# There are some guesses here, but with more sensors samples it will get closer.
19307			# note: using arrays starting at 1 for all fan arrays to make it easier overall
19308			# we have to be sure we are working with the actual real string before assigning
19309			# data to real variables and arrays. Extracting C/F degree unit as well to use
19310			# when constructing temp items for array.
19311			# note that because of charset issues, no "°" degree sign used, but it is required
19312			# in testing regex to avoid error. It might be because I got that data from a forum post,
19313			# note directly via debugger.
19314			if ($_ =~ /^T?(AMBIENT|M\/B|MB|Motherboard|SIO|SYS).*:([0-9\.]+)[\s°]*(C|F)/i){
19315				# avoid SYSTIN: 118 C
19316				if (main::is_numeric($2) && $2 < 90){
19317					$sensors{'mobo-temp'} = $2;
19318					$working_unit = $3;
19319					$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19320				}
19321			}
19322			# issue 58 msi/asus show wrong for CPUTIN so overwrite it if PECI 0 is present
19323			# http://www.spinics.net/lists/lm-sensors/msg37308.html
19324			# NOTE: had: ^CPU.*\+([0-9]+): but that misses: CPUTIN and anything not with + in starter
19325			# However, "CPUTIN is not a reliable measurement because it measures difference to Tjmax,
19326			# which is the maximum CPU temperature reported as critical temperature by coretemp"
19327			# NOTE: I've seen an inexplicable case where: CPU:52.0°C fails to match with [\s°] but
19328			# does match with: [\s°]*. I can't account for this, but that's why the * is there
19329			# Tdie is a new k10temp-pci syntax for cpu die temp
19330			elsif ($_ =~ /^(T?CPU.*|Tdie.*):([0-9\.]+)[\s°]*(C|F)/i){
19331				$temp_working = $2;
19332				$working_unit = $3;
19333				if (!$sensors{'cpu-temp'} ||
19334				 (defined $temp_working && $temp_working > 0 && $temp_working > $sensors{'cpu-temp'})){
19335					$sensors{'cpu-temp'} = $temp_working;
19336				}
19337				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19338			}
19339			elsif ($_ =~ /^PECI\sAgent\s0.*:([0-9\.]+)[\s°]*(C|F)/i){
19340				$sensors{'cpu-peci-temp'} = $1;
19341				$working_unit = $2;
19342				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19343			}
19344			elsif ($_ =~ /^T?(P\/S|Power).*:([0-9\.]+)[\s°]*(C|F)/i){
19345				$sensors{'psu-temp'} = $2;
19346				$working_unit = $3;
19347				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19348			}
19349			elsif ($_ =~ /^T?(dimm|mem|sodimm).*:([0-9\.]+)[\s°]*(C|F)/i){
19350				$sensors{'sodimm-temp'} = $1;
19351				$working_unit = $2;
19352				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19353			}
19354			# for temp1/2 only use temp1/2 if they are null or greater than the last ones
19355			elsif ($_ =~ /^temp1:([0-9\.]+)[\s°]*(C|F)/i){
19356				$temp_working = $1;
19357				$working_unit = $2;
19358				if (!$sensors{'temp1'} ||
19359				 (defined $temp_working && $temp_working > 0 && $temp_working > $sensors{'temp1'})){
19360					$sensors{'temp1'} = $temp_working;
19361				}
19362				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19363			}
19364			elsif ($_ =~ /^temp2:([0-9\.]+)[\s°]*(C|F)/i){
19365				$temp_working = $1;
19366				$working_unit = $2;
19367				if (!$sensors{'temp2'} ||
19368				 (defined $temp_working && $temp_working > 0 && $temp_working > $sensors{'temp2'})){
19369					$sensors{'temp2'} = $temp_working;
19370				}
19371				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19372			}
19373			# temp3 is only used as an absolute override for systems with all 3 present
19374			elsif ($_ =~ /^temp3:([0-9\.]+)[\s°]*(C|F)/i){
19375				$temp_working = $1;
19376				$working_unit = $2;
19377				if (!$sensors{'temp3'} ||
19378				 (defined $temp_working && $temp_working > 0 && $temp_working > $sensors{'temp3'})){
19379					$sensors{'temp3'} = $temp_working;
19380				}
19381				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19382			}
19383			# final fallback if all else fails, funtoo user showed sensors putting
19384			# temp on wrapped second line, not handled
19385			elsif ($_ =~ /^T?(core0|core 0|Physical id 0)(.*):([0-9\.]+)[\s°]*(C|F)/i){
19386				$temp_working = $3;
19387				$working_unit = $4;
19388				if (!$sensors{'core-0-temp'} ||
19389				 (defined $temp_working && $temp_working > 0 && $temp_working > $sensors{'core-0-temp'})){
19390					$sensors{'core-0-temp'} = $temp_working;
19391				}
19392				$sensors{'temp-unit'} = set_temp_unit($sensors{'temp-unit'},$working_unit) if $working_unit;
19393			}
19394			# note: can be cpu fan:, cpu fan speed:, etc.
19395			elsif (!$sensors{'fan-main'}->[1] && $_ =~ /^F?(CPU|Processor).*:([0-9]+)[\s]RPM/i){
19396				$sensors{'fan-main'}->[1] = $2;
19397			}
19398			elsif (!$sensors{'fan-main'}->[2] && $_ =~ /^F?(M\/B|MB|SYS|Motherboard).*:([0-9]+)[\s]RPM/i){
19399				$sensors{'fan-main'}->[2] = $2;
19400			}
19401			elsif (!$sensors{'fan-main'}->[3] && $_ =~ /F?(Power|P\/S|POWER).*:([0-9]+)[\s]RPM/i){
19402				$sensors{'fan-main'}->[3] = $2;
19403			}
19404			elsif (!$sensors{'fan-main'}->[4] && $_ =~ /F?(dimm|mem|sodimm).*:([0-9]+)[\s]RPM/i){
19405				$sensors{'fan-main'}->[4] = $2;
19406			}
19407			# note that the counters are dynamically set for fan numbers here
19408			# otherwise you could overwrite eg aux fan2 with case fan2 in theory
19409			# note: cpu/mobo/ps/sodimm are 1/2/3/4
19410			elsif ($_ =~ /^F?(AUX|CASE|CHASSIS|FRONT|REAR).*:([0-9]+)[\s]RPM/i){
19411				$temp_working = $2;
19412				for (my $i = 5; $i < 30; $i++){
19413					next if defined $sensors{'fan-main'}->[$i];
19414					if (!defined $sensors{'fan-main'}->[$i]){
19415						$sensors{'fan-main'}->[$i] = $temp_working;
19416						last;
19417					}
19418				}
19419			}
19420			# in rare cases syntax is like: fan1: xxx RPM
19421			elsif ($_ =~ /^FAN(1)?:([0-9]+)[\s]RPM/i){
19422				$sensors{'fan-default'}->[1] = $2;
19423			}
19424			elsif ($_ =~ /^FAN([2-9]|1[0-9]).*:([0-9]+)[\s]RPM/i){
19425				$fan_working = $2;
19426				$sys_fan_nu = $1;
19427				if ($sys_fan_nu =~ /^([0-9]+)$/){
19428					# add to array if array index does not exist OR if number is > existing number
19429					if (defined $sensors{'fan-default'}->[$sys_fan_nu]){
19430						if ($fan_working >= $sensors{'fan-default'}->[$sys_fan_nu]){
19431							$sensors{'fan-default'}->[$sys_fan_nu] = $fan_working;
19432						}
19433					}
19434					else {
19435						$sensors{'fan-default'}->[$sys_fan_nu] = $fan_working;
19436					}
19437				}
19438			}
19439			if ($extra > 0){
19440				if ($_ =~ /^[+]?(12 Volt|12V|V\+?12).*:([0-9\.]+)\sV/i){
19441					$sensors{'volts-12'} = $2;
19442				}
19443				# note: 5VSB is a field name
19444				elsif ($_ =~ /^[+]?(5 Volt|5V|V\+?5):([0-9\.]+)\sV/i){
19445					$sensors{'volts-5'} = $2;
19446				}
19447				elsif ($_ =~ /^[+]?(3\.3 Volt|3\.3V|V\+?3\.3).*:([0-9\.]+)\sV/i){
19448					$sensors{'volts-3.3'} = $2;
19449				}
19450				elsif ($_ =~ /^(Vbat).*:([0-9\.]+)\sV/i){
19451					$sensors{'volts-vbat'} = $2;
19452				}
19453				elsif ($_ =~ /^v(dimm|mem|sodimm).*:([0-9\.]+)\sV/i){
19454					$sensors{'volts-mem'} = $2;
19455				}
19456			}
19457		}
19458	}
19459	print Data::Dumper::Dumper \%sensors if $dbg[31];
19460	%sensors = process_data(%sensors) if %sensors;
19461	main::log_data('dump','lm-sensors: %sensors',\%sensors) if $b_log;
19462	print Data::Dumper::Dumper \%sensors if $dbg[31];
19463	eval $end if $b_log;
19464	return %sensors;
19465}
19466sub process_lm_sensors {
19467	eval $start if $b_log;
19468	my (@data,@sensors_data,@values);
19469	my ($adapter,$holder,$type) = ('','','');
19470	if ($fake{'sensors'}){
19471		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/amdgpu-w-fan-speed-stretch-k10.txt";
19472		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/peci-tin-geggo.txt";
19473		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-w-other-biker.txt";
19474		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-asus-chassis-1.txt";
19475		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-devnull-1.txt";
19476		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-jammin1.txt";
19477		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-mx-incorrect-1.txt";
19478		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-maximus-arch-1.txt";
19479		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/kernel-58-sensors-ant-1.txt";
19480		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sensors/sensors-zenpower-nvme-2.txt";
19481		#@sensors_data = main::reader($file);
19482	}
19483	else {
19484		# only way to get sensor array data? Unless using sensors -j, but can't assume json
19485		@sensors_data = main::grabber($alerts{'sensors'}->{'path'} . ' 2>/dev/null');
19486	}
19487	# print join("\n", @sensors_data), "\n";
19488	if (@sensors_data){
19489		@sensors_data = map {$_ =~ s/\s*:\s*\+?/:/;$_} @sensors_data;
19490		push(@sensors_data, 'END');
19491	}
19492	# print Data::Dumper::Dumper \@sensors_data;
19493	foreach (@sensors_data){
19494		# print 'st:', $_, "\n";
19495		next if /^\s*$/;
19496		$_ = main::trimmer($_);
19497		if (@values && $adapter && (/^Adapter/ || $_ eq 'END')){
19498			# note: drivetemp: known, but many others could exist
19499			if ($adapter =~ /^(drive|nvme)/){
19500				$type = 'disk';
19501			}
19502			elsif ($adapter =~ /^(amdgpu|intel|nouveau|radeon)-/){
19503				$type = 'gpu';
19504			}
19505			# ath/iwl: wifi; enp/eno/eth: lan nic
19506			elsif ($adapter =~ /^(ath|iwl|en[op][0-9]|eth)[\S]+-/){
19507				$type = 'network';
19508			}
19509			elsif ($adapter =~ /^(.*hwmon)-/){
19510				$type = 'hwmon';
19511			}
19512			else {
19513				$type = 'main';
19514			}
19515			$sensors_raw{$type}->{$adapter} = [@values];
19516			@values = ();
19517			$adapter = '';
19518		}
19519		if (/^Adapter/){
19520			$adapter = $holder;
19521		}
19522		elsif (/\S:\S/){
19523			push(@values, $_);
19524		}
19525		else {
19526			$holder = $_;
19527		}
19528	}
19529	$loaded{'lm-sensors'} = 1;
19530	if ($dbg[18]){
19531		print 'lm sensors: ' , Data::Dumper::Dumper \%sensors_raw;
19532	}
19533	if ($b_log){
19534		main::log_data('dump','lm-sensors data: %sensors_raw',\%sensors_raw);
19535	}
19536	eval $end if $b_log;
19537	return @data;
19538}
19539
19540# bsds sysctl may have hw.sensors data
19541sub sysctl_data {
19542	eval $start if $b_log;
19543	my (@data,%sensors);
19544	# assume always starts at 0, can't do dynamic because freebsd shows tz1 first
19545	my $add = 1;
19546	foreach (@{$sysctl{'sensor'}}){
19547		my ($sensor,$type,$number,$value);
19548		if (/^hw\.sensors\.([a-z]+)([0-9]+)\.(cpu|temp|fan|volt)([0-9])/){
19549			$sensor = $1;
19550			$type = $3;
19551			$number = $4;
19552			# hw.sensors.cpu0.temp0:47.00 degC
19553			# hw.sensors.acpitz0.temp0:43.00 degC
19554			$type = 'cpu' if $sensor eq 'cpu';
19555		}
19556		elsif (/^hw\.sensors\.(acpi)\.(thermal)\.(tz)([0-9]+)\.(temperature)/){
19557			$sensor = $1 . $3; # eg acpitz
19558			$type = ($5 eq 'temperature') ? 'temp': $5;
19559			$number = $4;
19560		}
19561		elsif (/^dev\.(cpu)\.([0-9]+)\.(temperature)/){
19562			$sensor = $1;
19563			$type = $3;
19564			$number = $2;
19565			$type = 'cpu' if $sensor eq 'cpu';
19566		}
19567		if ($sensor && $type){
19568			if ($sensor && ((@sensors_use && !(grep {/$sensor/} @sensors_use)) ||
19569			 (@sensors_exclude && (grep {/$sensor/} @sensors_exclude)))){
19570				next;
19571			}
19572			my $working = (split(':\s*', $_))[1];
19573			if (defined $working && $working =~ /^([0-9\.]+)\s?((deg)?([CF]))?\b/){
19574				 $value = $1 ;
19575				 $sensors{'temp-unit'} = $4 if $4 && !$sensors{'temp-unit'};
19576			}
19577			else {
19578				next;
19579			}
19580			$number += $add;
19581			if ($type eq 'cpu' && !defined $sensors{'cpu-temp'}){
19582				$sensors{'cpu-temp'} = $value;
19583			}
19584			elsif ($type eq 'temp' && !defined $sensors{'temp' . $number}){
19585				$sensors{'temp' . $number} = $value;
19586			}
19587			elsif ($type eq 'fan' && !defined $sensors{'fan-main'}->[$number]){
19588				$sensors{'fan-main'}->[$number] = $value;
19589			}
19590			elsif ($type eq 'volt'){
19591				if ($working =~ /\+3\.3V/i){
19592					$sensors{'volts-3.3'} = $value;
19593				}
19594				elsif ($working =~ /\+5V/i){
19595					$sensors{'volts-5'} = $value;
19596				}
19597				elsif ($working =~ /\+12V/i){
19598					$sensors{'volts-12'} = $value;
19599				}
19600				elsif ($working =~ /VBAT/i){
19601					$sensors{'volts-vbat'} = $value;
19602				}
19603			}
19604		}
19605	}
19606	%sensors = process_data(%sensors) if %sensors;
19607	main::log_data('dump','%sensors',\%sensors) if $b_log;
19608	print Data::Dumper::Dumper \%sensors if $dbg[31];;
19609	eval $end if $b_log;
19610	return %sensors;
19611}
19612sub set_temp_unit {
19613	my ($sensors,$working) = @_;
19614	my $return_unit = '';
19615	if (!$sensors && $working){
19616		$return_unit = $working;
19617	}
19618	elsif ($sensors){
19619		$return_unit = $sensors;
19620	}
19621	return $return_unit;
19622}
19623
19624sub process_data {
19625	eval $start if $b_log;
19626	my (%sensors) = @_;
19627	my ($cpu_temp,$cpu2_temp,$cpu3_temp,$cpu4_temp,$mobo_temp,$psu_temp);
19628	my ($fan_type,$i,$j,$index_count_fan_default,$index_count_fan_main) = (0,0,0,0,0);
19629	my $temp_diff = 20; # for C, handled for F after that is determined
19630	my (@fan_main,@fan_default);
19631	# first we need to handle the case where we have to determine which temp/fan to use for cpu and mobo:
19632	# note, for rare cases of weird cool cpus, user can override in their prefs and force the assignment
19633	# this is wrong for systems with > 2 tempX readings, but the logic is too complex with 3 variables
19634	# so have to accept that it will be wrong in some cases, particularly for motherboard temp readings.
19635	if ($sensors{'temp1'} && $sensors{'temp2'}){
19636		if ($sensors_cpu_nu){
19637			$fan_type = $sensors_cpu_nu;
19638		}
19639		else {
19640			# first some fringe cases with cooler cpu than mobo: assume which is cpu temp based on fan speed
19641			# but only if other fan speed is 0.
19642			if ($sensors{'temp1'} >= $sensors{'temp2'} &&
19643			 defined $fan_default[1] && defined $fan_default[2] && $fan_default[1] == 0 && $fan_default[2] > 0){
19644				$fan_type = 2;
19645			}
19646			elsif ($sensors{'temp2'} >= $sensors{'temp1'} &&
19647			 defined $fan_default[1] && defined $fan_default[2] && $fan_default[2] == 0 && $fan_default[1] > 0){
19648				$fan_type = 1;
19649			}
19650			# then handle the standard case if these fringe cases are false
19651			elsif ($sensors{'temp1'} >= $sensors{'temp2'}){
19652				$fan_type = 1;
19653			}
19654			else {
19655				$fan_type = 2;
19656			}
19657		}
19658	}
19659	# need a case for no temps at all reported, like with old intels
19660	elsif (!$sensors{'temp2'} && !$sensors{'cpu-temp'}){
19661		if (!$sensors{'temp1'} && !$sensors{'mobo-temp'}){
19662			$fan_type = 1;
19663		}
19664		elsif ($sensors{'temp1'} && !$sensors{'mobo-temp'}){
19665			$fan_type = 1;
19666		}
19667		elsif ($sensors{'temp1'} && $sensors{'mobo-temp'}){
19668			$fan_type = 1;
19669		}
19670	}
19671	# convert the diff number for F, it needs to be bigger that is
19672	if ($sensors{'temp-unit'} && $sensors{'temp-unit'} eq "F"){
19673		$temp_diff = $temp_diff * 1.8
19674	}
19675	if ($sensors{'cpu-temp'}){
19676		# specific hack to handle broken CPUTIN temps with PECI
19677		if ($sensors{'cpu-peci-temp'} && ($sensors{'cpu-temp'} - $sensors{'cpu-peci-temp'}) > $temp_diff){
19678			$cpu_temp = $sensors{'cpu-peci-temp'};
19679		}
19680		# then get the real cpu temp, best guess is hottest is real, though only within narrowed diff range
19681		else {
19682			$cpu_temp = $sensors{'cpu-temp'};
19683		}
19684	}
19685	else {
19686		if ($fan_type){
19687			# there are some weird scenarios
19688			if ($fan_type == 1){
19689				if ($sensors{'temp1'} && $sensors{'temp2'} && $sensors{'temp2'} > $sensors{'temp1'}){
19690					$cpu_temp = $sensors{'temp2'};
19691				}
19692				else {
19693					$cpu_temp = $sensors{'temp1'};
19694				}
19695			}
19696			else {
19697				if ($sensors{'temp1'} && $sensors{'temp2'} && $sensors{'temp1'} > $sensors{'temp2'}){
19698					$cpu_temp = $sensors{'temp1'};
19699				}
19700				else {
19701					$cpu_temp = $sensors{'temp2'};
19702				}
19703			}
19704		}
19705		else {
19706			$cpu_temp = $sensors{'temp1'}; # can be null, that is ok
19707		}
19708		if ($cpu_temp){
19709			# using $sensors{'temp3'} is just not reliable enough, more errors caused than fixed imo
19710			# if ($sensors{'temp3'} && $sensors{'temp3'} > $cpu_temp){
19711			#	$cpu_temp = $sensors{'temp3'};
19712			# }
19713			# there are some absurdly wrong $sensors{'temp1'}: acpitz-virtual-0 $sensors{'temp1'}: +13.8°C
19714			if ($sensors{'core-0-temp'} && ($sensors{'core-0-temp'} - $cpu_temp) > $temp_diff){
19715				$cpu_temp = $sensors{'core-0-temp'};
19716			}
19717		}
19718	}
19719	# if all else fails, use core0/peci temp if present and cpu is null
19720	if (!$cpu_temp){
19721		if ($sensors{'core-0-temp'}){
19722			$cpu_temp = $sensors{'core-0-temp'};
19723		}
19724		# note that peci temp is known to be colder than the actual system
19725		# sometimes so it is the last fallback we want to use even though in theory
19726		# it is more accurate, but fact suggests theory wrong.
19727		elsif ($sensors{'cpu-peci-temp'}){
19728			$cpu_temp = $sensors{'cpu-peci-temp'};
19729		}
19730	}
19731	# then the real mobo temp
19732	if ($sensors{'mobo-temp'}){
19733		$mobo_temp = $sensors{'mobo-temp'};
19734	}
19735	elsif ($fan_type){
19736		if ($fan_type == 1){
19737			if ($sensors{'temp1'} && $sensors{'temp2'} && $sensors{'temp2'} > $sensors{'temp1'}){
19738				$mobo_temp = $sensors{'temp1'};
19739			}
19740			else {
19741				$mobo_temp = $sensors{'temp2'};
19742			}
19743		}
19744		else {
19745			if ($sensors{'temp1'} && $sensors{'temp2'} && $sensors{'temp1'} > $sensors{'temp2'}){
19746				$mobo_temp = $sensors{'temp2'};
19747			}
19748			else {
19749				$mobo_temp = $sensors{'temp1'};
19750			}
19751		}
19752		## NOTE: not safe to assume $sensors{'temp3'} is the mobo temp, sad to say
19753		# if ($sensors{'temp1'} && $sensors{'temp2'} && $sensors{'temp3'} && $sensors{'temp3'} < $mobo_temp){
19754		#		$mobo_temp = $sensors{'temp3'};
19755		# }
19756	}
19757	# in case with cpu-temp AND temp1 and not temp 2, or temp 2 only, fan type: 0
19758	else {
19759		if ($sensors{'cpu-temp'} && $sensors{'temp1'} &&
19760		 $sensors{'cpu-temp'} > $sensors{'temp1'}){
19761			$mobo_temp = $sensors{'temp1'};
19762		}
19763		elsif ($sensors{'temp2'}){
19764			$mobo_temp = $sensors{'temp2'};
19765		}
19766	}
19767	@fan_main = @{$sensors{'fan-main'}} if $sensors{'fan-main'};
19768	$index_count_fan_main = (@fan_main) ? scalar @fan_main : 0;
19769	@fan_default = @{$sensors{'fan-default'}} if $sensors{'fan-default'};
19770	$index_count_fan_default = (@fan_default) ? scalar @fan_default : 0;
19771	# then set the cpu fan speed
19772	if (!$fan_main[1]){
19773		# note, you cannot test for $fan_default[1] or [2] != ""
19774		# because that creates an array item in gawk just by the test itself
19775		if ($fan_type == 1 && defined $fan_default[1]){
19776			$fan_main[1] = $fan_default[1];
19777			$fan_default[1] = undef;
19778		}
19779		elsif ($fan_type == 2 && defined $fan_default[2]){
19780			$fan_main[1] = $fan_default[2];
19781			$fan_default[2] = undef;
19782		}
19783	}
19784	# clear out any duplicates. Primary fan real trumps fan working always if same speed
19785	for ($i = 1; $i <= $index_count_fan_main; $i++){
19786		if (defined $fan_main[$i] && $fan_main[$i]){
19787			for ($j = 1; $j <= $index_count_fan_default; $j++){
19788				if (defined $fan_default[$j] && $fan_main[$i] == $fan_default[$j]){
19789					$fan_default[$j] = undef;
19790				}
19791			}
19792		}
19793	}
19794	# now see if you can find the fast little mobo fan, > 5000 rpm and put it as mobo
19795	# note that gawk is returning true for some test cases when $fan_default[j] < 5000
19796	# which has to be a gawk bug, unless there is something really weird with arrays
19797	# note: 500 > $fan_default[j] < 1000 is the exact trigger, and if you manually
19798	# assign that value below, the > 5000 test works again, and a print of the value
19799	# shows the proper value, so the corruption might be internal in awk.
19800	# Note: gensub is the culprit I think, assigning type string for range 501-1000 but
19801	# type integer for all others, this triggers true for >
19802	for ($j = 1; $j <= $index_count_fan_default; $j++){
19803		if (defined $fan_default[$j] && $fan_default[$j] > 5000 && !$fan_main[2]){
19804			$fan_main[2] = $fan_default[$j];
19805			$fan_default[$j] = '';
19806			# then add one if required for output
19807			if ($index_count_fan_main < 2){
19808				$index_count_fan_main = 2;
19809			}
19810		}
19811	}
19812	# if they are ALL null, print error message. psFan is not used in output currently
19813	if (!$cpu_temp && !$mobo_temp && !$fan_main[1] && !$fan_main[2] && !$fan_main[1] && !@fan_default){
19814		%sensors = ();
19815	}
19816	else {
19817		my ($ambient_temp,$psu_fan,$psu1_fan,$psu2_fan,$psu_temp,$sodimm_temp,
19818		$v_12,$v_5,$v_3_3,$v_dimm_p1,$v_dimm_p2,$v_soc_p1,$v_soc_p2,$v_vbat);
19819		$psu_temp = $sensors{'psu-temp'} if $sensors{'psu-temp'};
19820		# sodimm fan is fan_main[4]
19821		$sodimm_temp = $sensors{'sodimm-temp'} if $sensors{'sodimm-temp'};
19822		$cpu2_temp = $sensors{'cpu2-temp'} if $sensors{'cpu2-temp'};
19823		$cpu3_temp = $sensors{'cpu3-temp'} if $sensors{'cpu3-temp'};
19824		$cpu4_temp = $sensors{'cpu4-temp'} if $sensors{'cpu4-temp'};
19825		$ambient_temp = $sensors{'ambient-temp'} if $sensors{'ambient-temp'};
19826		$psu_fan = $sensors{'fan-psu'} if $sensors{'fan-psu'};
19827		$psu1_fan = $sensors{'fan-psu-1'} if $sensors{'fan-psu-1'};
19828		$psu2_fan = $sensors{'fan-psu-2'} if $sensors{'fan-psu-2'};
19829		# so far only for ipmi, sensors data is junk for volts
19830		if ($extra > 0 &&
19831		    ($sensors{'volts-12'} || $sensors{'volts-5'} || $sensors{'volts-3.3'} || $sensors{'volts-vbat'})){
19832			$v_12 = $sensors{'volts-12'} if $sensors{'volts-12'};
19833			$v_5 = $sensors{'volts-5'} if $sensors{'volts-5'};
19834			$v_3_3 = $sensors{'volts-3.3'} if  $sensors{'volts-3.3'};
19835			$v_vbat = $sensors{'volts-vbat'} if $sensors{'volts-vbat'};
19836			$v_dimm_p1 = $sensors{'volts-dimm-p1'} if $sensors{'volts-dimm-p1'};
19837			$v_dimm_p2 = $sensors{'volts-dimm-p2'} if $sensors{'volts-dimm-p2'};
19838			$v_soc_p1 = $sensors{'volts-soc-p1'} if $sensors{'volts-soc-p1'};
19839			$v_soc_p2 = $sensors{'volts-soc-p2'} if $sensors{'volts-soc-p2'};
19840		}
19841		%sensors = (
19842		'ambient-temp' => $ambient_temp,
19843		'cpu-temp' => $cpu_temp,
19844		'cpu2-temp' => $cpu2_temp,
19845		'cpu3-temp' => $cpu3_temp,
19846		'cpu4-temp' => $cpu4_temp,
19847		'mobo-temp' => $mobo_temp,
19848		'psu-temp' => $psu_temp,
19849		'temp-unit' => $sensors{'temp-unit'},
19850		'fan-main' => \@fan_main,
19851		'fan-default' => \@fan_default,
19852		'fan-psu' => $psu_fan,
19853		'fan-psu1' => $psu1_fan,
19854		'fan-psu2' => $psu2_fan,
19855		);
19856		if ($psu_temp){
19857			$sensors{'psu-temp'} = $psu_temp;
19858		}
19859		if ($sodimm_temp){
19860			$sensors{'sodimm-temp'} = $sodimm_temp;
19861		}
19862		if ($extra > 0 && ($v_12 || $v_5 || $v_3_3 || $v_vbat)){
19863			$sensors{'volts-12'} = $v_12;
19864			$sensors{'volts-5'} = $v_5;
19865			$sensors{'volts-3.3'} = $v_3_3;
19866			$sensors{'volts-vbat'} = $v_vbat;
19867			$sensors{'volts-dimm-p1'} = $v_dimm_p1;
19868			$sensors{'volts-dimm-p2'} = $v_dimm_p2;
19869			$sensors{'volts-soc-p1'} = $v_soc_p1;
19870			$sensors{'volts-soc-p2'} = $v_soc_p2;
19871		}
19872	}
19873	eval $end if $b_log;
19874	return %sensors;
19875}
19876sub gpu_data {
19877	eval $start if $b_log;
19878	return @gpudata if $loaded{'gpu-data'};
19879	my ($cmd,@data,@data2,$path,@screens,$temp);
19880	my ($j) = (0);
19881	if ($path = main::check_program('nvidia-settings')){
19882		# first get the number of screens. This only work if you are in X
19883		if ($b_display){
19884			@data = main::grabber("$path -q screens 2>/dev/null");
19885			foreach (@data){
19886				if (/(:[0-9]\.[0-9])/){
19887					push(@screens, $1);
19888				}
19889			}
19890		}
19891		# do a guess, this will work for most users, it's better than nothing for out of X
19892		else {
19893			$screens[0] = ':0.0';
19894		}
19895		# now we'll get the gpu temp for each screen discovered. The print out function
19896		# will handle removing screen data for single gpu systems. -t shows only data we want
19897		# GPUCurrentClockFreqs: 520,600
19898		# GPUCurrentFanSpeed: 50 0-100, not rpm, percent I think
19899		# VideoRam: 1048576
19900		# CUDACores: 16
19901		# PCIECurrentLinkWidth: 16
19902		# PCIECurrentLinkSpeed: 5000
19903		# RefreshRate: 60.02 Hz [oer screen]
19904		# ViewPortOut=1280x1024+0+0}, DPY-1: nvidia-auto-select @1280x1024 +1280+0 {ViewPortIn=1280x1024,
19905		# ViewPortOut=1280x1024+0+0}
19906		# ThermalSensorReading: 50
19907		# PCIID: 4318,2661 - the pci stuff doesn't appear to work
19908		# PCIBus: 2
19909		# PCIDevice: 0
19910		# Irq: 30
19911		foreach my $screen (@screens){
19912			my $screen2 = $screen;
19913			$screen2 =~ s/\.[0-9]$//;
19914			$cmd = '-q GPUCoreTemp -q VideoRam -q GPUCurrentClockFreqs -q PCIECurrentLinkWidth ';
19915			$cmd .= '-q Irq -q PCIBus -q PCIDevice -q GPUCurrentFanSpeed';
19916			$cmd = "$path -c $screen2 $cmd 2>/dev/null";
19917			@data = main::grabber($cmd);
19918			main::log_data('cmd',$cmd) if $b_log;
19919			push(@data,@data2);
19920			$j = scalar @gpudata;
19921			foreach my $item (@data){
19922				if ($item =~ /^\s*Attribute\s\'([^']+)\'\s.*:\s*([\S]+)\.$/){
19923					my $attribute = $1;
19924					my $value = $2;
19925					$gpudata[$j]->{'type'} = 'nvidia';
19926					$gpudata[$j]->{'speed-unit'} = '%';
19927					$gpudata[$j]->{'screen'} = $screen;
19928					if (!$gpudata[$j]->{'temp'} && $attribute eq 'GPUCoreTemp'){
19929						$gpudata[$j]->{'temp'} = $value;
19930					}
19931					elsif (!$gpudata[$j]->{'ram'} && $attribute eq 'VideoRam'){
19932						$gpudata[$j]->{'ram'} = $value;
19933					}
19934					elsif (!$gpudata[$j]->{'clock'} && $attribute eq 'GPUCurrentClockFreqs'){
19935						$gpudata[$j]->{'clock'} = $value;
19936					}
19937					elsif (!$gpudata[$j]->{'bus'} && $attribute eq 'PCIBus'){
19938						$gpudata[$j]->{'bus'} = $value;
19939					}
19940					elsif (!$gpudata[$j]->{'bus-id'} && $attribute eq 'PCIDevice'){
19941						$gpudata[$j]->{'bus-id'} = $value;
19942					}
19943					elsif (!$gpudata[$j]->{'fan-speed'} && $attribute eq 'GPUCurrentFanSpeed'){
19944						$gpudata[$j]->{'fan-speed'} = $value;
19945					}
19946				}
19947			}
19948		}
19949	}
19950	if ($path = main::check_program('aticonfig')){
19951		# aticonfig --adapter=0 --od-gettemperature
19952		@data = main::grabber("$path --adapter=all --od-gettemperature 2>/dev/null");
19953		foreach (@data){
19954			if (/Sensor [^0-9]*([0-9\.]+) /){
19955				$j = scalar @gpudata;
19956				my $value = $1;
19957				$gpudata[$j]->{'type'} = 'amd';
19958				$gpudata[$j]->{'temp'} = $value;
19959			}
19960		}
19961	}
19962	if ($sensors_raw{'gpu'}){
19963		# my ($b_found,$holder) = (0,'');
19964		foreach my $adapter (keys %{$sensors_raw{'gpu'}}){
19965			$j = scalar @gpudata;
19966			$gpudata[$j]->{'type'} = $adapter;
19967			$gpudata[$j]->{'type'} =~ s/^(amdgpu|intel|nouveau|radeon)-.*/$1/;
19968			# print "ad: $adapter\n";
19969			foreach (@{$sensors_raw{'gpu'}->{$adapter}}){
19970				# print "val: $_\n";
19971				if (/^[^:]*mem[^:]*:([0-9\.]+).*\b(C|F)\b/i){
19972					$gpudata[$j]->{'temp-mem'} = $1;
19973					$gpudata[$j]->{'unit'} = $2;
19974					 # print "temp: $_\n";
19975				}
19976				elsif (/^[^:]+:([0-9\.]+).*\b(C|F)\b/i){
19977					$gpudata[$j]->{'temp'} = $1;
19978					$gpudata[$j]->{'unit'} = $2;
19979					 # print "temp: $_\n";
19980				}
19981				# speeds can be in percents or rpms, so need the 'fan' in regex
19982				elsif (/^.*fan.*:([0-9\.]+).*(RPM)?/i){
19983					$gpudata[$j]->{'fan-speed'} = $1;
19984					# NOTE: we test for nvidia %, everything else stays with nothing
19985					$gpudata[$j]->{'speed-unit'} = '';
19986				}
19987				elsif (/^[^:]+:([0-9\.]+)\s+W\s/i){
19988					$gpudata[$j]->{'watts'} = $1;
19989				}
19990				elsif (/^[^:]+:([0-9\.]+)\s+mV\s/i){
19991					$gpudata[$j]->{'mvolts'} = $1;
19992				}
19993			}
19994		}
19995	}
19996	main::log_data('dump','sensors output: video: @gpudata',\@gpudata);
19997	# we'll probably use this data elsewhere so make it a one time call
19998	$loaded{'gpu-data'} = 1;
19999	print 'gpudata: ', Data::Dumper::Dumper \@gpudata if $dbg[18];
20000	eval $end if $b_log;
20001	return @gpudata;
20002}
20003}
20004
20005## SlotItem
20006{
20007package SlotItem;
20008sub get {
20009	eval $start if $b_log;
20010	my (@rows,$key1,$val1);
20011	my $num = 0;
20012	if ($fake{'dmidecode'} || ($alerts{'dmidecode'}->{'action'} eq 'use' &&
20013	 (!$b_arm || $use{'slot-tool'}))){
20014		@rows = slot_output();
20015	}
20016	elsif ($b_arm && !$use{'slot-tool'}){
20017		$key1 = 'ARM';
20018		$val1 = main::row_defaults('arm-pci','');
20019		@rows = ({main::key($num++,0,1,$key1) => $val1,});
20020	}
20021	elsif ($alerts{'dmidecode'}->{'action'} ne 'use'){
20022		$key1 = $alerts{'dmidecode'}->{'action'};
20023		$val1 = $alerts{'dmidecode'}->{'message'};
20024		$key1 = ucfirst($key1);
20025		@rows = ({main::key($num++,0,1,$key1) => $val1,});
20026	}
20027	eval $end if $b_log;
20028	return @rows;
20029}
20030sub slot_output {
20031	eval $start if $b_log;
20032	my (@rows);
20033	my $num = 0;
20034	foreach my $entry (@dmi){
20035		$num = 1;
20036		if ($entry->[0] == 9){
20037			my ($designation,$id,$length,$type,$usage) = ('','','','','');
20038			# skip first two row, we don't need that data
20039			my $j = scalar @rows;
20040			foreach my $item (@$entry[2 .. $#$entry]){
20041				if ($item !~ /^~/){ # skip the indented rows
20042					my @value = split(/:\s+/, $item);
20043					if ($value[0] eq 'Type'){
20044						$type = $value[1];
20045					}
20046					if ($value[0] eq 'Designation'){
20047						$designation = $value[1];
20048					}
20049					if ($value[0] eq 'Current Usage'){
20050						$usage = $value[1];
20051
20052					}
20053					if ($value[0] eq 'ID'){
20054						$id = $value[1];
20055					}
20056					if ($extra > 1 && $value[0] eq 'Length'){
20057						$length = $value[1];
20058					}
20059				}
20060			}
20061			if ($type){
20062				$id = 'N/A' if ($id eq '');
20063				if ($type eq 'Other' && $designation){
20064					$type = $designation;
20065				}
20066				elsif ($type && $designation){
20067					$type = "$type $designation";
20068				}
20069				push(@rows, {
20070				main::key($num++,1,1,'Slot') => $id,
20071				main::key($num++,0,2,'type') => $type,
20072				main::key($num++,0,2,'status') => $usage,
20073				},);
20074				if ($extra > 1){
20075					$rows[$j]->{main::key($num++,0,2,'length')} = $length;
20076				}
20077			}
20078		}
20079	}
20080	if (!@rows){
20081		my $key = 'Message';
20082		push(@rows, {
20083		main::key($num++,0,1,$key) => main::row_defaults('pci-slot-data',''),
20084		},);
20085	}
20086	eval $end if $b_log;
20087	return @rows;
20088}
20089}
20090
20091## SwapItem
20092{
20093package SwapItem;
20094
20095sub get {
20096	eval $start if $b_log;
20097	my (@rows);
20098	my $num = 0;
20099	@rows = create_output();
20100	if (!@rows){
20101		push(@rows,
20102		{main::key($num++,0,1,'Alert') => main::row_defaults('swap-data')},
20103		);
20104	}
20105	eval $end if $b_log;
20106	return @rows;
20107}
20108sub create_output {
20109	eval $start if $b_log;
20110	my $num = 0;
20111	my $j = 0;
20112	my (@rows,$dev,$percent,$raw_size,$size,$used);
20113	PartitionData::set() if !$bsd_type && !$loaded{'partition-data'};
20114	DiskDataBSD::set() if $bsd_type && !$loaded{'disk-data-bsd'};
20115	main::set_mapper() if !$loaded{'mapper'};
20116	PartitionItem::swap_data() if !$loaded{'set-swap'};
20117	foreach my $row (@swaps){
20118		$num = 1;
20119		$size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A';
20120		$used = main::get_size($row->{'used'},'string','N/A'); # used can be 0
20121		$percent = (defined $row->{'percent-used'}) ? ' (' . $row->{'percent-used'} . '%)' : '';
20122		$dev = ($row->{'swap-type'} eq 'file') ? 'file' : 'dev';
20123		$row->{'swap-type'} = ($row->{'swap-type'}) ? $row->{'swap-type'} : 'N/A';
20124		if ($b_admin && !$bsd_type && $j == 0){
20125			$j = scalar @rows;
20126			if (defined $row->{'swappiness'} || defined $row->{'cache-pressure'}){
20127				$rows[$j]->{main::key($num++,1,1,'Kernel')} = '';
20128				if (defined $row->{'swappiness'}){
20129					$rows[$j]->{main::key($num++,0,2,'swappiness')} = $row->{'swappiness'};
20130				}
20131				if (defined $row->{'cache-pressure'}){
20132					$rows[$j]->{main::key($num++,0,2,'cache-pressure')} = $row->{'cache-pressure'};
20133				}
20134			}
20135			else {
20136				$rows[$j]->{main::key($num++,0,1,'Message')} = main::row_defaults('swap-admin');
20137			}
20138		}
20139		$j = scalar @rows;
20140		push(@rows, {
20141		main::key($num++,1,1,'ID') => $row->{'id'},
20142		main::key($num++,0,2,'type') => $row->{'swap-type'},
20143		});
20144		# not used for swap as far as I know
20145		if ($b_admin && $row->{'raw-size'}){
20146			# It's an error! permissions or missing tool
20147			$raw_size = main::get_size($row->{'raw-size'},'string');
20148			$rows[$j]->{main::key($num++,0,2,'raw-size')} = $raw_size;
20149		}
20150		# not used for swap as far as I know
20151		if ($b_admin && $row->{'raw-available'} && $size ne 'N/A'){
20152			$size .=  ' (' . $row->{'raw-available'} . '%)';
20153		}
20154		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
20155		$rows[$j]->{main::key($num++,0,2,'used')} = $used . $percent;
20156		# not used for swap as far as I know
20157		if ($b_admin && $row->{'block-size'}){
20158			$rows[$j]->{main::key($num++,0,2,'block-size')} = $row->{'block-size'} . ' B';;
20159			#$rows[$j]->{main::key($num++,0,2,'physical')} = $row->{'block-size'} . ' B';
20160			#$rows[$j]->{main::key($num++,0,2,'logical')} = $row->{'block-logical'} . ' B';
20161		}
20162		if ($extra > 1 && defined $row->{'priority'}){
20163			$rows[$j]->{main::key($num++,0,2,'priority')} = $row->{'priority'};
20164		}
20165		$row->{'mount'} =~ s|/home/[^/]+/(.*)|/home/$filter_string/$1| if $row->{'mount'} && $use{'filter'};
20166		$rows[$j]->{main::key($num++,1,2,$dev)} = ($row->{'mount'}) ? $row->{'mount'} : 'N/A';
20167		if ($b_admin && $row->{'maj-min'}){
20168			$rows[$j]->{main::key($num++,0,3,'maj-min')} = $row->{'maj-min'};
20169		}
20170		if ($extra > 0 && $row->{'dev-mapped'}){
20171			$rows[$j]->{main::key($num++,0,3,'mapped')} = $row->{'dev-mapped'};
20172		}
20173		if ($show{'label'} && ($row->{'label'} || $row->{'swap-type'} eq 'partition')){
20174			if ($use{'filter-label'}){
20175				$row->{'label'} = main::apply_partition_filter('part', $row->{'label'}, '');
20176			}
20177			$row->{'label'} ||= 'N/A';
20178			$rows[$j]->{main::key($num++,0,2,'label')} = $row->{'label'};
20179		}
20180		if ($show{'uuid'} && ($row->{'uuid'} || $row->{'swap-type'} eq 'partition')){
20181			if ($use{'filter-uuid'}){
20182				$row->{'uuid'} = main::apply_partition_filter('part', $row->{'uuid'}, '');
20183			}
20184			$row->{'uuid'} ||= 'N/A';
20185			$rows[$j]->{main::key($num++,0,2,'uuid')} = $row->{'uuid'};
20186		}
20187	}
20188	eval $end if $b_log;
20189	return @rows;
20190}
20191
20192}
20193
20194## UnmountedItem
20195{
20196package UnmountedItem;
20197
20198sub get {
20199	eval $start if $b_log;
20200	my (@data,@rows,$key1,$val1);
20201	my $num = 0;
20202	if ($bsd_type){
20203		DiskDataBSD::set() if !$loaded{'disk-data-bsd'};
20204		if (%disks_bsd && ($alerts{'disklabel'}->{'action'} eq 'use' ||
20205		 $alerts{'gpart'}->{'action'} eq 'use')){
20206			@data = bsd_data();
20207			if (!@data){
20208				$key1 = 'Message';
20209				$val1 = main::row_defaults('unmounted-data');
20210			}
20211			else {
20212				@rows = create_output(\@data);
20213			}
20214		}
20215		else {
20216			if ($alerts{'disklabel'}->{'action'} eq 'permissions'){
20217				$key1 = 'Message';
20218				$val1 = $alerts{'disklabel'}->{'message'};
20219			}
20220			else {
20221				$key1 = 'Message';
20222				$val1 = main::row_defaults('unmounted-data-bsd',$uname[0]);
20223			}
20224		}
20225	}
20226 	else {
20227		if ($system_files{'proc-partitions'}){
20228			@data = proc_data();
20229			if (!@data){
20230				$key1 = 'Message';
20231				$val1 = main::row_defaults('unmounted-data');
20232			}
20233			else {
20234				@rows = create_output(\@data);
20235			}
20236		}
20237		else {
20238			$key1 = 'Message';
20239			$val1 = main::row_defaults('unmounted-file');
20240		}
20241 	}
20242 	if (!@rows && $key1){
20243		@rows = ({main::key($num++,0,1,$key1) => $val1,});
20244 	}
20245	eval $end if $b_log;
20246	return @rows;
20247}
20248sub create_output {
20249	eval $start if $b_log;
20250	my ($unmounted) = @_;
20251	my (@rows,$fs);
20252	my ($j,$num) = (0,0);
20253	@$unmounted = sort { $a->{'dev-base'} cmp $b->{'dev-base'} } @$unmounted;
20254	my $fs_skip = PartitionItem::fs_excludes('label-uuid');
20255	foreach my $row (@$unmounted){
20256		$num = 1;
20257		my $size = ($row->{'size'}) ? main::get_size($row->{'size'},'string') : 'N/A';
20258		if ($row->{'fs'}){
20259			$fs = lc($row->{'fs'});
20260		}
20261		else {
20262			if ($bsd_type){
20263				$fs = 'N/A';
20264			}
20265			elsif (main::check_program('file')){
20266				$fs = ($b_root) ? 'N/A' : main::row_defaults('root-required');
20267			}
20268			else {
20269				$fs = main::row_defaults('tool-missing-basic','file');
20270			}
20271		}
20272		$j = scalar @rows;
20273		push(@rows, {
20274		main::key($num++,1,1,'ID') => "/dev/$row->{'dev-base'}",
20275		});
20276		if ($b_admin && $row->{'maj-min'}){
20277			$rows[$j]->{main::key($num++,0,2,'maj-min')} = $row->{'maj-min'};
20278		}
20279		if ($extra > 0 && $row->{'dev-mapped'}){
20280			$rows[$j]->{main::key($num++,0,2,'mapped')} = $row->{'dev-mapped'};
20281		}
20282		$row->{'label'} ||= 'N/A';
20283		$row->{'uuid'} ||= 'N/A';
20284		$rows[$j]->{main::key($num++,0,2,'size')} = $size;
20285		$rows[$j]->{main::key($num++,0,2,'fs')} = $fs;
20286		# don't show for fs known to not have label/uuid
20287		if (($show{'label'} || $show{'uuid'}) && $fs !~ /^$fs_skip$/){
20288			if ($show{'label'}){
20289				if ($use{'filter-label'}){
20290					$row->{'label'} = main::apply_partition_filter('part', $row->{'label'}, '');
20291				}
20292				$row->{'label'} ||= 'N/A';
20293				$rows[$j]->{main::key($num++,0,2,'label')} = $row->{'label'};
20294			}
20295			if ($show{'uuid'}){
20296				if ($use{'filter-uuid'}){
20297					$row->{'uuid'} = main::apply_partition_filter('part', $row->{'uuid'}, '');
20298				}
20299				$row->{'uuid'} ||= 'N/A';
20300				$rows[$j]->{main::key($num++,0,2,'uuid')} = $row->{'uuid'};
20301			}
20302		}
20303	}
20304	eval $end if $b_log;
20305	return @rows;
20306}
20307sub proc_data {
20308	eval $start if $b_log;
20309	my ($dev_mapped,$fs,$label,$maj_min,$size,$uuid,%part,@unmounted);
20310	# last filters to make sure these are dumped
20311	my @filters = ('scd[0-9]+','sr[0-9]+','cdrom[0-9]*','cdrw[0-9]*',
20312	'dvd[0-9]*','dvdrw[0-9]*','fd[0-9]','ram[0-9]*');
20313	my $num = 0;
20314	# set labels, uuid, gpart
20315	PartitionItem::set_partitions() if !$loaded{'set-partitions'};
20316	RaidItem::raid_data() if !$loaded{'raid'};
20317	my @mounted = get_mounted();
20318	# print join("\n",(@filters,@mounted)),"\n";
20319	foreach my $row (@proc_partitions){
20320		($dev_mapped,$fs,$label,$maj_min,$uuid,$size) = ('','','','','','');
20321		# note that size 1 means it is a logical extended partition container
20322		# lvm might have dm-1 type syntax
20323		# need to exclude loop type file systems, squashfs for example
20324		# NOTE: nvme needs special treatment because the main device is: nvme0n1
20325		# note: $working[2] != 1 is wrong, it's not related
20326		# note: for zfs using /dev/sda no partitions, previous rule would have removed
20327		# the unmounted report because sdb was found in sdb1, but match of eg sdb1 and sdb12
20328		# makes this a problem, so using zfs_member test instead to filter out zfs members.
20329		# For zfs using entire disk, ie, sda, in that case, all partitions sda1 sda9 (8BiB)
20330		# belong to zfs, and aren't unmmounted, so if sda and partition sda9,
20331		# remove from list. this only works for sdxx drives, but is better than no fix
20332		# This logic may also end up working for btrfs partitions, and maybe hammer?
20333		# In arm/android seen /dev/block/mmcblk0p12
20334		# print "mount: $row->[-1]\n";
20335		if ($row->[-1] !~ /^(nvme[0-9]+n|mmcblk|mtdblk|mtdblock)[0-9]+$/ &&
20336		    $row->[-1] =~ /[a-z][0-9]+$|dm-[0-9]+$/ &&
20337		    $row->[-1] !~ /\bloop/ &&
20338		    !(grep {$row->[-1] =~ /$_$/} (@filters,@mounted)) &&
20339		    !(grep {$_ =~ /(block\/)?$row->[-1]$/} @mounted) &&
20340		    !(grep {$_ =~ /^sd[a-z]+$/ && $row->[-1] =~ /^$_[0-9]+/} @mounted)){
20341			$dev_mapped = $dmmapper{$row->[-1]} if $dmmapper{$row->[-1]};
20342			if (@lsblk){
20343				my $id = ($dev_mapped) ? $dev_mapped: $row->[-1];
20344				%part = LsblkData::get($id);
20345				if (%part){
20346					$fs = $part{'fs'};
20347					$label = $part{'label'};
20348					$maj_min = $part{'maj-min'};
20349					$uuid = $part{'uuid'};
20350					$size = $part{'size'} if $part{'size'} && !$row->[2];
20351				}
20352			}
20353			$size ||= $row->[2];
20354			$fs = unmounted_filesystem($row->[-1]) if !$fs;
20355			# seen: (zfs|lvm2|linux_raid)_member; crypto_luks
20356			# note: lvm, raid members are never mounted. luks member is never mounted.
20357			next if $fs && $fs =~ /(bcache|crypto|luks|_member)$/i;
20358			# these components of lvm raid will show as partitions byt are reserved private lvm member
20359			# See man lvm for all current reserved private volume names
20360			next if $dev_mapped && $dev_mapped =~ /_([ctv]data|corig|[mr]image|mlog|[crt]meta|pmspare|pvmove|vorigin)(_[0-9]+)?$/;
20361			if (!$bsd_type){
20362				$label = PartitionItem::get_label("/dev/$row->[-1]") if !$label;
20363				$uuid = PartitionItem::get_uuid("/dev/$row->[-1]") if !$uuid;
20364			}
20365			else {
20366				my @temp = GpartData::get($row->[-1]);
20367				$label = $temp[1] if $temp[1];
20368				$uuid = $temp[2] if $temp[2];
20369			}
20370			$maj_min = "$row->[0]:$row->[1]" if !$maj_min;
20371			push(@unmounted, {
20372			'dev-base' => $row->[-1],
20373			'dev-mapped' => $dev_mapped,
20374			'fs' => $fs,
20375			'label' => $label,
20376			'maj-min' => $maj_min,
20377			'size' => $size,
20378			'uuid' => $uuid,
20379			});
20380		}
20381	}
20382	print Data::Dumper::Dumper \@unmounted if $dbg[35];
20383	main::log_data('dump','@unmounted',\@unmounted) if $b_log;
20384	eval $end if $b_log;
20385	return @unmounted;
20386}
20387sub bsd_data {
20388	eval $start if $b_log;
20389	my ($fs,$label,$size,$uuid,%part,@unmounted);
20390	PartitionItem::set_partitions() if !$loaded{'set-partitions'};
20391	RaidItem::raid_data() if !$loaded{'raid'};
20392	my @mounted = get_mounted();
20393	foreach my $id (sort keys %disks_bsd){
20394		next if !$disks_bsd{$id}->{'partitions'};
20395		foreach my $part (sort keys %{$disks_bsd{$id}->{'partitions'}}){
20396			if (!(grep {$_ =~ /$part$/} @mounted)){
20397				$fs = $disks_bsd{$id}->{'partitions'}{$part}{'fs'};
20398				next if $fs && $fs =~ /(raid|_member)$/i;
20399				$label = $disks_bsd{$id}->{'partitions'}{$part}{'label'};
20400				$size = $disks_bsd{$id}->{'partitions'}{$part}{'size'};
20401				$uuid = $disks_bsd{$id}->{'partitions'}{$part}{'uuid'};
20402				# $fs = unmounted_filesystem($part) if !$fs;
20403				push(@unmounted, {
20404				'dev-base' => $part,
20405				'dev-mapped' => '',
20406				'fs' => $fs,
20407				'label' => $label,
20408				'maj-min' => '',
20409				'size' => $size,
20410				'uuid' => $uuid,
20411				});
20412			}
20413		}
20414	}
20415	print Data::Dumper::Dumper \@unmounted if $dbg[35];
20416	main::log_data('dump','@unmounted',\@unmounted) if $b_log;
20417	eval $end if $b_log;
20418	return @unmounted;
20419}
20420sub get_mounted {
20421	eval $start if $b_log;
20422	my (@arrays,@mounted);
20423	foreach my $row (@partitions){
20424		push(@mounted, $row->{'dev-base'}) if $row->{'dev-base'};
20425	}
20426	# print Data::Dumper::Dumper \@zfs_raid;
20427	foreach my $row ((@btrfs_raid,@lvm_raid,@md_raid,@soft_raid,@zfs_raid)){
20428		# we want to not show md0 etc in unmounted report
20429		push(@mounted, $row->{'id'}) if $row->{'id'};
20430		# print Data::Dumper::Dumper $row;
20431		# row->arrays->components: zfs; row->components: lvm,mdraid,softraid
20432		if ($row->{'arrays'} && ref $row->{'arrays'} eq 'ARRAY'){
20433			push(@arrays,@{$row->{'arrays'}});
20434		}
20435		elsif ($row->{'components'} && ref $row->{'components'} eq 'ARRAY'){
20436			push(@arrays,$row);
20437		}
20438		@arrays = grep {defined $_} @arrays;
20439		# print Data::Dumper::Dumper \@arrays;
20440		foreach my $item (@arrays){
20441			# print Data::Dumper::Dumper $item;
20442			my @components = (ref $item->{'components'} eq 'ARRAY') ? @{$item->{'components'}} : ();
20443			foreach my $component (@components){
20444				# md has ~, not zfs,lvm,softraid
20445				my $temp = (split('~', $component->[0]))[0];
20446				push(@mounted, $temp);
20447			}
20448		}
20449	}
20450	eval $end if $b_log;
20451	return @mounted;
20452}
20453# bsds do not seem to return any useful data so only for linux
20454sub unmounted_filesystem {
20455	eval $start if $b_log;
20456	my ($item) = @_;
20457	my ($data,%part);
20458	my ($file,$fs,$path) = ('','','');
20459	if ($path = main::check_program('file')){
20460		$file = $path;
20461	}
20462	# order matters in this test!
20463	my @filesystems = ('ext2','ext3','ext4','ext5','ext','ntfs',
20464	'fat32','fat16','FAT\s\(.*\)','vfat','fatx','tfat','exfat','swap','btrfs',
20465	'ffs','hammer','hfs\+','hfs\splus','hfs\sextended\sversion\s[1-9]','hfsj',
20466	'hfs','apfs','jfs','nss','reiserfs','reiser4','ufs2','ufs','xfs','zfs');
20467	if ($file){
20468		# this will fail if regular user and no sudo present, but that's fine, it will just return null
20469		# note the hack that simply slices out the first line if > 1 items found in string
20470		# also, if grub/lilo is on partition boot sector, no file system data is available
20471		$data = (main::grabber("$sudoas$file -s /dev/$item 2>/dev/null"))[0];
20472		if ($data){
20473			foreach (@filesystems){
20474				if ($data =~ /($_)[\s,]/i){
20475					$fs = $1;
20476					$fs = main::trimmer($fs);
20477					last;
20478				}
20479			}
20480		}
20481	}
20482	main::log_data('data',"fs: $fs") if $b_log;
20483	eval $end if $b_log;
20484	return $fs;
20485}
20486}
20487
20488## UsbItem
20489{
20490package UsbItem;
20491sub get {
20492	eval $start if $b_log;
20493	my (@rows,$key1,$val1);
20494	my $num = 0;
20495	if (!$usb{'main'} && $alerts{'lsusb'}->{'action'} ne 'use' &&
20496	 $alerts{'usbdevs'}->{'action'} ne 'use' &&
20497	 $alerts{'usbconfig'}->{'action'} ne 'use'){
20498		if ($os eq 'linux'){
20499			$key1 = $alerts{'lsusb'}->{'action'};
20500			$val1 = $alerts{'lsusb'}->{'message'};
20501		}
20502		else {
20503			# note: usbdevs only has 'missing', usbconfig has missing/permissions
20504			# both have platform, but irrelevant since testing for linux here
20505			if ($alerts{'usbdevs'}->{'action'} eq 'missing' &&
20506			 $alerts{'usbconfig'}->{'action'} eq 'missing'){
20507				$key1 = $alerts{'usbdevs'}->{'action'};
20508				$val1 = main::row_defaults('tools-missing-bsd','usbdevs/usbconfig');
20509			}
20510			elsif ($alerts{'usbconfig'}->{'action'} eq 'permissions'){
20511				$key1 = $alerts{'usbconfig'}->{'action'};
20512				$val1 = $alerts{'usbconfig'}->{'message'};
20513			}
20514# 			elsif ($alerts{'lsusb'}->{'action'} eq 'missing'){
20515# 				$key1 = $alerts{'lsusb'}->{'action'};
20516# 				$val1 = $alerts{'lsusb'}->{'message'};
20517# 			}
20518		}
20519		$key1 = ucfirst($key1);
20520		@rows = ({main::key($num++,0,1,$key1) => $val1,});
20521	}
20522	else {
20523		@rows = usb_output();
20524		if (!@rows){
20525			my $key = 'Message';
20526			push(@rows, {
20527			main::key($num++,0,1,$key) => main::row_defaults('usb-data',''),
20528			},);
20529		}
20530	}
20531	eval $end if $b_log;
20532	return @rows;
20533}
20534sub usb_output {
20535	eval $start if $b_log;
20536	return if !$usb{'main'};
20537	my (@rows);
20538	my ($b_hub,$bus_id,$chip_id,$driver,$ind_sc,$path_id,$ports,$product,$serial,$speed,$type);
20539	my $num = 0;
20540	my $j = 0;
20541	# note: the data has been presorted in UsbData:
20542	# bus alpah id, so we don't need to worry about the order
20543	foreach my $id (@{$usb{'main'}}){
20544		$j = scalar @rows;
20545		($b_hub,$ind_sc,$num) = (0,3,1);
20546		($driver,$path_id,$ports,$product,
20547		$serial,$speed,$type) = ('','','','','','','');
20548		$speed  = (main::is_numeric($id->[8])) ? sprintf("%1.1f",$id->[8]) : $id->[8] if $id->[8];
20549		$product = main::cleaner($id->[13]) if $id->[13];
20550		$serial = main::apply_filter($id->[16]) if $id->[16];
20551		$product ||= 'N/A';
20552		$speed ||= 'N/A';
20553		$path_id = $id->[2] if $id->[2];
20554		$bus_id = "$path_id:$id->[1]";
20555		# it's a hub
20556		if ($id->[4] eq '09'){
20557			$ports = $id->[10] if $id->[10];
20558			$ports ||= 'N/A';
20559			# print "pt0:$protocol\n";
20560			push(@rows, {
20561			main::key($num++,1,1,'Hub') => $bus_id,
20562			main::key($num++,0,2,'info') => $product,
20563			main::key($num++,0,2,'ports') => $ports,
20564			main::key($num++,0,2,'rev') => $speed,
20565			},);
20566			$b_hub = 1;
20567			$ind_sc =2;
20568		}
20569		# it's a device
20570		else {
20571			$type = $id->[14] if $id->[14];
20572			$driver = $id->[15] if $id->[15];
20573			$type ||= 'N/A';
20574			$driver ||= 'N/A';
20575			# print "pt3:$class:$product\n";
20576			$rows[$j]->{main::key($num++,1,2,'Device')} = $bus_id;
20577			$rows[$j]->{main::key($num++,0,3,'info')} = $product;
20578			$rows[$j]->{main::key($num++,0,3,'type')} = $type;
20579			if ($extra > 0){
20580				$rows[$j]->{main::key($num++,0,3,'driver')} = $driver;
20581			}
20582			if ($extra > 2 && $id->[9]){
20583				$rows[$j]->{main::key($num++,0,3,'interfaces')} = $id->[9];
20584			}
20585			$rows[$j]->{main::key($num++,0,3,'rev')} = $speed;
20586		}
20587		# for either hub or device
20588		if ($extra > 1 && main::is_numeric($id->[17])){
20589			my $speed = $id->[17];
20590			if ($speed >= 1000){$speed = ($id->[17]/1000) . " Gb/s"}
20591			else {$speed = $id->[17] . " Mb/s"}
20592			$rows[$j]->{main::key($num++,0,$ind_sc,'speed')} = $speed;
20593		}
20594		if ($extra > 2 && $id->[19] && $id->[19] ne '0mA'){
20595			$rows[$j]->{main::key($num++,0,$ind_sc,'power')} = $id->[19];
20596		}
20597		if ($extra > 1){
20598			$chip_id = $id->[7];
20599			$chip_id ||= 'N/A';
20600			$rows[$j]->{main::key($num++,0,$ind_sc,'chip-ID')} = $chip_id;
20601		}
20602		if ($extra > 2 && defined $id->[5] && $id->[5] ne ''){
20603			my $id = sprintf("%02s",$id->[4]) . sprintf("%02s", $id->[5]);
20604			$rows[$j]->{main::key($num++,0,$ind_sc,'class-ID')} = $id;
20605		}
20606		if (!$b_hub && $extra > 2){
20607			if ($serial){
20608				$rows[$j]->{main::key($num++,0,$ind_sc,'serial')} = main::apply_filter($serial);
20609			}
20610		}
20611	}
20612	# print Data::Dumper::Dumper \@rows;
20613	eval $end if $b_log;
20614	return @rows;
20615}
20616}
20617
20618## WeatherItem
20619# add metric / imperial (us) switch
20620{
20621package WeatherItem;
20622sub get {
20623	eval $start if $b_log;
20624	my (@rows);
20625	my $num = 0;
20626	@rows = weather_output();
20627	eval $end if $b_log;
20628	return @rows;
20629}
20630sub weather_output {
20631	eval $start if $b_log;
20632	my ($j,$num) = (0,0);
20633	my (@location,@rows,$value,%weather,);
20634	my ($conditions) = ('NA');
20635	if ($show{'weather-location'}){
20636		my $location_string;
20637		$location_string = $show{'weather-location'};
20638		$location_string =~ s/\+/ /g;
20639		if ($location_string =~ /,/){
20640			my @temp = split(',', $location_string);
20641			my $sep = '';
20642			my $string = '';
20643			foreach (@temp){
20644				$_ = ucfirst($_);
20645				$string .= $sep . $_;
20646				$sep = ', ';
20647			}
20648			$location_string = $string;
20649		}
20650		$location_string = main::apply_filter($location_string);
20651		@location = ($show{'weather-location'},$location_string,'');
20652	}
20653	else {
20654		@location = get_location();
20655		if (!$location[0]){
20656			return @rows = ({
20657			main::key($num++,0,1,'Message') => main::row_defaults('weather-null','current location'),
20658			});
20659		}
20660	}
20661	%weather = get_weather(\@location);
20662	if ($weather{'error'}){
20663		return @rows = ({
20664		main::key($num++,0,1,'Message') => main::row_defaults('weather-error',$weather{'error'}),
20665		});
20666	}
20667	if (!$weather{'weather'}){
20668		return @rows = ({
20669		main::key($num++,0,1,'Message') => main::row_defaults('weather-null','weather data'),
20670		});
20671	}
20672	$conditions = "$weather{'weather'}";
20673	my $temp = process_unit($weather{'temp'},$weather{'temp-c'},'C',$weather{'temp-f'},'F');
20674	$j = scalar @rows;
20675	push(@rows, {
20676	main::key($num++,1,1,'Report') => '',
20677	main::key($num++,0,2,'temperature') => $temp,
20678	main::key($num++,0,2,'conditions') => $conditions,
20679	},);
20680	if ($extra > 0){
20681		my $pressure = process_unit($weather{'pressure'},$weather{'pressure-mb'},'mb',$weather{'pressure-in'},'in');
20682		my $wind = process_wind($weather{'wind'},$weather{'wind-direction'},$weather{'wind-mph'},$weather{'wind-ms'},
20683		$weather{'wind-gust-mph'},$weather{'wind-gust-ms'});
20684		$rows[$j]->{main::key($num++,0,2,'wind')} = $wind;
20685		if ($extra > 1){
20686			if (defined $weather{'cloud-cover'}){
20687				$rows[$j]->{main::key($num++,0,2,'cloud cover')} = $weather{'cloud-cover'} . '%';
20688			}
20689			if ($weather{'precip-1h-mm'} && defined $weather{'precip-1h-in'}){
20690				$value = process_unit('',$weather{'precip-1h-mm'},'mm',$weather{'precip-1h-in'},'in');
20691				$rows[$j]->{main::key($num++,0,2,'precipitation')} = $value;
20692			}
20693			if ($weather{'rain-1h-mm'} && defined $weather{'rain-1h-in'}){
20694				$value = process_unit('',$weather{'rain-1h-mm'},'mm',$weather{'rain-1h-in'},'in');
20695				$rows[$j]->{main::key($num++,0,2,'rain')} = $value;
20696			}
20697			if ($weather{'snow-1h-mm'} && defined $weather{'snow-1h-in'}){
20698				$value = process_unit('',$weather{'snow-1h-mm'},'mm',$weather{'snow-1h-in'},'in');
20699				$rows[$j]->{main::key($num++,0,2,'snow')} = $value;
20700			}
20701		}
20702		$rows[$j]->{main::key($num++,0,2,'humidity')} = $weather{'humidity'} . '%';
20703		if ($extra > 1){
20704			if ($weather{'dewpoint'} || (defined $weather{'dewpoint-c'} && defined $weather{'dewpoint-f'})){
20705				$value = process_unit($weather{'dewpoint'},$weather{'dewpoint-c'},'C',$weather{'dewpoint-f'},'F');
20706				$rows[$j]->{main::key($num++,0,2,'dew point')} = $value;
20707			}
20708		}
20709		$rows[$j]->{main::key($num++,0,2,'pressure')} = $pressure;
20710	}
20711	if ($extra > 1){
20712		if ($weather{'heat-index'} || (defined $weather{'heat-index-c'} && defined $weather{'heat-index-f'})){
20713			$value = process_unit($weather{'heat-index'},$weather{'heat-index-c'},'C',$weather{'heat-index-f'},'F');
20714			$rows[$j]->{main::key($num++,0,2,'heat index')} = $value;
20715		}
20716		if ($weather{'windchill'} || (defined $weather{'windchill-c'} && defined $weather{'windchill-f'})){
20717			$value = process_unit($weather{'windchill'},$weather{'windchill-c'},'C',$weather{'windchill-f'},'F');
20718			$rows[$j]->{main::key($num++,0,2,'wind chill')} = $value;
20719		}
20720		if ($extra > 2){
20721			if ($weather{'forecast'}){
20722				$j = scalar @rows;
20723				push(@rows, {
20724				main::key($num++,1,1,'Forecast') => $weather{'forecast'},
20725				},);
20726			}
20727		}
20728	}
20729	$j = scalar @rows;
20730	my $location = '';
20731	if ($extra > 2 && !$use{'filter'}){
20732		$location = complete_location($location[1],$weather{'city'},$weather{'state'},$weather{'country'});
20733	}
20734	push(@rows, {
20735	main::key($num++,1,1,'Locale') => $location,
20736	},);
20737	if ($extra > 2 && !$use{'filter'} && ($weather{'elevation-m'} || $weather{'elevation-ft'})){
20738		$rows[$j]->{main::key($num++,0,2,'altitude')} = process_elevation($weather{'elevation-m'},$weather{'elevation-ft'});
20739	}
20740	$rows[$j]->{main::key($num++,0,2,'current time')} = $weather{'date-time'},;
20741	if ($extra > 2){
20742		$weather{'observation-time-local'} = 'N/A' if !$weather{'observation-time-local'};
20743		$rows[$j]->{main::key($num++,0,2,'observation time')} = $weather{'observation-time-local'};
20744		if ($weather{'sunrise'}){
20745			$rows[$j]->{main::key($num++,0,2,'sunrise')} = $weather{'sunrise'};
20746		}
20747		if ($weather{'sunset'}){
20748			$rows[$j]->{main::key($num++,0,2,'sunset')} = $weather{'sunset'};
20749		}
20750		if ($weather{'moonphase'}){
20751			$value = $weather{'moonphase'} . '%';
20752			$value .= ($weather{'moonphase-graphic'}) ? ' ' . $weather{'moonphase-graphic'} :'';
20753			$rows[$j]->{main::key($num++,0,2,'moonphase')} = $value;
20754		}
20755	}
20756	if ($weather{'api-source'}){
20757		$rows[$j]->{main::key($num++,0,1,'Source')} = $weather{'api-source'};
20758	}
20759	eval $end if $b_log;
20760	return @rows;
20761}
20762sub process_elevation {
20763	eval $start if $b_log;
20764	my ($meters,$feet) = @_;
20765	my ($result,$i_unit,$m_unit) = ('','ft','m');
20766	$feet = sprintf("%.0f", 3.28 * $meters) if defined $meters && !$feet;
20767	$meters = sprintf("%.1f", $feet/3.28) if defined $feet && !$meters;
20768	$meters = sprintf("%.0f", $meters) if $meters;
20769	if (defined $meters  && $weather_unit eq 'mi'){
20770		$result = "$meters $m_unit ($feet $i_unit)";
20771	}
20772	elsif (defined $meters && $weather_unit eq 'im'){
20773		$result = "$feet $i_unit ($meters $m_unit)";
20774	}
20775	elsif (defined $meters && $weather_unit eq 'm'){
20776		$result = "$meters $m_unit";
20777	}
20778	elsif (defined $feet && $weather_unit eq 'i'){
20779		$result = "$feet $i_unit";
20780	}
20781	else {
20782		$result = 'N/A';
20783	}
20784	eval $end if $b_log;
20785	return $result;
20786}
20787sub process_unit {
20788	eval $start if $b_log;
20789	my ($primary,$metric,$m_unit,$imperial,$i_unit) = @_;
20790	my $result = '';
20791	if (defined $metric && defined $imperial && $weather_unit eq 'mi'){
20792		$result = "$metric $m_unit ($imperial $i_unit)";
20793	}
20794	elsif (defined $metric && defined $imperial && $weather_unit eq 'im'){
20795		$result = "$imperial $i_unit ($metric $m_unit)";
20796	}
20797	elsif (defined $metric && $weather_unit eq 'm'){
20798		$result = "$metric $m_unit";
20799	}
20800	elsif (defined $imperial && $weather_unit eq 'i'){
20801		$result = "$imperial $i_unit";
20802	}
20803	elsif ($primary){
20804		$result = $primary;
20805	}
20806	else {
20807		$result = 'N/A';
20808	}
20809	eval $end if $b_log;
20810	return $result;
20811}
20812sub process_wind {
20813	eval $start if $b_log;
20814	my ($primary,$direction,$mph,$ms,$gust_mph,$gust_ms) = @_;
20815	my ($result,$gust_kmh,$kmh,$i_unit,$m_unit,$km_unit) = ('','','','mph','m/s','km/h');
20816	# get rid of possible gust values if they are the same as wind values
20817	$gust_mph = undef if $gust_mph && $mph && $mph eq $gust_mph;
20818	$gust_ms = undef if $gust_ms && $ms && $ms eq $gust_ms;
20819	# calculate and round, order matters so that rounding only happens after math done
20820	$ms = 0.44704 * $mph if defined $mph && !defined $ms;
20821	$mph = $ms * 2.23694 if defined $ms && !defined $mph;
20822	$kmh = sprintf("%.0f",  18*$ms/5) if defined $ms;
20823	$ms = sprintf("%.1f", $ms) if defined $ms; # very low mph speeds yield 0, which is wrong
20824	$mph = sprintf("%.0f", $mph) if defined $mph;
20825	$gust_ms = 0.44704 * $gust_mph if $gust_mph && !$gust_ms;
20826	$gust_kmh = 18 * $gust_ms / 5 if $gust_ms;
20827	$gust_mph = $gust_ms * 2.23694 if $gust_ms && !$gust_mph;
20828	$gust_mph = sprintf("%.0f", $gust_mph) if $gust_mph;
20829	$gust_kmh = sprintf("%.0f", $gust_kmh) if $gust_kmh;
20830	$gust_ms = sprintf("%.0f", $gust_ms) if  $gust_ms;
20831	if (!defined $mph && $primary){
20832		$result = $primary;
20833	}
20834	elsif (defined $mph && defined $direction){
20835		if ($weather_unit eq 'mi'){
20836			$result = "from $direction at $ms $m_unit ($kmh $km_unit, $mph $i_unit)";
20837		}
20838		elsif ($weather_unit eq 'im'){
20839			$result = "from $direction at $mph $i_unit ($ms $m_unit, $kmh $km_unit)";
20840		}
20841		elsif ($weather_unit eq 'm'){
20842			$result = "from $direction at $ms $m_unit ($kmh $km_unit)";
20843		}
20844		elsif ($weather_unit eq 'i'){
20845			$result = "from $direction at $mph $i_unit";
20846		}
20847		if ($gust_mph){
20848			if ($weather_unit eq 'mi'){
20849				$result .= ". Gusting to $ms $m_unit ($kmh $km_unit, $mph $i_unit)";
20850			}
20851			elsif ($weather_unit eq 'im'){
20852				$result .= ". Gusting to $mph $i_unit ($ms $m_unit, $kmh $km_unit)";
20853			}
20854			elsif ($weather_unit eq 'm'){
20855				$result .= ". Gusting to $ms $m_unit ($kmh $km_unit)";
20856			}
20857			elsif ($weather_unit eq 'i'){
20858				$result .= ". Gusting to $mph $i_unit";
20859			}
20860		}
20861	}
20862	elsif ($primary){
20863		$result = $primary;
20864	}
20865	else {
20866		$result = 'N/A';
20867	}
20868	eval $end if $b_log;
20869	return $result;
20870}
20871sub get_weather {
20872	eval $start if $b_log;
20873	my ($location) = @_;
20874	my $now = POSIX::strftime "%Y%m%d%H%M", localtime;
20875	my ($date_time,$freshness,$tz,@weather_data,%weather);
20876	my $loc_name = lc($location->[0]);
20877	$loc_name =~ s/-\/|\s|,/-/g;
20878	$loc_name =~ s/--/-/g;
20879	my $file_cached = "$user_data_dir/weather-$loc_name-$weather_source.txt";
20880	if (-r $file_cached){
20881		@weather_data = main::reader($file_cached);
20882		$freshness = (split(/\^\^/, $weather_data[0]))[1];
20883		# print "$now:$freshness\n";
20884	}
20885	if (!$freshness || $freshness < ($now - 60)){
20886		@weather_data = download_weather($now,$file_cached,$location);
20887	}
20888	# print join("\n", @weather_data), "\n";
20889	# NOTE: because temps can be 0, we can't do if value tests
20890	foreach (@weather_data){
20891		my @working = split(/\s*\^\^\s*/, $_);
20892		next if ! defined $working[1] || $working[1] eq '';
20893		if ($working[0] eq 'api_source'){
20894			$weather{'api-source'} = $working[1];
20895		}
20896		elsif ($working[0] eq 'city'){
20897			$weather{'city'} = $working[1];
20898		}
20899		elsif ($working[0] eq 'cloud_cover'){
20900			$weather{'cloud-cover'} = $working[1];
20901		}
20902		elsif ($working[0] eq 'country'){
20903			$weather{'country'} = $working[1];
20904		}
20905		elsif ($working[0] eq 'dewpoint_string'){
20906			$weather{'dewpoint'} = $working[1];
20907			$working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/;
20908			$weather{'dewpoint-c'} = $2;;
20909			$weather{'dewpoint-f'} = $1;;
20910		}
20911		elsif ($working[0] eq 'dewpoint_c'){
20912			$weather{'dewpoint-c'} = $working[1];
20913		}
20914		elsif ($working[0] eq 'dewpoint_f'){
20915			$weather{'dewpoint-f'} = $working[1];
20916		}
20917		# WU: there are two elevations, we want the first one
20918		elsif (!$weather{'elevation-m'} && $working[0] eq 'elevation'){
20919			# note: bug in source data uses ft for meters, not 100% of time, but usually
20920			$weather{'elevation-m'} = $working[1];
20921			$weather{'elevation-m'} =~ s/\s*(ft|m).*$//;
20922		}
20923		elsif ($working[0] eq 'error'){
20924			$weather{'error'} = $working[1];
20925		}
20926		elsif ($working[0] eq 'forecast'){
20927			$weather{'forecast'} = $working[1];
20928		}
20929		elsif ($working[0] eq 'heat_index_string'){
20930			$weather{'heat-index'} = $working[1];
20931			$working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/;
20932			$weather{'heat-index-c'} = $2;;
20933			$weather{'heat-index-f'} = $1;
20934		}
20935		elsif ($working[0] eq 'heat_index_c'){
20936			$weather{'heat-index-c'} = $working[1];
20937		}
20938		elsif ($working[0] eq 'heat_index_f'){
20939			$weather{'heat-index-f'} = $working[1];
20940		}
20941		elsif ($working[0] eq 'relative_humidity'){
20942			$working[1] =~ s/%$//;
20943			$weather{'humidity'} = $working[1];
20944		}
20945		elsif ($working[0] eq 'local_time'){
20946			$weather{'local-time'} = $working[1];
20947		}
20948		elsif ($working[0] eq 'local_epoch'){
20949			$weather{'local-epoch'} = $working[1];
20950		}
20951		elsif ($working[0] eq 'moonphase'){
20952			$weather{'moonphase'} = $working[1];
20953		}
20954		elsif ($working[0] eq 'moonphase_graphic'){
20955			$weather{'moonphase-graphic'} = $working[1];
20956		}
20957		elsif ($working[0] eq 'observation_time_rfc822'){
20958			$weather{'observation-time-rfc822'} = $working[1];
20959		}
20960		elsif ($working[0] eq 'observation_epoch'){
20961			$weather{'observation-epoch'} = $working[1];
20962		}
20963		elsif ($working[0] eq 'observation_time'){
20964			$weather{'observation-time-local'} = $working[1];
20965			$weather{'observation-time-local'} =~ s/Last Updated on //;
20966		}
20967		elsif ($working[0] eq 'precip_mm'){
20968			$weather{'precip-1h-mm'} = $working[1];
20969		}
20970		elsif ($working[0] eq 'precip_in'){
20971			$weather{'precip-1h-in'} = $working[1];
20972		}
20973		elsif ($working[0] eq 'pressure_string'){
20974			$weather{'pressure'} = $working[1];
20975		}
20976		elsif ($working[0] eq 'pressure_mb'){
20977			$weather{'pressure-mb'} = $working[1];
20978		}
20979		elsif ($working[0] eq 'pressure_in'){
20980			$weather{'pressure-in'} = $working[1];
20981		}
20982		elsif ($working[0] eq 'rain_1h_mm'){
20983			$weather{'rain-1h-mm'} = $working[1];
20984		}
20985		elsif ($working[0] eq 'rain_1h_in'){
20986			$weather{'rain-1h-in'} = $working[1];
20987		}
20988		elsif ($working[0] eq 'snow_1h_mm'){
20989			$weather{'snow-1h-mm'} = $working[1];
20990		}
20991		elsif ($working[0] eq 'snow_1h_in'){
20992			$weather{'snow-1h-in'} = $working[1];
20993		}
20994		elsif ($working[0] eq 'state_name'){
20995			$weather{'state'} = $working[1];
20996		}
20997		elsif ($working[0] eq 'sunrise'){
20998			if ($working[1]){
20999				if ($working[1] !~ /^[0-9]+$/){
21000					$weather{'sunrise'} = $working[1];
21001				}
21002				# trying to figure out remote time from UTC is too hard
21003				elsif (!$show{'weather-location'}){
21004					$weather{'sunrise'} = POSIX::strftime "%T", localtime($working[1]);
21005				}
21006			}
21007		}
21008		elsif ($working[0] eq 'sunset'){
21009			if ($working[1]){
21010				if ($working[1] !~ /^[0-9]+$/){
21011					$weather{'sunset'} = $working[1];
21012				}
21013				# trying to figure out remote time from UTC is too hard
21014				elsif (!$show{'weather-location'}){
21015					$weather{'sunset'} = POSIX::strftime "%T", localtime($working[1]);
21016				}
21017			}
21018		}
21019		elsif ($working[0] eq 'temperature_string'){
21020			$weather{'temp'} = $working[1];
21021			$working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/;
21022			$weather{'temp-c'} = $2;;
21023			$weather{'temp-f'} = $1;
21024# 			$weather{'temp'} =~ s/\sF/\xB0 F/; # B0
21025# 			$weather{'temp'} =~ s/\sF/\x{2109}/;
21026# 			$weather{'temp'} =~ s/\sC/\x{2103}/;
21027		}
21028		elsif ($working[0] eq 'temp_f'){
21029			$weather{'temp-f'} = $working[1];
21030		}
21031		elsif ($working[0] eq 'temp_c'){
21032			$weather{'temp-c'} = $working[1];
21033		}
21034		elsif ($working[0] eq 'timezone'){
21035			$weather{'timezone'} = $working[1];
21036		}
21037		elsif ($working[0] eq 'visibility'){
21038			$weather{'visibility'} = $working[1];
21039		}
21040		elsif ($working[0] eq 'visibility_km'){
21041			$weather{'visibility-km'} = $working[1];
21042		}
21043		elsif ($working[0] eq 'visibility_mi'){
21044			$weather{'visibility-mi'} = $working[1];
21045		}
21046		elsif ($working[0] eq 'weather'){
21047			$weather{'weather'} = $working[1];
21048		}
21049		elsif ($working[0] eq 'wind_degrees'){
21050			$weather{'wind-degrees'} = $working[1];
21051		}
21052		elsif ($working[0] eq 'wind_dir'){
21053			$weather{'wind-direction'} = $working[1];
21054		}
21055		elsif ($working[0] eq 'wind_mph'){
21056			$weather{'wind-mph'} = $working[1];
21057		}
21058		elsif ($working[0] eq 'wind_gust_mph'){
21059			$weather{'wind-gust-mph'} = $working[1];
21060		}
21061		elsif ($working[0] eq 'wind_gust_ms'){
21062			$weather{'wind-gust-ms'} = $working[1];
21063		}
21064		elsif ($working[0] eq 'wind_ms'){
21065			$weather{'wind-ms'} = $working[1];
21066		}
21067		elsif ($working[0] eq 'wind_string'){
21068			$weather{'wind'} = $working[1];
21069		}
21070		elsif ($working[0] eq 'windchill_string'){
21071			$weather{'windchill'} = $working[1];
21072			$working[1] =~ /^([0-9\.]+)\sF\s\(([0-9\.]+)\sC\)/;
21073			$weather{'windchill-c'} = $2;
21074			$weather{'windchill-f'} = $1;
21075		}
21076		elsif ($working[0] eq 'windchill_c'){
21077			$weather{'windchill-c'} = $working[1];
21078		}
21079		elsif ($working[0] eq 'windchill_f'){
21080			$weather{'windchill_f'} = $working[1];
21081		}
21082	}
21083	if ($show{'weather-location'}){
21084		if ($weather{'observation-time-local'} &&
21085		 $weather{'observation-time-local'} =~ /^(.*)\s([a-z_]+\/[a-z_]+)$/i){
21086			$tz = $2;
21087		}
21088		if (!$tz && $weather{'timezone'}){
21089			$tz = $weather{'timezone'};
21090			$weather{'observation-time-local'} .= ' (' . $weather{'timezone'} . ')' if $weather{'observation-time-local'};
21091		}
21092		# very clever trick, just make the system think it's in the
21093		# remote timezone for this local block only
21094		local $ENV{'TZ'} = $tz if $tz;
21095		$date_time = POSIX::strftime "%c", localtime();
21096		$date_time = test_locale_date($date_time,'','');
21097		$weather{'date-time'} = $date_time;
21098		# only wu has rfc822 value, and we want the original observation time then
21099		if ($weather{'observation-epoch'} && $tz){
21100			$date_time = POSIX::strftime "%Y-%m-%d %T ($tz %z)", localtime($weather{'observation-epoch'});
21101			$date_time = test_locale_date($date_time,$show{'weather-location'},$weather{'observation-epoch'});
21102			$weather{'observation-time-local'} = $date_time;
21103		}
21104	}
21105	else {
21106		$date_time = POSIX::strftime "%c", localtime();
21107		$date_time = test_locale_date($date_time,'','');
21108		$tz = ($location->[2]) ? " ($location->[2])" : '';
21109		$weather{'date-time'} = $date_time . $tz;
21110	}
21111	# we get the wrong time using epoch for remote -W location
21112	if (!$show{'weather-location'} && $weather{'observation-epoch'}){
21113		$date_time = POSIX::strftime "%c", localtime($weather{'observation-epoch'});
21114		$date_time = test_locale_date($date_time,$show{'weather-location'},$weather{'observation-epoch'});
21115		$weather{'observation-time-local'} = $date_time;
21116	}
21117	eval $end if $b_log;
21118	return %weather;
21119}
21120sub download_weather {
21121	eval $start if $b_log;
21122	my ($now,$file_cached,$location) = @_;
21123	my (@weather,$temp,$ua,$url);
21124	$url = "https://smxi.org/opt/xr2.php?loc=$location->[0]&src=$weather_source";
21125	$ua = 'weather';
21126# 		{
21127# 			# my $file2 = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/weather-1.xml";
21128# 			# my $file2 = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/feed-oslo-1.xml";
21129# 			local $/;
21130# 			my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/weather-1.xml";
21131# 			open(my $fh, '<', $file) or die "can't open $file: $!";
21132# 			$temp = <$fh>;
21133# 		}
21134	$temp = main::download_file('stdout',$url,'',$ua);
21135	@weather = split('\n', $temp) if $temp;
21136	unshift(@weather, "timestamp^^$now");
21137	main::writer($file_cached,\@weather);
21138	# print "$file_cached: download/cleaned\n";
21139	eval $end if $b_log;
21140	return @weather;
21141}
21142# resolve wide character issue, if detected, switch to iso
21143# date format, we won't try to be too clever here.
21144sub test_locale_date {
21145	my ($date_time,$location,$epoch) = @_;
21146	# $date_time .= 'дек';
21147	# print "1: $date_time\n";
21148	if ($date_time =~ m/[^\x00-\x7f]/){
21149		if (!$location && $epoch){
21150			$date_time = POSIX::strftime "%Y-%m-%d %H:%M:%S", localtime($epoch);
21151		}
21152		else {
21153			$date_time = POSIX::strftime "%Y-%m-%d %H:%M:%S", localtime();
21154		}
21155	}
21156	$date_time =~ s/\s+$//;
21157	# print "2: $date_time\n";
21158	return $date_time;
21159}
21160sub get_location {
21161	eval $start if $b_log;
21162	my ($city,$country,$freshness,%loc,$loc_arg,$loc_string,@loc_data,$state);
21163	my $now = POSIX::strftime "%Y%m%d%H%M", localtime;
21164	my $file_cached = "$user_data_dir/location-main.txt";
21165	if (-r $file_cached){
21166		@loc_data = main::reader($file_cached);
21167		$freshness = (split(/\^\^/, $loc_data[0]))[1];
21168	}
21169	if (!$freshness || $freshness < $now - 90){
21170		my $temp;
21171		my $url = "http://geoip.ubuntu.com/lookup";
21172# 		{
21173# 			local $/;
21174# 			my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/weather/location-1.xml";
21175# 			open(my $fh, '<', $file) or die "can't open $file: $!";
21176# 			$temp = <$fh>;
21177# 		}
21178		$temp  = main::download_file('stdout',$url);
21179		@loc_data = split('\n', $temp);
21180		@loc_data = map {
21181		s/<\?.*<Response>//;
21182		s/<\/[^>]+>/\n/g;
21183		s/>/^^/g;
21184		s/<//g;
21185		$_;
21186		} @loc_data;
21187		@loc_data = split('\n', $loc_data[0]);
21188		unshift(@loc_data, "timestamp^^$now");
21189		main::writer($file_cached,\@loc_data);
21190		# print "$file_cached: download/cleaned\n";
21191	}
21192	foreach (@loc_data){
21193		my @working = split(/\s*\^\^\s*/, $_);
21194		# print "$working[0]:$working[1]\n";
21195		if ($working[0] eq 'CountryCode3'){
21196			$loc{'country3'} = $working[1];
21197		}
21198		elsif ($working[0] eq 'CountryCode'){
21199			$loc{'country'} = $working[1];
21200		}
21201		elsif ($working[0] eq 'CountryName'){
21202			$loc{'country2'} = $working[1];
21203		}
21204		elsif ($working[0] eq 'RegionCode'){
21205			$loc{'region-id'} = $working[1];
21206		}
21207		elsif ($working[0] eq 'RegionName'){
21208			$loc{'region'} = $working[1];
21209		}
21210		elsif ($working[0] eq 'City'){
21211			$loc{'city'} = $working[1];
21212		}
21213		elsif ($working[0] eq 'ZipPostalCode'){
21214			$loc{'zip'} = $working[1];
21215		}
21216		elsif ($working[0] eq 'Latitude'){
21217			$loc{'lat'} = $working[1];
21218		}
21219		elsif ($working[0] eq 'Longitude'){
21220			$loc{'long'} = $working[1];
21221		}
21222		elsif ($working[0] eq 'TimeZone'){
21223			$loc{'tz'} = $working[1];
21224		}
21225	}
21226	# print Data::Dumper::Dumper \%loc;
21227	# assign location, cascade from most accurate
21228	# latitude,longitude first
21229	if ($loc{'lat'} && $loc{'long'}){
21230		$loc_arg = "$loc{'lat'},$loc{'long'}";
21231	}
21232	# city,state next
21233	elsif ($loc{'city'} && $loc{'region-id'}){
21234		$loc_arg = "$loc{'city'},$loc{'region-id'}";
21235	}
21236	# postal code last, that can be a very large region
21237	elsif ($loc{'zip'}){
21238		$loc_arg = $loc{'zip'};
21239	}
21240	$country = ($loc{'country3'}) ? $loc{'country3'} : $loc{'country'};
21241	$city = ($loc{'city'}) ? $loc{'city'} : 'City N/A';
21242	$state = ($loc{'region-id'}) ? $loc{'region-id'} : 'Region N/A';
21243	$loc_string = main::apply_filter("$city, $state, $country");
21244	my @location = ($loc_arg,$loc_string,$loc{'tz'});
21245	# print ($loc_arg,"\n", join("\n", @loc_data), "\n",scalar @loc_data, "\n");
21246	eval $end if $b_log;
21247	return @location;
21248}
21249sub complete_location {
21250	eval $start if $b_log;
21251	my ($location,$city,$state,$country) = @_;
21252	if ($location && $location =~ /[0-9+-]/ && $city){
21253		$location = $country . ', ' . $location if $country && $location !~ m|$country|i;
21254		$location = $state . ', ' . $location if $state && $location !~ m|$state|i;
21255		$location = $city . ', ' . $location if $city && $location !~ m|$city|i;
21256	}
21257	eval $end if $b_log;
21258	return $location;
21259}
21260}
21261
21262#### -------------------------------------------------------------------
21263#### ITEM UTILITIES
21264#### -------------------------------------------------------------------
21265
21266# android only, for distro / OS id and machine data
21267sub set_build_prop {
21268	eval $start if $b_log;
21269	my $path = '/system/build.prop';
21270	$loaded{'build-prop'} = 1;
21271	return if ! -r $path;
21272	my @data = reader($path,'strip');
21273	foreach (@data){
21274		my @working = split('=', $_);
21275		next if $working[0] !~ /^ro\.(build|product)/;
21276		if ($working[0] eq 'ro.build.date.utc'){
21277			$build_prop{'build-date'} = strftime "%F", gmtime($working[1]);
21278		}
21279		# ldgacy, replaced by ro.product.device
21280		elsif ($working[0] eq 'ro.build.product'){
21281			$build_prop{'build-product'} = $working[1];
21282		}
21283		# this can be brand, company, android, it varies, but we don't want android value
21284		elsif ($working[0] eq 'ro.build.user'){
21285			$build_prop{'build-user'} = $working[1] if $working[1] !~ /android/i;
21286		}
21287		elsif ($working[0] eq 'ro.build.version.release'){
21288			$build_prop{'build-version'} = $working[1];
21289		}
21290		elsif ($working[0] eq 'ro.product.board'){
21291			$build_prop{'product-board'} = $working[1];
21292		}
21293		elsif ($working[0] eq 'ro.product.brand'){
21294			$build_prop{'product-brand'} = $working[1];
21295		}
21296		elsif ($working[0] eq 'ro.product.device'){
21297			$build_prop{'product-device'} = $working[1];
21298		}
21299		elsif ($working[0] eq 'ro.product.manufacturer'){
21300			$build_prop{'product-manufacturer'} = $working[1];
21301		}
21302		elsif ($working[0] eq 'ro.product.model'){
21303			$build_prop{'product-model'} = $working[1];
21304		}
21305		elsif ($working[0] eq 'ro.product.name'){
21306			$build_prop{'product-name'} = $working[1];
21307		}
21308		elsif ($working[0] eq 'ro.product.screensize'){
21309			$build_prop{'product-screensize'} = $working[1];
21310		}
21311	}
21312	log_data('dump','%build_prop',\%build_prop) if $b_log;
21313	print Dumper \%build_prop if $dbg[20];
21314	eval $end if $b_log;
21315}
21316
21317## CompilerVersion
21318{
21319package CompilerVersion;
21320sub get {
21321	eval $start if $b_log;
21322	my (@compiler);
21323	if (my $file = $system_files{'proc-version'}){
21324		@compiler = version_proc($file);
21325	}
21326	elsif ($bsd_type){
21327		@compiler = version_bsd();
21328	}
21329	eval $end if $b_log;
21330	return @compiler;
21331}
21332
21333sub version_bsd {
21334	eval $start if $b_log;
21335	my (@compiler,@working);
21336	if ($alerts{'sysctl'}->{'action'} && $alerts{'sysctl'}->{'action'} eq 'use'){
21337		if ($sysctl{'kernel'}){
21338			my @working;
21339			foreach (@{$sysctl{'kernel'}}){
21340				# Not every line will have a : separator though the processor should make
21341				# most have it. This appears to be 10.x late feature add, I don't see it
21342				# on earlier BSDs
21343				if (/^kern.compiler_version/){
21344					@working = split(/:\s*/, $_);
21345					$working[1] =~ /.*(gcc|clang)\sversion\s([\S]+)\s.*/;
21346					@compiler = ($1,$2);
21347					last;
21348				}
21349			}
21350		}
21351		# OpenBSD doesn't show compiler data in sysctl or dboot but it's going to
21352		# be Clang until way into the future, and it will be the installed version.
21353		if (!@compiler){
21354			if (my $path = main::check_program('clang')){
21355				 $compiler[0] = 'clang';
21356				 $compiler[1] = main::program_version($path,'clang',3,'--version');
21357			}
21358		}
21359	}
21360	main::log_data('dump','@compiler',\@compiler) if $b_log;
21361	eval $end if $b_log;
21362	return @compiler;
21363}
21364sub version_proc {
21365	eval $start if $b_log;
21366	my ($file) = @_;
21367	my (@compiler,$version);
21368	my @data = main::reader($file);
21369	my $result = $data[0] if @data;
21370	if ($result){
21371		if ($fake{'compiler'}){
21372			# $result = $result =~ /\*(gcc|clang)\*eval\*/;
21373			# $result='Linux version 5.4.0-rc1 (sourav@archlinux-pc) (clang version 9.0.0 (tags/RELEASE_900/final)) #1 SMP PREEMPT Sun Oct 6 18:02:41 IST 2019';
21374			# $result='Linux version 5.8.3-fw1 (fst@x86_64.frugalware.org) ( OpenMandriva 11.0.0-0.20200819.1 clang version 11.0.0 (/builddir/build/BUILD/llvm-project-release-11.x/clang 2a0076812cf106fcc34376d9d967dc5f2847693a), LLD 11.0.0)';
21375			# $result='Linux version 5.8.0-18-generic (buildd@lgw01-amd64-057) (gcc (Ubuntu 10.2.0-5ubuntu2) 10.2.0, GNU ld (GNU Binutils for Ubuntu) 2.35) #19-Ubuntu SMP Wed Aug 26 15:26:32 UTC 2020';
21376			# $result='Linux version 5.8.9-fw1 (fst@x86_64.frugalware.org) (gcc (Frugalware Linux) 9.2.1 20200215, GNU ld (GNU Binutils) 2.35) #1 SMP PREEMPT Tue Sep 15 16:38:57 CEST 2020';
21377			# $result='Linux version 5.8.0-2-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.0-9) 10.2.0, GNU ld (GNU Binutils for Debian) 2.35) #1 SMP Debian 5.8.10-1 (2020-09-19)';
21378			# $result='Linux version 5.9.0-5-amd64 (debian-kernel@lists.debian.org) (gcc-10 (Debian 10.2.1-1) 10.2.1 20201207, GNU ld (GNU Binutils for Debian) 2.35.1) #1 SMP Debian 5.9.15-1 (2020-12-17)';
21379			# $result='Linux version 2.6.1 (GNU 0.9 GNU-Mach 1.8+git20201007-486/Hurd-0.9 i686-AT386)';
21380			# $result='NetBSD version 9.1 (netbsd@localhost) (gcc version 7.5.0) NetBSD 9.1 (GENERIC) #0: Sun Oct 18 19:24:30 UTC 2020';
21381		}
21382		if ($result =~ /(gcc|clang).*version\s([^\s\)]+)/){
21383			$version = $2;
21384			$version ||= 'N/A';
21385			@compiler = ($1,$version);
21386		}
21387		elsif ($result =~ /\((gcc|clang)[^\(]*\([^\)]+\)\s+([0-9\.]+)(\s[^.]*)?,\s*/){
21388			$version = $2;
21389			$version ||= 'N/A';
21390			@compiler = ($1,$version);
21391		}
21392	}
21393	main::log_data('dump','@compiler',\@compiler) if $b_log;
21394	eval $end if $b_log;
21395	return @compiler;
21396}
21397}
21398
21399sub set_dboot_data {
21400	eval $start if $b_log;
21401	$loaded{'dboot'} = 1;
21402	my ($file,@db_data,@dm_data,@temp);
21403	my ($counter) = (0);
21404	if (!$fake{'dboot'}){
21405		$file = $system_files{'dmesg-boot'};
21406	}
21407	else {
21408		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/bsd-disks-diabolus.txt";
21409		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/freebsd-disks-solestar.txt";
21410		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/freebsd-enceladus-1.txt";
21411		## matches: toshiba: openbsd-5.6-sysctl-2.txt
21412		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/openbsd-5.6-dmesg.boot-1.txt";
21413		## matches: compaq: openbsd-5.6-sysctl-1.txt"
21414		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/openbsd-dmesg.boot-1.txt";
21415		$file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmesg-boot/openbsd-6.8-battery-sensors-1.txt";
21416	}
21417	if ($file){
21418		return if ! -r $file;
21419		@db_data = reader($file);
21420		# sometimes > 1 sessions stored, dump old ones
21421		for (@db_data){
21422			if (/^(Dragonfly|OpenBSD|NetBSD|FreeBSD is a registered trademark|Copyright.*Midnight)/){
21423				$counter++;
21424				undef @temp if $counter > 1;
21425			}
21426			push(@temp,$_);
21427		}
21428		@db_data = @temp;
21429		undef @temp;
21430		my @dm_data = grabber('dmesg 2>/dev/null');
21431		# clear out for netbsd, only 1 space following or lines won't match
21432		@dm_data = map {$_ =~ s/^\[[^\]]+\]\s//;$_} @dm_data;
21433		$counter = 0;
21434		# dump previous sessions, and also everything roughly before dmesg.boot
21435		# ends, it does't need to be perfect, we just only want the actual post
21436		# boot data
21437		for (@dm_data){
21438			if (/^(Dragonfly|OpenBSD|NetBSD|FreeBSD is a registered trademark|Copyright.*Midnight)/ ||
21439				/^(smbus[0-9]:|Security policy loaded|root on)/){
21440				$counter++;
21441				undef @temp if $counter > 1;
21442			}
21443			push(@temp,$_);
21444		}
21445		@dm_data = @temp;
21446		undef @temp;
21447		push(@db_data,'~~~~~',@dm_data);
21448		# uniq(\@db_data); # get rid of duplicate lines
21449		# some dmesg repeats, so we need to dump the second and > iterations
21450		# replace all indented items with ~ so we can id them easily while
21451		# processing note that if user, may get error of read permissions
21452		# for some weird reason, real mem and avail mem are use a '=' separator,
21453		# who knows why, the others are ':'
21454		foreach (@db_data){
21455			$_ =~ s/\s*=\s*|:\s*/:/;
21456			$_ =~ s/\"//g;
21457			$_ =~ s/^\s+/~/;
21458			$_ =~ s/\s\s/ /g;
21459			$_ =~ s/^(\S+)\sat\s/$1:at /; # ada0 at ahcich0
21460			push(@{$dboot{'main'}}, $_);
21461			if ($use{'bsd-battery'} && /^acpi(bat|cmb)/){
21462				push(@{$sysctl{'battery'}}, $_);
21463			}
21464			# ~Debug Features 0:<2 CTX BKPTs,4 Watchpoints,6 Breakpoints,PMUv3,Debugv8>
21465			elsif ($use{'bsd-cpu'} &&
21466			 (!/^~(Debug|Memory)/ && /(^cpu[0-9]+:|Features|^~*Origin:\s*)/)){
21467				push(@{$dboot{'cpu'}}, $_);
21468			}
21469			# FreeBSD: 'da*' is a USB device 'ada*' is a SATA device 'mmcsd*' is an SD card
21470			# OpenBSD: 'sd' is usb device, 'wd' normal drive. OpenBSD uses sd for nvme drives
21471			# but also has the nvme data:
21472			# nvme1 at pci6 dev 0 function 0 vendor "Phison", unknown product 0x5012 rev 0x01: msix, NVMe 1.3
21473			# nvme1: OWC Aura P12 1.0TB, firmware ECFM22.6, serial 2003100010208
21474			# scsibus2 at nvme1: 2 targets, initiator 0
21475			# sd1 at scsibus2 targ 1 lun 0: <NVMe, OWC Aura P12 1.0, ECFM>
21476			# sd1: 915715MB, 4096 bytes/sector, 234423126 sectors
21477			elsif ($use{'bsd-disk'} &&
21478			 /^(ad|ada|da|mmcblk|mmcsd|nvme([0-9]+n)?|sd|wd)[0-9]+(:|\sat\s|.*?\sdetached$)/){
21479				$_ =~ s/^\(//;
21480				push (@{$dboot{'disk'}},$_);
21481			}
21482			if ($use{'bsd-machine'} && /^bios[0-9]:(at|vendor)/){
21483				push(@{$sysctl{'machine'}}, $_);
21484			}
21485			elsif ($use{'bsd-machine'} && !$dboot{'machine-vm'} &&
21486			 /(\bhvm\b|innotek|\bkvm\b|microsoft.*virtual machine|openbsd[\s-]vmm|qemu|qumranet|vbox|virtio|virtualbox|vmware)/i){
21487				push(@{$dboot{'machine-vm'}}, $_);
21488			}
21489			elsif ($use{'bsd-optical'} && /^(cd)[0-9]+(\([^)]+\))?(:|\sat\s)/){
21490				push(@{$dboot{'optical'}},$_);
21491			}
21492			elsif ($use{'bsd-pci'} && /^(pci[0-9]+:at|\S+:at pci)/){
21493				push(@{$dboot{'pci'}},$_);
21494			}
21495			elsif ($use{'bsd-ram'} && /(^spdmem)/){
21496				push(@{$dboot{'ram'}}, $_);
21497			}
21498		}
21499		log_data('dump','$dboot{main}',$dboot{'main'}) if $b_log;
21500		print Dumper $dboot{'main'} if $dbg[11];
21501
21502		if ($dboot{'main'} && $b_log){
21503			log_data('dump','$dboot{cpu}',$dboot{'cpu'});
21504			log_data('dump','$dboot{disk}',$dboot{'disk'});
21505			log_data('dump','$dboot{machine-vm}',$dboot{'machine-vm'});
21506			log_data('dump','$dboot{optical}',$dboot{'optical'});
21507			log_data('dump','$dboot{ram}',$dboot{'ram'});
21508			log_data('dump','$dboot{usb}',$dboot{'usb'});
21509			log_data('dump','$sysctl{battery}',$sysctl{'battery'});
21510			log_data('dump','$sysctl{machine}',$sysctl{'machine'});
21511		}
21512		if ($dboot{'main'} && $dbg[11]){
21513			print("cpu:\n", Dumper $dboot{'cpu'});
21514			print("disk:\n", Dumper $dboot{'disk'});
21515			print("machine vm:\n", Dumper $dboot{'machine-vm'});
21516			print("optical:\n", Dumper $dboot{'optical'});
21517			print("ram:\n", Dumper $dboot{'ram'});
21518			print("usb:\n", Dumper $dboot{'usb'});
21519			print("sys battery:\n", Dumper $sysctl{'battery'});
21520			print("sys machine:\n", Dumper $sysctl{'machine'});
21521		}
21522		# this should help get rid of dmesg usb mounts not present
21523		# note if you take out one, put in another, it will always show the first
21524		# one, I think. Not great. Not using this means all drives attached
21525		# current session are shown, using it, possibly wrong drive shown, which is bad
21526		# not using this for now: && (my @disks = grep {/^hw\.disknames/} @{$dboot{'disk'}}
21527		if ($dboot{'disk'}){
21528			# hw.disknames:sd0:,sd1:3242432,sd2:
21529			#$disks[0] =~ s/(^hw\.disknames:|:[^,]*)//g;
21530			#@disks = split(',',$disks[0]) if $disks[0];
21531			my ($id,$value,%dboot_disks,@disks_live,@temp);
21532			# first, since openbsd has this, let's use it
21533			foreach (@{$dboot{'disk'}}){
21534				if (!@disks_live && /^hw\.disknames/){
21535					$_ =~ s/(^hw\.disknames:|:[^,]*)//g;
21536					@disks_live = split(/[,\s]/,$_) if $_;
21537				}
21538				else {
21539					push(@temp,$_);
21540				}
21541			}
21542			@{$dboot{'disk'}} = @temp if @temp;
21543			foreach my $row (@temp){
21544				$row =~ /^([^:\s]+)[:\s]+(.+)/;
21545				$id = $1;
21546				$value = $2;
21547				push(@{$dboot_disks{$id}},$value);
21548				# get rid of detached or non present drives
21549				if ((@disks_live && !(grep {$id =~ /^$_/} @disks_live)) ||
21550				 $value =~ /\b(destroyed|detached)$/){
21551					delete $dboot_disks{$id};
21552				}
21553			}
21554			$dboot{'disk'} = \%dboot_disks;
21555			log_data('dump','post: $dboot{disk}',$dboot{'disk'}) if $b_log;
21556			print("post: disk:\n",Dumper $dboot{'disk'}) if $dbg[11];
21557		}
21558		if ($use{'bsd-pci'} && $dboot{'pci'}){
21559			my $bus_id = 0;
21560			foreach (@{$dboot{'pci'}}){
21561				if (/^pci[0-9]+:at.*?bus\s([0-9]+)/){
21562					$bus_id = $1;
21563					next;
21564				}
21565				elsif (/:at pci[0-9]+\sdev/){
21566					$_ =~ s/^(\S+):at.*?dev\s([0-9]+)\sfunction\s([0-9]+)\s/$bus_id:$2:$3:$1:/;
21567					push(@temp,$_);
21568				}
21569			}
21570			$dboot{'pci'} = [@temp];
21571			log_data('dump','$dboot{pci}',$dboot{'pci'}) if $b_log;
21572			print("pci:\n",Dumper $dboot{'pci'}) if $dbg[11];
21573		}
21574	}
21575	eval $end if $b_log;
21576}
21577
21578## DesktopEnvironment
21579# returns array:
21580# 0 - desktop name
21581# 1 - version
21582# 2 - toolkit
21583# 3 - toolkit version
21584# 4 - info extra desktop data
21585# 5 - wm
21586# 6 - wm version
21587{
21588package DesktopEnvironment;
21589my ($b_gtk,$b_qt,$b_xprop,$desktop_session,$gdmsession,$kde_session_version,
21590$xdg_desktop,@desktop,@data,@xprop);
21591sub get {
21592	eval $start if $b_log;
21593	set_desktop_values();
21594	main::set_ps_gui() if !$loaded{'ps-gui'};
21595	get_kde_trinity_data();
21596	if (!@desktop){
21597		get_env_de_data();
21598	}
21599	if (!@desktop){
21600		get_env_xprop_gnome_based_data();
21601	}
21602	if (!@desktop && $b_xprop){
21603		get_env_xprop_non_gnome_based_data();
21604	}
21605	if (!@desktop){
21606		get_ps_de_data();
21607	}
21608	if ($extra > 2 && @desktop){
21609		set_info_data();
21610	}
21611	if ($b_display && !$force{'display'} && $extra > 1){
21612		get_wm();
21613	}
21614	set_gtk_data() if $b_gtk && $extra > 1;
21615	set_qt_data() if $b_qt && $extra > 1;
21616	main::log_data('dump','@desktop', \@desktop) if $b_log;
21617	# ($b_xprop,$kde_session_version,$xdg_desktop,@data,@xprop) = undef;
21618	eval $end if $b_log;
21619	return @desktop;
21620}
21621sub set_desktop_values {
21622	# NOTE $XDG_CURRENT_DESKTOP envvar is not reliable, but it shows certain desktops better.
21623	# most desktops are not using it as of 2014-01-13 (KDE, UNITY, LXDE. Not Gnome)
21624	$desktop_session = ($ENV{'DESKTOP_SESSION'}) ? prep_desktop_value($ENV{'DESKTOP_SESSION'}) : '';
21625	$xdg_desktop = ($ENV{'XDG_CURRENT_DESKTOP'}) ? prep_desktop_value($ENV{'XDG_CURRENT_DESKTOP'}) : '';
21626	$kde_session_version = ($ENV{'KDE_SESSION_VERSION'}) ? $ENV{'KDE_SESSION_VERSION'} : '';
21627	# for fallback to fallback protections re false gnome id
21628	$gdmsession = ($ENV{'GDMSESSION'}) ? prep_desktop_value($ENV{'GDMSESSION'}) : '';
21629}
21630# note: an ubuntu regresssion replaces or adds 'ubuntu' string to
21631# real value. Since ubuntu is the only distro I know that does this,
21632# will add more distro type filters as/if we come across them
21633sub prep_desktop_value {
21634	$_[0] = lc(main::trimmer($_[0]));
21635	$_[0] =~ s/\b(arch|debian|fedora|manjaro|mint|opensuse|ubuntu):?\s*//;
21636	return $_[0];
21637}
21638sub get_kde_trinity_data {
21639	eval $start if $b_log;
21640	my ($kded,$kded_name,$program,@version_data,@version_data2);
21641	my $kde_full_session = ($ENV{'KDE_FULL_SESSION'}) ? $ENV{'KDE_FULL_SESSION'} : '';
21642	# we can't rely on 3 using kded3, it could be kded
21643	if ($kde_full_session && ($program = main::check_program('kded' . $kde_full_session))){
21644		$kded = $program;
21645		$kded_name = 'kded' . $kde_full_session;
21646	}
21647	elsif ($program = main::check_program('kded')){
21648		$kded = $program;
21649		$kded_name = 'kded';
21650	}
21651	if ($desktop_session eq 'trinity' || $xdg_desktop eq 'trinity' || (grep {/^tde/} @ps_gui)){
21652		$desktop[0] = 'Trinity';
21653		if ($program = main::check_program('kdesktop')){
21654			@version_data = main::grabber("$program --version 2>/dev/null");
21655			$desktop[1] = main::awk(\@version_data,'^TDE:',2,'\s+') if @version_data;
21656		}
21657		if ($extra > 1 && @version_data){
21658			$desktop[2] = 'Qt';
21659			$desktop[3] = main::awk(\@version_data,'^Qt:',2,'\s+') if @version_data;
21660		}
21661	}
21662	# works on 4, assume 5 will id the same, why not, no need to update in future
21663	# KDE_SESSION_VERSION is the integer version of the desktop
21664	# NOTE: as of plasma 5, the tool: about-distro MAY be available, that will show
21665	# actual desktop data, so once that's in debian/ubuntu, if it gets in, add that test
21666	elsif ($xdg_desktop eq 'kde' || $kde_session_version){
21667		if ($kde_session_version && $kde_session_version <= 4){
21668			@data = ($kded_name) ? main::program_values($kded_name) : ();
21669			if (@data){
21670				$desktop[0] = $data[3];
21671				$desktop[1] = main::program_version($kded,$data[0],$data[1],$data[2],$data[5],$data[6]);
21672				# kded exists, so we can now get the qt data string as well
21673				if ($desktop[1] && $kded){
21674					@version_data = main::grabber("$kded --version 2>/dev/null");
21675				}
21676			}
21677			$desktop[0] = 'KDE' if !$desktop[0];
21678		}
21679		else {
21680			# NOTE: this command string is almost certain to change, and break, with next
21681			# major plasma desktop, ie, 6.
21682			# qdbus org.kde.plasmashell /MainApplication org.qtproject.Qt.QCoreApplication.applicationVersion
21683			# Qt: 5.4.2
21684			# KDE Frameworks: 5.11.0
21685			# kf5-config: 1.0
21686			# for QT, and Frameworks if we use it
21687			if (!@version_data && ($program = main::check_program("kf$kde_session_version-config"))){
21688				@version_data = main::grabber("$program --version 2>/dev/null");
21689			}
21690			if (!@version_data && ($program = main::check_program("kf-config"))){
21691				@version_data = main::grabber("$program --version 2>/dev/null");
21692			}
21693			# hope we don't use this fallback, not the same version as kde always
21694			if (!@version_data && $kded){
21695				@version_data = main::grabber("$kded --version 2>/dev/null");
21696			}
21697			if ($program = main::check_program("plasmashell")){
21698				@version_data2 = main::grabber("$program --version 2>/dev/null");
21699				$desktop[1] = main::awk(\@version_data2,'^plasmashell',-1,'\s+');
21700			}
21701			$desktop[0] = 'KDE Plasma';
21702		}
21703		if (!$desktop[1]){
21704			$desktop[1] = ($kde_session_version) ? $kde_session_version: main::row_defaults('unknown-desktop-version');
21705		}
21706		# print Data::Dumper::Dumper \@version_data;
21707		if ($extra > 1){
21708			if (@version_data){
21709				$desktop[3] = main::awk(\@version_data,'^Qt:', 2,'\s+');
21710			}
21711			# qmake can have variants, qt4-qmake, qt5-qmake, also qt5-default but not tested
21712			if (!$desktop[3] && main::check_program("qmake")){
21713				# note: this program has issues, it may appear to be in /usr/bin, but it
21714				# often fails to execute, so the below will have null output, but use as a
21715				# fall back test anyway.
21716				($desktop[2],$desktop[3]) = main::program_data('qmake');
21717			}
21718			$desktop[2] ||= 'Qt';
21719		}
21720	}
21721	# KDE_FULL_SESSION property is only available since KDE 3.5.5.
21722	elsif ($kde_full_session eq 'true'){
21723		@version_data = ($kded) ? main::grabber("$kded --version 2>/dev/null") : ();
21724		$desktop[0] = 'KDE';
21725		$desktop[1] = main::awk(\@version_data,'^KDE:',2,'\s+') if @version_data;
21726		if (!$desktop[1]){
21727			$desktop[1] = '3.5';
21728		}
21729		if ($extra > 1 && @version_data){
21730			$desktop[2] = 'Qt';
21731			$desktop[3] = main::awk(\@version_data,'^Qt:',2,'\s+') if @version_data;
21732		}
21733	}
21734	eval $end if $b_log;
21735}
21736sub get_env_de_data {
21737	eval $start if $b_log;
21738	my ($program,@version_data);
21739	if (!$desktop[0]){
21740		# 0: 1/0; 1: env var search; 2: data; 3: gtk tk; 4: qt tk; 5: ps_gui search
21741		my @desktops =(
21742		[1,'unity','unity',0,0],
21743		[0,'budgie','budgie-desktop',0,0],
21744		# debian package: lxde-core.
21745		# NOTE: some distros fail to set XDG data for root
21746		[1,'lxde','lxpanel',0,0,',^lxsession$'],
21747		[1,'razor','razor-session',0,1,'^razor-session$'],
21748		# BAD: lxqt-about opens dialogue, sigh.
21749		# Checked, lxqt-panel does show same version as lxqt-about
21750		[1,'lxqt','lxqt-panel',0,1,'^lxqt-session$'],
21751		[0,'^(razor|lxqt)$','lxqt-variant',0,1,'^(razor-session|lxqt-session)$'],
21752		# note, X-Cinnamon value strikes me as highly likely to change, so just
21753		# search for the last part
21754		[0,'cinnamon','cinnamon',1,0],
21755		# these so far have no cli version data
21756		[1,'deepin','deepin',0,1], # version comes from file read
21757		[1,'leftwm','leftwm',0,0],
21758		[1,'pantheon','pantheon',0,0],
21759		[1,'penrose','penrose',0,0],# unknown, just guessing
21760		[1,'lumina','lumina-desktop',0,1],
21761		[0,'manokwari','manokwari',1,0],
21762		[1,'ukui','ukui-session',0,1],
21763		);
21764		foreach my $item (@desktops){
21765			# Check if in xdg_desktop OR desktop_session OR if in $item->[6] and in ps_gui
21766			if ((($item->[0] && ($xdg_desktop eq $item->[1] || $desktop_session eq $item->[1])) ||
21767			   (!$item->[0] && ($xdg_desktop =~ /$item->[1]/ || $desktop_session  =~ /$item->[1]/))) ||
21768			   ($item->[5] && @ps_gui && (grep {/$item->[5]/} @ps_gui))){
21769				($desktop[0],$desktop[1]) = main::program_data($item->[2]);
21770				$b_gtk = $item->[3];
21771				$b_qt = $item->[4];
21772				last;
21773			}
21774		}
21775	}
21776	eval $end if $b_log;
21777}
21778sub get_env_xprop_gnome_based_data {
21779	eval $start if $b_log;
21780	my ($program,$value,@version_data);
21781	# NOTE: Always add to set_prop the search term if you add an item!!
21782	set_xprop();
21783	# add more as discovered
21784	return if $xdg_desktop eq 'xfce' || $gdmsession eq 'xfce';
21785	# note that cinnamon split from gnome, and and can now be id'ed via xprop,
21786	# but it will still trigger the next gnome true case, so this needs to go
21787	# before gnome test eventually this needs to be better organized so all the
21788	# xprop tests are in the same section, but this is good enough for now.
21789	# NOTE: was checking for 'muffin' but that's not part of cinnamon
21790	if ($xdg_desktop eq 'cinnamon' || $gdmsession eq 'cinnamon' ||
21791	 (main::check_program('muffin') || main::check_program('cinnamon-session')) &&
21792	 ($b_xprop && main::awk(\@xprop,'_muffin'))){
21793		($desktop[0],$desktop[1]) = main::program_data('cinnamon','cinnamon',0);
21794		$b_gtk = 1;
21795		$desktop[0] ||= 'Cinnamon';
21796	}
21797	elsif ($xdg_desktop eq 'mate' || $gdmsession eq 'mate' ||
21798	 ($b_xprop && main::awk(\@xprop,'_marco'))){
21799		# NOTE: mate-about and mate-sesssion vary which has the higher number, neither
21800		# consistently corresponds to the actual MATE version, so check both.
21801		my %versions = ('mate-about' => '','mate-session' => '');
21802		foreach my $key (keys %versions){
21803			if ($program = main::check_program($key)){
21804				@data = main::program_data($key,$program,0);
21805				$desktop[0] = $data[0];
21806				$versions{$key} = $data[1];
21807			}
21808		}
21809		# no consistent rule about which version is higher, so just compare them and take highest
21810		$desktop[1] = main::compare_versions($versions{'mate-about'},$versions{'mate-session'});
21811		# $b_gtk = 1;
21812		$desktop[0] ||= 'MATE';
21813	}
21814	# See sub for logic and comments
21815	elsif (check_gnome()){
21816		if (main::check_program('gnome-about')){
21817			($desktop[0],$desktop[1]) = main::program_data('gnome-about');
21818		}
21819		elsif (main::check_program('gnome-shell')){
21820			($desktop[0],$desktop[1]) = main::program_data('gnome','gnome-shell');
21821		}
21822		$b_gtk = 1;
21823		$desktop[0] ||= 'GNOME';
21824	}
21825	eval $end if $b_log;
21826}
21827# note, GNOME_DESKTOP_SESSION_ID is deprecated so we'll see how that works out
21828# https://bugzilla.gnome.org/show_bug.cgi?id=542880.
21829# NOTE: manjaro is leaving XDG data null, which forces the manual check for gnome, sigh...
21830# some gnome programs can trigger a false xprop gnome ID
21831# _GNOME_BACKGROUND_REPRESENTATIVE_COLORS(STRING) = "rgb(23,31,35)"
21832sub check_gnome {
21833	eval $start if $b_log;
21834	my ($b_gnome,$detection) = (0,'');
21835	if ($xdg_desktop && $xdg_desktop =~ /gnome/){
21836		$detection = 'xdg_current_desktop';
21837		$b_gnome = 1;
21838	}
21839	# should work as long as string contains gnome, eg: peppermint:gnome
21840	# filtered explicitly in set_desktop_values
21841	elsif ($xdg_desktop && $xdg_desktop !~ /gnome/){
21842		$detection = 'xdg_current_desktop';
21843	}
21844	# possible values: lightdm-xsession, only positive match tests will work
21845	elsif ($gdmsession && $gdmsession eq 'gnome'){
21846		$detection = 'gdmsession';
21847		$b_gnome = 1;
21848	}
21849	# risky: Debian: $DESKTOP_SESSION = lightdm-xsession; Manjaro/Arch = xfce
21850	# note that mate/cinnamon would already have been caught so no need to add
21851	# explicit tests for them
21852	elsif ($desktop_session && $desktop_session eq 'gnome'){
21853		$detection = 'desktop_session';
21854		$b_gnome = 1;
21855	}
21856	# possible value: this-is-deprecated, but I believe only gnome based desktops
21857	# set this variable, so it doesn't matter what it contains
21858	elsif ($ENV{'GNOME_DESKTOP_SESSION_ID'}){
21859		$detection = 'gnome_destkop_session_id';
21860		$b_gnome = 1;
21861	}
21862	# maybe use ^_gnome_session instead? try it for a while
21863	elsif ($b_xprop && main::check_program('gnome-shell') && main::awk(\@xprop,'^_gnome_session')){
21864		$detection = 'xprop-root';
21865		$b_gnome = 1;
21866	}
21867	main::log_data('data','$detection:$b_gnome>>' . $detection . ":$b_gnome") if $b_log;
21868	eval $end if $b_log;
21869	return $b_gnome;
21870}
21871sub get_env_xprop_non_gnome_based_data {
21872	eval $start if $b_log;
21873	my ($program,@version_data,$version);
21874	# print join("\n", @xprop), "\n";
21875	# String: "This is xfdesktop version 4.2.12"
21876	# alternate: xfce4-about --version > xfce4-about 4.10.0 (Xfce 4.10)
21877	# note: some distros/wm (e.g. bunsen) set xdg to xfce to solve some other
21878	# issues so don't test for that. $xdg_desktop eq 'xfce'
21879	if ($xdg_desktop eq 'xfce' || $gdmsession eq 'xfce' ||
21880	 (main::check_program('xfdesktop')) && main::awk(\@xprop,'^(xfdesktop|xfce)')){
21881		# this is a very expensive test that doesn't usually result in a find
21882		# talk to xfce to see what id they will be using for xfce 5
21883# 		if (main::awk(\@xprop, 'xfce4')){
21884# 			$version = '4';
21885# 		}
21886		if (main::awk(\@xprop, 'xfce5')){
21887			$version = '5';
21888		}
21889		else {
21890 			$version = '4';
21891		}
21892		@data = main::program_values('xfdesktop');
21893		$desktop[0] = $data[3];
21894		# xfdesktop --version out of x fails to get display, so no data
21895		@version_data = main::grabber('xfdesktop --version 2>/dev/null');
21896		# out of x, this error goes to stderr, so it's an empty result
21897		$desktop[1] = main::awk(\@version_data,$data[0],$data[1],'\s+');
21898		#$desktop[1] = main::program_version('xfdesktop',$data[0],$data[1],$data[2],$data[5],$data[6]);
21899		if (!$desktop[1]){
21900			@data = main::program_values("xfce${version}-panel");
21901			# print Data::Dumper::Dumper \@data;
21902			# this returns an error message to stdout in x, which breaks the version
21903			# xfce4-panel --version out of x fails to get display, so no data
21904			$desktop[1] = main::program_version("xfce${version}-panel",$data[0],$data[1],$data[2],$data[5],$data[6]);
21905			# out of x this kicks out an error: xfce4-panel: Cannot open display
21906			$desktop[1] = '' if $desktop[1] !~ /[0-9]\./;
21907		}
21908		$desktop[0] ||= 'Xfce';
21909		$desktop[1] ||= ''; # xfce isn't going to be 4 forever
21910		if ($extra > 1){
21911			@data = main::program_values('xfdesktop-toolkit');
21912			#$desktop[3] = main::program_version('xfdesktop',$data[0],$data[1],$data[2],$data[5],$data[6]);
21913			$desktop[3] = main::awk(\@version_data,$data[0],$data[1],'\s+');
21914			$desktop[2] = $data[3];
21915		}
21916	}
21917	elsif ($xdg_desktop eq 'moksha' || $gdmsession eq 'moksha' ||
21918	 (main::check_program('enlightenment') || main::check_program('moksha')) && main::awk(\@xprop,'moksha')){
21919		# no -v or --version but version is in xprop -root
21920		# ENLIGHTENMENT_VERSION(STRING) = "Moksha 0.2.0.15989"
21921		$desktop[0] = 'Moksha';
21922		$desktop[1] = main::awk(\@xprop,'(enlightenment|moksha)_version',2,'\s+=\s+');
21923		$desktop[1] =~ s/"?(Moksha|Enlightenment)\s([^"]+)"?/$2/i if $desktop[1];
21924	}
21925	elsif ($xdg_desktop eq 'enlightenment' || $gdmsession eq 'enlightenment' ||
21926	 (main::check_program('enlightenment') && main::awk(\@xprop,'enlightenment'))){
21927		# no -v or --version but version is in xprop -root
21928		# ENLIGHTENMENT_VERSION(STRING) = "Enlightenment 0.16.999.49898"
21929		$desktop[0] = 'Enlightenment';
21930		$desktop[1] = main::awk(\@xprop,'(enlightenment|moksha)_version',2,'\s+=\s+');
21931		$desktop[1] =~ s/"?(Moksha|Enlightenment)\s([^"]+)"?/$2/i if $desktop[1];
21932	}
21933	# the sequence here matters, some desktops like icewm, razor, let you set different
21934	# wm, so we want to get the main controlling desktop first, then fall back to the wm
21935	# detections. get_ps_de_data() and get_wm() will handle alternate wm detections.
21936	if (!$desktop[0]){
21937		# 0 check program; 1 xprop search; 2: data; 3 - optional: ps_gui search
21938		my @desktops =(
21939		['icewm','icewm','icewm'],
21940		# debian package: i3-wm
21941		['i3','i3','i3'],
21942		['mwm','^_motif','mwm'],
21943		# debian package name: wmaker
21944		['WindowMaker','^_?windowmaker','wmaker'],
21945		['wm2','^_wm2','wm2'],
21946		['herbstluftwm','herbstluftwm','herbstluftwm'],
21947		['fluxbox','blackbox_pid','fluxbox','^fluxbox$'],
21948		['blackbox','blackbox_pid','blackbox'],
21949		['openbox','openbox_pid','openbox'],
21950		['amiwm','amiwm','amiwm'],
21951		);
21952		foreach my $item (@desktops){
21953			if (main::check_program($item->[0]) && main::awk(\@xprop,$item->[1]) &&
21954			 (!$item->[4] || (@ps_gui && (grep {/$item->[4]/} @ps_gui)))){
21955				($desktop[0],$desktop[1]) =  main::program_data($item->[2]);
21956				last;
21957			}
21958		}
21959	}
21960	# need to check starts line because it's so short
21961	eval $end if $b_log;
21962}
21963sub get_ps_de_data {
21964	eval $start if $b_log;
21965	my ($program,@version_data);
21966	main::set_ps_gui() if !$loaded{'ps-gui'};
21967	if (@ps_gui){
21968		# the sequence here matters, some desktops like icewm, razor, let you set different
21969		# wm, so we want to get the main controlling desktop first
21970		# 1 check program; 2 ps_gui search; 3 data; 4: trigger alternate values/version
21971		my @desktops =(
21972		['icewm','icewm','icewm'],
21973		['WindowMaker','WindowMaker','wmaker',''],
21974		['2bwm','2bwm','2bwm',''],# unverified
21975		['9wm','9wm','9wm',''],
21976		['afterstep','afterstep','afterstep',''],
21977		['aewm++','aewm\+\+','aewm++',''],
21978		['aewm','aewm','aewm',''],
21979		['amiwm','amiwm','amiwm',''],
21980		['antiwm','antiwm','antiwm',''],
21981		['awesome','awesome','awesome',''],
21982		['blackbox','blackbox','blackbox',''],
21983		['bspwm','bspwm','bspwm',''],
21984		['cagebreak','cagebreak','cagebreak',''],
21985		['calmwm','calmwm','calmwm',''],
21986		['catwm','catwm','catwm',''],# unverified
21987		['clfswm','.*(sh|c?lisp)?.*clfswm','clfswm',''],
21988		['ctwm','ctwm','ctwm',''],
21989		['cwm','(openbsd-)?cwm','cwm',''],
21990		['dwm','dwm','dwm',''],
21991		['echinus','echinus','echinus',''],
21992		['evilwm','evilwm','evilwm',''],
21993		['fireplace','fireplace','fireplace',''],
21994		['fluxbox','fluxbox','fluxbox',''],
21995		['flwm','flwm','flwm',''],
21996		['flwm','flwm_topside','flwm',''],
21997		['fvwm-crystal','fvwm.*-crystal','fvwm-crystal','fvwm'],
21998		['fvwm1','fvwm1','fvwm1',''],
21999		['fvwm2','fvwm2','fvwm2',''],
22000		['fvwm3','fvwm3','fvwm3',''],
22001		['fvwm95','fvwm95','fvwm95',''],
22002		['fvwm','fvwm','fvwm',''],
22003		['glass','glass','glass',''],
22004		['hackedbox','hackedbox','hackedbox',''],
22005		['herbstluftwm','herbstluftwm','herbstluftwm'],
22006		['instantwm','instantwm','instantwm',''],
22007		['i3','i3','i3',''],
22008		['ion3','ion3','ion3',''],
22009		['jbwm','jbwm','jbwm',''],
22010		['jwm','jwm','jwm',''],
22011		['larswm','larswm','larswm',''],
22012		['leftwm','leftwm','leftwm',''],
22013		['lwm','lwm','lwm',''],
22014		['mcwm','mcwm','mcwm',''],# unverified
22015		['mini','mini','mini',''],
22016		['musca','musca','musca',''],
22017		['mvwm','mvwm','mvwm',''],
22018		['mwm','mwm','mwm',''],
22019		['nawm','nawm','nawm',''],
22020		['notion','notion','notion',''],
22021		['openbox','openbox','openbox',''],
22022		['orbital','orbital','orbital',''],
22023		['pekwm','pekwm','pekwm',''],
22024		['perceptia','perceptia','perceptia',''],
22025		['penrose','penrose','penrose',''],# unverified
22026		['qtile','.*(python.*)?qtile','qtile',''],
22027		['qvwm','qvwm','qvwm',''],
22028		['ratpoison','ratpoison','ratpoison',''],
22029		['sawfish','sawfish','sawfish',''],
22030		['scrotwm','scrotwm','scrotwm',''],
22031		['snapwm','snapwm','snapwm',''],# unverified
22032		['spectrwm','spectrwm','spectrwm',''],
22033		['stumpwm','(sh|c?lisp)?.*stumpwm','stumpwm',''],
22034		['sway','sway','sway',''],
22035		['matchbox-window-manager','matchbox-window-manager','matchbox-window-manager',''],
22036		['tinywm','tinywm','tinywm',''],
22037		['tvtwm','tvtwm','tvtwm',''],
22038		['twm','twm','twm',''],
22039		['uwm','uwm','uwm',''],# unverified
22040		['waycooler','waycooler','way-cooler',''],
22041		['way-cooler','way-cooler','way-cooler',''],
22042		['windowlab','windowlab','windowlab',''],
22043		['wmfs','wmfs','wmfs',''],# unverified
22044		['wmfs2','wmfs2','wmfs2',''],# unverified
22045		['wingo','wingo','wingo',''],# unverified
22046		# not in debian apt, current is wmii, version 3
22047		['wmii2','wmii2','wmii2',''],
22048		['wmii','wmii','wmii',''],
22049		['wmx','wmx','wmx',''],
22050		['xmonad','xmonad','xmonad',''],
22051		## fallback for xfce in case no xprop
22052		['xfdesktop','xfdesktop','xfdesktop',''],
22053		['yeahwm','yeahwm','yeahwm',''],
22054		);
22055		foreach my $item (@desktops){
22056			# no need to use check program with short list of ps_gui
22057			if (grep {/^$item->[1]$/} @ps_gui){
22058				($desktop[0],$desktop[1]) =  main::program_data($item->[2],$item->[3]);
22059				if ($extra > 1 && $item->[0] eq 'xfdesktop'){
22060					($desktop[2],$desktop[3]) =  main::program_data('xfdesktop-toolkit',$item->[0],1);
22061				}
22062				last;
22063			}
22064		}
22065	}
22066	eval $end if $b_log;
22067}
22068# NOTE: used to use a super slow method here, but gtk-launch returns
22069# the gtk version I believe
22070sub set_gtk_data {
22071	eval $start if $b_log;
22072	if (main::check_program('gtk-launch')){
22073		($desktop[2],$desktop[3]) = main::program_data('gtk-launch');
22074	}
22075	eval $end if $b_log;
22076}
22077sub set_qt_data {
22078	eval $start if $b_log;
22079	my ($program,@data,@version_data);
22080	my $kde_version = $kde_session_version;
22081	$program = '';
22082	if (!$kde_version){
22083		if ($program = main::check_program("kded6")){$kde_version = 6;}
22084		elsif ($program = main::check_program("kded5")){$kde_version = 5;}
22085		elsif ($program = main::check_program("kded4")){$kde_version = 4;}
22086		elsif ($program = main::check_program("kded")){$kde_version = '';}
22087	}
22088	# alternate: qt4-default, qt4-qmake or qt5-default, qt5-qmake
22089	# often this exists, is executable, but actually is nothing, shows error
22090	if (!$desktop[3] && main::check_program('qmake')){
22091		($desktop[2],$desktop[3]) = main::program_data('qmake');
22092	}
22093	if (!$desktop[3] && main::check_program('qtdiag')){
22094		($desktop[2],$desktop[3]) = main::program_data('qtdiag');
22095	}
22096	if (!$desktop[3] && ($program = main::check_program("kf$kde_version-config"))){
22097		@version_data = main::grabber("$program --version 2>/dev/null");
22098		$desktop[2] = 'Qt';
22099		$desktop[3] = main::awk(\@version_data,'^Qt:',2) if @version_data;
22100	}
22101	# note: qt 5 does not show qt version in kded5, sigh
22102	if (!$desktop[3] && ($program = main::check_program("kded$kde_version"))){
22103		@version_data = main::grabber("$program --version 2>/dev/null");
22104		$desktop[2] = 'Qt';
22105		$desktop[3] = main::awk(\@version_data,'^Qt:',2) if @version_data;
22106	}
22107	eval $end if $b_log;
22108}
22109
22110sub get_wm {
22111	eval $start if $b_log;
22112	if (!$force{'wmctrl'}){
22113		get_wm_main();
22114	}
22115	# note, some wm, like cinnamon muffin, do not appear in ps aux, but do in wmctrl
22116	if ((!$desktop[5] || $force{'wmctrl'}) && (my $program = main::check_program('wmctrl'))){
22117		get_wm_wmctrl($program);
22118	}
22119	eval $end if $b_log;
22120}
22121sub get_wm_main {
22122	eval $start if $b_log;
22123	my ($wms,$working);
22124	# xprop is set only if not kde/gnome/cinnamon/mate/budgie/lx..
22125	if ($b_xprop){
22126		#KWIN_RUNNING
22127		$wms = 'amiwm|blackbox|bspwm|compiz|kwin_wayland|kwin_x11|kwin|marco|';
22128		$wms .= 'motif|muffin|openbox|herbstluftwm|twin|ukwm|wm2|windowmaker|i3';
22129		foreach (@xprop){
22130			if (/($wms)/){
22131				$working = $1;
22132				$working = 'wmaker' if $working eq 'windowmaker';
22133				last;
22134			}
22135		}
22136	}
22137	if (!$desktop[5]){
22138		main::set_ps_gui() if !$loaded{'ps-gui'};
22139		# order matters, see above logic
22140		# due to lisp/python starters, clfswm/stumpwm/qtile will not detect here
22141		$wms = '2bwm|9wm|aewm\+\+|aewm|afterstep|amiwm|antiwm|awesome|blackbox|';
22142		$wms .= 'cagebreak|calmwm|catwm|clfswm|compiz|ctwm|(openbsd-)?cwm|fluxbox';
22143		$wms .= '|bspwm|budgie-wm|deepin-wm|dwm|echinus|evilwm|';
22144		$wms .= 'fireplace|flwm|fvwm-crystal|fvwm1|fvwm2|fvwm3|fvwm95|fvwm|';
22145		$wms .= 'gala|glass|gnome-shell|hackedbox|i3|instantwm|ion3|jbwm|jwm|';
22146		$wms .= 'twin|kwin_wayland|kwin_x11|kwin|larswm|leftwm|lwm|';
22147		$wms .= 'matchbox-window-manager|marco|mcwm|mini|muffin|';
22148		$wms .= 'musca|deepin-mutter|mutter|deepin-metacity|metacity|mvwm|mwm|';
22149		$wms .= 'nawm|notion|openbox|orbital|perceptia|qtile|qvwm|';
22150		$wms .= 'penrose|ratpoison|sawfish|scrotwm|snapwm|spectrwm|';
22151		$wms .= 'stumpwm|sway|tinywm|tvtwm|twm|ukwm|';
22152		$wms .= 'way-?cooler|windowlab|WindowMaker|wingo|wm2|wmfs2?|wmii2?|wmx|';
22153		$wms .= 'xfwm[45]?|xmonad|yeahwm';
22154		foreach (@ps_gui){
22155			if (/^($wms)$/){
22156				$working = $1;
22157				last;
22158			}
22159		}
22160	}
22161	get_wm_version('manual',$working) if $working;
22162	$desktop[5] = $working if !$desktop[5] && $working;
22163	eval $end if $b_log;
22164}
22165sub get_wm_wmctrl {
22166	eval $start if $b_log;
22167	my ($program) = @_;
22168	my $cmd = "$program -m 2>/dev/null";
22169	my @data = main::grabber($cmd,'','strip');
22170	main::log_data('dump','@data',\@data) if $b_log;
22171	$desktop[5] = main::awk(\@data,'^Name',2,'\s*:\s*');
22172	$desktop[5] = '' if $desktop[5] && $desktop[5] eq 'N/A';
22173	if ($desktop[5]){
22174		# variants: gnome shell;
22175		# IceWM 1.3.8 (Linux 3.2.0-4-amd64/i686) ; Metacity (Marco) ; Xfwm4
22176		$desktop[5] =~ s/\d+\.\d\S+|[\[\(].*\d+\.\d.*[\)\]]//g;
22177		$desktop[5] = main::trimmer($desktop[5]);
22178		# change Metacity (Marco) to marco
22179		if ($desktop[5] =~ /marco/i){$desktop[5] = 'marco'}
22180		elsif ($desktop[5] =~ /muffin/i){$desktop[5] = 'muffin'}
22181		elsif (lc($desktop[5]) eq 'gnome shell'){$desktop[5] = 'gnome-shell'}
22182		elsif ($desktop_session eq 'trinity' && lc($desktop[5]) eq 'kwin'){$desktop[5] = 'Twin'}
22183		get_wm_version('wmctrl',$desktop[5]);
22184	}
22185	eval $end if $b_log;
22186}
22187sub get_wm_version {
22188	eval $start if $b_log;
22189	my ($type,$wm) = @_;
22190	# we don't want the gnome-shell version, and the others have no --version
22191	# we also don't want to run --version again on stuff we already have tested
22192	return if !$wm || $wm =~ /^(budgie-wm|gnome-shell)$/ || ($desktop[0] && lc($desktop[0]) eq lc($wm));
22193	my $temp = (split(/\s+/, $wm))[0];
22194	if ($temp){
22195		$temp = (split(/\s+/, $temp))[0];
22196		$temp = lc($temp);
22197		$temp = 'wmaker' if $temp eq 'windowmaker';
22198		my @data = main::program_data($temp,$temp,3);
22199		return if !$data[0];
22200		# print Data::Dumper::Dumper \@data;
22201		$desktop[5] = $data[0] if $type eq 'manual';
22202		$desktop[6] = $data[1] if $data[1];
22203	}
22204	eval $end if $b_log;
22205}
22206
22207sub set_info_data {
22208	eval $start if $b_log;
22209	main::set_ps_gui() if !$loaded{'ps-gui'};
22210	my (@data,@info,$item);
22211	my $pattern = 'alltray|awn|bar|bmpanel|bmpanel2|budgie-panel|cairo-dock|';
22212	$pattern .= 'dde-dock|dmenu|dockbarx|docker|docky|dzen|dzen2|';
22213	$pattern .= 'fancybar|fbpanel|fspanel|glx-dock|gnome-panel|hpanel|i3bar|i3status|icewmtray|';
22214	$pattern .= 'kdocker|kicker|';
22215	$pattern .= 'latte|latte-dock|lemonbar|ltpanel|lxpanel|lxqt-panel|';
22216	$pattern .= 'matchbox-panel|mate-panel|ourico|';
22217	$pattern .= 'perlpanel|plank|plasma-desktop|plasma-netbook|polybar|pypanel|';
22218	$pattern .= 'razor-panel|razorqt-panel|stalonetray|swaybar|taskbar|tint2|trayer|';
22219	$pattern .= 'ukui-panel|vala-panel|wbar|wharf|wingpanel|witray|';
22220	$pattern .= 'xfce4-panel|xfce5-panel|xmobar|yabar';
22221	if (@data = grep {/^($pattern)$/} @ps_gui){
22222		# only one entry per type, can be multiple
22223		foreach $item (@data){
22224			if (! grep {$item =~ /$_/} @info){
22225				$item = main::trimmer($item);
22226				$item =~ s/.*\///;
22227				push(@info, (split(/\s+/, $item))[0]);
22228			}
22229		}
22230	}
22231	if (@info){
22232		main::uniq(\@info);
22233		$desktop[4] = join(', ', @info);
22234	}
22235	eval $end if $b_log;
22236}
22237
22238sub set_xprop {
22239	eval $start if $b_log;
22240	if (my $program = main::check_program('xprop')){
22241		@xprop = main::grabber("xprop -root $display_opt 2>/dev/null");
22242		if (@xprop){
22243			# add wm / de as required, but only add what is really tested for above
22244			# XFDESKTOP_IMAGE_FILE; XFCE_DESKTOP
22245			my $pattern = '^amiwm|blackbox_pid|bspwm|compiz|enlightenment|^_gnome|';
22246			$pattern .= 'herbstluftwm|^kwin_|^i3_|icewm|_marco|moksha|^_motif|_muffin|';
22247			$pattern .= 'openbox_pid|^_ukwm|^_?windowmaker|^_wm2|^(xfdesktop|xfce)';
22248			# let's only do these searches once
22249			@xprop = grep {/^\S/ && /($pattern)/i} @xprop;
22250			$_ = lc for @xprop;
22251			$b_xprop = 1 if scalar @xprop > 0;
22252		}
22253	}
22254	# print "@xprop\n";
22255	eval $end if $b_log;
22256}
22257
22258}
22259
22260## DeviceData / PCI / SOC
22261# creates arrays: $devices{'audio'}; $devices{'graphics'}; $devices{'hwraid'};
22262# $devices{'network'}; $devices{'timer'} and local @devices for logging/debugging
22263# 0 type
22264# 1 type_id
22265# 2 bus_id
22266# 3 sub_id
22267# 4 device
22268# 5 vendor_id
22269# 6 chip_id
22270# 7 rev
22271# 8 port
22272# 9 driver
22273# 10 modules
22274# 11 driver_nu [bsd, like: em0 - driver em; nu 0. Used to match IF in -n
22275# 12 subsystem/vendor
22276# 13 subsystem vendor_id:chip id
22277# 14 soc handle
22278# 15 serial number
22279{
22280package DeviceData;
22281my (@bluetooth,@devices,@files,@full_names,@pcis,@temp,@temp2,@temp3,%lspci_n);
22282my ($b_bt_check,$b_lspci_n);
22283my ($busid,$busid_nu,$chip_id,$content,$device,$driver,$driver_nu,$file,
22284$handle,$modules,$port,$rev,$serial,$temp,$type,$type_id,$vendor,$vendor_id);
22285
22286sub set {
22287	eval $start if $b_log;
22288	${$_[0]} = 1; # set check by reference
22289	if ($use{'pci'}){
22290		if (!$bsd_type){
22291			if ($alerts{'lspci'}->{'action'} eq 'use'){
22292				lspci_data();
22293			}
22294			# ! -d '/proc/bus/pci'
22295			# this is sketchy, a sbc won't have pci, but a non sbc arm may have it, so
22296			# build up both and see what happens
22297			if ($b_arm || $b_mips || $b_ppc || $b_sparc){
22298				soc_data();
22299			}
22300		}
22301		else {
22302			# if (1 == 1){
22303			if ($alerts{'pciconf'}->{'action'} eq 'use'){
22304				pciconf_data();
22305			}
22306			elsif ($alerts{'pcidump'}->{'action'} eq 'use'){
22307				pcidump_data();
22308			}
22309			elsif ($alerts{'pcictl'}->{'action'} eq 'use'){
22310				pcictl_data();
22311			}
22312		}
22313		if ($dbg[9]){
22314			print Data::Dumper::Dumper $devices{'audio'};
22315			print Data::Dumper::Dumper $devices{'bluetooth'};
22316			print Data::Dumper::Dumper $devices{'graphics'};
22317			print Data::Dumper::Dumper $devices{'network'};
22318			print Data::Dumper::Dumper $devices{'hwraid'};
22319			print Data::Dumper::Dumper $devices{'timer'};
22320			print "vm: $device_vm\n";
22321		}
22322		if ($b_log){
22323			main::log_data('dump','$devices{audio}',$devices{'audio'});
22324			main::log_data('dump','$devices{bluetooth}',$devices{'bluetooth'});
22325			main::log_data('dump','$devices{graphics}',$devices{'graphics'});
22326			main::log_data('dump','$devices{hwraid}',$devices{'hwraid'});
22327			main::log_data('dump','$devices{network}',$devices{'network'});
22328			main::log_data('dump','$devices{timer}',$devices{'timer'});
22329		}
22330	}
22331	@devices = undef;
22332	eval $end if $b_log;
22333}
22334
22335sub lspci_data {
22336	eval $start if $b_log;
22337	my ($busid_full,$subsystem,$subsystem_id);
22338	my @data = pci_grabber('lspci');
22339	# print Data::Dumper::Dumper \@data;
22340	foreach (@data){
22341		# print "$_\n";
22342		if ($device){
22343			if ($_ =~ /^~$/){
22344				@temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22345				$rev,$port,$driver,$modules,$driver_nu,$subsystem,$subsystem_id);
22346				assign_data('pci',\@temp);
22347				$device = '';
22348				# print "$busid $device_id r:$rev p: $port\n$type\n$device\n";
22349			}
22350			elsif ($_ =~ /^Subsystem.*\[([a-f0-9]{4}:[a-f0-9]{4})\]/){
22351				$subsystem_id = $1;
22352				$subsystem = (split(/^Subsystem:\s*/, $_))[1];
22353				$subsystem =~ s/(\s?\[[^\]]+\])+$//g;
22354				$subsystem = main::cleaner($subsystem);
22355				$subsystem = main::pci_cleaner($subsystem,'pci');
22356				$subsystem = main::pci_cleaner_subsystem($subsystem);
22357				# print "ss:$subsystem\n";
22358			}
22359			elsif ($_ =~ /^I\/O\sports/){
22360				$port = (split(/\s+/, $_))[3];
22361				# print "p:$port\n";
22362			}
22363			elsif ($_ =~ /^Kernel\sdriver\sin\suse/){
22364				$driver = (split(/:\s*/, $_))[1];
22365			}
22366			elsif ($_ =~ /^Kernel\smodules/i){
22367				$modules = (split(/:\s*/, $_))[1];
22368			}
22369		}
22370		# note: arm servers can have more complicated patterns
22371		# 0002:01:02.0 Ethernet controller [0200]: Cavium, Inc. THUNDERX Network Interface Controller virtual function [177d:a034] (rev 08)
22372		# seen cases of lspci trimming too long lines like this:
22373		# 01:00.0 Display controller [0380]: Advanced Micro Devices, Inc. [AMD/ATI] Topaz XT [Radeon R7 M260/M265 / M340/M360 / M440/M445 / 530/535 / 620/625 Mobile] [10... (rev c3) (prog-if 00 [Normal decode])
22374		# \s(.*)\s\[([0-9a-f]{4}):([0-9a-f]{4})\](\s\(rev\s([^\)]+)\))?
22375		elsif ($_ =~ /^((([0-9a-f]{2,4}:)?[0-9a-f]{2}:[0-9a-f]{2})[.:]([0-9a-f]+))\s+/){
22376			$busid_full = $1;
22377			$busid = $2;
22378			$busid_nu = hex($4);
22379			($chip_id,$rev,$type,$type_id,$vendor_id) = ('','','','','');
22380			$_ =~ s/^\Q$busid_full\E\s+//;
22381			# old systems didn't use [...] but type will get caught in lspci_n check
22382			if ($_ =~ /^(([^\[]+?)\s+\[([a-f0-9]{4})\]:\s+)/){
22383				$type = $2;
22384				$type_id = $3;
22385				$_ =~ s/^\Q$1\E//;
22386				$type = lc($type);
22387				$type = main::pci_cleaner($type,'pci');
22388				$type =~ s/\s+$//;
22389			}
22390			# trim off end prog-if and rev items
22391			if ($_ =~ /(\s+\(prog[^\)]+\))/){
22392				$_ =~ s/\Q$1\E//;
22393			}
22394			if ($_ =~ /(\s+\(rev\s+[^\)]+\))/){
22395				$rev = $2;
22396				$_ =~ s/\Q$1\E//;
22397			}
22398			# get rid of anything in parentheses at end in case other variants show
22399			# up, which they probably will.
22400			if ($_ =~ /((\s+\([^\)]+\))+)$/){
22401				$_ =~ s/\Q$1\E//;
22402			}
22403			if ($_ =~ /(\s+\[([0-9a-f]{4}):([0-9a-f]{4})\])$/){
22404				$vendor_id = $2;
22405				$chip_id = $3;
22406				$_ =~ s/\Q$1\E//;
22407			}
22408			# lspci -nnv string trunctation bug
22409			elsif ($_ =~ /(\s+\[[^\]]*\.\.\.)$/){
22410				$_ =~ s/\Q$1\E//;
22411			}
22412			$device = $_;
22413			# cases of corrupted string set to ''
22414			$device = main::cleaner($device);
22415			# corrupted lspci truncation bug; and ancient lspci, 2.4 kernels
22416			if (!$vendor_id){
22417				my @temp = lspci_n_data($busid_full);
22418				if (@temp){
22419					$vendor_id = $temp[1];
22420					$chip_id = $temp[2];
22421					$type_id = $temp[0] if !$type_id;
22422					$rev = $temp[3] if !$rev && $temp[3];
22423				}
22424			}
22425			$use{'hardware-raid'} = 1 if $type_id eq '0104';
22426			($driver,$driver_nu,$modules,$port,$subsystem,$subsystem_id) = ('','','','','','');
22427		}
22428	}
22429	print Data::Dumper::Dumper \@devices if $dbg[4];
22430	main::log_data('dump','lspci @devices',\@devices) if $b_log;
22431	eval $end if $b_log;
22432}
22433# arg: $1 - busID
22434# returns if valid busID: (classID,vendorID,productID,revNu)
22435# almost never used, only in case of lspci -nnv line truncation bug
22436sub lspci_n_data {
22437	eval $start if $b_log;
22438	my ($bus_id) = @_;
22439	if (!$b_lspci_n){
22440		$b_lspci_n = 1;
22441		my (@data);
22442		if ($fake{'lspci'}){
22443			# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/steve-mint-topaz-lspci-n.txt";
22444			# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/ben81-hwraid-lspci-n.txt";
22445			# @data = main::reader($file,'strip');
22446		}
22447		else {
22448			@data = main::grabber($alerts{'lspci'}->{'path'} . ' -n 2>/dev/null','','strip');
22449		}
22450		foreach (@data){
22451			if (/^([a-f0-9:\.]+)\s+([a-f0-9]{4}):\s+([a-f0-9]{4}):([a-f0-9]{4})(\s+\(rev\s+([0-9a-z\.]+)\))?/){
22452				my $rev = (defined $6) ? $6 : '';
22453				$lspci_n{$1} = [$2,$3,$4,$rev];
22454			}
22455		}
22456		print Data::Dumper::Dumper \%lspci_n if $dbg[4];
22457		main::log_data('dump','%lspci_n',\%lspci_n) if $b_log;
22458	}
22459	my @return = ($lspci_n{$bus_id}) ? @{$lspci_n{$bus_id}}: ();
22460	print Data::Dumper::Dumper \@return if $dbg[4];
22461	main::log_data('dump','@return',\@return) if $b_log;
22462	eval $end if $b_log;
22463	return @return;
22464}
22465
22466# em0@pci0:6:0:0:	class=0x020000 card=0x10d315d9 chip=0x10d38086 rev=0x00 hdr=0x00
22467#     vendor     = 'Intel Corporation'
22468#     device     = 'Intel 82574L Gigabit Ethernet Controller (82574L)'
22469#     class      = network
22470#     subclass   = ethernet
22471sub pciconf_data {
22472	eval $start if $b_log;
22473	my @data = pci_grabber('pciconf');
22474	foreach (@data){
22475		if ($driver){
22476			if ($_ =~ /^~$/){
22477				$vendor = main::cleaner($vendor);
22478				$device = main::cleaner($device);
22479				# handle possible regex in device name, like [ConnectX-3]
22480				# and which could make matches fail
22481				my $device_temp = main::regex_cleaner($device);
22482				if ($vendor && $device){
22483					if (main::regex_cleaner($vendor) !~ /\Q$device_temp\E/i){
22484						$device = "$vendor $device";
22485					}
22486				}
22487				elsif (!$device){
22488					$device = $vendor;
22489				}
22490				@temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22491				$rev,$port,$driver,$modules,$driver_nu);
22492				assign_data('pci',\@temp);
22493				$driver = '';
22494				# print "$busid $device_id r:$rev p: $port\n$type\n$device\n";
22495			}
22496			elsif ($_ =~ /^vendor/){
22497				$vendor = (split(/\s+=\s+/, $_))[1];
22498				# print "p:$port\n";
22499			}
22500			elsif ($_ =~ /^device/){
22501				$device = (split(/\s+=\s+/, $_))[1];
22502			}
22503			elsif ($_ =~ /^class/i){
22504				$type = (split(/\s+=\s+/, $_))[1];
22505			}
22506		}
22507		# pre freebsd 13, note chip is product+vendor
22508		# atapci0@pci0:0:1:1:	class=0x01018a card=0x00000000 chip=0x71118086 rev=0x01 hdr=0x00
22509		# freebsd 13
22510		# isab0@pci0:0:1:0:	class=0x060100 rev=0x00 hdr=0x00 vendor=0x8086 device=0x7000 subvendor=0x0000 subdevice=0x0000
22511		if (/^([^@]+)\@pci([0-9]{1,3}:[0-9]{1,3}:[0-9]{1,3}):([0-9]{1,3}):/){
22512			$driver = $1;
22513			$busid = $2;
22514			$busid_nu = $3;
22515			$driver = $1;
22516			$driver =~ s/([0-9]+)$//;
22517			$driver_nu = $1;
22518			# we don't use the sub sub class part of the class id, just first 4
22519			if (/\bclass=0x([\S]{4})\S*\b/){
22520				$type_id = $1;
22521			}
22522			if (/\brev=0x([\S]+)\b/){
22523				$rev = $1;
22524			}
22525			if (/\bvendor=0x([\S]+)\b/){
22526				$vendor_id = $1;
22527			}
22528			if (/\bdevice=0x([\S]+)\b/){
22529				$chip_id = $1;
22530			}
22531			# yes, they did it backwards, product+vendor id
22532			if (/\bchip=0x([a-f0-9]{4})([a-f0-9]{4})\b/){
22533				$chip_id = $1;
22534				$vendor_id = $2;
22535			}
22536			($device,$type,$vendor) = ('','','');
22537		}
22538	}
22539	print Data::Dumper::Dumper \@devices if $dbg[4];
22540	main::log_data('dump','pciconf @devices',\@devices) if $b_log;
22541	eval $end if $b_log;
22542}
22543
22544sub pcidump_data {
22545	eval $start if $b_log;
22546	my @data = pci_grabber('pcidump');
22547	main::set_dboot_data() if !$loaded{'dboot'};
22548	foreach (@data){
22549		if ($_ =~ /^~$/ && $busid && $device){
22550			@temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22551			$rev,$port,$driver,$modules,$driver_nu,'','','',$serial);
22552			assign_data('pci',\@temp);
22553			($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22554			$rev,$port,$driver,$modules,$driver_nu,$serial) = undef;
22555			next;
22556		}
22557		if ($_ =~ /^([0-9a-f:]+):([0-9]+):\s([^:]+)$/i){
22558			$busid = $1;
22559			$busid_nu = $2;
22560			($driver,$driver_nu) = pcidump_driver("$busid:$busid_nu") if $dboot{'pci'};
22561			$device = main::cleaner($3);
22562		}
22563		elsif ($_ =~ /^0x[\S]{4}:\s+Vendor ID:\s+([0-9a-f]{4}),?\s+Product ID:\s+([0-9a-f]{4})/){
22564			$vendor_id = $1;
22565			$chip_id = $2;
22566		}
22567		elsif ($_ =~ /^0x[\S]{4}:\s+Class:\s+([0-9a-f]{2})(\s[^,]+)?,?\s+Subclass:\s+([0-9a-f]{2})(\s+[^,]+)?,?(\s+Interface: ([0-9a-f]+),?\s+Revision: ([0-9a-f]+))?/){
22568			$type = pci_class($1);
22569			$type_id = "$1$3";
22570		}
22571		elsif (/^Serial Number:\s*(\S+)/){
22572			$serial = $1;
22573		}
22574	}
22575	print Data::Dumper::Dumper \@devices if $dbg[4];
22576	main::log_data('dump','pcidump @devices',\@devices) if $b_log;
22577	eval $end if $b_log;
22578}
22579sub pcidump_driver {
22580	eval $start if $b_log;
22581	my $bus_id = $_[0];
22582	my ($driver,$nu);
22583	for (@{$dboot{'pci'}}){
22584		if (/^$bus_id:([^0-9]+)([0-9]+):/){
22585			$driver = $1;
22586			$nu = $2;
22587			last;
22588		}
22589	}
22590	eval $end if $b_log;
22591	return ($driver,$nu);
22592}
22593sub pcictl_data {
22594	eval $start if $b_log;
22595	my @data = pci_grabber('pcictl');
22596	my @data2 = pci_grabber('pcictl-n');
22597	foreach (@data){
22598		if ($_ =~ /^~$/ && $busid && $device){
22599			@temp = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22600			$rev,$port,$driver,$modules,$driver_nu);
22601			assign_data('pci',\@temp);
22602			($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,
22603			$rev,$port,$driver,$modules,$driver_nu) = undef;
22604			next;
22605		}
22606		# it's too fragile to get these in one matching so match, trim, next match
22607		if (/\s+\[([^\]0-9]+)([0-9]+)\]$/){
22608			$driver = $1;
22609			$driver_nu = $2;
22610			$_ =~ s/\s+\[[^\]]+\]$//;
22611		}
22612		if (/\s+\(.*?(revision 0x([^\)]+))?\)/){
22613			$rev = $2 if $2;
22614			$_ =~ s/\s+\([^\)]+?\)$//;
22615		}
22616		if ($_ =~ /^([0-9a-f:]+):([0-9]+):\s+([^.]+?)$/i){
22617			$busid = $1;
22618			$busid_nu = $2;
22619			$device = main::cleaner($3);
22620			my $working = (grep {/^${busid}:${busid_nu}:\s/} @data2)[0];
22621			if ($working &&
22622			 $working =~ /^${busid}:${busid_nu}:\s+0x([0-9a-f]{4})([0-9a-f]{4})\s+\(0x([0-9a-f]{2})([0-9a-f]{2})[0-9a-f]+\)/){
22623				$vendor_id = $1;
22624				$chip_id = $2;
22625				$type = pci_class($3);
22626				$type_id = "$3$4";
22627			}
22628		}
22629	}
22630	print Data::Dumper::Dumper \@devices if $dbg[4];
22631	main::log_data('dump','pcidump @devices',\@devices) if $b_log;
22632	eval $end if $b_log;
22633}
22634
22635sub pci_grabber {
22636	eval $start if $b_log;
22637	my ($program) = @_;
22638	my ($args,$path,$pattern,@data,@working);
22639	if ($program eq 'lspci'){
22640		# 2.2.8 lspci did not support -k, added in 2.2.9, but -v turned on -k
22641		$args = ' -nnv';
22642		$path = $alerts{'lspci'}->{'path'};
22643		$pattern = '^[0-9a-f]+:';
22644	}
22645	elsif ($program eq 'pciconf'){
22646		$args = ' -lv';
22647		$path = $alerts{'pciconf'}->{'path'};
22648		$pattern = '^([^@]+)\@pci';
22649	}
22650	elsif ($program eq 'pcidump'){
22651		$args = ' -v';
22652		$path = $alerts{'pcidump'}->{'path'};
22653		$pattern = '^[0-9a-f]+:';
22654	}
22655	elsif ($program eq 'pcictl'){
22656		$args = ' pci0 list -N';
22657		$path = $alerts{'pcictl'}->{'path'};
22658		$pattern = '^[0-9a-f:]+:';
22659	}
22660	elsif ($program eq 'pcictl-n'){
22661		$args = ' pci0 list -n';
22662		$path = $alerts{'pcictl'}->{'path'};
22663		$pattern = '^[0-9a-f:]+:';
22664	}
22665	if ($fake{'lspci'} || $fake{'pciconf'} || $fake{'pcictl'} || $fake{'pcidump'}){
22666		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/pciconf/pci-freebsd-8.2-2";
22667		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/pcidump/pci-openbsd-6.1-vm.txt";
22668		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/pcictl/pci-netbsd-9.1-vm.txt";
22669		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/racermach-1-knnv.txt";
22670		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/rk016013-knnv.txt";
22671		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/kot--book-lspci-nnv.txt";
22672		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/steve-mint-topaz-lspci-nnkv.txt";
22673		# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/lspci/ben81-hwraid-lspci-nnv.txt";
22674		# @data = main::reader($file,'strip');
22675	}
22676	else {
22677		@data = main::grabber("$path $args 2>/dev/null",'','strip');
22678	}
22679	if (@data){
22680		$use{'pci-tool'} = 1 if scalar @data > 10;
22681		foreach (@data){
22682			# this is the group separator and assign trigger
22683			if ($_ =~ /$pattern/i){
22684				push(@working, '~');
22685			}
22686			push(@working, $_);
22687		}
22688		push(@working, '~');
22689	}
22690	print Data::Dumper::Dumper \@working if $dbg[30];
22691	eval $end if $b_log;
22692	return @working;
22693}
22694
22695sub soc_data {
22696	eval $start if $b_log;
22697	soc_devices_files();
22698	soc_devices();
22699	soc_devicetree();
22700	print Data::Dumper::Dumper \@devices if $dbg[4];
22701	main::log_data('dump','soc @devices',\@devices) if $b_log;
22702	eval $end if $b_log;
22703}
22704# 1: /sys/devices/platform/soc/1c30000.ethernet/uevent:["DRIVER=dwmac-sun8i", "OF_NAME=ethernet",
22705# "OF_FULLNAME=/soc/ethernet@1c30000", "OF_COMPATIBLE_0=allwinner,sun8i-h3-emac",
22706# "OF_COMPATIBLE_N=1", "OF_ALIAS_0=ethernet0", # "MODALIAS=of:NethernetT<NULL>Callwinner,sun8i-h3-emac"]
22707# 2: /sys/devices/platform/soc:audio/uevent:["DRIVER=bcm2835_audio", "OF_NAME=audio", "OF_FULLNAME=/soc/audio",
22708# "OF_COMPATIBLE_0=brcm,bcm2835-audio", "OF_COMPATIBLE_N=1", "MODALIAS=of:NaudioT<NULL>Cbrcm,bcm2835-audio"]
22709# 3: /sys/devices/platform/soc:fb/uevent:["DRIVER=bcm2708_fb", "OF_NAME=fb", "OF_FULLNAME=/soc/fb",
22710# "OF_COMPATIBLE_0=brcm,bcm2708-fb", "OF_COMPATIBLE_N=1", "MODALIAS=of:NfbT<NULL>Cbrcm,bcm2708-fb"]
22711# 4: /sys/devices/platform/soc/1c40000.gpu/uevent:["OF_NAME=gpu", "OF_FULLNAME=/soc/gpu@1c40000",
22712# "OF_COMPATIBLE_0=allwinner,sun8i-h3-mali", "OF_COMPATIBLE_1=allwinner,sun7i-a20-mali",
22713# "OF_COMPATIBLE_2=arm,mali-400", "OF_COMPATIBLE_N=3",
22714# "MODALIAS=of:NgpuT<NULL>Callwinner,sun8i-h3-maliCallwinner,sun7i-a20-maliCarm,mali-400"]
22715# 5: /sys/devices/platform/soc/soc:internal-regs/d0018180.gpio/uevent
22716# 6: /sys/devices/soc.0/1180000001800.mdio/8001180000001800:05/uevent
22717#  ["DRIVER=AR8035", "OF_NAME=ethernet-phy"
22718# 7: /sys/devices/soc.0/1c30000.eth/uevent
22719# 8: /sys/devices/wlan.26/uevent [from pine64]
22720# 9: /sys/devices/platform/audio/uevent:["DRIVER=bcm2835_AUD0", "OF_NAME=audio"
22721# 10: /sys/devices/vio/71000002/uevent:["DRIVER=ibmveth", "OF_NAME=l-lan"
22722# 11: /sys/devices/platform/soc:/soc:i2c-hdmi:/i2c-2/2-0050/uevent:['OF_NAME=hdmiddc'
22723# 12: /sys/devices/platform/soc:/soc:i2c-hdmi:/uevent:['DRIVER=i2c-gpio', 'OF_NAME=i2c-hdmi'
22724# 13: /sys/devices/platform/scb/fd580000.ethernet/uevent
22725# 14: /sys/devices/platform/soc/fe300000.mmcnr/mmc_host/mmc1/mmc1:0001/mmc1:0001:1/uevent (wifi, pi 3,4)
22726# 15: Pi BT: /sys/devices/platform/soc/fe201000.serial/uevent
22727# 16: Pi BT: /sys/devices/platform/soc/fe201000.serial/tty/ttyAMA0/hci0
22728sub soc_devices_files {
22729	eval $start if $b_log;
22730	if (-d '/sys/devices/platform/'){
22731		@files = main::globber('/sys/devices/platform/soc*/*/uevent');
22732		@temp2 = main::globber('/sys/devices/platform/soc*/*/*/uevent');
22733		push(@files,@temp2) if @temp2;
22734		if (-e '/sys/devices/platform/scb'){
22735			@temp2 = main::globber('/sys/devices/platform/scb/*/uevent');
22736			push(@files,@temp2) if @temp2;
22737			@temp2 = main::globber('/sys/devices/platform/scb/*/*/uevent');
22738			push(@files,@temp2) if @temp2;
22739		}
22740		@temp2 = main::globber('/sys/devices/platform/*/uevent');
22741		push(@files,@temp2) if @temp2;
22742	}
22743	if (main::globber('/sys/devices/soc*')){
22744		@temp2 = main::globber('/sys/devices/soc*/*/uevent');
22745		push(@files,@temp2) if @temp2;
22746		@temp2 = main::globber('/sys/devices/soc*/*/*/uevent');
22747		push(@files,@temp2) if @temp2;
22748	}
22749	@temp2 = main::globber('/sys/devices/*/uevent'); # see case 8
22750	push(@files,@temp2) if @temp2;
22751	@temp2 = main::globber('/sys/devices/*/*/uevent'); # see case 10
22752	push(@files,@temp2) if @temp2;
22753	@temp2 = undef;
22754	# not sure why, but even as root/sudo, /subsystem|driver/uevent are unreadable with -r test true
22755	@files = grep {!/\/(subsystem|driver)\//} @files if @files;
22756	main::uniq(\@files);
22757	eval $end if $b_log;
22758}
22759
22760sub soc_devices {
22761	eval $start if $b_log;
22762	my (@working);
22763	set_bluetooth() if !$b_bt_check;
22764	foreach $file (@files){
22765		next if -z $file;
22766		$chip_id = $file;
22767		# variants: /soc/20100000.ethernet/ /soc/soc:audio/ /soc:/ /soc@0/ /soc:/12cb0000.i2c:/
22768		# mips: /sys/devices/soc.0/1180000001800.mdio/8001180000001800:07/
22769		# ppc: /sys/devices/vio/71000002/
22770		$chip_id =~ /\/sys\/devices\/(platform\/)?(soc[^\/]*\/)?([^\/]+\/)?([^\/]+\/)?([^\/\.:]+)([\.:])?([^\/:]+)?:?\/uevent$/;
22771		$chip_id = $5;
22772		$temp = $7;
22773		@working = main::reader($file, 'strip') if -r $file;
22774		($device,$driver,$handle,$type,$vendor_id) = (undef,undef,undef,undef,undef);
22775		foreach my $data (@working){
22776			@temp2 = split('=', $data);
22777			if ($temp2[0] eq 'DRIVER'){
22778				$driver = $temp2[1];
22779				$driver =~ s/-/_/g if $driver; # kernel uses _, not - in module names
22780			}
22781			elsif ($temp2[0] eq 'OF_NAME'){
22782				$type = $temp2[1];
22783			}
22784			# we'll use these paths to test in device tree pci completer
22785			elsif ($temp2[0] eq 'OF_FULLNAME' && $temp2[1]){
22786				# we don't want the short names like /soc, /led and so on
22787				push(@full_names, $temp2[1]) if (() = $temp2[1] =~ /\//g) > 1;
22788				$handle = (split('@', $temp2[1]))[-1] if $temp2[1] =~ /@/;
22789			}
22790			elsif ($temp2[0] eq 'OF_COMPATIBLE_0'){
22791				@temp3 = split(',', $temp2[1]);
22792				$device = $temp3[-1];
22793				$vendor_id = $temp3[0];
22794			}
22795		}
22796		# it's worthless, we can't use it
22797		next if ! defined $type;
22798		$type_id = $type;
22799		if (@bluetooth && $type eq 'serial'){
22800			my $file_temp = $file;
22801			$file_temp =~ s/uevent$//;
22802			$type = 'bluetooth' if grep {/$file_temp/} @bluetooth;
22803		}
22804		$chip_id = '' if ! defined $chip_id;
22805		$vendor_id = '' if ! defined $vendor_id;
22806		$driver = '' if ! defined $driver;
22807		$handle = '' if ! defined $handle;
22808		$busid = (defined $temp && main::is_int($temp)) ? $temp: 0;
22809		$type = soc_type($type,$vendor_id,$driver);
22810		($busid_nu,$modules,$port,$rev) = (0,'','','');
22811		@temp3 = ($type,$type_id,$busid,$busid_nu,$device,$vendor_id,$chip_id,$rev,
22812		$port,$driver,$modules,'','','',$handle);
22813		assign_data('soc',\@temp3);
22814		main::log_data('dump','soc devices: @devices @temp3',\@temp3) if $b_log;
22815	}
22816	eval $end if $b_log;
22817}
22818sub soc_devicetree {
22819	eval $start if $b_log;
22820	# now we want to fill in stuff that was not in /sys/devices/
22821	if (-d '/sys/firmware/devicetree/base/soc'){
22822		@files = main::globber('/sys/firmware/devicetree/base/soc/*/compatible');
22823		my $test = (@full_names) ? join('|', sort @full_names) : 'xxxxxx';
22824		set_bluetooth() if !$b_bt_check;
22825		foreach $file (@files){
22826			if ($file !~ m%$test%){
22827				($handle,$content,$device,$type,$type_id,$vendor_id) = ('','','','','','');
22828				$content = main::reader($file, 'strip',0) if -r $file;
22829				$file =~ m%soc/([^@]+)@([^/]+)/compatible$%;
22830				$type = $1;
22831				next if !$type || !$content;
22832				$handle = $2 if $2;
22833				$type_id = $type;
22834				if (@bluetooth && $type eq 'serial'){
22835					my $file_temp = $file;
22836					$file_temp =~ s/uevent$//;
22837					$type = 'bluetooth' if grep {/$file_temp/} @bluetooth;
22838				}
22839				if ($content){
22840					@temp3 = split(',', $content);
22841					$vendor_id = $temp3[0];
22842					$device = $temp3[-1];
22843					# strip off those weird device tree special characters
22844					$device =~ s/\x01|\x02|\x03|\x00//g;
22845				}
22846				$type = soc_type($type,$vendor_id,'');
22847				@temp3 = ($type,$type_id,0,0,$device,$vendor_id,'soc','','','','','','','',$handle);
22848				assign_data('soc',\@temp3);
22849				main::log_data('dump','devicetree: @devices @temp3',\@temp3) if $b_log;
22850			}
22851		}
22852	}
22853	eval $end if $b_log;
22854}
22855sub set_bluetooth {
22856	# special case of pi bt on ttyAMA0
22857	$b_bt_check = 1;
22858	@bluetooth = main::globber('/sys/class/bluetooth/*') if -e '/sys/class/bluetooth';
22859	@bluetooth = map {$_ = Cwd::abs_path($_);$_} @bluetooth if @bluetooth;
22860	@bluetooth = grep {!/usb/} @bluetooth if @bluetooth; # we only want non usb bt
22861	main::log_data('dump','soc bt: @bluetooth', \@bluetooth) if $b_log;
22862}
22863sub assign_data {
22864	my ($tool,$data) = @_;
22865	if (check_graphics($data->[0],$data->[1])){
22866		push(@{$devices{'graphics'}},[@$data]);
22867		$use{'soc-gfx'} = 1 if $tool eq 'soc';
22868	}
22869	# for hdmi, we need gfx/audio both
22870	if (check_audio($data->[0],$data->[1])){
22871		push(@{$devices{'audio'}},[@$data]);
22872		$use{'soc-audio'} = 1 if $tool eq 'soc';
22873	}
22874	if (check_bluetooth($data->[0],$data->[1])){
22875		push(@{$devices{'bluetooth'}},[@$data]);
22876		$use{'soc-bluetooth'} = 1 if $tool eq 'soc';
22877	}
22878	elsif (check_hwraid($data->[0],$data->[1])){
22879		push(@{$devices{'hwraid'}},[@$data]);
22880		$use{'soc-hwraid'} = 1 if $tool eq 'soc';
22881	}
22882	elsif (check_network($data->[0],$data->[1])){
22883		push(@{$devices{'network'}},[@$data]);
22884		$use{'soc-network'} = 1 if $tool eq 'soc';
22885	}
22886	elsif (check_timer($data->[0],$data->[1])){
22887		push(@{$devices{'timer'}},[@$data]);
22888		$use{'soc-timer'} = 1 if $tool eq 'soc';
22889	}
22890	# not used at this point, -M comes before ANG
22891	# $device_vm = check_vm($data[4]) if ((!$b_ppc && !$b_mips) && !$device_vm);
22892	push(@devices,[@$data]);
22893}
22894# note: for soc, these have been converted in soc_type()
22895sub check_audio {
22896	if (($_[1] && length($_[1]) == 4 && $_[1] =~ /^04/) ||
22897	 ($_[0] && $_[0] =~ /^(audio|hdmi|multimedia|sound)$/i)){
22898		return 1;
22899	}
22900	else {return 0}
22901}
22902sub check_bluetooth {
22903	if (($_[1] && length($_[1]) == 4 && $_[1] eq '0d11') ||
22904	 ($_[0] && $_[0] =~ /^(bluetooth)$/i)){
22905		return 1;
22906	}
22907	else {return 0}
22908}
22909sub check_graphics {
22910	# note: multimedia class 04 is video if 0400. 'tv' is risky I think
22911	if (($_[1] && length($_[1]) == 4 &&  ($_[1] =~ /^03/ || $_[1] eq '0400' ||
22912	 $_[1] eq '0d80')) ||
22913	 ($_[0] && $_[0] =~ /^(vga|display|hdmi|3d|video|tv|television)$/i)){
22914		return 1;
22915	}
22916	else {return 0}
22917}
22918sub check_hwraid {
22919	return 1 if ($_[1] && $_[1] eq '0104');
22920}
22921# NOTE: class 06 subclass 80
22922# https://www-s.acm.illinois.edu/sigops/2007/roll_your_own/7.c.1.html
22923# 0d20: 802.11a 0d21: 802.11b 0d80: other wireless
22924sub check_network {
22925	if (($_[1] && length($_[1]) == 4 && ($_[1] =~/^02/ || $_[1] =~ /^0d2/ || $_[1] eq '0680')) ||
22926	 ($_[0] && $_[0] =~  /^(ethernet|network|wifi|wlan)$/i)){
22927		return 1;
22928	}
22929	else {return 0}
22930}
22931sub check_timer {
22932	return 1 if ($_[0] && $_[0] eq 'timer');
22933}
22934sub check_vm {
22935	if ($_[0] && $_[0] =~ /(innotek|vbox|virtualbox|vmware|qemu)/i){
22936		return $1
22937	}
22938	else {return ''}
22939}
22940
22941sub soc_type {
22942	my ($type,$info,$driver) = @_;
22943	# I2S or i2s. I2C is i2 controller |[iI]2[Ss]. note: odroid hdmi item is sound only
22944	# snd_soc_dummy. simple-audio-amplifier driver: speaker_amp
22945	if (($driver && $driver =~ /codec/) || ($info && $info =~ /codec/) ||
22946	 ($type && $type =~ /codec/)){
22947		$type = 'codec';
22948	}
22949	elsif (($driver && $driver =~ /dummy/i) || ($info && $info =~ /dummy/i)){
22950		$type = 'dummy';
22951	}
22952	# rome_vreg reg_fixed_voltage regulator-fixed wlan_en_vreg
22953	elsif (($driver && $driver =~ /\bv?reg(ulat|_)|voltage/i) ||
22954	 ($info && $info =~ /_v?reg|\bv?reg(ulat|_)|voltage/i)){
22955		$type = 'regulator';
22956	}
22957	elsif ($type =~ /^(daudio|.*hifi.*|.*sound[_-]card|.*dac[0-9]?)$/i ||
22958	 ($info && $info !~ /amp/i && $info =~ /(sound|audio)/i) ||
22959	 ($driver && $driver =~ /(audio|snd|sound)/i)){
22960		$type = 'audio';
22961	}
22962	# no need for bluetooth since that's only found in pi, handled above
22963	elsif ($type =~ /^((meson-?)?fb|disp|display(-[^\s]+)?|gpu|.*mali|vpu)$/i){
22964		$type = 'display';
22965	}
22966	# includes ethernet-phy, meson-eth
22967	elsif ($type =~ /^(([^\s]+-)?eth|ethernet(-[^\s]+)?|lan|l-lan)$/i){
22968		$type = 'ethernet';
22969	}
22970	elsif ($type =~ /^(.*wlan.*|.*wifi.*|.*mmcnr.*)$/i){
22971		$type = 'wifi';
22972	}
22973	# needs to catch variants like hdmi-tx but not hdmi-connector
22974	elsif ($type =~ /^(.*hdmi(-?tx)?)$/i){
22975		$type = 'hdmi';
22976	}
22977	elsif ($type =~ /^timer$/i){
22978		$type = 'timer';
22979	}
22980	return $type;
22981}
22982sub pci_class {
22983	eval $start if $b_log;
22984	my ($id) = @_;
22985	$id = lc($id);
22986	my %classes = (
22987	'00' => 'unclassified',
22988	'01' => 'mass-storage',
22989	'02' => 'network',
22990	'03' => 'display',
22991	'04' => 'audio',
22992	'05' => 'memory',
22993	'06' => 'bridge',
22994	'07' => 'communication',
22995	'08' => 'peripheral',
22996	'09' => 'input',
22997	'0a' => 'docking',
22998	'0b' => 'processor',
22999	'0c' => 'serialbus',
23000	'0d' => 'wireless',
23001	'0e' => 'intelligent',
23002	'0f' => 'satellite',
23003	'10' => 'encryption',
23004	'11' => 'signal-processing',
23005	'12' => 'processing-accelerators',
23006	'13' => 'non-essential-instrumentation',
23007	'40' => 'coprocessor',
23008	'ff' => 'unassigned',
23009	);
23010	my $type = (defined $classes{$id}) ? $classes{$id}: 'unhandled';
23011	eval $end if $b_log;
23012	return $type;
23013}
23014}
23015
23016## DiskDataBSD
23017# handles disks and partition extra data for disks bsd, raid-zfs,
23018# partitions, swap, unmounted
23019# glabel: partID, logical/physical-block-size, uuid, label, size
23020# disklabel: partID, block-size, fs, size
23021{
23022package DiskDataBSD;
23023# sets initial pure dboot data, and fills it in with
23024# disklabel/gpart partition and advanced data
23025sub set {
23026	eval $start if $b_log;
23027	$loaded{'disk-data-bsd'} = 1;
23028	set_dboot_disks();
23029	if ($use{'bsd-partition'}){
23030		if ($alerts{'gpart'}->{'action'} eq 'use'){
23031			set_gpart_data();
23032		}
23033		elsif ($alerts{'disklabel'}->{'action'} eq 'use'){
23034			set_disklabel_data();
23035		}
23036	}
23037	eval $end if $b_log;
23038}
23039sub get {
23040	eval $start if $b_log;
23041	my $id = $_[0];
23042	return if !$id || !%disks_bsd;
23043	$id =~ s|^/dev/||;
23044	my (%data);
23045	# this handles mainly zfs, which can be either disk or part
23046	if ($disks_bsd{$id}){
23047		%data = %{$disks_bsd{$id}};
23048		delete $data{'partitions'} if $data{'partitions'};
23049	}
23050	else {
23051		OUTER: foreach my $key (keys %disks_bsd){
23052			if ($disks_bsd{$key}->{'partitions'}){
23053				foreach my $part (keys %{$disks_bsd{$key}->{'partitions'}}){
23054					if ($part eq $id){
23055						%data = %{$disks_bsd{$key}->{'partitions'}{$part}};
23056						last OUTER;
23057					}
23058				}
23059			}
23060		}
23061	}
23062	eval $end if $b_log;
23063	return %data;
23064}
23065sub set_dboot_disks {
23066	eval $start if $b_log;
23067	my ($working,@temp);
23068	foreach my $id (sort keys %{$dboot{'disk'}}){
23069		next if !@{$dboot{'disk'}->{$id}};
23070		foreach (@{$dboot{'disk'}->{$id}}){
23071			my @row = split(/:\s*/, $_);
23072			next if !$row[0];
23073			# no dots, note: ada2: 2861588MB BUT: ada2: 600.000MB/s
23074			# print "$_ i: $i\n";
23075			# openbsd/netbsd matches will often work
23076			if ($row[0] =~ /(^|,\s*)([0-9\.]+\s*[MGTPE])i?B?[,.\s]+([0-9]+)\ssectors$|^</){
23077				$working = main::translate_size($2);
23078				# seen:  for some reason, size/sectors did not result in clean integer value
23079				$disks_bsd{$id}->{'block-physical'} = POSIX::ceil(($working/$3)*1024) if $3;
23080				$disks_bsd{$id}->{'size'} = $working;
23081			}
23082			# don't set both, if smartctl installed, we want to use its data so having
23083			# only one of logical/physical will trip use of smartctl values
23084			if ($row[0] =~ /[\s,]+([0-9]+)\sbytes?[\s\/]sect/){
23085				#$disks_bsd{$id}->{'block-logical'} = $1;
23086				$disks_bsd{$id}->{'block-physical'} = $1;
23087			}
23088			if ($row[1]){
23089				if ($row[1] =~ /<([^>]+)>/){
23090					$disks_bsd{$id}->{'model'} = $1 if $1;
23091					$disks_bsd{$id}->{'type'} = 'removable' if $_ =~ /removable/;
23092					# <Generic-, Compact Flash, 1.00>
23093					my $count = ($disks_bsd{$id}->{'model'} =~ tr/,//);
23094					if ($count && $count > 1){
23095						@temp = split(/,\s*/, $disks_bsd{$id}->{'model'});
23096						$disks_bsd{$id}->{'model'} = $temp[1];
23097					}
23098				}
23099				if ($row[1] =~ /\bserial\.(\S*)/){
23100					$disks_bsd{$id}->{'serial'} = $1;
23101				}
23102			}
23103			if (!$disks_bsd{$id}->{'serial'} && $row[0] =~ /^Serial\sNumber\s(.*)/){
23104				$disks_bsd{$id}->{'serial'} = $1;
23105			}
23106			# mmcsd0:32GB <SDHC SL32G 8.0 SN 27414E9E MFG 07/2014 by 3 SD> at mmc0 50.0MHz/4bit/65535-block
23107			if (!$disks_bsd{$id}->{'serial'} && $row[0] =~ /(\s(SN|s\/n)\s(\S+))[>\s]/){
23108				$disks_bsd{$id}->{'serial'} = $3;
23109				# strip out the SN/MFG so it won't show in model
23110				$row[0] =~ s/$1//;
23111				$row[0] =~ s/\sMFG\s[^>]+//;
23112			}
23113			# these were mainly FreeBSD/Dragonfly matches
23114			if (!$disks_bsd{$id}->{'size'} && $row[0] =~ /^([0-9]+\s*[KMGTPE])i?B?[\s,]/){
23115				$working = main::translate_size($1);
23116				$disks_bsd{$id}->{'size'} = $working;
23117			}
23118			if ($row[0] =~ /(device$|^([0-9\.]+\s*[KMGT]B\s+)?<)/){
23119				$row[0] =~ s/\bdevice$//g;
23120				$row[0] =~ /<([^>]*)>(\s(.*))?/;
23121				$disks_bsd{$id}->{'model'} = $1 if $1;
23122				$disks_bsd{$id}->{'spec'} = $3 if $3;
23123			}
23124			if ($row[0] =~ /^([0-9\.]+[MG][B]?\/s)/){
23125				$disks_bsd{$id}->{'speed'} = $1;
23126				$disks_bsd{$id}->{'speed'} =~ s/\.[0-9]+// if $disks_bsd{$id}->{'speed'};
23127			}
23128			$disks_bsd{$id}->{'model'} = main::disk_cleaner($disks_bsd{$id}->{'model'});
23129			if (!$disks_bsd{$id}->{'serial'} && $show{'disk'} && $extra > 1 &&
23130			 $alerts{'bioctl'}->{'action'} eq 'use'){
23131				$disks_bsd{$id}->{'serial'} = bioctl_data($id);
23132			}
23133		}
23134	}
23135	print Data::Dumper::Dumper \%disks_bsd if $dbg[34];
23136	main::log_data('dump','%disks_bsd',\%disks_bsd) if $b_log;
23137	eval $end if $b_log;
23138}
23139sub bioctl_data {
23140	eval $start if $b_log;
23141	my $id = $_[0];
23142	my $serial;
23143	my $working = (main::grabber($alerts{'bioctl'}->{'path'} . " $id  2>&1",'','strip'))[0];
23144	if ($working){
23145		if ($working =~ /permission/i){
23146			$alerts{'bioctl'}->{'action'} = 'permissions';
23147		}
23148		elsif ($working =~ /serial[\s-]?(number|n[ou]\.?)?\s+(\S+)$/i){
23149			$serial = $2;
23150		}
23151	}
23152	eval $end if $b_log;
23153	return $serial;
23154}
23155sub set_disklabel_data {
23156	eval $start if $b_log;
23157	my ($cmd,@data,@working);
23158	# see docs/inxi-data.txt for fs info
23159	my %fs = (
23160	'4.2bsd' => 'ffs',
23161	'4.4lfs' => 'lfs',
23162	);
23163	foreach my $id (keys %disks_bsd){
23164		$cmd = "$alerts{'disklabel'}->{'path'} $id 2>&1";
23165		@data = main::grabber($cmd,'','strip');
23166		main::log_data('dump','disklabel @data', \@data) if $b_log;
23167		if (scalar @data < 4 && (grep {/permission/i} @data)){
23168			$alerts{'disklabel'}->{'action'} = 'permissions';
23169			$alerts{'disklabel'}->{'message'} = main::row_defaults('root-feature');
23170			last;
23171		}
23172		else {
23173			my ($b_part,$duid,$part_id,$bytes_sector) = undef;
23174			if ($extra > 2 && $show{'disk'} && $alerts{'fdisk'}->{'action'} eq 'use'){
23175				$disks_bsd{$id}->{'partition-table'} = fdisk_data($id);
23176			}
23177			foreach my $row (@data){
23178				if ($row =~ /^\d+\spartitions:/){
23179					$b_part = 1;
23180					next;
23181				}
23182				if (!$b_part){
23183					@working = split(/:\s*/, $row);
23184					if ($working[0] eq 'bytes/sector'){
23185						$disks_bsd{$id}->{'block-physical'} = $working[1];
23186						$bytes_sector = $working[1];
23187					}
23188					elsif ($working[0] eq 'duid'){
23189						$working[1] =~ s/^0+$//; # dump duid if all 0s
23190						$disks_bsd{$id}->{'duid'} = $working[1];
23191					}
23192					elsif ($working[0] eq 'label'){
23193						$disks_bsd{$id}->{'dlabel'} = $working[1];
23194					}
23195				}
23196				# part:        size [bytes*sector]      offset    fstype [fsize bsize cpg]# mount
23197				# d:          8388608         18838976  4.2BSD   2048 16384 12960 # /tmp
23198				else {
23199					@working = split(/:?\s+#?\s*/, $row);
23200					# netbsd: disklabel: super block size 0 AFTER partitions started!
23201					# note: 'unused' fs type is NOT unused space, it's often the entire disk!!
23202					if (($working[0] && $working[0] eq 'disklabel') ||
23203					 ($working[3] && $working[3] =~ /ISO9660|unused/i) ||
23204					 (!$working[1] || !main::is_numeric($working[1]))){
23205						next;
23206					}
23207					$part_id = $id . $working[0];
23208					$working[1] = $working[1]*$bytes_sector/1024 if $working[1];
23209					$disks_bsd{$id}->{'partitions'}{$part_id}{'size'} = $working[1];
23210					if ($working[3]){ # fs
23211						$working[3] = lc($working[3]);
23212						$working[3] = $fs{$working[3]} if $fs{$working[3]}; #translate
23213					}
23214					$disks_bsd{$id}->{'partitions'}{$part_id}{'fs'} = $working[3];
23215					# OpenBSD: mount point; NetBSD: (Cyl. 0 - 45852*)
23216					if ($working[7] && $working[7] =~ m|^/|){
23217						$disks_bsd{$id}->{'partitions'}{$part_id}{'mount'} = $working[7];
23218					}
23219					$disks_bsd{$id}->{'partitions'}{$part_id}{'uuid'} = '';
23220					$disks_bsd{$id}->{'partitions'}{$part_id}{'label'} = '';
23221				}
23222			}
23223		}
23224	}
23225	print Data::Dumper::Dumper \%disks_bsd if $dbg[34];
23226	main::log_data('dump', '%disks_bsd', \%disks_bsd) if $b_log;
23227	eval $end if $b_log;
23228}
23229sub fdisk_data {
23230	eval $start if $b_log;
23231	my $id = $_[0];
23232	my ($scheme);
23233	my @data = main::grabber($alerts{'fdisk'}->{'path'} . " -v $id  2>&1",'','strip');
23234	foreach (@data){
23235		if (/permission/i){
23236			$alerts{'fdisk'}->{'action'} = 'permissions';
23237			last;
23238		}
23239		elsif (/^(GUID|MBR):/){
23240			$scheme = ($1 eq 'GUID') ? 'GPT' : $1;
23241			last;
23242		}
23243	}
23244	eval $start if $b_log;
23245	return $scheme;
23246}
23247# 2021-03: openbsd: n/a; dragonfly: no 'list'; freebsd: yes
23248sub set_gpart_data {
23249	eval $start if $b_log;
23250	my @data = main::grabber($alerts{'gpart'}->{'path'} . " list 2>/dev/null",'','strip');
23251	main::log_data('dump', 'gpart: @data', \@data) if $b_log;
23252	my ($b_cd,$id,$part_id,$type);
23253	for (@data){
23254		my @working = split(/\s*:\s*/, $_);
23255		if ($working[0] eq 'Geom name'){
23256			$id = $working[1];
23257			# [1. Name|Geom name]: iso9660/FVBE
23258			$b_cd = ($id =~ /iso9660/i) ? 1: 0;
23259			next;
23260		}
23261		elsif ($working[0] eq 'scheme'){
23262			$disks_bsd{$id}->{'scheme'} = $working[1];
23263			next;
23264		}
23265		elsif ($working[0] eq 'Consumers'){
23266			$type = 'disk';
23267			next;
23268		}
23269		elsif ($working[0] eq 'Providers'){
23270			$type = 'part';
23271			next;
23272		}
23273		if (!$b_cd && $type && $type eq 'part'){
23274			if ($working[0] =~ /^[0-9]+\.\s*Name/){
23275				$part_id = $working[1];
23276			}
23277			# eg: label:(null) - we want to show null
23278			elsif ($working[0] eq 'label'){
23279				$working[1] =~ s/\(|\)//g;
23280				$disks_bsd{$id}->{'partitions'}{$part_id}{'label'} = $working[1];
23281			}
23282			elsif ($working[0] eq 'Mediasize'){
23283				$working[1] =~ s/\s+\(.*$//; # trim off the (2.4G)
23284				# gpart shows in bytes, not KiB. For the time being...
23285				$disks_bsd{$id}->{'partitions'}{$part_id}{'size'} = $working[1]/1024 if $working[1];
23286			}
23287			elsif ($working[0] eq 'rawuuid'){
23288				$working[1] =~ s/\(|\)//g;
23289				$disks_bsd{$id}->{'partitions'}{$part_id}{'uuid'} = $working[1];
23290			}
23291			elsif ($working[0] eq 'Sectorsize'){
23292				$disks_bsd{$id}->{'partitions'}{$part_id}{'physical-block-size'} = $working[1];
23293			}
23294			elsif ($working[0] eq 'Stripesize'){
23295				$disks_bsd{$id}->{'partitions'}{$part_id}{'logical-block-size'} = $working[1];
23296			}
23297			elsif ($working[0] eq 'type'){
23298				$working[1] =~ s/\(|\)//g;
23299				$disks_bsd{$id}->{'partitions'}{$part_id}{'fs'} = $working[1];
23300			}
23301		}
23302		# really strange results happen if no dboot disks were found and it's zfs!
23303		elsif (!$b_cd && $type && $type eq 'disk' && $disks_bsd{$id}->{'size'}){
23304			# need to see raid, may be > 1 Consumers
23305			if ($working[0] =~ /^[0-9]+\.\s*Name/){
23306				$id = $working[1];
23307			}
23308			elsif ($working[0] eq 'Mediasize'){
23309				$working[1] =~ s/\s+\(.*$//; # trim off the (2.4G)
23310				# gpart shows in bytes, not KiB. For the time being...
23311				$disks_bsd{$id}->{'size'} = $working[1]/1024 if $working[1];
23312			}
23313			elsif ($working[0] eq 'Sectorsize'){
23314				$disks_bsd{$id}->{'block-physical'} = $working[1];
23315			}
23316		}
23317	}
23318	print Data::Dumper::Dumper \%disks_bsd if $dbg[34];
23319	main::log_data('dump', '%disks_bsd', \%disks_bsd) if $b_log;
23320	eval $end if $b_log;
23321}
23322}
23323
23324sub get_display_manager {
23325	eval $start if $b_log;
23326	my (@data,@found,$path,$working,$b_run,$b_vrun,$b_vrunrc);
23327	# ldm - LTSP display manager. Note that sddm does not appear to have a .pid
23328	# extension in Arch note: to avoid positives with directories, test for -f
23329	# explicitly, not -e. Guessing on cdm.pid. pcdm uses vt, PCDM-vt9.pid
23330	my @dms = qw(cdm.pid entranced.pid gdm.pid gdm3.pid kdm.pid kdm3.pid ldm.pid
23331	lightdm.pid lxdm.pid mdm.pid nodm.pid pcdm.pid sddm.pid slim.lock
23332	slim.pid tdm.pid udm.pid wdm.pid xdm.pid xenodm.pid);
23333	# these are the only one I know of so far that have version info
23334	my @dms_version = qw(gdm gdm3 lightdm slim);
23335	$b_run = 1 if -d "/run";
23336	# in most linux, /var/run is a sym link to /run, so no need to check it twice
23337	if (-d "/var/run"){
23338		my $rdlink = readlink('/var/run');
23339		$b_vrun = 1 if !$rdlink || ($rdlink && $rdlink ne '/run');
23340		$b_vrunrc = 1 if -d "/var/run/rc.d";
23341	}
23342	foreach my $id (@dms){
23343		# note: $working will create a dir name out of the dm $id, then
23344		# test if pid is in that note: sddm, in an effort to be unique and special,
23345		# do not use a pid/lock file, but rather a random string inside a directory
23346		# called /run/sddm/ so assuming the existence of the pid inside a directory named
23347		# from the dm. Hopefully this change will not have negative results.
23348		$working = $id;
23349		$working =~ s/\.\S+$//;
23350		# note: there were issues with duplicated dm's in inxi, checking @found corrects it
23351		if ((($b_run && (-f "/run/$id" || -d "/run/$working")) ||
23352		 ($b_vrun && (-f "/var/run/$id" || -d "/var/run/$working")) ||
23353		 ($b_vrunrc && (-f "/var/run/rc.d/$working" || -d "/var/run/rc.d/$id"))) &&
23354		 !grep {/$working/i} @found){
23355			if ($extra > 2 && awk(\@dms_version, $working) && ($path = check_program($working))){}
23356			else {$path = $working;}
23357			# print "$path $extra\n";
23358			@data = program_data($working,$path,3);
23359			$working = $data[0];
23360			$working .= ' ' . $data[1] if $data[1];
23361			push(@found, $working);
23362		}
23363	}
23364	if (!@found){
23365		# ly does not have a run/pid file
23366		if (grep {$_ eq 'ly'} @ps_gui){
23367			@data = program_data('ly','ly',3);
23368			$found[0] = $data[0];
23369			$found[0] .= ' ' . $data[1] if $data[1];
23370		}
23371		elsif (grep {/startx$/} @ps_gui){
23372			$found[0] = 'startx';
23373		}
23374		elsif (grep {$_ eq 'xinit'} @ps_gui){
23375			$found[0] = 'xinit';
23376		}
23377	}
23378	# might add this in, but the rate of new dm's makes it more likely it's an
23379	# unknown dm, so we'll keep output to N/A
23380	log_data('dump','display manager: @found',\@found) if $b_log;
23381	eval $end if $b_log;
23382	return join(', ', @found) if @found;
23383}
23384
23385## DistroData
23386{
23387package DistroData;
23388my (@distro_files,@osr,@working);
23389my ($distro,$distro_file,$distro_id,$system_base) = ('','','','');
23390my ($etc_issue,$lc_issue,$os_release) = ('','','/etc/os-release');
23391sub get {
23392	eval $start if $b_log;
23393	if ($bsd_type){
23394		get_bsd_os();
23395	}
23396	else {
23397		get_linux_distro();
23398	}
23399	eval $end if $b_log;
23400	return ($distro,$system_base);
23401}
23402
23403sub get_bsd_os {
23404	eval $start if $b_log;
23405	if ($bsd_type eq 'darwin'){
23406		$distro_file = '/System/Library/CoreServices/SystemVersion.plist';
23407		if (-f $distro_file){
23408			@working = main::reader($distro_file);
23409			@working = grep {/(ProductName|ProductVersion)/} @working if @working;
23410			@working = grep {/<string>/} @working if @working;
23411			@working = map {s/<[\/]?string>//g; } @working if @working;
23412			$distro = join(' ', @working);
23413		}
23414	}
23415	if (!$distro){
23416		my $bsd_type_osr = 'dragonfly';
23417		@osr = main::reader($os_release) if -r $os_release;
23418		if (@osr && $bsd_type =~ /($bsd_type_osr)/ && (grep {/($bsd_type_osr)/i} @osr)){
23419			$distro = get_os_release();
23420			$distro_id = lc($1);
23421		}
23422	}
23423	if (!$distro){
23424		my $bsd_type_version = 'truenas';
23425		my ($version_file,$version_info) = ('/etc/version','');
23426		$version_info = main::reader($version_file,'strip') if -r $version_file;
23427		if ($version_info && $version_info =~ /($bsd_type_version)/i){
23428			$distro = $version_info;
23429			$distro_id = lc($1);
23430		}
23431	}
23432	if (!$distro){
23433		# seen a case without osx file, or was it permissions?
23434		# this covers all the other bsds anyway, no problem.
23435		$distro = "$uname[0] $uname[2]";
23436		$distro_id = lc($uname[0]);
23437	}
23438	if ($distro &&
23439	 (-e '/etc/pkg/GhostBSD.conf' || -e '/usr/local/etc/pkg/repos/GhostBSD.conf') &&
23440	  $distro =~ /freebsd/i){
23441		my $version = (main::grabber("pkg query '%v' os-generic-userland-base 2>/dev/null"))[0];
23442		# only swap if we get result from the query
23443		if ($version){
23444			$system_base = $distro;
23445			$distro = "GhostBSD $version";
23446		}
23447	}
23448	system_base_bsd() if $extra > 0;
23449	eval $end if $b_log;
23450}
23451
23452sub get_linux_distro {
23453	# order matters!
23454	my @derived = qw(antix-version aptosid-version bodhibuilder.conf kanotix-version
23455	knoppix-version pclinuxos-release mandrake-release manjaro-release mx-version
23456	pardus-release porteus-version q4os_version sabayon-release siduction-version sidux-version
23457	slitaz-release solusos-release turbolinux-release zenwalk-version);
23458	my $derived_s = join('|', @derived);
23459	my @primary = qw(altlinux-release arch-release gentoo-release redhat-release slackware-version
23460	SuSE-release);
23461	my $primary_s = join('|', @primary);
23462	my $exclude_s = 'debian_version|devuan_version|ubuntu_version';
23463	# note, pclinuxos has all these mandrake/mandriva files, careful!
23464	my $lsb_good_s = 'mandrake-release|mandriva-release|mandrakelinux-release|manjaro-release';
23465	my $os_release_good_s = 'altlinux-release|arch-release|pclinuxos-release|rpi-issue|SuSE-release';
23466 	my ($b_issue,$b_lsb,$b_skip_issue,$b_skip_osr);
23467	my ($issue,$lsb_release) = ('/etc/issue','/etc/lsb-release');
23468	$b_issue = 1 if -f $issue;
23469	$b_lsb = 1 if -f $lsb_release;
23470	# note: OpenSuse Tumbleweed 2018-05 has made /etc/issue created by sym link to /run/issue
23471	# and then made that resulting file 700 permissions, which is obviously a mistake
23472	$etc_issue = main::reader($issue,'strip',0) if -r $issue;
23473	# debian issue can end with weird escapes like \n \l
23474	# antergos: Antergos Linux \r (\l)
23475	$etc_issue = main::clean_characters($etc_issue) if $etc_issue;
23476	# note: always exceptions, so wild card after release/version:
23477	# /etc/lsb-release-crunchbang
23478	# wait to handle since crunchbang file is one of the few in the world that
23479	# uses this method
23480	@distro_files = main::globber('/etc/*[-_]{[rR]elease,[vV]ersion,issue}*');
23481	push(@distro_files, '/etc/bodhibuilder.conf') if -r '/etc/bodhibuilder.conf';
23482	@osr = main::reader($os_release) if -r $os_release;
23483	if ($etc_issue){
23484		$lc_issue = lc($etc_issue);
23485		if ($lc_issue =~ /(antergos|grml|linux lite|openmediavault)/){
23486			$distro_id = $1;
23487			$b_skip_issue = 1;
23488		}
23489		# this raspbian detection fails for raspberry pi os
23490		elsif ($lc_issue =~ /(raspbian|peppermint)/){
23491			$distro_id = $1;
23492			$distro_file = $os_release if @osr;
23493		}
23494		# note: wrong fix, applies to both raspbian and raspberry pi os
23495		# assumption here is that r pi os fixes this before stable release
23496		elsif ($lc_issue =~ /^debian/ && -e '/etc/apt/sources.list.d/raspi.list' &&
23497		(grep {/[^#]+raspberrypi\.org/} main::reader('/etc/apt/sources.list.d/raspi.list'))){
23498			$distro_id = 'raspios' ;
23499		}
23500	}
23501	# Note that antergos changed this around 	# 2018-05, and now lists
23502	# antergos in os-release, sigh... We want these distros to use os-release
23503	# if it contains their names. Last check below
23504	if (@osr && (grep {/(manjaro|antergos|chakra|guix|pclinuxos|raspberry pi os|zorin)/i} @osr)){
23505		$distro_file = $os_release;
23506	}
23507	if (grep {/armbian/} @distro_files){
23508		$distro_id = 'armbian' ;
23509	}
23510	main::log_data('dump','@distro_files',\@distro_files) if $b_log;
23511	main::log_data('data',"distro_file-1: $distro_file") if $b_log;
23512	if (!$distro_file){
23513		if (scalar @distro_files == 1){
23514			$distro_file = $distro_files[0];
23515		}
23516		elsif (scalar @distro_files > 1){
23517			# special case, to force manjaro/antergos which also have arch-release
23518			# manjaro should use lsb, which has the full info, arch uses os release
23519			# antergos should use /etc/issue. We've already checked os-release above
23520			if ($distro_id eq 'antergos' || (grep {/antergos|chakra|manjaro/} @distro_files)){
23521				@distro_files = grep {!/arch-release/} @distro_files;
23522			}
23523			my $distro_files_s = join('|', @distro_files);
23524			@working = (@derived,@primary);
23525			foreach my $file (@working){
23526				if ("/etc/$file" =~ /($distro_files_s)$/){
23527					# Now lets see if the distro file is in the known-good working-lsb-list
23528					# if so, use lsb-release, if not, then just use the found file
23529					# this is for only those distro's with self named release/version files
23530					# because Mint does not use such, it must be done as below
23531					if (@osr && $file =~ /($os_release_good_s)$/){
23532						$distro_file = $os_release;
23533					}
23534					elsif ($b_lsb && $file =~ /$lsb_good_s/){
23535						$distro_file = $lsb_release;
23536					}
23537					else {
23538						$distro_file = "/etc/$file";
23539					}
23540					last;
23541				}
23542			}
23543		}
23544	}
23545	main::log_data('data',"distro_file-2: $distro_file") if $b_log;
23546	# first test for the legacy antiX distro id file
23547	if (-r '/etc/antiX'){
23548		@working = main::reader('/etc/antiX');
23549		$distro = main::awk(\@working,'antix.*\.iso') if @working;
23550		$distro = main::clean_characters($distro) if $distro;
23551	}
23552	# this handles case where only one release/version file was found, and it's lsb-release.
23553	# This would never apply for ubuntu or debian, which will filter down to the following
23554	# conditions. In general if there's a specific distro release file available, that's to
23555	# be preferred, but this is a good backup.
23556	elsif ($distro_file && $b_lsb && ($distro_file =~ /\/etc\/($lsb_good_s)$/ || $distro_file eq $lsb_release)){
23557		$distro = get_lsb_release();
23558	}
23559	elsif ($distro_file && $distro_file eq $os_release){
23560		$distro = get_os_release();
23561		$b_skip_osr = 1;
23562	}
23563	# if distro id file was found and it's not in the exluded primary distro file list, read it
23564	elsif ($distro_file && -s $distro_file && $distro_file !~ /\/etc\/($exclude_s)$/){
23565		# new opensuse uses os-release, but older ones may have a similar syntax, so just use
23566		# the first line
23567		if ($distro_file eq '/etc/SuSE-release'){
23568			# leaving off extra data since all new suse have it, in os-release, this file has
23569			# line breaks, like os-release  but in case we  want it, it's:
23570			# CODENAME = Mantis  | VERSION = 12.2
23571			# for now, just take first occurrence, which should be the first line, which does
23572			# not use a variable type format
23573			@working = main::reader($distro_file);
23574			$distro = main::awk(\@working,'suse');
23575		}
23576		elsif ($distro_file eq '/etc/bodhibuilder.conf'){
23577			@working = main::reader($distro_file);
23578			$distro = main::awk(\@working,'^LIVECDLABEL',2,'\s*=\s*');
23579			$distro =~ s/"//g if $distro;
23580		}
23581		else {
23582			$distro = main::reader($distro_file,'',0);
23583			# only contains version number. Why? who knows.
23584			if ($distro_file eq '/etc/q4os_version' && $distro !~ /q4os/i){
23585				$distro = "Q4OS $distro" ;
23586			}
23587		}
23588		$distro = main::clean_characters($distro) if $distro;
23589	}
23590	# otherwise try  the default debian/ubuntu/distro /etc/issue file
23591	elsif ($b_issue){
23592		if (!$distro_id && $lc_issue && $lc_issue =~ /(mint|lmde)/){
23593			$distro_id = $1;
23594			$b_skip_issue = 1;
23595		}
23596		# os-release/lsb gives more manageable and accurate output than issue,
23597		# but mint should use issue for now. Antergos uses arch os-release, but issue shows them
23598		if (!$b_skip_issue && @osr){
23599			$distro = get_os_release();
23600			$b_skip_osr = 1;
23601		}
23602		elsif (!$b_skip_issue && $b_lsb){
23603			$distro = get_lsb_release();
23604		}
23605		elsif ($etc_issue){
23606			if (-d '/etc/guix' && $lc_issue =~ /^this is the gnu system\./){
23607				$distro = 'Guix';
23608				# they didn't use any standard paths or files for os data, sigh, use pm version
23609				my $version = main::program_version('guix', '^guix', '4','--version',1);
23610				$distro .= " $version" if $version;
23611				$b_skip_issue = 1;
23612			}
23613			else {
23614				$distro =  $etc_issue;
23615				# this handles an arch bug where /etc/arch-release is empty and /etc/issue
23616				# is corrupted only older arch installs that have not been updated should
23617				# have this fallback required, new ones use os-release
23618				if ($distro =~ /arch linux/i){
23619					$distro = 'Arch Linux';
23620				}
23621			}
23622		}
23623	}
23624	# a final check. If a long value, before assigning the debugger output, if os-release
23625	# exists then let's use that if it wasn't tried already. Maybe that will be better.
23626	# not handling the corrupt data, maybe later if needed. 10 + distro: (8) + string
23627	if ($distro && length($distro) > 60){
23628		if (!$b_skip_osr && @osr){
23629			$distro = get_os_release();
23630			$b_skip_osr = 1;
23631		}
23632	}
23633	# test for /etc/lsb-release as a backup in case of failure, in cases
23634	# where > one version/release file were found but the above resulted
23635	# in null distro value.
23636	if (!$distro && $b_cygwin){
23637		$distro = $uname[0]; # like so: CYGWIN_NT-10.0-19043
23638		$b_skip_osr = 1;
23639	}
23640	if (!$distro){
23641		if (!$b_skip_osr && @osr){
23642			$distro = get_os_release();
23643			$b_skip_osr = 1;
23644		}
23645		elsif ($b_lsb){
23646			$distro = get_lsb_release();
23647		}
23648	}
23649	# now some final null tries
23650	if (!$distro){
23651		# if the file was null but present, which can happen in some cases, then use
23652		# the file name itself to set the distro value. Why say unknown if we have
23653		# a pretty good idea, after all?
23654		if ($distro_file){
23655			$distro_file =~ s/\/etc\/|[-_]|release|version//g;
23656			$distro = $distro_file;
23657		}
23658	}
23659	system_base() if $extra > 0;
23660	# some last customized changes, double check if possible to verify still valid
23661	if ($distro){
23662		if ($distro_id eq 'armbian'){
23663			$distro =~ s/Debian/Armbian/;
23664		}
23665		elsif ($distro_id eq 'raspios'){
23666			$system_base = $distro;
23667			# no need to repeat the debian version info if base:
23668			if ($extra == 0){$distro =~ s/Debian\s*GNU\/Linux/Raspberry Pi OS/;}
23669			else {$distro = 'Raspberry Pi OS';}
23670		}
23671		elsif (-d '/etc/salixtools/' && $distro =~ /Slackware/i){
23672			$distro =~ s/Slackware/Salix/;
23673		}
23674	}
23675	else {
23676		# android fallback, sometimes requires root, sometimes doesn't
23677		android_info() if $b_android;
23678	}
23679	## finally, if all else has failed, give up
23680	$distro ||= 'unknown';
23681	eval $end if $b_log;
23682}
23683
23684sub android_info {
23685	eval $start if $b_log;
23686	main::set_build_prop() if !$loaded{'build-prop'};;
23687	$distro = 'Android';
23688	$distro .= ' ' . $build_prop{'build-version'} if $build_prop{'build-version'};
23689	$distro .= ' ' . $build_prop{'build-date'} if $build_prop{'build-date'};
23690	if (!$show{'machine'}){
23691		if ($build_prop{'product-manufacturer'} && $build_prop{'product-model'}){
23692			$distro .= ' (' . $build_prop{'product-manufacturer'} . ' ' . $build_prop{'product-model'} . ')';
23693		}
23694		elsif ($build_prop{'product-device'}){
23695			$distro .= ' (' . $build_prop{'product-device'} . ')';
23696		}
23697		elsif ($build_prop{'product-name'}){
23698			$distro .= ' (' . $build_prop{'product-name'} . ')';
23699		}
23700	}
23701	eval $end if $b_log;
23702}
23703
23704sub system_base_bsd {
23705	eval $start if $b_log;
23706	# ghostbsd is handled in main bsd section
23707	if (lc($uname[1]) eq 'nomadbsd' && $distro_id eq 'freebsd'){
23708		$system_base = $distro;
23709		$distro = $uname[1];
23710	}
23711	elsif (-f '/etc/pkg/HardenedBSD.conf' && $distro_id eq 'freebsd'){
23712		$system_base = $distro;
23713		$distro = 'HardenedBSD';
23714	}
23715	elsif ($distro_id =~ /^(truenas)$/){
23716		$system_base = "$uname[0] $uname[2]";
23717	}
23718	eval $end if $b_log;
23719}
23720
23721sub system_base {
23722	eval $start if $b_log;
23723	my $base_arch_distro = 'anarchy|antergos|archbang|archlabs|archman|archstrike|arco|artix';
23724	# note: arch linux derived distro page claims kaos as arch derived but it is NOT
23725	$base_arch_distro .= '|blackarch|bluestar|chakra|ctios|endeavour|garuda|hyperbola|linhes';
23726	$base_arch_distro .= '|manjaro|mysys2|netrunner\s?rolling|ninja|obarun|parabola';
23727	$base_arch_distro .= '|puppyrus-?a|reborn|snal|talkingarch|ubos';
23728	my $base_debian_version_distro = 'sidux';
23729	my $base_debian_version_osr = '\belive|lmde|neptune|parrot|pureos|rescatux|septor|sparky|tails';
23730	# osr has base ids
23731	my $base_default = 'antix-version|mx-version';
23732	# base only found in issue
23733	my $base_issue = 'bunsen';
23734	# synthesize, no direct data available
23735	my $base_manual = 'blankon|deepin|kali';
23736	# osr base, distro id in list of distro files
23737	my $base_osr = 'aptosid|grml|q4os|siduction|bodhi';
23738	# osr base, distro id in issue
23739	my $base_osr_issue = 'grml|linux lite|openmediavault';
23740	# osr has distro name but has ubuntu  ID_LIKE/UBUNTU_CODENAME
23741	my $base_osr_ubuntu = 'mint|neon|nitrux|pop!_os|zorin';
23742	my $base_upstream_lsb = '/etc/upstream-release/lsb-release';
23743	my $base_upstream_osr = '/etc/upstream-release/os-release';
23744	# first: try, some distros have upstream-release, elementary, new mint
23745	# and anyone else who uses this method for fallback ID
23746	if (-r $base_upstream_osr){
23747		my @osr_working = main::reader($base_upstream_osr);
23748		if (@osr_working){
23749			my (@osr_temp);
23750			@osr_temp = @osr;
23751			@osr = @osr_working;
23752			$system_base = get_os_release();
23753			@osr = @osr_temp if !$system_base;
23754			(@osr_temp,@osr_working) = (undef,undef);
23755		}
23756	}
23757	elsif (-r $base_upstream_lsb){
23758		$system_base = get_lsb_release($base_upstream_lsb);
23759	}
23760	if (!$system_base && @osr){
23761		my ($base_type) = ('');
23762		if ($etc_issue && (grep {/($base_issue)/i} @osr)){
23763			$system_base = $etc_issue;
23764		}
23765		# more tests added here for other ubuntu derived distros
23766		elsif (@distro_files && (grep {/($base_default)/} @distro_files)){
23767			$base_type = 'default';
23768		}
23769		# must go before base_osr_ubuntu test
23770		elsif (grep {/($base_debian_version_osr)/i} @osr){
23771			$system_base = debian_id();
23772		}
23773		elsif (grep {/($base_osr_ubuntu)/i} @osr){
23774			$base_type = 'ubuntu';
23775		}
23776		elsif ((($distro_id && $distro_id =~ /($base_osr_issue)/) ||
23777		 (@distro_files && (grep {/($base_osr)/} @distro_files))) &&
23778		 !(grep {/($base_osr)/i} @osr)){
23779			$system_base = get_os_release();
23780		}
23781		if (!$system_base && $base_type){
23782			$system_base = get_os_release($base_type);
23783		}
23784	}
23785	if (!$system_base && @distro_files &&
23786	 (grep {/($base_debian_version_distro)/i} @distro_files)){
23787		$system_base = debian_id();
23788	}
23789	if (!$system_base && $lc_issue && $lc_issue =~ /($base_manual)/){
23790		my $id = $1;
23791		my %manual = (
23792		'blankon' => 'Debian unstable',
23793		'deepin' => 'Debian unstable',
23794		'kali' => 'Debian testing',
23795		);
23796		$system_base = $manual{$id};
23797	}
23798	if (!$system_base && $distro && $distro =~ /^($base_arch_distro)/i){
23799		$system_base = 'Arch Linux';
23800	}
23801	if ($distro && -d '/etc/salixtools/' && $distro =~ /Slackware/i){
23802		$system_base = $distro;
23803	}
23804	eval $end if $b_log;
23805}
23806
23807sub get_lsb_release {
23808	eval $start if $b_log;
23809	my ($lsb_file) = @_;
23810	$lsb_file ||= '/etc/lsb-release';
23811	my ($distro,$id,$release,$codename,$description) = ('','','','','');
23812	my @content = main::reader($lsb_file);
23813	main::log_data('dump','@content',\@content) if $b_log;
23814	@content = map {s/,|\*|\\||\"|[:\47]|^\s+|\s+$|n\/a//ig; $_} @content if @content;
23815	foreach (@content){
23816		next if /^\s*$/;
23817		my @working = split(/\s*=\s*/, $_);
23818		next if !$working[0];
23819		if ($working[0] eq 'DISTRIB_ID' && $working[1]){
23820			if ($working[1] =~ /^Manjaro/i){
23821				$id = 'Manjaro Linux';
23822			}
23823			# in the old days, arch used lsb_release
23824# 			elsif ($working[1] =~ /^Arch$/i){
23825# 				$id = 'Arch Linux';
23826# 			}
23827			else {
23828				$id = $working[1];
23829			}
23830		}
23831		elsif ($working[0] eq 'DISTRIB_RELEASE' && $working[1]){
23832			$release = $working[1];
23833		}
23834		elsif ($working[0] eq 'DISTRIB_CODENAME' && $working[1]){
23835			$codename = $working[1];
23836		}
23837		# sometimes some distros cannot do their lsb-release files correctly,
23838		# so here is one last chance to get it right.
23839		elsif ($working[0] eq 'DISTRIB_DESCRIPTION' && $working[1]){
23840			$description = $working[1];
23841		}
23842	}
23843	if (!$id && !$release && !$codename && $description){
23844		$distro = $description;
23845	}
23846	else {
23847		# avoid duplicates
23848		$distro = $id;
23849		$distro .= " $release" if $distro !~ /$release/;
23850		$distro .= " $codename" if $distro !~ /$codename/;
23851		$distro =~ s/^\s+|\s\s+|\s+$//g; # get rid of double and trailing spaces
23852	}
23853	eval $end if $b_log;
23854	return $distro;
23855}
23856sub get_os_release {
23857	eval $start if $b_log;
23858	my ($base_type) = @_;
23859	my ($base_id,$base_name,$base_version,$distro,$distro_name,$pretty_name,
23860	$lc_name,$name,$version_name,$version_id) = ('','','','','','','','','','');
23861	my @content = @osr;
23862	main::log_data('dump','@content',\@content) if $b_log;
23863	@content = map {s/\\||\"|[:\47]|^\s+|\s+$|n\/a//ig; $_} @content if @content;
23864	foreach (@content){
23865		next if /^\s*$/;
23866		my @working = split(/\s*=\s*/, $_);
23867		next if !$working[0];
23868		if ($working[0] eq 'PRETTY_NAME' && $working[1]){
23869			$pretty_name = $working[1];
23870		}
23871		elsif ($working[0] eq 'NAME' && $working[1]){
23872			$name = $working[1];
23873			$lc_name = lc($name);
23874		}
23875		elsif ($working[0] eq 'VERSION' && $working[1]){
23876			$version_name = $working[1];
23877			$version_name =~ s/,//g;
23878		}
23879		elsif ($working[0] eq 'VERSION_ID' && $working[1]){
23880			$version_id = $working[1];
23881		}
23882		# for mint/zorin, other ubuntu base system base
23883		if ($base_type){
23884			if ($working[0] eq 'ID_LIKE' && $working[1]){
23885				if ($base_type eq 'ubuntu'){
23886					# popos shows debian
23887					$working[1] =~ s/^(debian|ubuntu\sdebian|debian\subuntu)/ubuntu/;
23888					$working[1] = 'ubuntu' if $working[1] eq 'debian';
23889				}
23890				$base_name = ucfirst($working[1]);
23891			}
23892			elsif ($base_type eq 'ubuntu' && $working[0] eq 'UBUNTU_CODENAME' && $working[1]){
23893				$base_version = ucfirst($working[1]);
23894			}
23895			elsif ($base_type eq 'debian' && $working[0] eq 'DEBIAN_CODENAME' && $working[1]){
23896				$base_version = $working[1];
23897			}
23898		}
23899	}
23900	# NOTE: tumbleweed has pretty name but pretty name does not have version id
23901	# arco shows only the release name, like kirk, in pretty name. Too many distros
23902	# are doing pretty name wrong, and just putting in the NAME value there
23903	if (!$base_type){
23904		if ($name && $version_name){
23905			$distro = $name;
23906			$distro = 'Arco Linux' if $lc_name =~ /^arco/;
23907			if ($version_id && $version_name !~ /$version_id/){
23908				$distro .= ' ' . $version_id;
23909			}
23910			$distro .= " $version_name";
23911		}
23912		elsif ($pretty_name && ($pretty_name !~ /tumbleweed/i && $lc_name ne 'arcolinux')){
23913			$distro = $pretty_name;
23914		}
23915		elsif ($name){
23916			$distro = $name;
23917			if ($version_id){
23918				$distro .= ' ' . $version_id;
23919			}
23920		}
23921	}
23922	# note: mint has varying formats here, some have ubuntu as name, 17 and earlier
23923	else {
23924		# mint 17 used ubuntu os-release,  so won't have $base_version
23925		if ($base_name && $base_version){
23926			$base_id = ubuntu_id($base_version) if $base_type eq 'ubuntu' && $base_version;
23927			$base_id = '' if $base_id && "$base_name$base_version" =~ /$base_id/;
23928			$base_id .= ' ' if $base_id;
23929			$distro = "$base_name $base_id$base_version";
23930		}
23931		elsif ($base_type eq 'default' && ($pretty_name || ($name && $version_name))){
23932			$distro = ($name && $version_name) ? "$name $version_name" : $pretty_name;
23933		}
23934		# LMDE 2 has only limited data in os-release, no _LIKE values. 3 has like and debian_codename
23935		elsif ($base_type eq 'ubuntu' && $lc_name =~ /^(debian|ubuntu)/ && ($pretty_name || ($name && $version_name))){
23936			$distro = ($name && $version_name) ? "$name $version_name": $pretty_name;
23937		}
23938		elsif ($base_type eq 'debian' && $base_version){
23939			$distro = debian_id($base_version);
23940		}
23941	}
23942	eval $end if $b_log;
23943	return $distro;
23944}
23945# arg: 1 - optional: debian codename
23946sub debian_id {
23947	eval $start if $b_log;
23948	my ($codename) = @_;
23949	my ($debian_version,$id);
23950	$debian_version = main::reader('/etc/debian_version','strip',0) if -r '/etc/debian_version';
23951	$id = 'Debian';
23952	return if !$debian_version && !$codename;
23953	# note, 3.0, woody, 3.1, sarge, but after it's integer per version
23954	my %debians = (
23955	'4' => 'etch',
23956	'5' => 'lenny',
23957	'6' => 'squeeze',
23958	'7' => 'wheezy',
23959	'8' => 'jessie',
23960	'9' => 'stretch',
23961	'10' => 'buster',
23962	'11' => 'bullseye',
23963	'12' => 'bookworm',
23964	'13' => 'trixie',
23965	);
23966	if (main::is_numeric($debian_version)){
23967		$id .= " $debian_version " . $debians{int($debian_version)};
23968	}
23969	elsif ($codename){
23970		my %by_value = reverse %debians;
23971		my $version = (main::is_numeric($debian_version)) ? "$debian_version $codename": $debian_version;
23972		$id .= " $version";
23973	}
23974	# like buster/sid
23975	elsif ($debian_version){
23976		$id .= " $debian_version";
23977	}
23978	eval $end if $b_log;
23979	return $id;
23980}
23981
23982# note, these are only for matching derived names, no need to go
23983# all the way back here, update as new names are known. This is because
23984# Mint is using UBUNTU_CODENAME without ID data.
23985sub ubuntu_id {
23986	eval $start if $b_log;
23987	my ($codename) = @_;
23988	$codename = lc($codename);
23989	my ($id) = ('');
23990	my %codenames = (
23991	'hirsute' => '21.04',
23992	'groovy' => '20.10',
23993	'focal' => '20.04 LTS',
23994	'eoan' => '19.10',
23995	'disco' => '19.04',
23996	'cosmic' => '18.10',
23997	'bionic' => '18.04 LTS',
23998	'artful' => '17.10',
23999	'zesty' => '17.04',
24000	'yakkety' => '16.10',
24001	'xenial' => '16.04 LTS',
24002	'wily' => '15.10',
24003	'vivid' => '15.04',
24004	'utopic' => '14.10',
24005	'trusty' => '14.04 LTS ',
24006	'saucy' => '13.10',
24007	'raring' => '13.04',
24008	'quantal' => '12.10',
24009	'precise' => '12.04 LTS ',
24010	);
24011	$id = $codenames{$codename} if defined $codenames{$codename};
24012	eval $end if $b_log;
24013	return $id;
24014}
24015}
24016
24017## DmidecodeData
24018{
24019package DmidecodeData;
24020# note, all actual tests have already been run in check_tools so if we
24021# got here, we're good.
24022sub set {
24023	eval $start if $b_log;
24024	${$_[0]} = 1; # set check boolean by reference
24025	if ($fake{'dmidecode'} || $alerts{'dmidecode'}->{'action'} eq 'use'){
24026		generate_data();
24027	}
24028	eval $end if $b_log;
24029}
24030
24031sub generate_data {
24032	eval $start if $b_log;
24033	my ($content,@data,@working,$type,$handle);
24034	if ($fake{'dmidecode'}){
24035		my $file;
24036		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/pci-freebsd-8.2-2";
24037		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/dmidecode-loki-1.txt";
24038		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/dmidecode-t41-1.txt";
24039		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/dmidecode-mint-20180106.txt";
24040		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/dmidecode-vmware-ram-1.txt";
24041		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/dmidecode/dmidecode-tyan-4408.txt";
24042		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ram/dmidecode-speed-configured-1.txt";
24043		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ram/dmidecode-speed-configured-2.txt";
24044		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/ram/00srv-dmidecode-mushkin-1.txt";
24045		# open(my $fh, '<', $file) or die "can't open $file: $!";
24046		# chomp(@data = <$fh>);
24047	}
24048	else {
24049		$content = qx($alerts{'dmidecode'}->{'path'} 2>/dev/null);
24050		@data = split('\n', $content);
24051	}
24052	# we don't need the opener lines of dmidecode output
24053	# but we do want to preserve the indentation. Empty lines
24054	# won't matter, they will be skipped, so no need to handle them.
24055	# some dmidecodes do not use empty line separators
24056	splice(@data, 0, 5) if @data;
24057	my $j = 0;
24058	my $b_skip = 1;
24059	foreach (@data){
24060		if (!/^Hand/){
24061			next if $b_skip;
24062			if (/^[^\s]/){
24063				$_ = lc($_);
24064				$_ =~ s/\s(information)//;
24065				push(@working, $_);
24066			}
24067			elsif (/^\t/){
24068				$_ =~ s/^\t\t/~/;
24069				$_ =~ s/^\t|\s+$//g;
24070				push(@working, $_);
24071			}
24072		}
24073		elsif (/^Handle\s(0x[0-9A-Fa-f]+).*DMI\stype\s([0-9]+),.*/){
24074			$j = scalar @dmi;
24075			$handle = hex($1);
24076			$type = $2;
24077			$use{'slot-tool'} = 1 if $type && $type == 9;
24078			$b_skip = ($type > 126) ? 1 : 0;
24079			next if $b_skip;
24080			# we don't need 32, system boot, or 127, end of table
24081			if (@working){
24082				if ($working[0] != 32 && $working[0] < 127){
24083					$dmi[$j] = (
24084					[@working],
24085					);
24086				}
24087			}
24088			@working = ($type,$handle);
24089		}
24090	}
24091	if (@working && $working[0] != 32 && $working[0] != 127){
24092		$j = scalar @dmi;
24093		$dmi[$j] = \@working;
24094	}
24095	# last by not least, sort it by dmi type, now we don't have to worry
24096	# about random dmi type ordering in the data, which happens. Also sort
24097	# by handle, as secondary sort.
24098	@dmi = sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] } @dmi;
24099	main::log_data('dump','@dmi',\@dmi) if $b_log;
24100	print Data::Dumper::Dumper \@dmi if $dbg[2];
24101	eval $end if $b_log;
24102}
24103}
24104
24105# return all device modules not including driver
24106sub get_driver_modules {
24107	eval $start if $b_log;
24108	my ($driver,$modules) = @_;
24109	return if !$modules;
24110	my @mods = split(/,\s+/, $modules);
24111	if ($driver){
24112		@mods = grep {!/^$driver$/} @mods;
24113		$modules = join(',', @mods);
24114	}
24115	log_data('data','$modules',$modules) if $b_log;
24116	eval $end if $b_log;
24117	return $modules;
24118}
24119
24120# 1: driver; 2: modules, comma separated, return only modules
24121# which do not equal the driver string itself. Sometimes the module
24122# name is different from the driver name, even though it's the same thing.
24123sub get_gcc_data {
24124	eval $start if $b_log;
24125	my ($gcc,@data,@gccs,@temp);
24126	# NOTE: We can't use program_version because we don't yet know where
24127	# the version number is
24128	if (my $program = check_program('gcc')){
24129		@data = grabber("$program --version 2>/dev/null");
24130		$gcc = awk(\@data,'^gcc');
24131	}
24132	if ($gcc){
24133		# strip out: gcc (Debian 6.3.0-18) 6.3.0 20170516
24134		# gcc (GCC) 4.2.2 20070831 prerelease [FreeBSD]
24135		$gcc =~ s/\([^\)]*\)//g;
24136		$gcc = get_piece($gcc,2);
24137	}
24138	if ($extra > 1){
24139		# glob /usr/bin for gccs, strip out all non numeric values
24140		@temp = globber('/usr/bin/gcc-*');
24141		foreach (@temp){
24142			if (/\/gcc-([0-9.]+)$/){
24143				push(@gccs, $1);
24144			}
24145		}
24146	}
24147	unshift(@gccs, $gcc);
24148	log_data('dump','@gccs',\@gccs) if $b_log;
24149	eval $end if $b_log;
24150	return @gccs;
24151}
24152
24153## GlabelData - set/get
24154# used only to get RAID ZFS gptid path standard name, like ada0p1
24155{
24156package GlabelData;
24157# gptid/c5e940f1-5ce2-11e6-9eeb-d05099ac4dc2     N/A  ada0p1
24158sub get {
24159	eval $start if $b_log;
24160	my ($gptid) = @_;
24161	set() if !$loaded{'glabel'};
24162	return if !@glabel || !$gptid;
24163	my ($dev_id) = ('');
24164	foreach (@glabel){
24165		my @temp = split(/\s+/, $_);
24166		my $gptid_trimmed = $gptid;
24167		# slice off s[0-9] from end in case they use slice syntax
24168		$gptid_trimmed =~ s/s[0-9]+$//;
24169		if (defined $temp[0] && ($temp[0] eq $gptid || $temp[0] eq $gptid_trimmed)){
24170			$dev_id = $temp[2];
24171			last;
24172		}
24173	}
24174	$dev_id ||= $gptid; # no match? return full string
24175	eval $end if $b_log;
24176	return $dev_id;
24177}
24178sub set {
24179	eval $start if $b_log;
24180	$loaded{'glabel'} = 1;
24181	if (my $path = main::check_program('glabel')){
24182		@glabel = main::grabber("$path status 2>/dev/null");
24183	}
24184	main::log_data('dump','@glabel:with Headers',\@glabel) if $b_log;
24185	# get rid of first header line
24186	shift @glabel;
24187	eval $end if $b_log;
24188}
24189}
24190
24191sub get_hostname {
24192	eval $start if $b_log;
24193	my $hostname = '';
24194	if ($ENV{'HOSTNAME'}){
24195		$hostname = $ENV{'HOSTNAME'};
24196	}
24197	elsif (!$bsd_type && -r "/proc/sys/kernel/hostname"){
24198		$hostname = reader('/proc/sys/kernel/hostname','',0);
24199	}
24200	# puppy removed this from core modules, sigh
24201	# this is faster than subshell of hostname
24202	elsif (check_perl_module('Sys::Hostname')){
24203		Sys::Hostname->import;
24204		$hostname = Sys::Hostname::hostname();
24205	}
24206	elsif (my $program = check_program('hostname')){
24207		$hostname = (grabber("$program 2>/dev/null"))[0];
24208	}
24209	$hostname ||= 'N/A';
24210	eval $end if $b_log;
24211	return $hostname;
24212}
24213
24214sub get_init_data {
24215	eval $start if $b_log;
24216	my $runlevel = get_runlevel_data();
24217	my $default = ($extra > 1) ? get_runlevel_default() : '';
24218	my ($init,$init_version,$rc,$rc_version,$program) = ('','','','','');
24219	my $comm = (-r '/proc/1/comm') ? reader('/proc/1/comm','',0) : '';
24220	my (@data);
24221	# this test is pretty solid, if pid 1 is owned by systemd, it is systemd
24222	# otherwise that is 'init', which covers the rest of the init systems.
24223	# more data may be needed for other init systems.
24224	if ($comm){
24225		if ($comm =~ /systemd/){
24226			$init = 'systemd';
24227			if ($program = check_program('systemd')){
24228				$init_version = program_version($program,'^systemd','2','--version',1);
24229			}
24230			if (!$init_version && ($program = check_program('systemctl'))){
24231				$init_version = program_version($program,'^systemd','2','--version',1);
24232			}
24233		}
24234		# epoch version == Epoch Init System 1.0.1 "Sage"
24235		elsif ($comm =~ /epoch/){
24236			$init = 'Epoch';
24237			$init_version = program_version('epoch', '^Epoch', '4','version');
24238		}
24239		# missing data: note, runit can install as a dependency without being the
24240		# init system: http://smarden.org/runit/sv.8.html
24241		# NOTE: the proc test won't work on bsds, so if runit is used on bsds we
24242		# will need more datas
24243		elsif ($comm =~ /runit/){
24244			$init = 'runit';
24245		}
24246		elsif ($comm =~ /shepherd/){
24247			$init = 'Shepherd';
24248			$init_version = program_version('shepherd', '^shepherd', '4','--version',1);
24249		}
24250		elsif ($comm =~ /^s6/){
24251			$init = 's6';
24252		}
24253	}
24254	if (!$init){
24255		# output: /sbin/init --version:  init (upstart 1.1)
24256		# init (upstart 0.6.3)
24257		# openwrt /sbin/init hangs on --version command, I think
24258		if ((!$b_mips && !$b_sparc && !$b_arm) &&
24259		 ($init_version = program_version('init', 'upstart', '3','--version'))){
24260			$init = 'Upstart';
24261		}
24262		elsif (check_program('launchctl')){
24263			$init = 'launchd';
24264		}
24265		elsif (-f '/etc/inittab'){
24266			$init = 'SysVinit';
24267			if (check_program('strings')){
24268				@data = grabber('strings /sbin/init');
24269				$init_version = awk(\@data,'^version\s+[0-9]',2);
24270			}
24271		}
24272		elsif (-f '/etc/ttys'){
24273			$init = 'init (BSD)';
24274		}
24275	}
24276	if ((grep { /openrc/ } globber('/run/*openrc*')) || (grep {/openrc/} @ps_cmd)){
24277		$rc = 'OpenRC';
24278		# /sbin/openrc --version == openrc (OpenRC) 0.13
24279		if ($program = check_program('openrc')){
24280			$rc_version = program_version($program, '^openrc', '3','--version');
24281		}
24282		# /sbin/rc --version == rc (OpenRC) 0.11.8 (Gentoo Linux)
24283		elsif ($program = check_program('rc')){
24284			$rc_version = program_version($program, '^rc', '3','--version');
24285		}
24286		if (-r '/run/openrc/softlevel'){
24287			$runlevel = reader('/run/openrc/softlevel','',0);
24288		}
24289		elsif (-r '/var/run/openrc/softlevel'){
24290			$runlevel = reader('/var/run/openrc/softlevel','',0);
24291		}
24292		elsif ($program = check_program('rc-status')){
24293			$runlevel = (grabber("$program -r 2>/dev/null"))[0];
24294		}
24295	}
24296	my %init = (
24297	'init-type' => $init,
24298	'init-version' => $init_version,
24299	'rc-type' => $rc,
24300	'rc-version' => $rc_version,
24301	'runlevel' => $runlevel,
24302	'default' => $default,
24303	);
24304	eval $end if $b_log;
24305	return %init;
24306}
24307
24308## IpData
24309{
24310package IpData;
24311sub set {
24312	eval $start if $b_log;
24313	if ($alerts{'ip'}->{'action'} eq 'use'){
24314		set_ip_addr();
24315	}
24316	elsif ($alerts{'ifconfig'}->{'action'} eq 'use'){
24317		set_ifconfig();
24318	}
24319	eval $end if $b_log;
24320}
24321
24322sub set_ip_addr {
24323	eval $start if $b_log;
24324	my @data = main::grabber($alerts{'ip'}->{'path'} . " addr 2>/dev/null",'\n','strip');
24325	# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/if/scope-ipaddr-1.txt";
24326	# my $file = "$ENV{'HOME'}/bin/scripts/inxi/data/networking/ip-addr-blue-advance.txt";
24327	# my @data = reader($file,'strip') or die $!;
24328	my ($b_skip,$broadcast,$if,$ip,@ips,$scope,$if_id,$type,@temp,@temp2);
24329	foreach (@data){
24330		if (/^[0-9]/){
24331			# print "$_\n";
24332			if (@ips){
24333			# print "$if\n";
24334				@temp = ($if,[@ips]);
24335				push(@ifs,@temp);
24336				@ips = ();
24337			}
24338			@temp = split(/:\s+/, $_);
24339			$if = $temp[1];
24340			if ($if eq 'lo'){
24341				$b_skip = 1;
24342				$if = '';
24343				next;
24344			}
24345			$b_skip = 0;
24346			@temp = ();
24347		}
24348		elsif (!$b_skip && /^inet/){
24349			# print "$_\n";
24350			@temp = split(/\s+/, $_);
24351			($broadcast,$ip,$scope,$if_id,$type) = ('','','','','');
24352			$ip = $temp[1];
24353			$type = ($temp[0] eq 'inet') ? 4 : 6 ;
24354			if ($temp[2] eq 'brd'){
24355				$broadcast = $temp[3];
24356			}
24357			if (/scope\s([^\s]+)(\s(.+))?/){
24358				$scope = $1;
24359				$if_id = $3;
24360			}
24361			@temp = ($type,$ip,$broadcast,$scope,$if_id);
24362			push(@ips,[@temp]);
24363			# print Data::Dumper::Dumper \@ips;
24364		}
24365	}
24366	# print Data::Dumper::Dumper \@ips if $dbg[4];
24367	if (@ips){
24368		@temp = ($if,[@ips]);
24369		push(@ifs,@temp);
24370	}
24371	main::log_data('dump','@ifs',\@ifs) if $b_log;
24372	print Data::Dumper::Dumper \@ifs if $dbg[3];
24373	eval $end if $b_log;
24374}
24375
24376sub set_ifconfig {
24377	eval $start if $b_log;
24378	my @data = main::grabber($alerts{'ifconfig'}->{'path'} . " 2>/dev/null",'\n','');
24379	# my @data = reader("$ENV{'HOME'}/bin/scripts/inxi/data/if/vps-ifconfig-1.txt",'') or die $!;
24380	my ($b_skip,$broadcast,$if,@ips_bsd,$ip,@ips,$scope,$if_id,$type,@temp,@temp2);
24381	my ($state,$speed,$duplex,$mac);
24382	foreach (@data){
24383		if (/^[\S]/i){
24384			# print "$_\n";
24385			if (@ips){
24386			# print "here\n";
24387				@temp = ($if,[@ips]);
24388				push(@ifs,@temp);
24389				@ips = ();
24390			}
24391			if ($mac){
24392				@temp = ($if,[($state,$speed,$duplex,$mac)]);
24393				push(@ifs_bsd,@temp);
24394				($state,$speed,$duplex,$mac,$if_id) = ('','','','','');
24395			}
24396			$if = (split(/\s+/, $_))[0];
24397			$if =~ s/:$//; # em0: flags=8843
24398			$if_id = $if;
24399			$if = (split(':', $if))[0] if $if;
24400			if ($if =~ /^lo/){
24401				$b_skip = 1;
24402				$if = '';
24403				$if_id = '';
24404				next;
24405			}
24406			$b_skip = 0;
24407		}
24408		elsif (!$b_skip && $bsd_type && /^\s+(address|ether|media|status|lladdr)/){
24409			$_ =~ s/^\s+//;
24410			# freebsd 7.3: media: Ethernet 100baseTX <full-duplex>
24411			# Freebsd 8.2/12.2: media: Ethernet autoselect (1000baseT <full-duplex>)
24412			# Netbsd 9.1: media: Ethernet autoselect (1000baseT full-duplex)
24413			# openbsd: media: Ethernet autoselect (1000baseT full-duplex)
24414			if (/^media/){
24415				if ($_ =~ /[\s\(]([1-9][^\(\s]+)?\s<([^>]+)>/){
24416					$speed = $1 if $1;
24417					$duplex = $2;
24418				}
24419				if (!$duplex && $_ =~ /\s\(([\S]+)\s([^\s<]+)\)/){
24420					$speed = $1;
24421					$duplex = $2;
24422				}
24423				if (!$speed && $_ =~ /\s\(([1-9][\S]+)\s/){
24424					$speed = $1;
24425				}
24426			}
24427			# lladdr openbsd/address netbsd/ether freebsd
24428			elsif (!$mac && /^(address|ether|lladdr)/){
24429				$mac = (split(/\s+/, $_))[1];
24430			}
24431			elsif (/^status:\s*(.*)/){
24432				$state = $1;
24433			}
24434		}
24435		elsif (!$b_skip && /^\s+inet/){
24436			# print "$_\n";
24437			$_ =~ s/^\s+//;
24438			$_ =~ s/addr:\s/addr:/;
24439			@temp = split(/\s+/, $_);
24440			($broadcast,$ip,$scope,$type) = ('','','','');
24441			$ip = $temp[1];
24442			# fe80::225:90ff:fe13:77ce%em0
24443# 			$ip =~ s/^addr:|%([\S]+)//;
24444			if ($1 && $1 ne $if_id){
24445				$if_id = $1;
24446			}
24447			$type = ($temp[0] eq 'inet') ? 4 : 6 ;
24448			if (/(Bcast:|broadcast\s)([\S]+)/){
24449				$broadcast = $2;
24450			}
24451			if (/(scopeid\s[^<]+<|Scope:|scopeid\s)([^>]+)[>]?/){
24452				$scope = $2;
24453			}
24454			$scope = 'link' if $ip =~ /^fe80/;
24455			@temp = ($type,$ip,$broadcast,$scope,$if_id);
24456			push(@ips,[@temp]);
24457			# print Data::Dumper::Dumper \@ips;
24458		}
24459	}
24460	if (@ips){
24461		@temp = ($if,[@ips]);
24462		push(@ifs,@temp);
24463	}
24464	if ($mac){
24465		@temp = ($if,[($state,$speed,$duplex,$mac)]);
24466		push(@ifs_bsd,@temp);
24467		($state,$speed,$duplex,$mac) = ('','','','');
24468	}
24469	print Data::Dumper::Dumper \@ifs if $dbg[3];
24470	print Data::Dumper::Dumper \@ifs_bsd if $dbg[3];
24471	main::log_data('dump','@ifs',\@ifs) if $b_log;
24472	main::log_data('dump','@ifs_bsd',\@ifs_bsd) if $b_log;
24473	eval $end if $b_log;
24474}
24475}
24476
24477sub get_kernel_data {
24478	eval $start if $b_log;
24479	my ($kernel,$ksplice) = ('','');
24480	# Linux; yawn; 4.9.0-3.1-liquorix-686-pae; #1 ZEN SMP PREEMPT liquorix 4.9-4 (2017-01-14); i686
24481	# 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
24482	if (@uname){
24483		$kernel = $uname[2];
24484		if ((my $program = check_program('uptrack-uname')) && $kernel){
24485			$ksplice = qx($program -rm);
24486			$ksplice = trimmer($ksplice);
24487			$kernel = ($ksplice) ? $ksplice . ' (ksplice)' : $kernel;
24488		}
24489		$kernel .= ' ' . $uname[-1];
24490		$kernel = ($bsd_type) ? $uname[0] . ' ' . $kernel : $kernel;
24491	}
24492	$kernel ||= 'N/A';
24493	log_data('data',"kernel: $kernel ksplice: $ksplice") if $b_log;
24494	log_data('dump','perl @uname', \@uname) if $b_log;
24495	eval $end if $b_log;
24496	return $kernel;
24497}
24498
24499sub get_kernel_bits {
24500	eval $start if $b_log;
24501	my $bits = '';
24502	if (my $program = check_program('getconf')){
24503		$bits = (grabber("$program LONG_BIT 2>/dev/null"))[0];
24504	}
24505	# fallback test
24506	if (!$bits && @uname){
24507		$bits = $uname[-1];
24508		$bits = ($bits =~ /64/) ? 64 : 32;
24509	}
24510	$bits ||= 'N/A';
24511	eval $end if $b_log;
24512	return $bits;
24513}
24514
24515## KernelParameters
24516{
24517package KernelParameters;
24518sub get {
24519	eval $start if $b_log;
24520	my ($parameters);
24521	if (my $file = $system_files{'proc-cmdline'}){
24522		$parameters = parameters_linux($file);
24523	}
24524	elsif ($bsd_type){
24525		$parameters = parameters_bsd();
24526	}
24527	eval $end if $b_log;
24528	return $parameters;
24529}
24530sub parameters_linux {
24531	eval $start if $b_log;
24532	my ($file) = @_;
24533	# unrooted android may have file only root readable
24534	my $line = main::reader($file,'',0) if -r $file;
24535	eval $end if $b_log;
24536	return $line;
24537}
24538sub parameters_bsd {
24539	eval $start if $b_log;
24540	my ($parameters);
24541	eval $end if $b_log;
24542	return $parameters;
24543}
24544}
24545
24546## LsblkData - set/get
24547{
24548package LsblkData;
24549# 1 - partition name
24550sub get {
24551	eval $start if $b_log;
24552	my $item = $_[0];
24553	return if !@lsblk;
24554	my (%device);
24555	foreach my $device (@lsblk){
24556		if ($device->{'name'} eq $item){
24557			%device = %$device;
24558			last;
24559		}
24560	}
24561	eval $start if $b_log;
24562	return %device;
24563}
24564sub set {
24565	eval $start if $b_log;
24566	$loaded{'lsblk'} = 1;
24567	if ($alerts{'lsblk'} && $alerts{'lsblk'}->{'path'}){
24568		# check to see if lsblk removes : - separators from accepted input syntax
24569		my $cmd = $alerts{'lsblk'}->{'path'} . ' -bP --output NAME,TYPE,RM,FSTYPE,';
24570		$cmd .= 'SIZE,LABEL,UUID,SERIAL,MOUNTPOINT,PHY-SEC,LOG-SEC,PARTFLAGS,';
24571		$cmd .= 'MAJ:MIN,PKNAME 2>/dev/null';
24572		print "cmd: $cmd\n" if $dbg[32];
24573		my @working = main::grabber($cmd);
24574		print Data::Dumper::Dumper \@working if $dbg[32];
24575		# note: lsblk 2.37 changeed - and : to _ in the output.
24576		my $pattern = 'NAME="([^"]*)"\s+TYPE="([^"]*)"\s+RM="([^"]*)"\s+';
24577		$pattern .= 'FSTYPE="([^"]*)"\s+SIZE="([^"]*)"\s+LABEL="([^"]*)"\s+';
24578		$pattern .= 'UUID="([^"]*)"\s+SERIAL="([^"]*)"\s+MOUNTPOINT="([^"]*)"\s+';
24579		$pattern .= 'PHY[_-]SEC="([^"]*)"\s+LOG[_-]SEC="([^"]*)"\s+';
24580		$pattern .= 'PARTFLAGS="([^"]*)"\s+MAJ[:_-]MIN="([^"]*)"\s+PKNAME="([^"]*)"';
24581		foreach (@working){
24582			if (/$pattern/){
24583				my $size = ($5) ? $5/1024: 0;
24584				# some versions of lsblk do not return serial, fs, uuid, or label
24585				push(@lsblk, {
24586				'name' => $1,
24587				'type' => $2,
24588				'rm' => $3,
24589				'fs' => $4,
24590				'size' => $size,
24591				'label' => $6,
24592				'uuid' => $7,
24593				'serial' => $8,
24594				'mount' => $9,
24595				'block-physical' => $10,
24596				'block-logical' => $11,
24597				'partition-flags' => $12,
24598				'maj-min' => $13,
24599				'parent' => $14,
24600				});
24601				# must be below assignments!! otherwise the result of the match replaces values
24602				# note: for bcache and luks, the device that has that fs is the parent!!
24603				if ($show{'logical'}){
24604					$use{'logical-lvm'} = 1 if !$use{'logical-lvm'} && $2 && $2 eq 'lvm';
24605					if (!$use{'logical-general'} && (($4 && ($4 eq 'crypto_LUKS' || $4 eq 'bcache'))
24606					    || ($2 && ($2 eq 'dm' && $1 =~ /veracrypt/i) ||
24607					    $2 eq 'crypto' || $2 eq 'mpath' || $2 eq 'multipath'))){
24608						$use{'logical-general'} = 1;
24609					}
24610				}
24611			}
24612		}
24613	}
24614	print Data::Dumper::Dumper \@lsblk if $dbg[32];
24615	main::log_data('dump','@lsblk',\@lsblk) if $b_log;
24616	eval $end if $b_log;
24617}
24618}
24619
24620sub set_mapper {
24621	eval $start if $b_log;
24622	$loaded{'mapper'} = 1;
24623	return if ! -d '/dev/mapper';
24624	foreach ((globber('/dev/mapper/*'))){
24625		my ($key,$value) = ($_,Cwd::abs_path("$_"));
24626		next if !$value;
24627		$key =~ s|^/.*/||;
24628		$value =~ s|^/.*/||;
24629		$mapper{$key} = $value;
24630	}
24631	%dmmapper = reverse %mapper if %mapper;
24632	eval $end if $b_log;
24633}
24634
24635## MemoryData
24636{
24637package MemoryData;
24638
24639sub get {
24640	eval $start if $b_log;
24641	my ($type) = @_;
24642	my ($memory);
24643	# note: netbsd 8.0 has meminfo!
24644	$loaded{'memory'} = 1;
24645	# netbsd uses meminfo, but it uses it in a weird way
24646	if (!$force{'vmstat'} && (!$bsd_type || ($force{'meminfo'} && $bsd_type)) &&
24647	 (my $file = $system_files{'proc-meminfo'})){
24648		$memory = meminfo_data($type,$file);
24649	}
24650	else {
24651		$memory = bsd_data($type);
24652	}
24653	eval $end if $b_log;
24654	return $memory;
24655}
24656sub full {
24657	eval $start if $b_log;
24658	my ($source) = @_;
24659	my $num = 0;
24660	my ($memory,@rows);
24661	my ($gpu_ram,$percent,$total,$used) = (0,'','','');
24662	$loaded{'memory'} = 1;
24663	$memory = get('splits');
24664	if ($memory){
24665		my @temp = split(':', $memory);
24666		$gpu_ram = $temp[3] if $temp[3];
24667		$total = ($temp[0]) ? main::get_size($temp[0],'string') : 'N/A';
24668		$used = ($temp[1]) ? main::get_size($temp[1],'string') : 'N/A';
24669		$used .= " ($temp[2]%)" if $temp[2];
24670		if ($gpu_ram){
24671			$gpu_ram = main::get_size($gpu_ram,'string');
24672		}
24673	}
24674	my $key = ($source eq 'process') ? 'System RAM': 'RAM';
24675	$rows[0]->{main::key($num++,1,1,$key)} = '';
24676	$rows[0]->{main::key($num++,0,2,'total')} = $total;
24677	$rows[0]->{main::key($num++,0,2,'used')} = $used;
24678	$rows[0]->{main::key($num++,0,2,'gpu')} = $gpu_ram if $gpu_ram;
24679	eval $end if $b_log;
24680	return @rows;
24681}
24682sub meminfo_data {
24683	eval $start if $b_log;
24684	my ($type,$file) = @_;
24685	my ($available,$buffers,$cached,$free,$gpu,$memory,$not_used,$total) = (0,0,0,0,0,'',0,0);
24686	my @data = main::reader($file);
24687	foreach (@data){
24688		if ($_ =~ /^MemTotal:/){
24689			$total = main::get_piece($_,2);
24690		}
24691		elsif ($_ =~ /^MemFree:/){
24692			$free = main::get_piece($_,2);
24693		}
24694		elsif ($_ =~ /^Buffers:/){
24695			$buffers = main::get_piece($_,2);
24696		}
24697		elsif ($_ =~ /^Cached:/){
24698			$cached = main::get_piece($_,2);
24699		}
24700		elsif ($_ =~ /^MemAvailable:/){
24701			$available = main::get_piece($_,2);
24702		}
24703	}
24704	$gpu = gpu_ram_arm() if $b_arm;
24705	#$gpu = main::translate_size('128M');
24706	$total += $gpu;
24707	if ($available){
24708		$not_used = $available;
24709	}
24710	# seen fringe cases, where total - free+buff+cach < 0
24711	# the idea is that the OS must be using 10MiB of ram or more
24712	elsif (($total - ($free + $buffers + $cached)) > 10000){
24713		$not_used = ($free + $buffers + $cached);
24714	}
24715	# netbsd goes < 0, but it's wrong, so dump the cache
24716	elsif (($total - ($free + $buffers)) > 10000){
24717		$not_used = ($free + $buffers);
24718	}
24719	else {
24720		$not_used = $free;
24721	}
24722	my $used = ($total - $not_used);
24723	my $percent = ($used && $total) ? sprintf("%.1f", ($used/$total)*100) : '';
24724	if ($type eq 'string'){
24725		$percent = " ($percent%)" if $percent;
24726		$memory = sprintf("%.1f/%.1f MiB", $used/1024, $total/1024) . $percent;
24727	}
24728	else {
24729		$memory = "$total:$used:$percent:$gpu";
24730	}
24731	main::log_data('data',"memory: $memory") if $b_log;
24732	eval $end if $b_log;
24733	return $memory;
24734}
24735
24736## openbsd/linux
24737# procs    memory       page                    disks    traps          cpu
24738# r b w    avm     fre  flt  re  pi  po  fr  sr wd0 wd1  int   sys   cs us sy id
24739# 0 0 0  55256 1484092  171   0   0   0   0   0   2   0   12   460   39  3  1 96
24740## openbsd 6.3? added in M/G/T etc, sigh...
24741# 2 57 55M 590M 789 0 0 0...
24742## freebsd:
24743# procs      memory      page                    disks     faults         cpu
24744# r b w     avm    fre   flt  re  pi  po    fr  sr ad0 ad1   in   sy   cs us sy id
24745# 0 0 0  21880M  6444M   924  32  11   0   822 827   0   0  853  832  463  8  3 88
24746# with -H
24747# 2 0 0 14925812  936448    36  13  10   0    84  35   0   0   84   30   42 11  3 86
24748## dragonfly: V1, supported -H
24749#  procs      memory      page                    disks     faults      cpu
24750#  r b w     avm    fre  flt  re  pi  po  fr  sr ad0 ad1   in   sy  cs us sy id
24751#  0 0 0       0  84060 30273993 2845 12742 1164 407498171 320960902   0   0 ....
24752## dragonfly: V2, no avm, no -H support
24753sub bsd_data {
24754	eval $start if $b_log;
24755	my ($type) = @_;
24756	my $memory = '';
24757	my ($avm,$av_pages,$cnt,$fre,$free_mem,$mult,$real_mem,$total) = (0,0,0,0,0,0,0,0);
24758	my (@data,$message);
24759	# my $arg = ($bsd_type ne 'openbsd' && $bsd_type ne 'dragonfly') ? '-H' : '';
24760	if (my $program = main::check_program('vmstat')){
24761		# see above, it's the last line. -H makes it hopefully all in kB so no need
24762		# for K/M/G tests, note that -H not consistently supported, so don't use.
24763		my @vmstat = main::grabber("vmstat 2>/dev/null",'\n','strip');
24764		main::log_data('dump','@vmstat',\@vmstat) if $b_log;
24765		my @header = split(/\s+/, $vmstat[1]);
24766		foreach (@header){
24767			if ($_ eq 'avm'){$avm = $cnt}
24768			elsif ($_ eq 'fre'){$fre = $cnt}
24769			elsif ($_ eq 'flt'){last;}
24770			$cnt++;
24771		}
24772		my $row = $vmstat[-1];
24773		if ($row){
24774			@data = split(/\s+/, $row);
24775			# openbsd 6.3, dragonfly 5.x introduced an M / G character, sigh.
24776			if ($avm > 0 && $data[$avm] && $data[$avm] =~ /^([0-9\.]+[KGMT])(iB|B)?$/){
24777				$data[$avm] = main::translate_size($1);
24778			}
24779			if ($fre > 0 && $data[$fre] && $data[$fre] =~ /^([0-9\.]+[KGMT])(iB|B)?$/){
24780				$data[$fre] = main::translate_size($1);
24781			}
24782			# dragonfly can have 0 avg, or no avm, sigh, but they may fix that so make test dynamic
24783			if ($avm > 0 && $data[$avm] != 0){
24784				$av_pages = ($bsd_type !~ /^(net|open)bsd$/) ? sprintf('%.1f',$data[$avm]/1024) : $data[$avm];
24785			}
24786			if ($fre > 0 && $data[$fre] != 0){
24787				$free_mem = sprintf('%.1f',$data[$fre]);
24788			}
24789		}
24790	}
24791	## code to get total goes here:
24792	if ($alerts{'sysctl'}->{'action'} eq 'use'){
24793		# for dragonfly, we will use free mem, not used because free is 0
24794		my @working;
24795		if ($sysctl{'memory'}){
24796			foreach (@{$sysctl{'memory'}}){
24797				# freebsd seems to use bytes here
24798				if (!$real_mem && /^hw.physmem:/){
24799					@working = split(/:\s*/, $_);
24800					# if ($working[1]){
24801						$working[1] =~ s/^[^0-9]+|[^0-9]+$//g;
24802						$real_mem = sprintf("%.1f", $working[1]/1024);
24803					# }
24804					last if $free_mem;
24805				}
24806				# But, it uses K here. Openbsd/Dragonfly do not seem to have this item
24807				# this can be either: Free Memory OR Free Memory Pages
24808				elsif (/^Free Memory:/){
24809					@working = split(/:\s*/, $_);
24810					$working[1] =~ s/[^0-9]+//g;
24811					$free_mem = sprintf("%.1f", $working[1]);
24812					last if $real_mem;
24813				}
24814			}
24815		}
24816	}
24817	else {
24818		$message = "sysctl $alerts{'sysctl'}->{'action'}"
24819	}
24820	# not using, but leave in place for a bit in case we want it
24821	# my $type = ($free_mem) ? ' free':'' ;
24822	# hack: temp fix for openbsd/darwin: in case no free mem was detected but we have physmem
24823	if (($av_pages || $free_mem) && !$real_mem){
24824		my $error = ($message) ? $message: 'total N/A';
24825		my $used = (!$free_mem) ? $av_pages : $real_mem - $free_mem;
24826		if ($type eq 'string'){
24827			$used = sprintf("%.1f",$used/1024);
24828			$memory = "$used/($error) MiB";
24829		}
24830		else {
24831			$memory = "$error:$used:";
24832		}
24833	}
24834	# use openbsd/dragonfly avail mem data if available
24835	elsif (($av_pages || $free_mem) && $real_mem){
24836		my $used = (!$free_mem) ? $av_pages : $real_mem - $free_mem;
24837		my $percent = ($used && $real_mem) ? sprintf("%.1f", ($used/$real_mem)*100) : '';
24838		if ($type eq 'string'){
24839			$percent = " ($percent)" if $percent;
24840			$memory = sprintf("%.1f/%.1f MiB", $used/1024, $real_mem/1024) . $percent;
24841		}
24842		else {
24843			$memory = "$real_mem:$used:$percent:0";
24844		}
24845	}
24846	eval $end if $b_log;
24847	return $memory;
24848}
24849# raspberry pi only
24850sub gpu_ram_arm {
24851	eval $start if $b_log;
24852	my ($gpu_ram) = (0);
24853	if (my $program = main::check_program('vcgencmd')){
24854		# gpu=128M
24855		# "VCHI initialization failed" - you need to add video group to your user
24856		my $working = (main::grabber("$program get_mem gpu 2>/dev/null"))[0];
24857		$working = (split(/\s*=\s*/, $working))[1] if $working;
24858		$gpu_ram = main::translate_size($working) if $working;
24859	}
24860	main::log_data('data',"gpu ram: $gpu_ram") if $b_log;
24861	eval $end if $b_log;
24862	return $gpu_ram;
24863}
24864# standard systems, not used currently, but maybe one day?
24865sub get_gpu_ram {
24866	eval $start if $b_log;
24867	my ($gpu_ram) = (0);
24868	eval $end if $b_log;
24869	return $gpu_ram;
24870}
24871}
24872
24873sub get_module_version {
24874	eval $start if $b_log;
24875	my ($module) = @_;
24876	return if !$module;
24877	my ($version);
24878	my $path = "/sys/module/$module/version";
24879	if (-r $path){
24880		$version = reader($path,'',0);
24881	}
24882	elsif (-f "/sys/module/$module/uevent"){
24883		$version = 'kernel';
24884	}
24885	# print "version:$version\n";
24886	if (!$version){
24887		if (my $path = check_program('modinfo')){
24888			my @data = grabber("$path $module 2>/dev/null");
24889			$version = awk(\@data,'^version',2,':\s+') if @data;
24890		}
24891	}
24892	$version ||= '';
24893	eval $end if $b_log;
24894	return $version;
24895}
24896
24897## PackageData
24898# Note: this outputs the key/value pairs ready to go and is
24899# called from either -r or -Ix, -r precedes.
24900{
24901package PackageData;
24902my ($count,%counts,@list,$num,%output,$program,$type);
24903$counts{'total'} = 0;
24904sub get {
24905	eval $start if $b_log;
24906	# $num passed by reference to maintain incrementing where requested
24907	($type,$num) = @_;
24908	$loaded{'packages'} = 1;
24909	package_counts();
24910	appimage_counts();
24911	create_output();
24912	eval $end if $b_log;
24913	return %output;
24914}
24915sub create_output {
24916	eval $start if $b_log;
24917	my $total;
24918	if ($counts{'total'}){
24919		$total = $counts{'total'};
24920	}
24921	else {
24922		if ($type eq 'inner' || $counts{'note'}){
24923			$total = 'N/A';
24924		}
24925		else {
24926			$total = main::row_defaults('package-data');
24927		}
24928	}
24929	if ($counts{'total'} && $extra > 1){
24930		delete $counts{'total'};
24931		my $b_mismatch;
24932		foreach (keys %counts){
24933			next if $_ eq 'note';
24934			if ($counts{$_}->[0] && $counts{$_}->[0] != $total){
24935				$b_mismatch = 1;
24936				last;
24937			}
24938		}
24939		$total = '' if !$b_mismatch;
24940	}
24941	$output{main::key($$num++,1,1,'Packages')} = $total;
24942	# if blocked pm secondary, only show if admin
24943	if ($counts{'note'} && (!$counts{'total'} || $b_admin || $total < 100)){
24944		$output{main::key($$num++,0,2,'note')} = $counts{'note'};
24945	}
24946	if ($extra > 1 && %counts){
24947		foreach (sort keys %counts){
24948			my ($cont,$ind) = (1,2);
24949			# if package mgr command returns error, this will not be an array
24950			next if ref $counts{$_} ne 'ARRAY';
24951			if ($counts{$_}->[0] || $b_admin){
24952				my $key = $_;
24953				$key =~ s/^zzz-//; # get rid of the special sorters for items to show last
24954				$output{main::key($$num++,$cont,$ind,$key)} = $counts{$_}->[0];
24955				if ($b_admin && $counts{$_}->[1]){
24956					($cont,$ind) = (0,3);
24957					$output{main::key($$num++,$cont,$ind,'lib')} = $counts{$_}->[1];
24958				}
24959			}
24960		}
24961	}
24962	# print Data::Dumper::Dumper \%output;
24963	eval $end if $b_log;
24964}
24965sub package_counts {
24966	eval $start if $b_log;
24967	my ($type) = @_;
24968	# 0: key; 1: program; 2: p/d; 3: arg/path; 4: 0/1 use lib;
24969	# 5: lib slice; 6: lib splitter; 7 - optional eval test
24970	# needed: cards [nutyx], urpmq [mageia]
24971	my @pkg_managers = (
24972	['alps','alps','p','showinstalled',1,0,''],
24973	['apk','apk','p','info',1,0,''],
24974	# older dpkg-query do not support -f values consistently: eg ${binary:Package}
24975	['apt','dpkg-query','p','-W -f=\'${Package}\n\'',1,0,''],
24976	# ['aptd','dpkg-query','d','/usr/lib/*',1,3,'\\/'],
24977	# mutyx. do cards test because there is a very slow pkginfo python pkg mgr
24978	['cards','pkginfo','p','-i',1,1,'','main::check_program(\'cards\')'],
24979	['emerge','emerge','d','/var/db/pkg/*/*/',1,5,'\\/'],
24980	['eopkg','eopkg','d','/var/lib/eopkg/package/*',1,5,'\\/'],
24981	['guix-sys','guix','p','package -p "/run/current-system/profile" -I',1,0,''],
24982	['guix-usr','guix','p','package -I',1,0,''],
24983	['kiss','kiss','p','list',1,0,''],
24984	['mport','mport','p','list',1,0,''],
24985	['nix-sys','nix-store','p','-qR /run/current-system/sw',1,1,'-'],
24986	['nix-usr','nix-store','p','-qR ~/.nix-profile',1,1,'-'],
24987	['nix-default','nix-store','p','-qR /nix/var/nix/profiles/default',1,2,'-'],
24988	['pacman','pacman','p','-Qq --color never',1,0,'',
24989	 '!main::check_program(\'pacman-g2\')'], # pacman-g2 has sym link to pacman
24990	['pacman-g2','pacman-g2','p','-Q',1,0,''],
24991	['pkg','pkg','d','/var/db/pkg/*',1,0,''], # 'pkg list' returns non programs
24992	['pkg_info','pkg_info','p','',1,0,''],
24993	# like cards, avoid pkginfo directly due to python pm being so slow
24994	# but pkgadd is also found in scratch
24995	['pkgutils','pkginfo','p','-i',1,0,'','main::check_program(\'pkgadd\')'],
24996	# slack 15 moves packages to /var/lib/pkgtools/packages but links to /var/log/packages
24997	['pkgtool','pkgtool','d','/var/lib/pkgtools/packages',1,4,'\\/',
24998	 '-d \'/var/lib/pkgtools/packages\''],
24999	['pkgtool','pkgtool','d','/var/log/packages/',1,5,'\\/',
25000	 '! -d \'/var/lib/pkgtools/packages\' && -d \'/var/log/packages/\''],
25001	# rpm way too slow without nodigest/sig!! confirms packages exist
25002	# but even with, MASSIVELY slow in some cases, > 20, 30 seconds!!!!
25003	# find another way to get rpm package counts or don't show this feature for rpm!!
25004	['rpm','rpm','pkg','-qa --nodigest --nosignature',1,0,''],
25005	# scratch is a programming language too, with software called scratch
25006	['scratch','pkgbuild','d','/var/lib/scratchpkg/index/*/.pkginfo',1,5,'\\/',
25007	 '-d \'/var/lib/scratchpkg\''],
25008	# note',' slapt-get, spkg, and pkgtool all return the same count
25009	# ['slapt-get','slapt-get','p','--installed',1,0,''],
25010	# ['spkg','spkg','p','--installed',1,0,''],
25011	['tce','tce-status','p','-i',1,0,''],
25012	# note: I believe mageia uses rpm internally but confirm
25013	# ['urpmi','urpmq','p','??',1,0,''],
25014	['xbps','xbps-query','p','-l',1,1,''],
25015	# ['xxx-brew','brew','p','--cellar',0,0,''], # verify how this works
25016	['zzz-flatpak','flatpak','p','list',0,0,''],
25017	['zzz-snap','snap','p','list',0,0,'','@ps_cmd && (grep {/\bsnapd\b/} @ps_cmd)'],
25018	);
25019	my $libs;
25020	foreach (@pkg_managers){
25021		if ($program = main::check_program($_->[1])){
25022			next if $_->[7] && !eval $_->[7];
25023			my $error;
25024			if ($_->[2] eq 'p' || ($_->[2] eq 'pkg' && $force{'pkg'})){
25025				chomp(@list = qx($program $_->[3] 2>/dev/null));
25026			}
25027			elsif ($_->[2] eq 'd'){
25028				@list = main::globber($_->[3]);
25029			}
25030			else {
25031				@list = undef;
25032				$error = main::row_defaults('pm-disabled');
25033			}
25034			$libs = undef;
25035			# print Data::Dumper::Dumper \@list;
25036			if (!$error){
25037				$count = scalar @list;
25038				if ($b_admin && $count && $_->[4]){
25039					$libs = count_libs(\@list,$_->[5],$_->[6]);
25040				}
25041				$counts{$_->[0]} = [$count,$libs];
25042				$counts{'total'} += $count;
25043			}
25044			else {
25045				$counts{'note'} = $error;
25046			}
25047			# print Data::Dumper::Dumper \%counts;
25048		}
25049	}
25050	# print Data::Dumper::Dumper \%counts;
25051	main::log_data('dump','Package managers: %counts',\%counts) if $b_log;
25052	eval $end if $b_log;
25053}
25054sub appimage_counts {
25055	if (@ps_cmd && (grep {/\bappimaged\b/} @ps_cmd)){
25056		@list = main::globber($ENV{'HOME'} . '/.local/bin/*.appimage');
25057		$count = scalar @list;
25058		$counts{'zzz-appimage'} = [$count,undef] if $count;
25059		$counts{'total'} += $count;
25060	}
25061}
25062sub count_libs {
25063	my ($items,$pos,$split) = @_;
25064	my (@data);
25065	my $i = 0;
25066	$split ||= '\\s+';
25067	# print scalar @$items, '::', $split, '::', $pos, "\n";
25068	foreach (@$items){
25069		@data = split(/$split/, $_);
25070		# print scalar @data, '::', $data[$pos], "\n";
25071		$i++ if $data[$pos] && $data[$pos] =~ m%^lib%;
25072	}
25073	return $i;
25074}
25075}
25076
25077## PartitionData - set/get
25078# for /proc/partitions only, see DiskDataBSD for BSD partition data.
25079{
25080package PartitionData;
25081sub set {
25082	my ($type) = @_;
25083	$loaded{'partition-data'} = 1;
25084	if (my $file = $system_files{'proc-partitions'}){
25085		proc_data($file);
25086	}
25087}
25088# 1 - partition name, without /dev, like sda1, sde
25089sub get {
25090	eval $start if $b_log;
25091	my $item = $_[0];
25092	return if !@proc_partitions;
25093	my (@device);
25094	foreach my $device (@proc_partitions){
25095		if ($device->[3] eq $item){
25096			@device = @$device;
25097			last;
25098		}
25099	}
25100	eval $start if $b_log;
25101	return @device;
25102}
25103
25104sub proc_data {
25105	eval $start if $b_log;
25106	my $file = $_[0];
25107	if ($fake{'partitions'}){
25108		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/proc-partitions-1.txt";
25109	}
25110	my @parts = main::reader($file,'strip');
25111	# print Data::Dumper::Dumper \@parts;
25112	shift @parts if @parts; # get rid of headers
25113	for (@parts){
25114		my @temp = split(/\s+/, $_);
25115		next if !defined $temp[2];
25116		push (@proc_partitions,[$temp[0],$temp[1],$temp[2],$temp[3]]);
25117	}
25118	eval $end if $b_log;
25119}
25120}
25121
25122# args: 1 - pci device string; 2 - pci cleaned subsystem string
25123sub get_pci_vendor {
25124	eval $start if $b_log;
25125	my ($device, $subsystem) = @_;
25126	return if !$subsystem;
25127	my ($vendor,$sep,$temp) = ('','','');
25128	# get rid of any [({ type characters that will make regex fail
25129	# and similar matches show as non-match
25130	$subsystem = regex_cleaner($subsystem);
25131	my @data = split(/\s+/, $subsystem);
25132	# when using strings in patterns for regex have to escape them
25133	foreach (@data){
25134		$temp = $_;
25135		$temp =~ s/(\+|\$|\?|\^|\*)/\\$1/g;
25136		if ($device !~ m|\b$temp\b|){
25137			$vendor .= $sep . $_;
25138			$sep = ' ';
25139		}
25140		else {
25141			last;
25142		}
25143	}
25144	eval $end if $b_log;
25145	return $vendor;
25146}
25147
25148sub set_ps_aux {
25149	eval $start if $b_log;
25150	my ($header,@ps,@temp);
25151	# note: some ps cut off output based on terminal width
25152	# ww sets width unlimited
25153	$loaded{'ps-aux'} = 1;
25154	@ps = grabber("ps wwaux 2>/dev/null",'','strip');
25155	if (@ps){
25156		$header = shift @ps; # get rid of header row
25157		# handle busy box, which has 3 columns, regular ps aux has 11
25158		# avoid deprecated implicit split error in older Perls
25159		@temp = split(/\s+/, $header);
25160	}
25161	$ps_cols = $#temp; # the indexes, not the scalar count
25162	if ($ps_cols < 10){
25163		my $version = qx(ps --version 2>&1);
25164		$b_busybox_ps = 1 if $version =~ /busybox/i;
25165	}
25166	return if !@ps; # note: mips/openwrt ps has no 'a'
25167	for (@ps){
25168		next if !$_;
25169		next if $self_name eq 'inxi' && /\/$self_name\b/;
25170		$_ = lc;
25171		push (@ps_aux,$_);
25172		my @split = split(/\s+/, $_);
25173		# slice out 10th to last elements of ps aux rows
25174		my $final = $#split;
25175		# some stuff has a lot of data, chrome for example
25176		$final = ($final > ($ps_cols + 2)) ? $ps_cols + 2 : $final;
25177		# handle case of ps wrapping lines despite ww unlimited width, which
25178		# should NOT be happening, but is.
25179		next if !defined $split[$ps_cols];
25180		if ($split[$ps_cols] !~ /^\[/){
25181			push(@ps_cmd,join(' ', @split[$ps_cols .. $final]));
25182		}
25183	}
25184	# never, because ps loaded before option handler
25185	# print Dumper \@ps_cmd; # if $dbg[5];
25186	eval $end if $b_log;
25187}
25188
25189sub set_ps_gui {
25190	eval $start if $b_log;
25191	$loaded{'ps-gui'} = 1;
25192	my ($working,@match,@temp);
25193	# desktops / wm (some wm also compositors)
25194	if ($show{'system'}){
25195		@temp=qw(razor-desktop razor-session lxsession lxqt-session
25196		tdelauncher tdeinit_phase1);
25197		push(@match,@temp);
25198		@temp=qw(2bwm 3dwm 9wm afterstep aewm aewm\+\+ amiwm antiwm awesome
25199		blackbox bspwm
25200		cagebreak calmwm catwm (sh|c?lisp).*clfswm ctwm (openbsd-)?cwm dwm evilwm
25201		fluxbox flwm flwm_topside fvwm.*-crystal fvwm1 fvwm2 fvwm3 fvwm95 fvwm
25202		herbstluftwm i3 icewm instantwm ion3 jbwm jwm larswm leftwm lwm
25203		matchbox-window-manager mcwm mini monsterwm musca mwm nawm notion
25204		openbox orbital pekwm penrose perceptia python.*qtile qtile qvwm ratpoison
25205		sawfish scrotwm snapwm spectrwm (sh|c?lisp).*stumpwm sway
25206		tinywm tvtwm twm uwm
25207		waycooler way-cooler windowlab WindowMaker wingo wm2 wmfs wmfs2 wmii2 wmii
25208		wmx
25209		xfdesktop xmonad yeahwm);
25210		push(@match,@temp);
25211	}
25212	# wm:
25213	if ($show{'system'} && $extra > 1){
25214		@temp=qw(budgie-wm compiz deepin-wm gala gnome-shell
25215		twin kwin_wayland kwin_x11 kwin marco
25216		deepin-metacity metacity metisse mir muffin deepin-mutter mutter
25217		ukwm xfwm[45]?);
25218		push(@match,@temp);
25219		# startx: /bin/sh /usr/bin/startx
25220		@temp=qw(ly .*startx xinit); # possible dm values
25221		push(@match,@temp);
25222	}
25223	# info: NOTE: glx-dock is cairo-dock
25224	if ($show{'system'} && $extra > 2){
25225		@temp=qw(alltray awn bar bmpanel bmpanel2 budgie-panel
25226		cairo-dock dde-dock dmenu dockbarx docker docky dzen dzen2
25227		fbpanel fspanel glx-dock gnome-panel hpanel i3bar icewmtray
25228		kdocker kicker latte latte-dock lemonbar ltpanel lxpanel lxqt-panel
25229		matchbox-panel mate-panel ourico
25230		perlpanel plank plasma-desktop plasma-netbook polybar pypanel
25231		razor-panel razorqt-panel stalonetray swaybar taskbar tint2 trayer
25232		ukui-panel vala-panel wbar wharf wingpanel witray
25233		xfce[45]?-panel xmobar yabar);
25234		push(@match,@temp);
25235	}
25236	# compositors (for wayland these are also the server, note.
25237	# for wayland always show, so always load these
25238	if ($show{'graphic'} && $extra > 0){
25239		@temp=qw(3dwm asc budgie-wm compiz compton deepin-wm dwc dcompmgr
25240		enlightenment fireplace gnome-shell grefson kmscon kwin_wayland kwin_x11
25241		liri marco metisse mir moblin motorcar muffin mutter
25242		orbital papyros perceptia picom rustland sommelier sway swc
25243		ukwm unagi unity-system-compositor
25244		wavy waycooler way-cooler wayfire wayhouse westford weston xcompmgr
25245		xfwm[45]?);
25246		push(@match,@temp);
25247	}
25248	uniq(\@match);
25249	my $matches = join('|', @match);
25250	foreach (@ps_cmd){
25251		if (/^(|[\S]*\/)($matches)(\/|\s|$)/){
25252			$working = $2;
25253			push(@ps_gui, $working); # deal with duplicates with uniq
25254		}
25255	}
25256	uniq(\@ps_gui) if @ps_gui;
25257	print Dumper \@ps_gui if $dbg[5];
25258	log_data('dump','@ps_gui',\@ps_gui) if $b_log;
25259	eval $end if $b_log;
25260}
25261
25262# # check? /var/run/nologin for bsds?
25263sub get_runlevel_data {
25264	eval $start if $b_log;
25265	my $runlevel = '';
25266	if (my $program = check_program('runlevel')){
25267		$runlevel = (grabber("$program 2>/dev/null"))[0];
25268		$runlevel =~ s/[^\d]//g if $runlevel;
25269		# print_line($runlevel . ";;");
25270	}
25271	eval $end if $b_log;
25272	return $runlevel;
25273}
25274
25275# note: it appears that at least as of 2014-01-13, /etc/inittab is going
25276# to be used for default runlevel in upstart/sysvinit. systemd default is
25277# not always set so check to see if it's linked.
25278sub get_runlevel_default {
25279	eval $start if $b_log;
25280	my @data;
25281	my $default = '';
25282	my $b_systemd = 0;
25283	my $inittab = '/etc/inittab';
25284	my $systemd = '/etc/systemd/system/default.target';
25285	my $upstart = '/etc/init/rc-sysinit.conf';
25286	# note: systemd systems do not necessarily have this link created
25287	if (-e $systemd){
25288		$default = readlink($systemd);
25289		$default =~ s/.*\/// if $default;
25290		$b_systemd = 1;
25291	}
25292	# http://askubuntu.com/questions/86483/how-can-i-see-or-change-default-run-level
25293	# note that technically default can be changed at boot but for inxi purposes
25294	# that does not matter, we just want to know the system default
25295	elsif (-r $upstart){
25296		# env DEFAULT_RUNLEVEL=2
25297		@data = reader($upstart);
25298		$default = awk(\@data,'^env\s+DEFAULT_RUNLEVEL',2,'=');
25299	}
25300	# handle weird cases where null but inittab exists
25301	if (!$default && -r $inittab){
25302		@data = reader($inittab);
25303		$default = awk(\@data,'^id.*initdefault',2,':');
25304	}
25305	eval $end if $b_log;
25306	return $default;
25307}
25308
25309sub get_self_version {
25310	eval $start if $b_log;
25311	my $patch = $self_patch;
25312	if ($patch ne ''){
25313		# for cases where it was for example: 00-b1 clean to -b1
25314		$patch =~ s/^[0]+-?//;
25315		$patch = "-$patch" if $patch;
25316	}
25317	eval $end if $b_log;
25318	return $self_version . $patch;
25319}
25320
25321## ServiceData
25322{
25323package ServiceData;
25324my ($key,$service,$type);
25325sub get {
25326	eval $start if $b_log;
25327	($type,$service) = @_;
25328	my $value;
25329	set() if !$loaded{'service-tool'};
25330	$key = (keys %service_tool)[0] if %service_tool;
25331	if ($key){
25332		if ($type eq 'status'){
25333			$value = process_status();
25334		}
25335		elsif ($type eq 'tool'){
25336			$value = $service_tool{$key}->[1];
25337		}
25338	}
25339	eval $end if $b_log;
25340	return $value;
25341}
25342sub process_status {
25343	eval $start if $b_log;
25344	my ($cmd,$status,@data);
25345	my ($result,$value) = ('','');
25346	my %translate = (
25347	'active' => 'running',
25348	'down' => 'stopped',
25349	'fail' => 'not found',
25350	'failed' => 'not found',
25351	'inactive' => 'stopped',
25352	'ok' => 'running',
25353	'not running' => 'stopped',
25354	'run' => 'running',
25355	'started' => 'running',
25356	);
25357	if ($key eq 'systemctl'){
25358		$cmd = "$service_tool{$key}->[0] status $service";
25359	}
25360	# can be /etc/init.d or /etc/rc.d; ghostbsd/gentoo have this
25361	elsif ($key eq 'rc-service'){
25362		$cmd = "$service_tool{$key}->[0] $service status";
25363	}
25364	elsif ($key eq 'rcctl'){
25365		$cmd = "$service_tool{$key}->[0] check $service";
25366	}
25367	# dragonfly/netbsd/freebsd have this
25368	elsif ($key eq 'service'){
25369		$cmd = "$service_tool{$key}->[0] $service status";
25370	}
25371	# runit
25372	elsif ($key eq 'sv'){
25373		$cmd = "$service_tool{$key}->[0] status $service";
25374	}
25375	# check or status or onestatus (netbsd)
25376	elsif ($key eq 'rc.d'){
25377		if (-e "$service_tool{$key}->[0]$service"){
25378			$status =  ($bsd_type && $bsd_type =~ /(dragonfly)/) ? 'status' : 'check';
25379			$cmd = "$service_tool{$key}->[0]$service check";
25380		}
25381		else {
25382			$result = 'not found';
25383		}
25384	}
25385	elsif ($key eq 'init.d'){
25386		if (-e "$service_tool{$key}->[0]$service"){
25387			$cmd = "$service_tool{$key}->[0]$service status";
25388		}
25389		else {
25390			$result = 'not found';
25391		}
25392	}
25393	@data = main::grabber("$cmd  2>&1",'','strip') if $cmd;
25394	# @data = ('bluetooth is running.');
25395	print "key: $key\n", Data::Dumper::Dumper \@data if $dbg[29];
25396	main::log_data('dump','service @data',\@data) if $b_log;
25397	for my $row (@data){
25398		my @working = split(/\s*:\s*/,$row);
25399		($value) = ('');
25400		# print "$working[0]::$working[1]\n";
25401		if ($working[0] eq 'Loaded'){
25402			# note: sshd shows ssh for ssh.service
25403			$working[1] =~ /^(.+?)\s*\(.*?\.service;\s+(\S+?);.*/;
25404			$result = lc($1) if $1;
25405			$result = lc($2) if $2; # this will be enabled/disabled
25406		}
25407		elsif ($working[0] eq 'Active'){
25408			$working[1] =~ /^(.+?)\s*\((\S+?)\).*/;
25409			$value = lc($1) if $1 && (!$result || $result ne 'disabled');
25410			$value = $translate{$value} if $value && $translate{$value};
25411			$result .= ",$value" if ($result && $value);
25412			last;
25413		}
25414		# valid syntax, but service does not exist
25415		# * rc-service: service 'ntp' does not exist ::
25416		elsif ($row =~ /$service.*?(not (exist|(be )?found)|no such (directory|file)|unrecognized)/i){
25417			$result = 'not found';
25418			last;
25419		}
25420		# means command directive doesn't exist, we don't know if service exists or not
25421		# * ntpd: unknown function 'disable' ::
25422		elsif ($row =~ /unknown (directive|function)|Usage/i){
25423			last;
25424		}
25425		# rc-service: * status: started :: * status: stopped, fail handled in not exist test
25426		elsif ($working[0] eq '* status' && $working[1]){
25427			$result = lc($working[1]);
25428			$result = $translate{$result} if $translate{$result};
25429			last;
25430		}
25431		## start exists status detections
25432		elsif ($working[0] =~ /\b$service is ([a-z\s]+?)(\s+as\s.*|\s+\.\.\..*)?\.?$/){
25433			$result = lc($1);
25434			$result = $translate{$result} if $translate{$result};
25435			last;
25436		}
25437		# runit sv: run/down/fail - fail means not found
25438		# run: udevd: (pid 631) 641s :: down: sshd: 9s, normally up
25439		elsif ($working[1] && $working[1] eq $service && $working[0] =~ /^([a-z]+)$/){
25440			$result = lc($1);
25441			$result = $translate{$result} if $translate{$result};
25442			$result = "enabled,$result" if $working[2] && $working[2] =~ /normally up/i;
25443		}
25444		# OpenBSD: sshd(ok)
25445		elsif ($working[0] =~ /\b$service\s*\(([^\)]+)\)/){
25446			$result = lc($1);
25447			$result = $translate{$result} if $translate{$result};
25448			last;
25449		}
25450	}
25451	print "service result: $result\n" if $dbg[29];
25452	main::log_data('data',"result: $result") if $b_log;
25453	eval $end if $b_log;
25454	return $result;
25455}
25456sub set {
25457	eval $start if $b_log;
25458	$loaded{'service-tool'} = 1;
25459	my ($path);
25460	if ($path = main::check_program('systemctl')){
25461		# systemctl status ssh :: Loaded: / Active:
25462		%service_tool = ('systemctl' => [$path,'systemctl']);
25463	}
25464	elsif ($path = main::check_program('rc-service')){
25465		# rc-service ssh status ::  * status: stopped
25466		%service_tool = ('rc-service' => [$path,'rc-service']);
25467	}
25468	elsif ($path = main::check_program('rcctl')){
25469		# rc-service ssh status ::  * status: stopped
25470		%service_tool = ('rcctl' => [$path,'rcctl']);
25471	}
25472	elsif ($path = main::check_program('service')){
25473		# service sshd status
25474		%service_tool = ('service' => [$path,'service']);
25475	}
25476	elsif ($path = main::check_program('sv')){
25477		# service sshd status
25478		%service_tool = ('sv' => [$path,'sv']);
25479	}
25480	# freebsd does not have 'check', netbsd does not have status
25481	elsif (-d '/etc/rc.d/'){
25482		# /etc/rc.d/ssh check :: ssh(ok|failed)
25483		%service_tool = ('rc.d' => ['/etc/rc.d/','/etc/rc.d']);
25484	}
25485	elsif (-d '/etc/init.d/'){
25486		# /etc/init.d/ssh status :: Loaded: loaded (...)/ Active: active (...)
25487		%service_tool = ('init.d' => ['/etc/init.d/','/etc/init.d']);
25488	}
25489	eval $end if $b_log;
25490}
25491}
25492# $dbg[29] = 1; set_path(); print ServiceData::get('status','bluetooth'),"\n";
25493
25494## ShellData
25495{
25496package ShellData;
25497my $b_debug = 0; # disable all debugger output in case forget to comment out!
25498
25499# public. This does not depend on using ps -jfp, open/netbsd do not
25500# at this point support it, so we only want to use -jp to get parent
25501# $ppid set in initialize(). shell_launcher will use -f so it only
25502# runs in case we got $pppid. $client{'pppid'} will be used to trigger
25503# launcher tests. If started with sshd via ssh user@address 'pinxi -Ia'
25504# will show sshd as shell, which is fine, that's what it is.
25505sub set {
25506	eval $start if $b_log;
25507	my ($cmd,$parent,$pppid,$shell);
25508	$loaded{'shell-data'} = 1;
25509	$cmd = "ps -wwp $ppid -o comm= 2>/dev/null";
25510	$shell = qx($cmd);
25511	# we'll be using these $client pppid/parent values in shell_launcher()
25512	$pppid = $client{'pppid'} = get_pppid($ppid);
25513	$pppid ||= '';
25514	$client{'pppid'} ||= '';
25515	# print "sh: $shell\n";
25516	main::log_data('cmd',$cmd) if $b_log;
25517	chomp($shell);
25518	if ($shell){
25519		# print "shell pre: $shell\n";
25520		# when run in debugger subshell, would return sh as shell,
25521		# and parent as perl, that is, pinxi itself, which is actually right.
25522		# trim leading /.../ off just in case. ps -p should return the name, not path
25523		# but at least one user dataset suggests otherwise so just do it for all.
25524		$shell =~ s/^.*\///;
25525		# NOTE: su -c "inxi -F" results in shell being su
25526		# but: su - results in $parent being su
25527		my $i=0;
25528		$parent = $client{'parent'} = parent_name($pppid) if $pppid;
25529		$parent ||= '';
25530		print "1: shell: $shell $ppid parent: $parent $pppid\n" if $b_debug;
25531 		# this will fail in this case: sudo su -c 'inxi -Ia'
25532		if ($shell =~ /^(doas|login|sudo|su)$/){
25533			$client{'su-start'} = $shell if $shell ne 'login';
25534			$shell = $parent if $parent;
25535		}
25536		# eg: su to root, then sudo
25537		elsif ($parent && $client{'parent'} =~ /^(doas|sudo|su)$/){
25538			$client{'su-start'} = $parent;
25539			$parent = '';
25540		}
25541		print "2: shell: $shell parent: $parent\n" if $b_debug;
25542		my $working = $ENV{'SHELL'};
25543		if ($working){
25544			$working =~ s/^.*\///;
25545			# a few manual changes for known
25546			# Note: parent when fizsh shows as zsh but SHELL is fizsh, but other times
25547			# SHELL is default shell, but in zsh, SHELL is default shell, not zfs
25548			if ($shell eq 'zsh' && $working eq 'fizsh'){
25549				$shell = $working;
25550			}
25551		}
25552		# print "3: shell post: $shell working: $working\n";
25553		# since there are endless shells, we'll keep a list of non program value
25554		# set shells since there is little point in adding those to program values
25555		if (shell_test($shell)){
25556			# do nothing, just leave $shell as is
25557		}
25558		# note: not all programs return version data. This may miss unhandled shells!
25559		elsif ((@app = main::program_data(lc($shell),lc($shell),1)) && $app[0]){
25560			$shell = $app[0];
25561			$client{'version'} = $app[1] if $app[1];
25562			print "3: app test $shell v: $client{'version'}\n" if $b_debug;
25563		}
25564		else {
25565			# NOTE: we used to guess here with position 2 --version but this cuold lead
25566			# to infinite loops when inxi called from a script 'infos' that is in PATH and
25567			# script does not have any start arg handlers or bad arg handlers:
25568			# eg: shell -> infos -> inxi -> sh -> infos --version -> infos -> inxi...
25569			# Basically here we are hoping that the grandparent is a shell, or at least
25570			# recognized as a known possible program
25571			# print "app not shell?: $shell\n";
25572			if ($shell){
25573				 print "shell 4: $shell StartClientVersionType: $parent\n" if $b_debug;
25574				if ($parent){
25575					if (shell_test($parent)){
25576						$shell = $parent;
25577					}
25578					elsif ((@app = main::program_data(lc($parent),lc($parent),0)) && $app[0]){
25579						$shell = $app[0];
25580						$client{'version'} = $app[1] if $app[1];
25581					}
25582					print "shell 5: $shell version: $client{'version'}\n" if $b_debug;
25583				}
25584			}
25585			else {
25586				$client{'version'} = main::row_defaults('unknown-shell');
25587			}
25588			print "6: shell not app version: $client{'version'}\n" if $b_debug;
25589		}
25590		$client{'version'} ||= '';
25591		$client{'version'} =~ s/(\(.*|-release|-version)// if $client{'version'};
25592		$shell =~ s/^[\s-]+|[\s-]+$//g if $shell; # sometimes will be like -sh
25593		$client{'name'} = lc($shell);
25594		$client{'name-print'} = $shell;
25595		print "7: shell: $client{'name-print'} version: $client{'version'}\n" if $b_debug;
25596		if ($extra > 2 && $working && lc($shell) ne lc($working)){
25597			if (@app = main::program_data(lc($working))){
25598				$client{'default-shell'} = $app[0];
25599				$client{'default-shell-v'} = $app[1];
25600				$client{'default-shell-v'} =~ s/(\s*\(.*|-release|-version)// if $client{'default-shell-v'};
25601			}
25602			else {
25603				$client{'default-shell'} = $working;
25604			}
25605		}
25606	}
25607	else {
25608		$client{'name'} = 'shell';
25609		$client{'name-print'} = 'Unknown Shell';
25610	}
25611	if (!$client{'su-start'}){
25612		$client{'su-start'} = 'sudo' if $ENV{'SUDO_USER'};
25613		$client{'su-start'} = 'doas' if $ENV{'DOAS_USER'};
25614	}
25615	if ($parent && $parent eq 'login'){
25616		$client{'su-start'} = ($client{'su-start'}) ? $client{'su-start'} . ',' . $parent: $parent;
25617	}
25618	eval $end if $b_log;
25619}
25620# public, returns shell launcher, terminal, program, whatever
25621# depends on $pppid so only runs if that is set.
25622sub shell_launcher {
25623	eval $start if $b_log;
25624	my (@data);
25625	my ($msg,$pppid,$shell_parent) = ('','','');
25626	$pppid = $client{'pppid'};
25627	if ($b_log){
25628		$msg = ($ppid) ? "pppid: $pppid ppid: $ppid": "ppid: undefined";
25629		main::log_data('data',$msg);
25630	}
25631	# print "self parent: $pppid ppid: $ppid\n";
25632	if ($pppid){
25633		$shell_parent = $client{'parent'};
25634		# print "shell parent 1: $shell_parent\n";
25635		if ($b_log){
25636			$msg = ($shell_parent) ? "shell parent 1: $shell_parent": "shell parent 1: undefined";
25637			main::log_data('data',$msg);
25638		}
25639		# in case sudo starts inxi, parent is shell (or perl inxi if run by debugger)
25640		# so: perl (2) started pinxi with sudo (3) in sh (4) in terminal
25641		my $shells = 'ash|bash|busybox|cicada|csh|dash|doas|elvish|fish|fizsh|ksh|ksh93|';
25642		$shells .= 'lksh|login|loksh|mksh|nash|oh|oil|osh|pdksh|perl|posh|';
25643		$shells .= 'su|sudo|tcsh|xonsh|yash|zsh';
25644		$shells .= shell_test('return');
25645		my $i = 0;
25646		print "self::pppid-0: $pppid :: $shell_parent\n" if $b_debug;
25647		# note that new shells not matched will keep this loop spinning until it ends.
25648		# All we really can do about that is update with new shell name when we find them.
25649		while ($i < 8 && $shell_parent && $shell_parent =~ /^($shells)$/){
25650			# bash > su > parent
25651			$i++;
25652			$pppid = get_pppid($pppid);
25653			$shell_parent = parent_name($pppid);
25654			print "self::pppid-${i}: $pppid :: $shell_parent\n" if $b_debug;
25655			if ($b_log){
25656				$msg = ($shell_parent) ? "parent-$i: $shell_parent": "shell parent $i: undefined";
25657				main::log_data('data',$msg);
25658			}
25659		}
25660	}
25661	if ($b_log){
25662		$pppid ||= '';
25663		$shell_parent ||= '';
25664		main::log_data('data',"parents: pppid: $pppid parent-name: $shell_parent");
25665	}
25666	eval $end if $b_log;
25667	return $shell_parent;
25668}
25669# arg: 1 - parent id
25670# returns SID/start ID
25671sub get_pppid {
25672	eval $start if $b_log;
25673	my ($ppid) = @_;
25674	return 0 if !$ppid;
25675	# ps -j -fp : some bsds ps do not have -f for PPID, so we can't get the ppid
25676	my $cmd = "ps -wwjfp $ppid 2>/dev/null";
25677	main::log_data('cmd',$cmd) if $b_log;
25678	my @data = main::grabber($cmd);
25679	# shift @data if @data;
25680	my $pppid = main::awk(\@data,"$ppid",3,'\s+');
25681	eval $end if $b_log;
25682	return $pppid;
25683}
25684# arg: 1 - parent id
25685# returns parent command name
25686sub parent_name {
25687	eval $start if $b_log;
25688	my ($ppid) = @_;
25689	return '' if !$ppid;
25690	my ($parent_name);
25691	my $cmd = "ps -wwjp $ppid 2>/dev/null";
25692	main::log_data('cmd',$cmd) if $b_log;
25693	my @data = main::grabber($cmd,'','strip');
25694	# dump the headers if they exist
25695	$parent_name = (grep {/$ppid/} @data)[0] if @data;
25696	if ($parent_name){
25697		# we don't want to worry about column position, just slice off all
25698		# the first part before the command
25699		$parent_name =~ s/^.*[0-9]+:[0-9\.]+\s+//;
25700		# then get the command
25701		$parent_name = (split(/\s+/,$parent_name))[0];
25702		# get rid of /../ path info if present
25703		$parent_name =~ s|^.*/|| if $parent_name;
25704		# to work around a ps -p or gnome-terminal bug, which returns
25705		# gnome-terminal- trim -/_ off start/end; _su, etc, which breaks detections
25706		$parent_name =~ s/^[_-]|[_-]$//g;
25707	}
25708	eval $end if $b_log;
25709	return $parent_name;
25710}
25711# list of program_values non-handled shells, or known to have no version
25712# Move shell to set_program_values for print name, or version if available
25713# $1 - return|[shell name to test
25714# returns test list OR shell name/''
25715sub shell_test {
25716	my ($test) = @_;
25717	# these shells are not verified or tested
25718	my $shells = 'apush|ccsh|ch|esh?|eshell|heirloom|hush|';
25719	$shells .= 'ion|imrsh|larryshell|mrsh|msh(ell)?|murex|nsh|nu(shell)?|';
25720	$shells .= 'oksh|psh|pwsh|pysh(ell)?|rush|sash|xsh?|';
25721	# these shells are tested and have no version info
25722	$shells .= 'es|rc|scsh|sh';
25723	return '|' . $shells if $test eq 'return';
25724	return ($test =~ /^($shells)$/) ? $test : '';
25725}
25726# this will test against default IP like: (:0) vs full IP to determine
25727# ssh status. Surprisingly easy test? Cross platform
25728sub ssh_status {
25729	eval $start if $b_log;
25730	my ($b_ssh,$ssh);
25731	# fred   pts/10       2018-03-24 16:20 (:0.0)
25732	# fred-remote pts/1        2018-03-27 17:13 (43.43.43.43)
25733	if (my $program = main::check_program('who')){
25734		$ssh = (main::grabber("$program am i 2>/dev/null"))[0];
25735		# crude IP validation
25736		if ($ssh && $ssh =~ /\(([:0-9a-f]{8,}|[1-9][\.0-9]{6,})\)$/){
25737			$b_ssh = 1;
25738		}
25739	}
25740	eval $end if $b_log;
25741	return $b_ssh;
25742}
25743# If IRC: called if root for -S, -G, or if not in display for user.
25744sub console_irc_tty {
25745	eval $start if $b_log;
25746	$loaded{'con-irc-tty'} = 1;
25747	# not set for root in or out of display
25748	if (defined $ENV{'XDG_VTNR'}){
25749		$client{'con-irc-tty'} = $ENV{'XDG_VTNR'};
25750	}
25751	else {
25752		# ppid won't work with name, so this is assuming there's only one client running
25753		# if in display, -G returns vt size, not screen dimensions in rowsxcols.
25754		$client{'con-irc-tty'} = main::awk(\@ps_aux,'.*\b' . $client{'name'} . '\b.*',7,'\s+');
25755		$client{'con-irc-tty'} =~ s/^(tty|\?)// if defined $client{'con-irc-tty'};
25756	}
25757	$client{'con-irc-tty'} = '' if !defined $client{'con-irc-tty'};
25758	main::log_data('data',"console-irc-tty:$client{'con-irc-tty'}") if $b_log;
25759	eval $end if $b_log;
25760}
25761sub tty_number {
25762	eval $start if $b_log;
25763	$loaded{'tty-number'} = 1;
25764	# note: ttyname returns undefined if pinxi is > redirected output
25765	# variants: /dev/pts/1 /dev/tty1 /dev/ttyp2 /dev/ttyra [hex number a]
25766	$client{'tty-number'} = POSIX::ttyname(1);
25767	# but tty direct works fine in that case
25768	if (!defined $client{'tty-number'} && (my $program = main::check_program('tty'))){
25769		chomp($client{'tty-number'} = qx($program 2>/dev/null));
25770		if (defined $client{'tty-number'} && $client{'tty-number'} =~ /^not/){
25771			undef $client{'tty-number'};
25772		}
25773	}
25774	if (defined $client{'tty-number'}){
25775		$client{'tty-number'} =~ s/^\/dev\/(tty)?//;
25776	}
25777	else {
25778		$client{'tty-number'} = '';
25779	}
25780	# systemd only item, usually same as tty in console, not defined
25781	# for root or non systemd systems.
25782	if (defined $ENV{'XDG_VTNR'} && $client{'tty-number'} ne '' &&
25783	 $ENV{'XDG_VTNR'} ne $client{'tty-number'}){
25784		$client{'tty-number'} = "$client{'tty-number'} (vt $ENV{'XDG_VTNR'})";
25785	}
25786	elsif ($client{'tty-number'} eq '' && defined $ENV{'XDG_VTNR'}){
25787		$client{'tty-number'} = $ENV{'XDG_VTNR'};
25788	}
25789	main::log_data('data',"tty:$client{'tty-number'}") if $b_log;
25790	eval $end if $b_log;
25791}
25792}
25793
25794sub set_sysctl_data {
25795	eval $start if $b_log;
25796	return if !$alerts{'sysctl'} || $alerts{'sysctl'}->{'action'} ne 'use';
25797	my (@temp);
25798	# darwin sysctl has BOTH = and : separators, and repeats data. Why?
25799	if (!$fake{'sysctl'}){
25800		# just on odd chance we hit a bsd with /proc/cpuinfo, don't want to
25801		# sleep 2x
25802		if ($use{'bsd-sleep'} && !$system_files{'proc-cpuinfo'}){
25803			if ($b_hires){
25804				eval 'Time::HiRes::usleep($sleep)';
25805			}
25806			else {
25807				select(undef, undef, undef, $cpu_sleep);
25808			}
25809		}
25810		@temp = grabber($alerts{'sysctl'}->{'path'} . " -a 2>/dev/null");
25811	}
25812	else {
25813		my $file;
25814		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/obsd_6.1_sysctl_soekris6501_root.txt";
25815		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/obsd_6.1sysctl_lenovot500_user.txt";
25816		## matches: compaq: openbsd-dmesg.boot-1.txt
25817		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/openbsd-5.6-sysctl-1.txt";
25818		## matches: toshiba: openbsd-5.6-dmesg.boot-1.txt
25819		# $file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/openbsd-5.6-sysctl-2.txt";
25820		$file = "$ENV{'HOME'}/bin/scripts/inxi/data/sysctl/obsd-6.8-sysctl-a-battery-sensor-1.txt";
25821		@temp = reader($file);
25822	}
25823	foreach (@temp){
25824		$_ =~ s/\s*=\s*|:\s+/:/;
25825		$_ =~ s/\"//g;
25826		push(@{$sysctl{'main'}}, $_);
25827		# we're building these here so we can use these arrays per feature
25828		if ($use{'bsd-audio'} && /^hw\.snd\./){
25829			push(@{$sysctl{'audio'}}, $_); # not used currently, just test data
25830		}
25831		# note: we could use ac0 to indicate plugged in but messes with battery output
25832		elsif ($use{'bsd-battery'} && /^hw\.sensors\.acpi(bat|cmb)/){
25833			push(@{$sysctl{'battery'}}, $_);
25834		}
25835		# hw.cpufreq.temperature: 40780 :: dev.cpu0.temperature
25836		# hw.acpi.thermal.tz2.temperature: 27.9C :: hw.acpi.thermal.tz1.temperature: 42.1C
25837		# hw.acpi.thermal.tz0.temperature: 42.1C
25838		elsif ($use{'bsd-sensor'} &&((/^hw\.sensors/ && !/^hw\.sensors\.acpi(ac|bat|cmb)/ &&
25839		 !/^hw\.sensors\.softraid/) || /^hw\.acpi\.thermal/ || /^dev\.cpu\.[0-9]+\.temp/)){
25840			push(@{$sysctl{'sensor'}}, $_);
25841		}
25842		# Must go AFTER sensor because sometimes freebsd puts sensors in dev.cpu
25843		# hw.l1dcachesize hw.l2cachesize
25844		elsif ($use{'bsd-cpu'} && (/^hw\.(busfreq|clock|n?cpu|l[123].?cach|model)/ ||
25845			/^dev\.cpu/ || /^machdep\.cpu/)){
25846			push(@{$sysctl{'cpu'}}, $_);
25847		}
25848		# only activate if using the diskname feature in dboot!!
25849		elsif ($use{'bsd-disk'} && /^hw\.disknames/){
25850			push(@{$dboot{'disk'}}, $_);
25851		}
25852		elsif ($use{'bsd-kernel'} && /^kern.compiler_version/){
25853			push(@{$dboot{'kernel'}}, $_);
25854		}
25855		elsif ($use{'bsd-machine'} &&
25856		 /^(hw\.|machdep\.dmi\.(bios|board|system)-)(date|product|serial(no)?|uuid|vendor|version)/){
25857			push(@{$sysctl{'machine'}}, $_);
25858		}
25859		# let's rely on dboot, we really just want the hardware specs for solid ID
25860		# elsif ($use{'bsd-machine'} && !$dboot{'machine-vm'} &&
25861		#	/(\bhvm\b|innotek|\bkvm\b|microsoft.*virtual machine|openbsd[\s-]vmm|qemu|qumranet|vbox|virtio|virtualbox|vmware)/i){
25862		#	push(@{$dboot{'machine-vm'}}, $_);
25863		# }
25864		elsif ($use{'bsd-memory'} && /^(hw\.(physmem|usermem)|Free Memory)/){
25865			push(@{$sysctl{'memory'}}, $_);
25866		}
25867
25868		elsif ($use{'bsd-raid'} && /^hw\.sensors\.softraid[0-9]\.drive[0-9]/){
25869			push(@{$sysctl{'softraid'}}, $_);
25870		}
25871	}
25872	if ($dbg[7]){
25873		print("main\n", Dumper $sysctl{'main'});
25874		print("dboot-machine-vm\n", Dumper $dboot{'machine-vm'});
25875		print("audio\n", Dumper $sysctl{'audio'});
25876		print("battery\n", Dumper $sysctl{'battery'});
25877		print("cpu\n", Dumper $sysctl{'cpu'});
25878		print("kernel\n", Dumper $sysctl{'kernel'});
25879		print("machine\n", Dumper $sysctl{'machine'});
25880		print("memory\n", Dumper $sysctl{'memory'});
25881		print("sensors\n", Dumper $sysctl{'sensor'});
25882		print("softraid\n", Dumper $sysctl{'softraid'});
25883	}
25884	# this thing can get really long.
25885	if ($b_log){
25886		main::log_data('dump','$sysctl{main}',$sysctl{'main'});
25887		main::log_data('dump','$dboot{machine-vm}',$sysctl{'machine-vm'});
25888		main::log_data('dump','$sysctl{audio}',$sysctl{'audio'});
25889		main::log_data('dump','$sysctl{battery}',$sysctl{'battery'});
25890		main::log_data('dump','$sysctl{cpu}',$sysctl{'cpu'});
25891		main::log_data('dump','$sysctl{kernel}',$sysctl{'kernel'});
25892		main::log_data('dump','$sysctl{machine}',$sysctl{'machine'});
25893		main::log_data('dump','$sysctl{memory}',$sysctl{'memory'});
25894		main::log_data('dump','$sysctl{sensors}',$sysctl{'sensor'});
25895		main::log_data('dump','$sysctl{softraid}',$sysctl{'softraid'});
25896	}
25897	eval $end if $b_log;
25898}
25899
25900sub get_uptime {
25901	eval $start if $b_log;
25902	my ($days,$hours,$minutes,$seconds,$sys_time,$uptime) = ('','','','','','');
25903	if (check_program('uptime')){
25904		$uptime = qx(uptime);
25905		$uptime = trimmer($uptime);
25906		if ($fake{'uptime'}){
25907			# $uptime = '2:58PM  up 437 days,  8:18, 3 users, load averages: 2.03, 1.72, 1.77';
25908			# $uptime = '04:29:08 up  3:18,  3 users,  load average: 0,00, 0,00, 0,00';
25909			# $uptime = '10:23PM  up 5 days, 16:17, 1 user, load averages: 0.85, 0.90, 1.00';
25910			# $uptime = '05:36:47 up 1 day,  3:28,  4 users,  load average: 1,88, 0,98, 0,62';
25911			# $uptime = '05:36:47 up 1 day,  3 min,  4 users,  load average: 1,88, 0,98, 0,62';
25912			# $uptime = '04:41:23 up  2:16,  load average: 7.13, 6.06, 3.41 # root openwrt';
25913			# $uptime = '9:51 PM  up 2 mins, 1 user, load average: 0:58, 0.27, 0.11';
25914			# $uptime = '05:36:47 up 3 min,  4 users,  load average: 1,88, 0,98, 0,62';
25915			# $uptime = '9:51 PM  up 49 secs, 1 user, load average: 0:58, 0.27, 0.11';
25916			# $uptime = '04:11am  up   0:00,  1 user,  load average: 0.08, 0.03, 0.01'; # openSUSE 13.1 (Bottle)
25917			# $uptime = '11:21:43  up 1 day  5:53,  4 users,  load average: 0.48, 0.62, 0.48'; # openSUSE Tumbleweed 20210515
25918		}
25919		if ($uptime){
25920			# trim off and store system time and up, and cut off user/load data
25921			$uptime =~ s/^([0-9:])\s*([AP]M)?.+up\s+|,?\s*([0-9]+\suser|load).*$//gi;
25922			# print "ut: $uptime\n";
25923			if ($1){
25924				$sys_time = $1;
25925				$sys_time .= lc($2) if $2;
25926			}
25927			if ($uptime =~ /\b([0-9]+)\s+day[s]?\b/){
25928				$days = ($1 + 0) . 'd';
25929			}
25930			if ($uptime =~ /\b([0-9]{1,2}):([0-9]{1,2})\b/){
25931				$hours = ($1 + 0) . 'h';
25932				$minutes = ($2 + 0) . 'm';
25933			}
25934			else {
25935				if ($uptime =~ /\b([0-9]+)\smin[s]?\b/){
25936					$minutes = ($1 + 0) . 'm';
25937				}
25938				if ($uptime =~ /\b([0-9]+)\ssec[s]?\b/){
25939					$seconds = ($1 + 0) . 's';
25940				}
25941			}
25942			$days .= ' ' if $days && ($hours || $minutes || $seconds);
25943			$hours .= ' ' if $hours && $minutes;
25944			$minutes .= ' ' if $minutes && $seconds;
25945			$uptime = $days . $hours . $minutes . $seconds;
25946		}
25947	}
25948	$uptime ||= 'N/A';
25949	eval $end if $b_log;
25950	return $uptime;
25951}
25952
25953## UsbData
25954# %usb array indexes
25955# 0 - bus id / sort id
25956# 1 - device id
25957# 2 - path_id
25958# 3 - path
25959# 4 - class id
25960# 5 - subclass id
25961# 6 - protocol id
25962# 7 - vendor:chip id
25963# 8 - usb version
25964# 9 - interfaces
25965# 10 - ports
25966# 11 - vendor
25967# 12 - product
25968# 13 - device-name
25969# 14 - type string
25970# 15 - driver
25971# 16 - serial
25972# 17 - speed
25973# 18 - configuration - not used
25974# 19 - power mW bsd only, not used yet
25975# 20 - product rev number
25976# 21 - driver_nu [bsd only]
25977{
25978package UsbData;
25979my (@working);
25980my (@asound_ids,$b_asound,$b_hub,$addr_id,$bus_id,$bus_id_alpha,
25981$chip_id,$class_id,$device_id,$driver,$driver_nu,$ids,$interfaces,
25982$name,$network_regex,$path,$path_id,$power,$product,$product_id,
25983$protocol_id,$rev,$serial,$speed,$subclass_id,$type,$version,$vendor,
25984$vendor_id);
25985my $b_live = 1; # debugger file data
25986sub set {
25987	eval $start if $b_log;
25988	${$_[0]} = 1; # set checked boolean
25989	# note: bsd package usbutils has lsusb in it, but we dont' want it for default
25990	# usbdevs is best, has most data, and runs as user
25991	if ($alerts{'usbdevs'}->{'action'} eq 'use'){
25992		usbdevs_data();
25993	}
25994	# usbconfig has weak/poor output, and requires root, only fallback
25995	elsif ($alerts{'usbconfig'}->{'action'} eq 'use'){
25996		usbconfig_data();
25997	}
25998	# if user config sets USB_SYS you can override with --usb-tool
25999	elsif ((!$force{'usb-sys'} || $force{'lsusb'}) && $alerts{'lsusb'}->{'action'} eq 'use'){
26000		lsusb_data();
26001	}
26002	elsif (-d '/sys/bus/usb/devices'){
26003		sys_data('main');
26004	}
26005	@{$usb{'main'}} = sort {$a->[0] cmp $b->[0]} @{$usb{'main'}} if $usb{'main'};
26006	main::log_data('dump','$usb{audio}: ',$usb{'audio'}) if $b_log;
26007	main::log_data('dump','$usb{bluetooth}: ',$usb{'bluetooth'}) if $b_log;
26008	main::log_data('dump','$usb{graphics}: ',$usb{'graphics'}) if $b_log;
26009	main::log_data('dump','$usb{network}: ',$usb{'network'}) if $b_log;
26010	eval $end if $b_log;
26011}
26012
26013sub lsusb_data {
26014	eval $start if $b_log;
26015	my (@temp);
26016	my @data = usb_grabber('lsusb');
26017	foreach (@data){
26018		next if /^~$|^Couldn't/; # expensive second call: || /UNAVAIL/
26019		@working = split(/\s+/, $_);
26020		next unless defined $working[1] && defined $working[3];
26021		$working[3] =~ s/:$//;
26022		# Don't use this fix, the data is garbage in general! Seen FreeBSD lsusb with:
26023		# Bus /dev/usb Device /dev/ugen0.3: ID 24ae:1003 Shenzhen Rapoo Technology Co., Ltd.
26024		# hub, note incomplete data: Bus /dev/usb Device /dev/ugen0.1: ID 0000:0000
26025		# linux:
26026		# Bus 005 Device 007: ID 0d8c:000c C-Media Electronics, Inc. Audio Adapter
26027		# if ($working[3] =~ m|^/dev/ugen([0-9]+)\.([0-9]+)|){
26028		#	$working[1] = $1;
26029		#	$working[3] = $2;
26030		# }
26031		next unless main::is_numeric($working[1]) && main::is_numeric($working[3]);
26032		$addr_id = int($working[3]);
26033		$bus_id = int($working[1]);
26034		$path_id = "$bus_id-$addr_id";
26035		$chip_id = $working[5];
26036		@temp = @working[6..$#working];
26037		$name = main::remove_duplicates(join(' ', @temp));
26038		$name = $name;
26039		# $type = check_type($name,'','');
26040		$type ||= '';
26041		# do NOT set bus_id_alpha here!!
26042		# print "$name\n";
26043		$working[0] = $bus_id;
26044		$working[1] = $addr_id;
26045		$working[2] = $path_id;
26046		$working[3] = '';
26047		$working[4] = '00';
26048		$working[5] = '';
26049		$working[6] = '';
26050		$working[7] = $chip_id;
26051		$working[8] = '';
26052		$working[9] = '';
26053		$working[10] = 0;
26054		$working[11] = '';
26055		$working[12] = '';
26056		$working[13] = $name;
26057		$working[14] = '';# $type;
26058		$working[15] = '';
26059		$working[16] = '';
26060		$working[17] = '';
26061		$working[18] = '';
26062		$working[19] = '';
26063		$working[20] = '';
26064		push(@{$usb{'main'}},[@working]);
26065		# print join("\n",@working),"\n\n=====\n";
26066	}
26067	print Data::Dumper::Dumper $usb{'main'} if $dbg[6];
26068	sys_data('lsusb') if $usb{'main'};
26069	print Data::Dumper::Dumper $usb{'main'} if $dbg[6];
26070	main::log_data('dump','$usb{main}: plain',$usb{'main'}) if $b_log;
26071	eval $end if $b_log;
26072}
26073# ugen0.1: <Apple OHCI root HUB> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=SAVE (0mA)
26074# ugen0.2: <MediaTek 802.11 n WLAN> at usbus0, cfg=0 md=HOST spd=FULL (12Mbps) pwr=ON (160mA)
26075# note: tried getting driver/ports from dmesg, impossible, waste of time
26076sub usbconfig_data {
26077	eval $start if $b_log;
26078	my ($cfg,$hub_id,$ports);
26079	my @data = usb_grabber('usbconfig');
26080	foreach (@data){
26081		if ($_ eq '~' && @working){
26082			$chip_id = ($vendor_id || $product_id) ? "$vendor_id:$product_id" : '';
26083			$working[7] = $chip_id;
26084			$product ||= '';
26085			$vendor ||= '';
26086			$working[13] = main::remove_duplicates("$vendor $product") if $product || $vendor;
26087			# leave the ugly vendor/product ids unless chip-ID shows!
26088			$working[13] = $chip_id if $extra < 2 && $chip_id && !$working[13];
26089			if (defined $class_id && defined $subclass_id && defined $protocol_id){
26090				$class_id = hex($class_id);
26091				$subclass_id = hex($subclass_id);
26092				$protocol_id = hex($protocol_id);
26093				$type = device_type("$class_id/$subclass_id/$protocol_id");
26094			}
26095			if ($working[13] && (!$type || $type eq '<vendor defined>')){
26096				$type = check_type($working[13],'','');
26097			}
26098			$working[14] = $type;
26099			push(@{$usb{'main'}},[@working]);
26100			assign_usb_type([@working]);
26101			undef @working;
26102		}
26103		elsif (/^([a-z_-]+)([0-9]+)\.([0-9]+):\s+<[^>]+>\s+at usbus([0-9]+)\b/){
26104			($class_id,$cfg,$power,$speed,$subclass_id,$type) = undef;
26105			($product,$product_id,$vendor,$vendor_id) = ('','','','');
26106			$hub_id = $2;
26107			$addr_id = $3;
26108			$bus_id = $4;
26109			$path_id = "$bus_id-$hub_id.$addr_id";
26110			$bus_id_alpha = bus_id_alpha($path_id);
26111			if (/\bcfg\s*=\s*([0-9]+)/){
26112				$cfg = $1;
26113			}
26114			if (/\bmd\s*=\s*([\S]+)/){
26115				# nothing
26116			}
26117			# odd, using \b after ) doesn't work as expected
26118			# note that bsd spd=FULL has no interest since we get that from the speed
26119			if (/\b(speed|spd)\s*=\s*([\S]+)\s+\(([^\)]+)\)/){
26120				$speed = prep_speed($3);
26121			}
26122			if (/\b(power|pwr)\s*=\s*([\S]+)\s+\(([0-9]+mA)\)/){
26123				$power = $3;
26124				process_power(\$power) if $power;
26125			}
26126			$working[0] = $bus_id_alpha;
26127			$working[1] = $addr_id;
26128			$working[2] = $path_id;
26129			$working[3] = '';
26130			$working[8] = usb_rev($speed);
26131			$working[9] = '';
26132			$working[10] = $ports;
26133			$working[15] = $driver;
26134			$working[17] = $speed;
26135			$working[18] = $cfg;
26136			$working[19] = $power;
26137			$working[20] = '';
26138			$working[21] = $driver_nu;
26139		}
26140		elsif (/^bDeviceClass\s*=\s*0x00([a-f0-9]{2})\s*(<([^>]+)>)?/){
26141			$class_id = $1;
26142			$working[4] = $class_id;
26143		}
26144		elsif (/^bDeviceSubClass\s*=\s*0x00([a-f0-9]{2})/){
26145			$subclass_id = $1;
26146			$working[5] = $subclass_id;
26147		}
26148		elsif (/^bDeviceProtocol\s*=\s*0x00([a-f0-9]{2})/){
26149			$protocol_id = $1;
26150			$working[6] = $protocol_id;
26151		}
26152		elsif (/^idVendor\s*=\s*0x([a-f0-9]{4})/){
26153			$vendor_id = $1;
26154		}
26155		elsif (/^idProduct\s*=\s*0x([a-f0-9]{4})/){
26156			$product_id = $1;
26157		}
26158		elsif (/^iManufacturer\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){
26159			$vendor = main::cleaner($3);
26160			$vendor =~ s/^0x.*//; # seen case where vendor string was ID
26161			$working[11] = $vendor;
26162		}
26163		elsif (/^iProduct\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){
26164			$product = main::cleaner($3);
26165			$product =~ s/^0x.*//; # in case they put product ID in, sigh
26166			$working[12] = $product;
26167		}
26168		elsif (/^iSerialNumber\s*=\s*0x([a-f0-9]{4})\s*(<([^>]+)>)?/){
26169			$working[16] = main::cleaner($3);
26170		}
26171	}
26172	main::log_data('dump','$usb{main}: usbconfig',$usb{'main'}) if $b_log;
26173	print Data::Dumper::Dumper $usb{'main'} if $dbg[6];
26174	eval $end if $b_log;
26175}
26176# Controller /dev/usb2:
26177# addr 1: full speed, self powered, config 1, UHCI root hub(0x0000), Intel(0x8086), rev 1.00
26178#  port 1 addr 2: full speed, power 98 mA, config 1, USB Receiver(0xc52b), Logitech(0x046d), rev 12.01
26179#  port 2 powered
26180sub usbdevs_data {
26181	eval $start if $b_log;
26182	my ($b_multi,$class,$config,$hub_id,$port,$port_value,$product_rev);
26183	my ($ports) = (0);
26184	my @data = usb_grabber('usbdevs');
26185	foreach (@data){
26186		if ($_ eq '~' && @working){
26187			$working[10] = $ports;
26188			push(@{$usb{'main'}},[@working]);
26189			assign_usb_type([@working]);
26190			undef @working;
26191			($config,$driver,$power,$rev) = ('','','','');
26192		}
26193		elsif (/^Controller\s\/dev\/usb([0-9]+)/){
26194			$bus_id = $1;
26195		}
26196		elsif (/^addr\s([0-9]+):\s([^,]+),[^,0-9]+([0-9]+ mA)?,\s+config\s+([0-9]+),\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){
26197			$hub_id = $1;
26198			$addr_id = $1;
26199			$speed = prep_speed($2);
26200			$power = $3;
26201			$chip_id = "$6:$8";
26202			$config = $4;
26203			$name = main::remove_duplicates("$7 $5");
26204			# print "p1:$protocol\n";
26205			$path_id = "$bus_id-$hub_id";
26206			$bus_id_alpha = bus_id_alpha($path_id);
26207			$ports = 0;
26208			process_power(\$power) if $power;
26209			$port_value = '';
26210			$working[0] = $bus_id_alpha;
26211			$working[1] = $addr_id;
26212			$working[2] = $path_id;
26213			$working[3] = '';
26214			$working[4] = '09';
26215			$working[5] = '';
26216			$working[6] = '';
26217			$working[7] = $chip_id;
26218			$working[8] = usb_rev($speed);
26219			$working[9] = '';
26220			$working[10] = $ports;
26221			$working[13] = $name;
26222			$working[14] = 'Hub';
26223			$working[15] = '';
26224			$working[16] = '';
26225			$working[17] = $speed;
26226			$working[18] = $config;
26227			$working[19] = $power;
26228			$working[20] = '';
26229		}
26230		elsif (/^port\s([0-9]+)\saddr\s([0-9]+):\s([^,]+),[^,0-9]*([0-9]+\s?mA)?,\s+config\s+([0-9]+),\s?([^,]+)\(0x([0-9a-f]{4})\),\s?([^,]+)\s?\(0x([0-9a-f]{4})\)/){
26231			$port = $1;
26232			$addr_id = "$2";
26233			$power = $4;
26234			$config = $5;
26235			$speed = prep_speed($3);
26236			$chip_id = "$7:$9";
26237			$name = main::remove_duplicates("$8 $6");
26238			$type = check_type($name,'','');
26239			$type ||= '';
26240			# print "p2:$protocol\n";
26241			$ports++;
26242			$path_id = "$bus_id-$hub_id.$port";
26243			$bus_id_alpha = bus_id_alpha($path_id);
26244			process_power(\$power) if $power;
26245			$working[0] = $bus_id_alpha;
26246			$working[1] = $addr_id;
26247			$working[2] = $path_id;
26248			$working[3] = '';
26249			$working[4] = '01';
26250			$working[5] = '';
26251			$working[6] = '';
26252			$working[7] = $chip_id;
26253			$working[8] = usb_rev($speed);
26254			$working[9] = '';
26255			$working[10] = $ports;
26256			$working[11] = '';
26257			$working[12] = '';
26258			$working[13] = $name;
26259			$working[14] = $type;
26260			$working[15] = '';
26261			$working[16] = '';
26262			$working[17] = $speed;
26263			$working[18] = $config;
26264			$working[19] = $power;
26265			$working[20] = '';
26266		}
26267		elsif (/^port\s([0-9]+)\spowered/){
26268			$ports++;
26269		}
26270		# newer openbsd usbdevs totally changed their syntax and layout, but it is better...
26271		elsif (/^addr\s*([0-9a-f]+):\s+([a-f0-9]{4}:[a-f0-9]{4})\s*([^,]+)?(,\s[^,]+?)?,\s+([^,]+)$/){
26272			$addr_id = $1;
26273			$chip_id = $2;
26274			$vendor = main::cleaner($3) if $3;
26275			$vendor ||= '';
26276			$name = main::remove_duplicates("$vendor $5");
26277			$type = check_type($name,'','');
26278			$class_id = ($name =~ /hub/i) ? '09': '01';
26279			$path_id = "$bus_id-$addr_id";
26280			$bus_id_alpha = bus_id_alpha($path_id);
26281			$ports = 0;
26282			$b_multi = 1;
26283			$working[0] = $bus_id_alpha;
26284			$working[1] = $addr_id;
26285			$working[2] = $path_id;
26286			$working[3] = '';
26287			$working[4] = $class_id;
26288			$working[5] = '';
26289			$working[6] = '';
26290			$working[7] = $chip_id;
26291			$working[8] = '';
26292			$working[9] = '';
26293			$working[10] = $ports;
26294			$working[11] = '';
26295			$working[12] = '';
26296			$working[13] = $name;
26297			$working[14] = $type;
26298			$working[15] = '';
26299			$working[16] = '';
26300			$working[17] = '';
26301			$working[18] = '';
26302			$working[19] = '';
26303			$working[20] = '';
26304		}
26305		elsif ($b_multi &&
26306		 /^([^,]+),\s+(self powered|power\s+([0-9]+\s+mA)),\s+config\s([0-9]+),\s+rev\s+([0-9\.]+)(,\s+i?Serial\s(\S*))?/i){
26307			$speed = prep_speed($1);
26308			$rev = usb_rev($speed);
26309			$power = $3;
26310			process_power(\$power) if $power;
26311			$working[8] = $rev;
26312			$working[16] = $7 if $7;
26313			$working[17] = $speed;
26314			$working[18] = $4; # config number
26315			$working[19] = $power;
26316			$working[20] = $5; # product rev
26317		}
26318		# 1 or more drivers supported
26319		elsif ($b_multi && /^driver:\s*([^,]+)$/){
26320			my $temp = $1;
26321			$working[4] = '09' if $temp =~ /hub[0-9]/;
26322			$temp =~ s/([0-9]+)$//;
26323			$working[21] = $1; # driver nu
26324			# drivers, note that when numbers trimmed off, drivers can have same name
26325			$working[15] = ($working[15] && $working[15] !~ /\b$temp\b/) ? "$working[15],$temp" : $temp;
26326			# now that we have the driver, let's recheck the type
26327			if (!$type && $name && $working[15]){
26328				$type = check_type($name,$working[15],'');
26329				$working[14] = $type if $type;
26330			}
26331		}
26332		elsif ($b_multi && /^port\s[0-9]/){
26333			$ports++;
26334		}
26335	}
26336	main::log_data('dump','$usb{main}: usbdevs',$usb{'main'}) if $b_log;
26337	print Data::Dumper::Dumper $usb{'main'} if $dbg[6];
26338	eval $end if $b_log;
26339}
26340
26341sub usb_grabber {
26342	eval $start if $b_log;
26343	my ($program) = @_;
26344	my ($args,$path,$pattern,@data,@working);
26345	if ($program eq 'lsusb'){
26346		$args = '';
26347		$path = $alerts{'lsusb'}->{'path'};
26348		$pattern = '^Bus [0-9]';
26349	}
26350	elsif ($program eq 'usbconfig'){
26351		$args = 'dump_device_desc';
26352		$path = $alerts{'usbconfig'}->{'path'};
26353		$pattern = '^[a-z_-]+[0-9]+\.[0-9]+:';
26354	}
26355	elsif ($program eq 'usbdevs'){
26356		$args = '-vv';
26357		$path = $alerts{'usbdevs'}->{'path'};
26358		$pattern = '^(addr\s[0-9a-f]+:|port\s[0-9]+\saddr\s[0-9]+:)';
26359	}
26360	if ($b_live && !$fake{'usbdevs'} && !$fake{'usbconfig'}){
26361		@data = main::grabber("$path $args 2>/dev/null",'','strip');
26362	}
26363	else {
26364		my $file;
26365		if ($fake{'usbdevs'}){
26366			$file = "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/bsd-usbdevs-v-1.txt";
26367		}
26368		elsif ($fake{'usbconfig'}){
26369			$file = "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/bsd-usbconfig-list-v-1.txt";
26370		}
26371		else {
26372			$file = "$ENV{'HOME'}/bin/scripts/inxi/data/lsusb/mdmarmer-lsusb.txt";
26373		}
26374		@data = main::reader($file,'strip');
26375	}
26376	if (@data){
26377		$use{'usb-tool'} = 1 if scalar @data > 2;
26378		foreach (@data){
26379			# this is the group separator and assign trigger
26380			push(@working, '~') if $_ =~ /$pattern/i;
26381			push(@working, $_);
26382		}
26383		push(@working, '~');
26384	}
26385	print Data::Dumper::Dumper \@working if $dbg[30];
26386	eval $end if $b_log;
26387	return @working;
26388}
26389
26390sub sys_data {
26391	eval $start if $b_log;
26392	my ($source) = @_;
26393	my ($configuration,$ports,$usb_version);
26394	my (@drivers,@uevent);
26395	my $i = 0;
26396	my @files = main::globber('/sys/bus/usb/devices/*');
26397	# we want to get rid of the hubs with x-0: syntax, those are hubs found in /usbx
26398	@files = grep {!/\/[0-9]+-0:/} @files;
26399	# print join("\n", @files);
26400	foreach (@files){
26401		# be careful, sometimes uevent is not readable
26402		@uevent = (-r "$_/uevent") ? main::reader("$_/uevent") : undef;
26403		if (@uevent && ($ids = main::awk(\@uevent,'^(DEVNAME|DEVICE\b)',2,'='))){
26404			@drivers = ();
26405			($b_hub,$class_id,$protocol_id,$subclass_id) = (0,0,0,0);
26406			($configuration,$driver,$interfaces,$name,$ports,$product,$serial,$speed,
26407			$type,$usb_version,$vendor) = ('','','','','','','','','','','');
26408			# print Cwd::abs_path($_),"\n";
26409			# print "f1: $_\n";
26410			$path_id = $_;
26411			$path_id =~ s/^.*\///;
26412			$path_id =~ s/^usb([0-9]+)/$1-0/;
26413			# if DEVICE= then path = /proc/bus/usb/001/001 else: bus/usb/006/001
26414			$ids =~ s/^\///;
26415			@working = split('/', $ids);
26416			shift @working if $working[0] eq 'proc';
26417			$bus_id = int($working[2]);
26418			$bus_id_alpha = bus_id_alpha($path_id);
26419			$device_id = int($working[3]);
26420			# this will be a hex number
26421			$class_id = sys_item("$_/bDeviceClass");
26422			# $subclass_id = sys_item("$_/bDeviceSubClass");
26423			$class_id = hex($class_id) if $class_id;
26424			$power = sys_item("$_/bMaxPower");
26425			process_power(\$power) if $power;
26426			# this populates class, subclass, and protocol id with decimal numbers
26427			@drivers = uevent_data("$_/[0-9]*/uevent");
26428			push(@drivers, uevent_data("$_/[0-9]*/*/uevent")) if !$b_hub;
26429			$ports = sys_item("$_/maxchild") if $b_hub;
26430			if (@drivers){
26431				main::uniq(\@drivers);
26432				$driver = join(',', sort @drivers);
26433			}
26434			$interfaces = sys_item("$_/bNumInterfaces");
26435			$serial = sys_item("$_/serial");
26436			$usb_version = sys_item("$_/version");
26437			$speed = sys_item("$_/speed");
26438			$configuration = sys_item("$_/configuration");
26439			$power = sys_item("$_/bMaxPower");
26440			process_power(\$power) if $power;
26441			$class_id = sprintf("%02x", $class_id) if defined $class_id && $class_id ne '';
26442			$subclass_id = sprintf("%02x", $subclass_id) if defined $subclass_id && $subclass_id ne '';
26443			if ($source eq 'lsusb'){
26444				for ($i = 0; $i < scalar @{$usb{'main'}}; $i++){
26445					if ($usb{'main'}->[$i][0] eq $bus_id && $usb{'main'}->[$i][1] == $device_id){
26446						if (!$b_hub && $usb{'main'}->[$i][13] && (!$type || $type eq '<vendor specific>')){
26447							$type = check_type($usb{'main'}->[$i][13],$driver,$type);
26448						}
26449						# print $type,"\n";
26450						$usb{'main'}->[$i][0] = $bus_id_alpha;
26451						$usb{'main'}->[$i][2] = $path_id;
26452						$usb{'main'}->[$i][3] = $_;
26453						$usb{'main'}->[$i][4] = $class_id;
26454						$usb{'main'}->[$i][5] = $subclass_id;
26455						$usb{'main'}->[$i][6] = $protocol_id;
26456						$usb{'main'}->[$i][8] = $usb_version;
26457						$usb{'main'}->[$i][9] = $interfaces;
26458						$usb{'main'}->[$i][10] = $ports if $ports;
26459						if ($type && $b_hub && (!$usb{'main'}->[$i][13] ||
26460						 $usb{'main'}->[$i][13] =~ /^linux foundation/i)){
26461							$usb{'main'}->[$i][13] = "$type";
26462						}
26463						$usb{'main'}->[$i][14] = $type if ($type && !$b_hub);
26464						$usb{'main'}->[$i][15] = $driver if $driver;
26465						$usb{'main'}->[$i][16] = $serial if $serial;
26466						$usb{'main'}->[$i][17] = $speed if $speed;
26467						$usb{'main'}->[$i][18] = $configuration;
26468						$usb{'main'}->[$i][19] = $power;
26469						$usb{'main'}->[$i][20] = '';
26470						assign_usb_type($usb{'main'}->[$i]);
26471						# print join("\n",@{$usb{'main'}->[$i]}),"\n\n";# if !$b_hub;
26472						last;
26473					}
26474				}
26475			}
26476			else {
26477				$chip_id = sys_item("$_/idProduct");
26478				$vendor_id = sys_item("$_/idVendor");
26479				# we don't want the device, it's probably a bad path in /sys/bus/usb/devices
26480				next if !$vendor_id && !$chip_id;
26481				$product = sys_item("$_/product");
26482				$product = main::cleaner($product) if $product;
26483				$vendor = sys_item("$_/manufacturer");
26484				$vendor = main::cleaner($vendor) if $vendor;
26485				if (!$b_hub && ($product || $vendor)){
26486					if ($vendor && $product && $product !~ /$vendor/){
26487						$name = "$vendor $product";
26488					}
26489					elsif ($product){
26490						$name = $product;
26491					}
26492					elsif ($vendor){
26493						$name = $vendor;
26494					}
26495				}
26496				elsif ($b_hub){
26497					$name = $type;
26498				}
26499				$name = main::remove_duplicates($name) if $name;
26500				if (!$b_hub && $name && (!$type || $type eq '<vendor specific>')){
26501					$type = check_type($name,$driver,$type);
26502				}
26503				# this isn't that useful, but save in case something shows up
26504				# if ($configuration){
26505				#	$name = ($name) ? "$name $configuration" : $configuration;
26506				# }
26507				$type = 'Hub' if $b_hub;
26508				$usb{'main'}->[$i][0] = $bus_id_alpha;
26509				$usb{'main'}->[$i][1] = $device_id;
26510				$usb{'main'}->[$i][2] = $path_id;
26511				$usb{'main'}->[$i][3] = $_;
26512				$usb{'main'}->[$i][4] = $class_id;
26513				$usb{'main'}->[$i][5] = $subclass_id;
26514				$usb{'main'}->[$i][6] = $protocol_id;
26515				$usb{'main'}->[$i][7] = "$vendor_id:$chip_id";
26516				$usb{'main'}->[$i][8] = $usb_version;
26517				$usb{'main'}->[$i][9] = $interfaces;
26518				$usb{'main'}->[$i][10] = $ports;
26519				$usb{'main'}->[$i][11] = $vendor;
26520				$usb{'main'}->[$i][12] = $product;
26521				$usb{'main'}->[$i][13] = $name;
26522				$usb{'main'}->[$i][14] = $type;
26523				$usb{'main'}->[$i][15] = $driver;
26524				$usb{'main'}->[$i][16] = $serial;
26525				$usb{'main'}->[$i][17] = $speed;
26526				$usb{'main'}->[$i][18] = $configuration;
26527				$usb{'main'}->[$i][19] = $power;
26528				$usb{'main'}->[$i][20] = '';
26529				assign_usb_type($usb{'main'}->[$i]);
26530				$i++;
26531			}
26532			# print "$path_id ids: $bus_id:$device_id driver: $driver ports: $ports\n==========\n"; # if $dbg[6];;
26533		}
26534	}
26535	print Data::Dumper::Dumper $usb{'main'} if $source eq 'main' && $dbg[6];
26536	main::log_data('dump','$usb{main}: sys',$usb{'main'}) if $source eq 'main' && $b_log;
26537	eval $end if $b_log;
26538}
26539# get driver, interface [type:] data
26540sub uevent_data {
26541	my ($path) = @_;
26542	my ($interface,$interfaces,$temp,@interfaces,@drivers);
26543	my @files = main::globber($path);
26544	@files = grep {!/\/(subsystem|driver|ep_[^\/]+)\/uevent$/} @files if @files;
26545	foreach (@files){
26546		last if $b_hub;
26547		# print "f2: $_\n";
26548		($interface) = ('');
26549		@working = main::reader($_) if -r $_;
26550		# print join("\n",@working), "\n";
26551		if (@working){
26552			$driver = main::awk(\@working,'^DRIVER',2,'=');
26553			$interface = main::awk(\@working,'^INTERFACE',2,'=');
26554			if ($interface){
26555				$interface = device_type($interface);
26556				if ($interface){
26557					if ($interface ne '<vendor specific>'){
26558						push(@interfaces, $interface);
26559					}
26560					# networking requires more data but this test is reliable
26561					elsif (!@interfaces){
26562						$temp = $_;
26563						$temp =~ s/\/uevent$//;
26564						push(@interfaces, 'Network') if -d "$temp/net/";
26565					}
26566					if (!@interfaces){
26567						push(@interfaces, $interface);
26568					}
26569				}
26570			}
26571		}
26572		# print "driver:$driver\n";
26573		$b_hub = 1 if $driver && $driver eq 'hub';
26574		$driver = '' if $driver && ($driver eq 'usb' || $driver eq 'hub');
26575		push(@drivers,$driver) if $driver;
26576	}
26577	if (@interfaces){
26578		main::uniq(\@interfaces);
26579		# clear out values like: <vendor specific>,Printer
26580		if (scalar @interfaces > 1 && (grep {!/^<vendor/} @interfaces) && (grep {/^<vendor/} @interfaces)){
26581			@interfaces = grep {!/^<vendor/} @interfaces;
26582		}
26583		$type = join(',', @interfaces) if @interfaces;
26584		# print "type:$type\n";
26585	}
26586	return @drivers;
26587}
26588sub sys_item {
26589	my ($path) = @_;
26590	my ($item);
26591	$item = main::reader($path,'',0) if -r $path;
26592	$item = '' if ! defined $item;
26593	$item = main::trimmer($item) if $item;
26594	return $item;
26595}
26596sub assign_usb_type {
26597	my ($row) = @_;
26598	# it's a hub
26599	# a device will always be the second or > device on the bus, although
26600	# nested hubs of course can be > 1 too. No need to build these if none of
26601	# lines are showing.
26602	if (($row->[4] && $row->[4] eq '09') ||
26603	  ($row->[14] && $row->[14] eq 'Hub') ||
26604	  $row->[1] <= 1 ||
26605	  (!$show{'audio'} && !$show{'bluetooth'} && !$show{'graphic'} && !$show{'network'})){
26606		return;
26607	}
26608	$row->[13] = '' if !defined $row->[13]; # product
26609	$row->[14] = '' if !defined $row->[14]; # type
26610	$row->[15] = '' if !defined $row->[15]; # driver
26611	set_asound_ids() if $show{'audio'} && !$b_asound;
26612	set_network_regex() if $show{'network'} && !$network_regex;
26613	# NOTE: a device, like camera, can be audio+graphic
26614	if ($show{'audio'} && (
26615	  (@asound_ids && $row->[7] && (grep {$row->[7] eq $_} @asound_ids)) ||
26616	  ($row->[14] =~ /Audio/) ||
26617	  ($row->[15] && $row->[15] =~ /audio/) ||
26618	  ($row->[13] && lc($row->[13]) =~ /(audio|\bdac[0-9]*\b|headphone|\bmic(rophone)?\b)/))){
26619		push(@{$usb{'audio'}},$row);
26620	}
26621	if ($show{'graphic'} && (
26622	  $row->[14] && ($row->[14] =~ /Video/) ||
26623	  ($row->[15] && $row->[15] =~ /video/) ||
26624	  ($row->[13] && lc($row->[13]) =~ /(camera|\bdvb-t|\b(pc)?tv\b|video|webcam)/))){
26625		push(@{$usb{'graphics'}},$row);
26626	}
26627	elsif ($show{'bluetooth'} && (
26628	  $row->[14] && $row->[14] =~ /Bluetooth/ ||
26629	  ($row->[15] && $row->[15] =~ /\b(btusb|ubt)\b/))){
26630		push(@{$usb{'bluetooth'}},$row);
26631	}
26632	elsif ($show{'network'} && (
26633	   ($row->[14] && $row->[14] =~ /(Ethernet|Network|WiFi)/i) ||
26634	   ($row->[15] && $row->[15] =~ /(^ipw|^iwl|wifi)/) ||
26635	   ($row->[13] && $row->[13] =~ /($network_regex)/i))){
26636		# print "$1\n";
26637		push(@{$usb{'network'}},$row);
26638	}
26639}
26640sub device_type {
26641	my ($data) = @_;
26642	my ($type);
26643	# note: the 3/0/0 value passed will be decimal, not hex
26644	my @types = split('/', $data) if $data;
26645	# print @types,"\n";
26646	if (!@types || $types[0] eq '0' || scalar @types != 3){return '';}
26647	elsif ($types[0] eq '255'){ return '<vendor specific>';}
26648	if (scalar @types == 3){
26649		$class_id = $types[0];
26650		$subclass_id = $types[1];
26651		$protocol_id = $types[2];
26652	}
26653	if ($types[0] eq '1'){$type = 'Audio';}
26654	elsif ($types[0] eq '2'){
26655		if ($types[1] eq '2'){$type = 'Abstract (modem)';}
26656		elsif ($types[1] eq '6'){$type = 'Ethernet Network';}
26657		elsif ($types[1] eq '10'){$type = 'Mobile Direct Line';}
26658		elsif ($types[1] eq '12'){$type = 'Ethernet Emulation';}
26659		else {$type = 'Communication';}
26660	}
26661	elsif ($types[0] eq '3'){
26662		if ($types[2] eq '0'){$type = 'HID';} # actual value: None
26663		elsif ($types[2] eq '1'){$type = 'Keyboard';}
26664		elsif ($types[2] eq '2'){$type = 'Mouse';}
26665	}
26666	elsif ($types[0] eq '6'){$type = 'Still Imaging';}
26667	elsif ($types[0] eq '7'){$type = 'Printer';}
26668	elsif ($types[0] eq '8'){$type = 'Mass Storage';}
26669	elsif ($types[0] eq '9'){
26670		if ($types[2] eq '0'){$type = 'Full speed (or root) Hub';}
26671		elsif ($types[2] eq '1'){$type = 'Hi-speed hub with single TT';}
26672		elsif ($types[2] eq '2'){$type = 'Hi-speed hub with multiple TTs';}
26673	}
26674	elsif ($types[0] eq '10'){$type = 'CDC-Data';}
26675	elsif ($types[0] eq '11'){$type = 'Smart Card';}
26676	elsif ($types[0] eq '13'){$type = 'Content Security';}
26677	elsif ($types[0] eq '14'){$type = 'Video';}
26678	elsif ($types[0] eq '15'){$type = 'Personal Healthcare';}
26679	elsif ($types[0] eq '16'){$type = 'Audio-Video';}
26680	elsif ($types[0] eq '17'){$type = 'Billboard';}
26681	elsif ($types[0] eq '18'){$type = 'Type-C Bridge';}
26682	elsif ($types[0] eq '88'){$type = 'Xbox';}
26683	elsif ($types[0] eq '220'){$type = 'Diagnostic';}
26684	elsif ($types[0] eq '224'){
26685		if ($types[1] eq '1'){$type = 'Bluetooth';}
26686		elsif ($types[1] eq '2'){
26687			if ($types[2] eq '1'){$type = 'Host Wire Adapter';}
26688			elsif ($types[2] eq '2'){$type = 'Device Wire Adapter';}
26689			elsif ($types[2] eq '3'){$type = 'Device Wire Adapter';}
26690		}
26691	}
26692	# print "$data: $type\n";
26693	return $type;
26694}
26695# device name/driver string based test, return <vendor specific> if not detected
26696# for linux based tests, and empty for bsd tests
26697sub check_type {
26698	my ($name,$driver,$type) = @_;
26699	$name = lc($name);
26700	if (($driver && $driver =~ /hub/) || $name =~ /\b(hub)/i){
26701		$type = 'Hub';
26702	}
26703	elsif ($name =~ /(audio|\bdac[0-9]*\b|(head|micro|tele)phone|hifi|\bmidi\b|\bmic\b|sound)/){
26704		$type = 'Audio';
26705	}
26706	# Broadcom HP Portable SoftSailing
26707	elsif (($driver && $driver =~ /\b(btusb|ubt)\b/) || $name =~ /(bluetooth)/){
26708		$type = 'Bluetooth'
26709	}
26710	elsif (($driver && $driver =~ /video/) ||
26711	  $name =~ /(camera|display|\bdvb-t|\b(pc)?tv\bvideo|webcam)/){
26712		$type = 'Video';
26713	}
26714	elsif ($name =~ /(wlan|wi-?fi|802\.1[15]|(11|54|108|240|300|450|1300)\s?mbps|(11|54|108|240)g\b|wireless[\s-][gn]\b|wireless.*adapter)/){
26715		$type = 'WiFi';
26716	}
26717	# note, until freebsd match to actual drivers, these top level driver matches aren't interesting
26718	elsif (($driver && $bsd_type && $driver =~ /\b(muge)\b/) ||
26719	 $name =~ /(ethernet|\blan|802\.3|100?\/1000?|gigabit)/){
26720		$type = 'Ethernet';
26721	}
26722	# note: audio devices show HID sometimes, not sure why
26723	elsif ($name =~ /(joystick|keyboard|mouse|trackball)/){
26724		$type = 'HID';
26725	}
26726	elsif (($driver && $driver =~ /^(umass)$/) ||
26727	  $name =~ /\b(disk|drive|flash)\b/){
26728		$type = 'Mass Storage';
26729	}
26730	return $type;
26731}
26732# linux only, will create a positive match to sound devices
26733sub set_asound_ids {
26734	$b_asound = 1;
26735	if (-d '/proc/asound'){
26736		# note: this will double the data, but it's easier this way.
26737		# binxi tested for -L in the /proc/asound files, and used only those.
26738		my @files = main::globber('/proc/asound/*/usbid');
26739		foreach (@files){
26740			my $id = main::reader($_,'',0);
26741			push(@asound_ids, $id) if ($id && !(grep {/$id/} @asound_ids));
26742		}
26743	}
26744	main::log_data('dump','@asound_ids',\@asound_ids) if $b_log;
26745}
26746### USB networking search string data, because some brands can have other products than
26747### wifi/nic cards, they need further identifiers, with wildcards.
26748### putting the most common and likely first, then the less common, then some specifics
26749sub set_network_regex {
26750	# belkin=050d; d-link=07d1; netgear=0846; ralink=148f; realtek=0bda;
26751	# Atmel, Atheros make other stuff. NOTE: exclude 'networks': IMC Networks
26752	# ralink has bluetooth as well as networking; (WG|WND?A)[0-9][0-9][0-9] netgear IDs
26753	$network_regex = 'Ethernet|gigabit|\bISDN|\bLAN\b|Mobile\s?Broadband|';
26754	$network_regex .= '\bNIC\b|wi-?fi|Wireless[\s-][GN]\b|WLAN|';
26755	$network_regex .= '802\.(1[15]|3)|(10|11|54|108|240|300|450|1300)\s?Mbps|(11|54|108|240)g\b|100?\/1000?|';
26756	$network_regex .= '(100?|N)Base-?T\b|';
26757	$network_regex .= '(Actiontec|AirLink|Asus|Belkin|Buffalo|Dell|D-Link|DWA-|ENUWI-|';
26758	$network_regex .= 'Ralink|Realtek|Rosewill|RNX-|Samsung|Sony|TEW-|TP-Link';
26759	$network_regex .= 'Zonet.*ZEW.*).*Wireless|';
26760	$network_regex .= '(\bD-Link|Network(ing)?|Wireless).*(Adapter|Interface)|';
26761	$network_regex .= '(Linksys|Netgear|Davicom)|';
26762	$network_regex .= 'Range(Booster|Max)|Samsung.*LinkStick|\b(WG|WND?A)[0-9][0-9][0-9]|';
26763	$network_regex .= '\b(050d:935b|0bda:8189|0bda:8197)\b';
26764}
26765# try to guess at usb rev version from speeds
26766sub usb_rev {
26767	return if !$_[0] || ! main::is_numeric($_[0]);
26768	my $rev;
26769	if ($_[0] < 2){$rev = '1.0';}
26770	elsif ($_[0] < 13){$rev = '1.1';}
26771	elsif ($_[0] < 481){$rev = '2.0';}
26772	elsif ($_[0] < 5001){$rev = '3.0';}
26773	elsif ($_[0] < 10001){$rev = '3.1';}
26774	elsif ($_[0] < 20001){$rev = '3.2';}
26775	elsif ($_[0] < 40001){$rev = '4.0';}
26776	return $rev;
26777}
26778sub prep_speed {
26779	return if !$_[0];
26780	my $speed = $_[0];
26781	if ($_[0] =~ /^([0-9\.]+)\s*Mb/){
26782		$speed = $1;
26783	}
26784	elsif ($_[0] =~ /^([0-9\.]+)+\s*Gb/){
26785		$speed = $1 * 1000;
26786	}
26787	# could be 3.2, 20000 too, also superspeed+
26788	elsif ($_[0] =~ /super[\s-]?speed\s?(\+|plus)/i){
26789		$speed = 10000;# 3.1; # can't trust bsds to use superspeed+ but we'll hope
26790	}
26791	elsif ($_[0] =~ /super[\s-]?speed/i){
26792		$speed = 5000;# 3.0;
26793	}
26794	elsif ($_[0] =~ /hi(gh)?[\s-]?speed/i){
26795		$speed = 480; # 2.0,
26796	}
26797	elsif ($_[0] =~ /full[\s-]?speed/i){
26798		$speed = 12; # 1.1 - could be full speed 1.1/2.0
26799	}
26800	elsif ($_[0] =~ /low?[\s-]?speed/i){
26801		$speed = 1.5; # 1.5 - could be 1.0, or low speed 1.1/2.0
26802	}
26803	return $speed;
26804}
26805sub process_power {
26806	return if !${$_[0]};
26807	${$_[0]} =~ s/\s//g;
26808	# ${$_[0]} = '' if ${$_[0]} eq '0mA'; # better to handle on output
26809}
26810# this is used to create an alpha sortable bus id for main $usb[0]
26811sub bus_id_alpha {
26812	my ($id) = @_;
26813	$id =~ s/^([1-9])-/0$1-/;
26814	$id =~ s/([-\.:])([0-9])\b/${1}0$2/g;
26815	return $id;
26816}
26817}
26818
26819# note: seen instance in android where reading file hangs endlessly!!!
26820sub get_wakeups {
26821	eval $start if $b_log;
26822	return if $b_arm || $b_mips || $b_ppc;
26823	my ($wakeups);
26824	my $path = '/sys/power/wakeup_count';
26825	$wakeups = reader($path,'strip',0) if -r $path;
26826	eval $end if $b_log;
26827	return $wakeups;
26828}
26829
26830########################################################################
26831#### GENERATE OUTPUT
26832########################################################################
26833
26834## OutputGenerator
26835# Also creates Short, Info, and System items
26836{
26837package OutputGenerator;
26838
26839sub generate {
26840	eval $start if $b_log;
26841	my (%row,%checks);
26842	main::set_ps_aux() if !$loaded{'ps-aux'};
26843	main::set_sysctl_data() if $use{'sysctl'};
26844	main::set_dboot_data() if $bsd_type && !$loaded{'dboot'};
26845	# note: ps aux loads before logging starts, so create debugger data here
26846	if ($b_log){
26847		# I don't think we need to see this, it's long, but leave in case we do
26848		# main::log_data('dump','@ps_aux',\@ps_aux);
26849		main::log_data('dump','@ps_cmd',\@ps_cmd);
26850	}
26851	if ($show{'short'}){
26852		%row = short_output();
26853		assign_data(\%row);
26854	}
26855	else {
26856		if ($show{'system'}){
26857			%row = system_item();
26858			assign_data(\%row);
26859		}
26860		if ($show{'machine'}){
26861			DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'};
26862			%row = item_handler('Machine','machine');
26863			assign_data(\%row);
26864		}
26865		if ($show{'battery'}){
26866			DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'};
26867			%row = item_handler('Battery','battery');
26868			if (%row || $show{'battery-forced'}){
26869				assign_data(\%row);
26870			}
26871		}
26872		if ($show{'ram'}){
26873			DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'};
26874			%row = item_handler('Memory','ram');
26875			assign_data(\%row);
26876		}
26877		if ($show{'slot'}){
26878			DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'};
26879			%row = item_handler('PCI Slots','slot');
26880			assign_data(\%row);
26881		}
26882		if ($show{'cpu'} || $show{'cpu-basic'}){
26883			DeviceData::set(\$checks{'device'}) if $b_arm && !$checks{'device'};
26884			DmidecodeData::set(\$checks{'dmi'}) if $use{'dmidecode'} && !$checks{'dmi'};
26885			my $arg = ($show{'cpu-basic'}) ? 'basic' : 'full' ;
26886			%row = item_handler('CPU','cpu',$arg);
26887			assign_data(\%row);
26888		}
26889		if ($show{'graphic'}){
26890			UsbData::set(\$checks{'usb'}) if !$checks{'usb'};
26891			DeviceData::set(\$checks{'device'}) if !$checks{'device'};
26892			%row = item_handler('Graphics','graphic');
26893			assign_data(\%row);
26894		}
26895		if ($show{'audio'}){
26896			UsbData::set(\$checks{'usb'}) if !$checks{'usb'};
26897			DeviceData::set(\$checks{'device'}) if !$checks{'device'};
26898			%row = item_handler('Audio','audio');
26899			assign_data(\%row);
26900		}
26901		if ($show{'network'}){
26902			UsbData::set(\$checks{'usb'}) if !$checks{'usb'};
26903			DeviceData::set(\$checks{'device'}) if !$checks{'device'};
26904			IpData::set() if ($show{'ip'} || ($bsd_type && $show{'network-advanced'}));
26905			%row = item_handler('Network','network');
26906			assign_data(\%row);
26907		}
26908		if ($show{'bluetooth'}){
26909			UsbData::set(\$checks{'usb'}) if !$checks{'usb'};
26910			DeviceData::set(\$checks{'device'}) if !$checks{'device'};
26911			%row = item_handler('Bluetooth','bluetooth');
26912			assign_data(\%row);
26913		}
26914		if ($show{'logical'}){
26915			%row = item_handler('Logical','logical');
26916			assign_data(\%row);
26917		}
26918		if ($show{'raid'}){
26919			DeviceData::set(\$checks{'device'}) if !$checks{'device'};
26920			%row = item_handler('RAID','raid');
26921			assign_data(\%row);
26922		}
26923		if ($show{'disk'} || $show{'disk-basic'} || $show{'disk-total'} || $show{'optical'}){
26924			%row = item_handler('Drives','disk');
26925			assign_data(\%row);
26926		}
26927		if ($show{'partition'} || $show{'partition-full'}){
26928			%row = item_handler('Partition','partition');
26929			assign_data(\%row);
26930		}
26931		if ($show{'swap'}){
26932			%row = item_handler('Swap','swap');
26933			assign_data(\%row);
26934		}
26935		if ($show{'unmounted'}){
26936			%row = item_handler('Unmounted','unmounted');
26937			assign_data(\%row);
26938		}
26939		if ($show{'usb'}){
26940			UsbData::set(\$checks{'usb'}) if !$checks{'usb'};
26941			%row = item_handler('USB','usb');
26942			assign_data(\%row);
26943		}
26944		if ($show{'sensor'}){
26945			%row = item_handler('Sensors','sensor');
26946			assign_data(\%row);
26947		}
26948		if ($show{'repo'}){
26949			%row = item_handler('Repos','repo');
26950			assign_data(\%row);
26951		}
26952		if ($show{'process'}){
26953			%row = item_handler('Processes','process');
26954			assign_data(\%row);
26955		}
26956		if ($show{'weather'}){
26957			%row = item_handler('Weather','weather');
26958			assign_data(\%row);
26959		}
26960		if ($show{'info'}){
26961			%row = info_item();
26962			assign_data(\%row);
26963		}
26964	}
26965	if ($output_type ne 'screen'){
26966		main::output_handler(\%rows);
26967	}
26968	eval $end if $b_log;
26969}
26970## Short, Info, System Items ##
26971sub short_output {
26972	eval $start if $b_log;
26973	my $num = 0;
26974	my $kernel_os = ($bsd_type) ? 'OS' : 'Kernel';
26975	my ($cpu_string,$speed,$speed_key,$type) = ('','','speed','');
26976	my $memory = MemoryData::get('string');
26977 	my @cpu = CpuItem::get('short');
26978 	if (scalar @cpu > 1){
26979		$type = ($cpu[2]) ? " (-$cpu[2]-)" : '';
26980		($speed,$speed_key) = ('','');
26981		if ($cpu[6]){
26982			$speed_key = "$cpu[3]/$cpu[5]";
26983			$cpu[4] =~ s/ MHz//;
26984			$speed = "$cpu[4]/$cpu[6]";
26985		}
26986		else {
26987			$speed_key = $cpu[3];
26988			$speed = $cpu[4];
26989		}
26990		$cpu[1] ||= main::row_defaults('cpu-model-null');
26991		$cpu_string = $cpu[0] . ' ' . $cpu[1] . $type;
26992	}
26993	elsif ($bsd_type){
26994		if ($alerts{'sysctl'}->{'action'}){
26995			if ($alerts{'sysctl'}->{'action'} ne 'use'){
26996				$cpu_string = "sysctl $alerts{'sysctl'}->{'action'}";
26997				$speed = "sysctl $alerts{'sysctl'}->{'action'}";
26998			}
26999			else {
27000				$cpu_string = 'bsd support coming';
27001				$speed = 'bsd support coming';
27002			}
27003		}
27004	}
27005	my @disk = DriveItem::get('short');
27006	# print Dumper \@disk;
27007	my $disk_string = 'N/A';
27008	my ($size,$used) = ('','');
27009	my ($size_holder,$used_holder);
27010	if (@disk){
27011		$size = ($disk[0]->{'logical-size'}) ? $disk[0]->{'logical-size'} : $disk[0]->{'size'};
27012		# must be > 0
27013		if ($size && main::is_numeric($size)){
27014			$size_holder = $size;
27015			$size = main::get_size($size,'string');
27016		}
27017		$used = $disk[0]->{'used'};
27018		if ($used && main::is_numeric($disk[0]->{'used'})){
27019			$used_holder = $disk[0]->{'used'};
27020			$used = main::get_size($used,'string');
27021		}
27022		# in some fringe cases size can be 0 so only assign 'N/A' if no percents etc
27023		if ($size_holder && $used_holder){
27024			my $percent = ' (' . sprintf("%.1f", $used_holder/$size_holder*100) . '% used)';
27025			$disk_string = "$size$percent";
27026		}
27027		else {
27028			$size ||= main::row_defaults('disk-size-0');
27029			$disk_string = "$used/$size";
27030		}
27031	}
27032	$memory ||= 'N/A';
27033 	# print join('; ', @cpu), " sleep: $cpu_sleep\n";
27034 	if (!$loaded{'shell-data'} && $ppid && (!$b_irc || !$client{'name-print'})){
27035		ShellData::set();
27036	}
27037	my $client = $client{'name-print'};
27038	my $client_shell = ($b_irc) ? 'Client' : 'Shell';
27039	if ($client{'version'}){
27040		$client .= ' ' . $client{'version'};
27041	}
27042	my @data = ({
27043		main::key($num++,0,0,'CPU') => $cpu_string,
27044		main::key($num++,0,0,$speed_key) => $speed,
27045		main::key($num++,0,0,$kernel_os) => main::get_kernel_data(),
27046		main::key($num++,0,0,'Up') => main::get_uptime(),
27047		main::key($num++,0,0,'Mem') => $memory,
27048		main::key($num++,0,0,'Storage') => $disk_string,
27049		# could make -1 for ps aux itself, -2 for ps aux and self
27050		main::key($num++,0,0,'Procs') => scalar @ps_aux,
27051		main::key($num++,0,0,$client_shell) => $client,
27052		main::key($num++,0,0,$self_name) => main::get_self_version(),
27053	},);
27054	my %row = (
27055	main::key($prefix,1,0,'SHORT') => [(@data),],
27056	);
27057	eval $end if $b_log;
27058	return %row;
27059}
27060sub info_item{
27061	eval $start if $b_log;
27062	my $num = 0;
27063	my $gcc_alt = '';
27064	my $running_in = '';
27065	my $data_name = main::key($prefix++,1,0,'Info');
27066	my ($b_gcc,$gcc,$index);
27067	my ($gpu_ram,$parent,$percent,$total,$used) = (0,'','','','');
27068	my @gccs = main::get_gcc_data();
27069	if (@gccs){
27070		$gcc = shift @gccs;
27071		if ($extra > 1 && @gccs){
27072			$gcc_alt = join('/', @gccs);
27073		}
27074		$b_gcc = 1;
27075	}
27076	$gcc ||= 'N/A';
27077	my %data = (
27078	$data_name => [{
27079	main::key($num++,0,1,'Processes') => scalar @ps_aux,
27080	main::key($num++,1,1,'Uptime') => main::get_uptime(),
27081	},],
27082	);
27083	$index = scalar(@{$data{$data_name}}) - 1;
27084	if ($extra > 2){
27085		my $wakeups = main::get_wakeups();
27086		$data{$data_name}->[$index]{main::key($num++,0,2,'wakeups')} = $wakeups if defined $wakeups;
27087	}
27088	if (!$loaded{'memory'}){
27089		my $memory = MemoryData::get('splits');
27090		if ($memory){
27091			my @temp = split(':', $memory);
27092			$gpu_ram = $temp[3] if $temp[3];
27093			$total = ($temp[0]) ? main::get_size($temp[0],'string') : 'N/A';
27094			$used = ($temp[1]) ? main::get_size($temp[1],'string') : 'N/A';
27095			$used .= " ($temp[2]%)" if $temp[2];
27096			if ($gpu_ram){
27097				$gpu_ram = main::get_size($gpu_ram,'string');
27098			}
27099		}
27100		$data{$data_name}->[$index]{main::key($num++,1,1,'Memory')} = $total;
27101		$data{$data_name}->[$index]{main::key($num++,0,2,'used')} = $used;
27102	}
27103	if ($gpu_ram){
27104		$data{$data_name}->[$index]{main::key($num++,0,2,'gpu')} = $gpu_ram;
27105	}
27106	if ((!$b_display || $force{'display'}) || $extra > 0){
27107		my %init = main::get_init_data();
27108		my $init_type = ($init{'init-type'}) ? $init{'init-type'}: 'N/A';
27109		$data{$data_name}->[$index]{main::key($num++,1,1,'Init')} = $init_type;
27110		if ($extra > 1){
27111			my $init_version = ($init{'init-version'}) ? $init{'init-version'}: 'N/A';
27112			$data{$data_name}->[$index]{main::key($num++,0,2,'v')} = $init_version;
27113		}
27114		if ($init{'rc-type'}){
27115			$data{$data_name}->[$index]{main::key($num++,1,2,'rc')} = $init{'rc-type'};
27116			if ($init{'rc-version'}){
27117				$data{$data_name}->[$index]{main::key($num++,0,3,'v')} = $init{'rc-version'};
27118			}
27119		}
27120		if ($init{'runlevel'}){
27121			$data{$data_name}->[$index]{main::key($num++,0,2,'runlevel')} = $init{'runlevel'};
27122		}
27123		if ($extra > 1){
27124			if ($init{'default'}){
27125				my $default = ($init{'init-type'} eq 'systemd' && $init{'default'} =~ /[^0-9]$/) ? 'target' : 'default';
27126				$data{$data_name}->[$index]{main::key($num++,0,2,$default)} = $init{'default'};
27127			}
27128			if ($b_admin && (my $tool = ServiceData::get('tool',''))){
27129				$data{$data_name}->[$index]{main::key($num++,0,2,'tool')} = $tool;
27130				undef %service_tool;
27131			}
27132		}
27133	}
27134	if ($extra > 0){
27135		my $b_clang;
27136		my $clang_version = '';
27137		if (my $path = main::check_program('clang')){
27138			$clang_version = main::program_version($path,'clang',3,'--version');
27139			$clang_version ||= 'N/A';
27140			$b_clang = 1;
27141		}
27142		my $compiler = ($b_gcc || $b_clang) ? '': 'N/A';
27143		$data{$data_name}->[$index]{main::key($num++,1,1,'Compilers')} = $compiler;
27144		if ($b_gcc){
27145			$data{$data_name}->[$index]{main::key($num++,1,2,'gcc')} = $gcc;
27146			if ($extra > 1 && $gcc_alt){
27147				$data{$data_name}->[$index]{main::key($num++,0,3,'alt')} = $gcc_alt;
27148			}
27149		}
27150		if ($b_clang){
27151			$data{$data_name}->[$index]{main::key($num++,0,2,'clang')} = $clang_version;
27152		}
27153	}
27154	if ($extra > 0 && !$loaded{'packages'}){
27155		my %packages = PackageData::get('inner',\$num);
27156		for (keys %packages){
27157			$data{$data_name}->[$index]{$_} = $packages{$_};
27158		}
27159	}
27160	if (!$loaded{'shell-data'} && $ppid && (!$b_irc || !$client{'name-print'})){
27161		ShellData::set();
27162	}
27163	my $client_shell = ($b_irc) ? 'Client' : 'Shell';
27164	my $client = $client{'name-print'};
27165	if (!$b_irc && $extra > 1){
27166		# some bsds don't support -f option to get PPPID
27167		# note: root/su - does not have $DISPLAY usually
27168		if ($b_display && !$force{'display'} && $ppid && $client{'pppid'}){
27169			$parent = ShellData::shell_launcher();
27170		}
27171		else {
27172			ShellData::tty_number() if !$loaded{'tty-number'};
27173			if ($client{'tty-number'} ne ''){
27174				my $tty_type = '';
27175				if ($client{'tty-number'} =~ /^[a-f0-9]+$/i){
27176					$tty_type = 'tty ';
27177				}
27178				elsif ($client{'tty-number'} =~ /pts/i){
27179					$tty_type = 'pty ';
27180				}
27181				$parent = "$tty_type$client{'tty-number'}";
27182			}
27183		}
27184		# can be tty 0 so test for defined
27185		$running_in = $parent if $parent;
27186		if ($extra > 2 && $running_in && ShellData::ssh_status()){
27187			$running_in .= ' (SSH)';
27188		}
27189		if ($extra > 2 && $client{'su-start'}){
27190			$client .= " ($client{'su-start'})";
27191		}
27192	}
27193	$data{$data_name}->[$index]{main::key($num++,1,1,$client_shell)} =  $client;
27194	if ($extra > 0 && $client{'version'}){
27195		$data{$data_name}->[$index]{main::key($num++,0,2,'v')} = $client{'version'};
27196	}
27197	if (!$b_irc){
27198		if ($extra > 2 && $client{'default-shell'}){
27199			$data{$data_name}->[$index]{main::key($num++,1,2,'default')} = $client{'default-shell'};
27200			$data{$data_name}->[$index]{main::key($num++,0,3,'v')} = $client{'default-shell-v'} if $client{'default-shell-v'};
27201		}
27202		if ($running_in){
27203			$data{$data_name}->[$index]{main::key($num++,0,2,'running-in')} = $running_in;
27204		}
27205	}
27206	$data{$data_name}->[$index]{main::key($num++,0,1,$self_name)} = main::get_self_version();
27207	eval $end if $b_log;
27208	return %data;
27209}
27210sub system_item {
27211	eval $start if $b_log;
27212	my ($cont_desk,$ind_dm,$num) = (1,2,0);
27213	my ($index);
27214	my $data_name = main::key($prefix++,1,0,'System');
27215	my ($desktop,$desktop_info,$desktop_key,$dm_key,$toolkit,$wm) = ('','','Desktop','dm','','');
27216	my (@desktop_data,$desktop_version);
27217	my %data = (
27218	$data_name => [{}],
27219	);
27220	$index = scalar(@{$data{$data_name}}) - 1;
27221	if ($show{'host'}){
27222		$data{$data_name}->[$index]{main::key($num++,0,1,'Host')} = main::get_hostname();
27223	}
27224	$data{$data_name}->[$index]{main::key($num++,1,1,'Kernel')} = main::get_kernel_data();
27225	$data{$data_name}->[$index]{main::key($num++,0,2,'bits')} = main::get_kernel_bits();
27226	if ($extra > 0){
27227		my @compiler = CompilerVersion::get(); # get compiler data
27228		if (scalar @compiler != 2){
27229			@compiler = ('N/A', '');
27230		}
27231		$data{$data_name}->[$index]{main::key($num++,1,2,'compiler')} = $compiler[0];
27232		# if no compiler, obviously no version, so don't waste space showing.
27233		if ($compiler[0] ne 'N/A'){
27234			$compiler[1] ||= 'N/A';
27235			$data{$data_name}->[$index]{main::key($num++,0,3,'v')} = $compiler[1];
27236		}
27237	}
27238	if ($b_admin && (my $params = KernelParameters::get())){
27239		$index = scalar(@{$data{$data_name}});
27240		# print "$params\n";
27241		if ($use{'filter-label'}){
27242			$params = main::apply_partition_filter('system', $params, 'label');
27243		}
27244		if ($use{'filter-uuid'}){
27245			$params = main::apply_partition_filter('system', $params, 'uuid');
27246		}
27247		$data{$data_name}->[$index]{main::key($num++,0,2,'parameters')} = $params;
27248		$index = scalar(@{$data{$data_name}});
27249	}
27250	# note: tty can have the value of 0 but the two tools
27251	# return '' if undefined, so we test for explicit ''
27252	if ($b_display){
27253		my @desktop_data = DesktopEnvironment::get();
27254		$desktop = $desktop_data[0] if $desktop_data[0];
27255		$desktop_version = $desktop_data[1] if $desktop_data[1];
27256		$desktop .= ' ' . $desktop_version if $desktop_version;
27257		if ($extra > 0 && $desktop_data[3]){
27258			# $desktop .= ' (' . $desktop_data[2];
27259			# $desktop .= ($desktop_data[3]) ? ' ' . $desktop_data[3] . ')' : ')';
27260			$toolkit = "$desktop_data[2] $desktop_data[3]";
27261		}
27262		if ($extra > 2 && $desktop_data[4]){
27263			$desktop_info = $desktop_data[4];
27264		}
27265		# don't print the desktop if it's a wm and the same
27266		if ($extra > 1 && $desktop_data[5] &&
27267		    (!$desktop_data[0] || $desktop_data[5] =~ /^(deepin.+|gnome[\s_-]shell|budgie.+)$/i ||
27268		    index(lc($desktop_data[5]),lc($desktop_data[0])) == -1)){
27269			$wm = $desktop_data[5];
27270			$wm .= ' ' . $desktop_data[6] if $extra > 2 && $desktop_data[6];
27271		}
27272	}
27273	if (!$b_display || (!$desktop && $b_root)){
27274		ShellData::tty_number() if !$loaded{'tty-number'};
27275		my $tty = $client{'tty-number'};
27276		if (!$desktop){
27277			$desktop_info = '';
27278		}
27279		# it is defined, as ''
27280		if ($tty eq '' && $client{'console-irc'}){
27281			ShellData::console_irc_tty() if !$loaded{'con-irc-tty'};
27282			$tty = $client{'con-irc-tty'};
27283		}
27284		if ($tty ne ''){
27285			my $tty_type = '';
27286			if ($tty =~ /^[a-f0-9]+$/i){
27287				$tty_type = 'tty ';
27288			}
27289			elsif ($tty =~ /pts/i){
27290				$tty_type = 'pty ';
27291			}
27292			$desktop = "$tty_type$tty";
27293		}
27294		$desktop_key = 'Console';
27295		$dm_key = 'DM';
27296		$ind_dm = 1;
27297		$cont_desk = 0;
27298	}
27299	$desktop ||= 'N/A';
27300	$data{$data_name}->[$index]{main::key($num++,$cont_desk,1,$desktop_key)} = $desktop;
27301	if ($toolkit){
27302		$data{$data_name}->[$index]{main::key($num++,0,2,'tk')} = $toolkit;
27303	}
27304	if ($extra > 2){
27305		if ($desktop_info){
27306			$data{$data_name}->[$index]{main::key($num++,0,2,'info')} = $desktop_info;
27307		}
27308	}
27309	if ($extra > 1){
27310		$data{$data_name}->[$index]{main::key($num++,0,2,'wm')} = $wm if $wm;
27311		if ($extra > 2 && $b_display && defined $ENV{'XDG_VTNR'}){
27312			$data{$data_name}->[$index]{main::key($num++,0,2,'vt')} = $ENV{'XDG_VTNR'};
27313		}
27314		my $dms = main::get_display_manager();
27315		if ($dms || $desktop_key ne 'Console'){
27316			$dms ||= 'N/A';
27317			$data{$data_name}->[$index]{main::key($num++,0,$ind_dm,$dm_key)} = $dms;
27318		}
27319	}
27320	# if ($extra > 2 && $desktop_key ne 'Console'){
27321	#	my $tty = ShellData::tty_number() if !$loaded{'tty-number'};
27322	#	$data{$data_name}->[$index]{main::key($num++,0,1,'vc')} = $tty if $tty ne '';
27323	# }
27324	my $distro_key = ($bsd_type) ? 'OS': 'Distro';
27325	my @distro_data = DistroData::get();
27326	my $distro = $distro_data[0];
27327	$distro ||= 'N/A';
27328	$data{$data_name}->[$index]{main::key($num++,1,1,$distro_key)} = $distro;
27329	if ($extra > 0 && $distro_data[1]){
27330		$data{$data_name}->[$index]{main::key($num++,0,2,'base')} = $distro_data[1];
27331	}
27332	eval $end if $b_log;
27333	return %data;
27334}
27335## Item Processors ##
27336sub assign_data {
27337	my ($row) = @_;
27338	return if ! %$row;
27339	if ($output_type eq 'screen'){
27340		main::print_data($row);
27341	}
27342	else {
27343		%rows = (%rows,%$row);
27344	}
27345}
27346sub item_handler {
27347	eval $start if $b_log;
27348	my ($key,$sub,$arg) = @_;
27349	my %subs = (
27350	'audio' => \&AudioItem::get,
27351	'battery' => \&BatteryItem::get,
27352	'bluetooth' => \&BluetoothItem::get,
27353	'cpu' => \&CpuItem::get,
27354	'disk' => \&DriveItem::get,
27355	'graphic' => \&GraphicItem::get,
27356	'logical' => \&LogicalItem::get,
27357	'machine' => \&MachineItem::get,
27358	'network' => \&NetworkItem::get,
27359	'partition' => \&PartitionItem::get,
27360	'raid' => \&RaidItem::get,
27361	'ram' => \&RamItem::get,
27362	'repo' => \&RepoItem::get,
27363	'process' => \&ProcessItem::get,
27364	'sensor' => \&SensorItem::get,
27365	'slot' => \&SlotItem::get,
27366	'swap' => \&SwapItem::get,
27367	'unmounted' => \&UnmountedItem::get,
27368	'usb' => \&UsbItem::get,
27369	'weather' => \&WeatherItem::get,
27370	);
27371	my (%data);
27372	my $data_name = main::key($prefix++,1,0,$key);
27373	my @rows = $subs{$sub}->($arg);
27374	if (@rows){
27375		%data = ($data_name => \@rows,);
27376	}
27377	eval $end if $b_log;
27378	return %data;
27379}
27380}
27381
27382#######################################################################
27383#### LAUNCH
27384########################################################################
27385
27386main(); ## From the End comes the Beginning
27387
27388## note: this EOF is needed for self updater, triggers the full download ok
27389###**EOF**###
27390