1<?php
2
3/*
4	Phoronix Test Suite
5	URLs: http://www.phoronix.com, http://www.phoronix-test-suite.com/
6	Copyright (C) 2009 - 2019, Phoronix Media
7	Copyright (C) 2009 - 2019, Michael Larabel
8	phodevi.php: The object for interacting with the PTS device framework
9
10	This program is free software; you can redistribute it and/or modify
11	it under the terms of the GNU General Public License as published by
12	the Free Software Foundation; either version 3 of the License, or
13	(at your option) any later version.
14
15	This program is distributed in the hope that it will be useful,
16	but WITHOUT ANY WARRANTY; without even the implied warranty of
17	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18	GNU General Public License for more details.
19
20	You should have received a copy of the GNU General Public License
21	along with this program. If not, see <http://www.gnu.org/licenses/>.
22*/
23
24class phodevi extends phodevi_base
25{
26	public static $vfs = false;
27	private static $device_cache = array();
28	private static $smart_cache = array();
29	private static $sensors = null;
30
31	private static $operating_system = null;
32	private static $graphics_detected = false;
33	private static $graphics = array(
34		'mesa' => false,
35		'ati' => false,
36		'nvidia' => false
37		);
38	private static $operating_systems = array(
39		'linux' => false,
40		'macosx' => false,
41		'solaris' => false,
42		'bsd' => false,
43		'hurd' => false,
44		'minix' => false,
45		'windows' => false
46		);
47
48	// A variable that modules can use to override Phodevi caching support, etc
49	public static $allow_phodevi_caching = true;
50
51	const no_caching = 1;
52	const std_caching = 2;
53	const smart_caching = 3;
54
55	public static function read_name($device)
56	{
57		return phodevi::read_property($device, 'identifier');
58	}
59	public static function load_sensors()
60	{
61		if(!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
62		{
63			// Phodevi sensors don't work prior to PHP 5.3
64			self::$sensors = array();
65			return false;
66		}
67
68		foreach(glob(dirname(__FILE__) . '/sensors/*') as $sensor_obj_file)
69		{
70			$sensor_obj_name = basename($sensor_obj_file, '.php');
71
72			if(!class_exists($sensor_obj_name, false))
73			{
74				include($sensor_obj_file);
75			}
76
77			$type = call_user_func(array($sensor_obj_name, 'get_type'));
78			$sensor = call_user_func(array($sensor_obj_name, 'get_sensor'));
79
80			if($type != null && $sensor != null)
81			{
82				self::$sensors[$type][$sensor] = $sensor_obj_name;
83			}
84		}
85	}
86	public static function available_sensors($limit_sensors = false)
87	{
88		static $available_sensors = null;
89
90		if($limit_sensors != false)
91		{
92			return self::select_sensors($limit_sensors);
93		}
94		else if($available_sensors == null)
95		{
96			$available_sensors = array();
97
98			foreach(self::$sensors as $sensor_type => &$sensor)
99			{
100				foreach(array_keys($sensor) as $sensor_senses)
101				{
102					array_push($available_sensors, array($sensor_type, $sensor_senses, self::$sensors[$sensor_type][$sensor_senses]));
103				}
104			}
105		}
106
107		return $available_sensors;
108	}
109	public static function select_sensors($limit_sensors = false)
110	{
111		if(!defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50300)
112		{
113			// Phodevi sensors don't work prior to PHP 5.3
114			return array();
115		}
116
117		$selected = array();
118		foreach(self::available_sensors() as $sensor)
119		{
120			if($limit_sensors == false || (is_array($limit_sensors) && in_array($sensor[2], $limit_sensors)))
121			{
122				array_push($selected, $sensor);
123			}
124		}
125
126		return $selected;
127	}
128	public static function is_sensor_supported($sensor)
129	{
130		$supported = false;
131		$sensors = self::supported_sensors();
132		foreach($sensors as $s)
133		{
134			if($s[0] == $sensor[0] && $s[1] == $sensor[1])
135			{
136				$supported = true;
137				break;
138			}
139		}
140
141		return $supported;
142	}
143	public static function supported_sensors($limit_sensors = false)
144	{
145		static $supported_sensors = null;
146
147		if($limit_sensors != false)
148		{
149			return self::select_sensors($limit_sensors);
150		}
151		else if($supported_sensors == null)
152		{
153			$supported_sensors = array();
154			foreach(self::available_sensors($limit_sensors) as $sensor)
155			{
156				if(self::sensor_supported($sensor))
157				{
158					array_push($supported_sensors, $sensor);
159				}
160			}
161		}
162
163		return $supported_sensors;
164	}
165	public static function unsupported_sensors()
166	{
167		static $unsupported_sensors = null;
168
169		if($unsupported_sensors == null)
170		{
171			$unsupported_sensors = array();
172			$supported_sensors = self::supported_sensors();
173
174			foreach(self::available_sensors() as $sensor)
175			{
176				if(!in_array($sensor, $supported_sensors))
177				{
178					array_push($unsupported_sensors, $sensor);
179				}
180			}
181		}
182
183		return $unsupported_sensors;
184	}
185	public static function read_sensor($sensor)
186	{
187		if($sensor instanceof phodevi_sensor)
188		{
189			$sensor_object = $sensor;
190		}
191		else
192		{
193			$sensor_object = new self::$sensors[$sensor[0]][$sensor[1]](null, null);
194		}
195
196		return $sensor_object->read_sensor();
197	}
198	public static function read_sensor_object_unit(&$sensor_object)
199	{
200		$sensor = array($sensor_object->get_type(), $sensor_object->get_sensor(), get_class($sensor_object));
201		return self::read_sensor_unit($sensor);
202	}
203	public static function read_sensor_object_unit_short(&$sensor_object)
204	{
205		$sensor_unit = self::read_sensor_object_unit($sensor_object);
206
207		switch($sensor_unit)
208		{
209			case 'Celsius':
210				$sensor_unit = 'C';
211				break;
212			case 'Percent':
213				$sensor_unit = '%';
214				break;
215			case 'Megabytes':
216				$sensor_unit = 'MB';
217				break;
218			case 'Megahertz':
219				$sensor_unit = 'MHz';
220				break;
221			case 'Volts':
222				$sensor_unit = 'V';
223				break;
224			case 'Kilobytes/seconds':
225				$sensor_unit = 'KBps';
226				break;
227			case 'Millivolts':
228				$sensor_unit = 'mV';
229				break;
230		}
231
232		return $sensor_unit;
233	}
234	public static function read_sensor_unit($sensor)
235	{
236		return call_user_func(array(self::$sensors[$sensor[0]][$sensor[1]], 'get_unit'));
237	}
238	public static function sensor_supported($sensor)
239	{
240		$sensor_object = new self::$sensors[$sensor[0]][$sensor[1]](null, null);
241
242		return isset(self::$sensors[$sensor[0]][$sensor[1]]) && $sensor_object->support_check();
243	}
244	public static function sensor_object_identifier(&$sensor_object)
245	{
246		$sensor = array($sensor_object->get_type(), $sensor_object->get_sensor(), get_class($sensor_object));
247		return self::sensor_identifier($sensor) . ($sensor_object->get_instance() != 0 ? '.' . $sensor_object->get_instance() : null);
248	}
249	public static function sensor_identifier($sensor)
250	{
251		return $sensor[0] . '.' . $sensor[1];
252	}
253	public static function sensor_object_name(&$sensor_object)
254	{
255		$sensor = array($sensor_object->get_type(), $sensor_object->get_sensor(), get_class($sensor_object));
256		$name = self::sensor_name($sensor);
257		$params = $sensor_object->get_readable_device_name();
258
259		if($params !== NULL)
260		{
261			$name .= ' (' . $params . ')';
262		}
263
264		return $name;
265	}
266	public static function sensor_name($sensor)
267	{
268		$type = call_user_func(array(self::$sensors[$sensor[0]][$sensor[1]], 'get_type'));
269		$sensor = call_user_func(array(self::$sensors[$sensor[0]][$sensor[1]], 'get_sensor'));
270
271		if(strlen($type) < 4)
272		{
273			$formatted = strtoupper($type);
274		}
275		else
276		{
277			$formatted = ucwords($type);
278		}
279
280		switch($formatted)
281		{
282			case 'SYS':
283				$formatted = 'System';
284				break;
285			case 'HDD':
286				$formatted = 'Drive';
287				break;
288		}
289
290		$formatted .= ' ';
291
292		switch($sensor)
293		{
294			case 'temp':
295				$formatted .= 'Temperature';
296				break;
297			case 'freq':
298				$formatted .= 'Frequency';
299				break;
300			case 'memory':
301				$formatted .= 'Memory Usage';
302				break;
303			case 'power':
304				$formatted .= 'Power Consumption';
305				break;
306			default:
307				$formatted .= ucwords(str_replace('-', ' ', $sensor));
308				break;
309		}
310
311		return $formatted;
312	}
313	public static function system_hardware($return_as_string = true)
314	{
315		return self::system_information_parse(self::available_hardware_devices(), $return_as_string);
316	}
317	public static function system_software($return_as_string = true)
318	{
319		return self::system_information_parse(self::available_software_components(), $return_as_string);
320	}
321	public static function system_centralized_view($return_as_string = true)
322	{
323		$core_count = phodevi::read_property('cpu', 'physical-core-count');
324		$thread_count = phodevi::read_property('cpu', 'thread-count');
325
326		$sys = array(
327			'Processor' => phodevi::read_property('cpu', 'model-and-speed'),
328				array(
329				'Core Count' => $core_count,
330				'Thread Count' => !empty($core_count) && $core_count == $thread_count ? '' : $thread_count, // don't show thread count if it's same as core count
331				'Extensions' => phodevi_cpu::instruction_set_extensions(),
332			//	'Virtualization' => (phodevi_cpu::virtualization_technology() ? phodevi_cpu::virtualization_technology() : ''),
333				'Cache Size' => phodevi::read_property('cpu', 'cache-size-string'),
334				'Microcode'=> phodevi::read_property('cpu', 'microcode-version'),
335				'Core Family' => phodevi::read_property('cpu', 'core-family-name'),
336				'Scaling Driver'=> phodevi::read_property('cpu', 'scaling-governor'),
337				),
338			'Graphics' => phodevi::read_name('gpu'),
339				array(
340				'Frequency' => phodevi::read_property('gpu', 'frequency'),
341				'BAR1 / Visible vRAM' => phodevi::read_property('gpu', 'bar1-visible-vram'),
342				'OpenGL' => phodevi::read_property('system', 'opengl-driver'),
343				'Vulkan' => phodevi::read_property('system', 'vulkan-driver'),
344				'OpenCL' => phodevi::read_property('system', 'opencl-driver'),
345				'Display Driver' => phodevi::read_property('system', 'display-driver-string'),
346				'Monitor' => phodevi::read_name('monitor'),
347				'Screen' => phodevi::read_property('gpu', 'screen-resolution-string'),
348				),
349			'Motherboard' => phodevi::read_name('motherboard'),
350				array(
351				'BIOS Version' => phodevi::read_property('motherboard', 'bios-version'),
352				'Chipset' => phodevi::read_name('chipset'),
353				'Audio' => phodevi::read_name('audio'),
354				'Network' => phodevi::read_name('network'),
355				'Platform Profile'=> phodevi::read_property('system', 'platform-profile'),
356				),
357			'Memory' => phodevi::read_name('memory'),
358				array(),
359			'Disk' => phodevi::read_name('disk'),
360				array(
361				'File-System' => phodevi::read_property('system', 'filesystem'),
362				'Mount Options' => phodevi::read_property('disk', 'mount-options-string'),
363				//'Block Size' => phodevi::read_property('disk', 'block-size'),
364				'Disk Scheduler' => phodevi::read_property('disk', 'scheduler'),
365				'Disk Details' => phodevi::read_property('disk', 'extra-disk-details'),
366				),
367			'Operating System' => phodevi::read_property('system', 'operating-system'),
368				array(
369				'Kernel' => phodevi::read_property('system', 'kernel-string'),
370				'Desktop' => phodevi::read_property('system', 'desktop-environment'),
371				'Display Server' => phodevi::read_property('system', 'display-server'),
372				'Compiler' => phodevi::read_property('system', 'compiler'),
373				'System Layer' => phodevi::read_property('system', 'system-layer'),
374				'Security' => phodevi::read_property('system', 'security-features'),
375				)
376			);
377
378		if($return_as_string)
379		{
380			$sys_string = null;
381			$tabled = array();
382			foreach($sys as $key => &$in)
383			{
384				$space_in = 2;
385				if(is_array($in))
386				{
387					$tabled = array();
388					foreach($in as $key => $value)
389					{
390						if(!empty($value))
391						{
392							if(isset($value[64]) && strpos($value, ' + ') !== false)
393							{
394								$values = explode(' + ', $value);
395								$tabled[] = array(pts_client::cli_just_bold($key) . ':' . str_repeat(' ', (20 - strlen($key))), array_shift($values));
396								foreach($values as $value)
397								{
398									$tabled[] = array(pts_client::cli_just_bold(' '), '+ ' . $value);
399								}
400							}
401							else
402							{
403								$tabled[] = array(pts_client::cli_just_bold($key) . ':' . str_repeat(' ', (20 - strlen($key))), $value);
404								//$sys_string .= '      ' . strtoupper($key) . ':' . $value . PHP_EOL;
405							}
406						}
407					}
408				}
409				else
410				{
411					if(($x = strpos($in, ' (')))
412					{
413						$in = substr($in, 0, $x);
414					}
415
416					if(!empty($tabled))
417					{
418						$sys_string .= pts_user_io::display_text_table($tabled, '    ', 0, 17) . PHP_EOL;
419					}
420
421					if(isset($in[80]) && strpos($in, ' + ') !== false)
422					{
423						$values = explode(' + ', $in);
424						$sys_string .= PHP_EOL . '  ' . pts_client::cli_colored_text(strtoupper($key), 'gray', true) . ': ' . str_repeat(' ', (22 - strlen($key))) . pts_client::cli_colored_text(array_shift($values), 'green', true);
425						foreach($values as $value)
426						{
427							$sys_string .= PHP_EOL . str_repeat(' ', 22) . pts_client::cli_colored_text('+ ' . $value, 'green', true);
428						}
429						$sys_string .= PHP_EOL;
430					}
431					else
432					{
433						$sys_string .= PHP_EOL . '  ' . pts_client::cli_colored_text(strtoupper($key), 'gray', true) . ': ' . str_repeat(' ', (22 - strlen($key))) . pts_client::cli_colored_text($in, 'green', true) . PHP_EOL;
434					}
435				}
436
437			}
438			if(!empty($tabled))
439			{
440				$sys_string .= pts_user_io::display_text_table($tabled, '    ', 0, 17) . PHP_EOL;
441			}
442			return $sys_string;
443		}
444
445		return $sys;
446	}
447	public static function system_id_string()
448	{
449		$extra = null;
450		foreach(array('CC', 'CXX', 'CFLAGS', 'CPPFLAGS', 'CXXFLAGS', 'USE_WINE') as $env)
451		{
452			$val = getenv($env);
453
454			if(!empty($val))
455			{
456				$extra .= $env . '=' . $val . ';';
457			}
458		}
459
460		$components = array(phodevi::read_property('cpu', 'model'), phodevi::read_property('system', 'operating-system'), phodevi::read_property('system', 'compiler'), $extra);
461		return base64_encode(implode('__', $components));
462	}
463	public static function read_property($device, $read_property, $strip_string = true)
464	{
465		static $properties_table = array();
466		$value = false;
467
468		if(!isset($properties_table[$device]))
469		{
470			$properties_table[$device] = call_user_func(array('phodevi_' . $device, 'properties'));
471		}
472
473		if(!isset($properties_table[$device][$read_property]))
474		{
475			echo 'NOTICE: ' . $read_property . ' does not exist for ' . $device . '.' . PHP_EOL;
476		}
477
478		if(!($properties_table[$device][$read_property] instanceof phodevi_device_property))
479		{
480			return $properties_table[$device][$read_property];
481		}
482
483		$cache_code = $properties_table[$device][$read_property]->cache_code();
484		if($cache_code != phodevi::no_caching && phodevi::$allow_phodevi_caching && isset(self::$device_cache[$device][$read_property]))
485		{
486			$value = self::$device_cache[$device][$read_property];
487		}
488		else
489		{
490			$dev_function_r = pts_arrays::to_array($properties_table[$device][$read_property]->get_device_function());
491			$dev_function = $dev_function_r[0];
492			$function_pass = array();
493
494			for($i = 1; $i < count($dev_function_r); $i++)
495			{
496				array_push($function_pass, $dev_function_r[$i]);
497			}
498			if(method_exists('phodevi_' . $device, $dev_function))
499			{
500				$value = call_user_func_array(array('phodevi_' . $device, $dev_function), $function_pass);
501				if(!is_array($value) && $value != null)
502				{
503					if($strip_string)
504					{
505						$value = pts_strings::strip_string($value);
506					}
507					if(function_exists('preg_replace'))
508					{
509						$value = preg_replace('/[^(\x20-\x7F)]*/','', $value);
510					}
511				}
512
513				if($cache_code != phodevi::no_caching)
514				{
515					self::$device_cache[$device][$read_property] = $value;
516
517					if($cache_code == phodevi::smart_caching)
518					{
519						// TODO: For now just copy the smart cache to other var, but come up with better yet efficient way
520						self::$smart_cache[$device][$read_property] = $value;
521					}
522				}
523			}
524		}
525
526		return $value;
527	}
528	public static function read_all_properties()
529	{
530		$all_properties = array();
531		$components = array();
532		foreach(glob(__DIR__ . '/components/phodevi_*.php') as $file)
533		{
534			$components[] = substr(basename($file, '.php'), 8);
535		}
536
537		foreach($components as $device)
538		{
539			$properties = call_user_func(array('phodevi_' . $device, 'properties'));
540			$all_properties[$device] = array();
541			foreach($properties as $id => $property)
542			{
543				$dev_function_r = pts_arrays::to_array($property->get_device_function());
544				$dev_function = $dev_function_r[0];
545				$function_pass = array();
546
547				for($i = 1; $i < count($dev_function_r); $i++)
548				{
549					array_push($function_pass, $dev_function_r[$i]);
550				}
551				if(method_exists('phodevi_' . $device, $dev_function))
552				{
553					$value = call_user_func_array(array('phodevi_' . $device, $dev_function), $function_pass);
554					if(!is_array($value) && $value != null)
555					{
556						$value = pts_strings::strip_string($value);
557						if(function_exists('preg_replace'))
558						{
559							$value = preg_replace('/[^(\x20-\x7F)]*/','', $value);
560						}
561					}
562					$all_properties[$device][$id] = $value;
563				}
564			}
565		}
566
567		return $all_properties;
568	}
569	public static function set_property($device, $set_property, $pass_args = array())
570	{
571		$return_value = false;
572
573		if(method_exists('phodevi_' . $device, 'set_property'))
574		{
575			$return_value = call_user_func(array('phodevi_' . $device, 'set_property'), $set_property, $pass_args);
576		}
577
578		return $return_value;
579	}
580	public static function create_vfs()
581	{
582		self::$vfs = new phodevi_vfs();
583	}
584	public static function initial_setup()
585	{
586		// Operating System Detection
587		$supported_operating_systems = pts_types::operating_systems();
588		$uname_s = strtolower(php_uname('s'));
589
590		foreach($supported_operating_systems as $os_check)
591		{
592			for($i = 0; $i < count($os_check); $i++)
593			{
594				if(strpos($uname_s, strtolower($os_check[$i])) !== false) // Check for OS
595				{
596					self::$operating_system = $os_check[0];
597					self::$operating_systems[strtolower($os_check[0])] = true;
598					break;
599				}
600			}
601
602			if(self::$operating_system != null)
603			{
604				break;
605			}
606		}
607
608		if(self::operating_system() == false)
609		{
610			self::$operating_system = 'Unknown';
611		}
612
613		self::load_sensors();
614	}
615	private static function detect_graphics()
616	{
617		if(self::$graphics_detected == true)
618		{
619			return;
620		}
621
622		// OpenGL / graphics detection
623		$graphics_detection = array('NVIDIA', array('Mesa', 'SGI'), array('AMD'));
624		$opengl_driver = phodevi::read_property('system', 'opengl-vendor') . ' ' . phodevi::read_property('system', 'opengl-driver') . ' ' . phodevi::read_property('system', 'dri-display-driver');
625		$opengl_driver = trim(str_replace('Corporation', '', $opengl_driver)); // Prevents a possible false positive for ATI being in CorporATIon
626
627		foreach($graphics_detection as $gpu_check)
628		{
629			if(!is_array($gpu_check))
630			{
631				$gpu_check = array($gpu_check);
632			}
633
634			for($i = 0; $i < count($gpu_check); $i++)
635			{
636				if(stripos($opengl_driver, $gpu_check[$i]) !== false) // Check for GPU
637				{
638					self::$graphics[(strtolower($gpu_check[0]))] = true;
639					break;
640				}
641			}
642		}
643
644		self::$graphics_detected = true;
645	}
646	public static function set_device_cache($cache_array)
647	{
648		if(is_array($cache_array) && !empty($cache_array))
649		{
650			self::$smart_cache = array_merge(self::$smart_cache, $cache_array);
651			self::$device_cache = array_merge(self::$device_cache, $cache_array);
652		}
653	}
654	public static function clear_cache()
655	{
656		self::$smart_cache = array();
657		self::$device_cache = array();
658	}
659	public static function get_phodevi_cache_object($store_dir, $client_version = 0)
660	{
661		return new phodevi_cache(self::$smart_cache, $store_dir, $client_version);
662	}
663	protected static function system_information_parse($component_array, $return_as_string = true)
664	{
665		// Returns string of hardware information
666		$info = array();
667
668		foreach($component_array as $string => $id)
669		{
670			if(is_array($id) && count($id) == 2)
671			{
672				$value = self::read_property($id[0], $id[1]);
673			}
674			else
675			{
676				$value = self::read_name($id);
677			}
678
679			if($value != -1 && !empty($value))
680			{
681				$info[$string] = $value;
682			}
683		}
684
685		if($return_as_string)
686		{
687			$info_array = $info;
688			$info = null;
689
690			foreach($info_array as $type => $value)
691			{
692				if($info != null)
693				{
694					$info .= ', ';
695				}
696
697				$info .= $type . ': ' . $value;
698			}
699		}
700
701		return $info;
702	}
703	public static function system_uptime()
704	{
705		// Returns the system's uptime in seconds
706		$uptime = 1;
707
708		if(phodevi::is_windows())
709		{
710			$uptime = trim(shell_exec('powershell "((get-date) - (gcim Win32_OperatingSystem).LastBootUpTime).TotalSeconds"'));
711			$uptime = is_numeric($uptime) && $uptime > 1 ? round($uptime) : 1;
712		}
713		else if(is_file('/proc/uptime'))
714		{
715			$uptime = pts_strings::first_in_string(pts_file_io::file_get_contents('/proc/uptime'));
716		}
717		else if(($uptime_cmd = pts_client::executable_in_path('uptime')) != false)
718		{
719			$uptime_counter = 0;
720			$uptime_output = shell_exec($uptime_cmd . ' 2>&1');
721			$uptime_output = substr($uptime_output, strpos($uptime_output, ' up') + 3);
722			$uptime_output = substr($uptime_output, 0, strpos($uptime_output, ' user'));
723			$uptime_output = substr($uptime_output, 0, strrpos($uptime_output, ',')) . ' ';
724
725			if(($day_end_pos = strpos($uptime_output, ' day')) !== false)
726			{
727				$day_output = substr($uptime_output, 0, $day_end_pos);
728				$day_output = substr($day_output, strrpos($day_output, ' ') + 1);
729
730				if(is_numeric($day_output))
731				{
732					$uptime_counter += $day_output * 86400;
733				}
734			}
735
736			if(($mins_end_pos = strpos($uptime_output, ' mins')) !== false)
737			{
738				$mins_output = substr($uptime_output, 0, $mins_end_pos);
739				$mins_output = substr($mins_output, strrpos($mins_output, ' ') + 1);
740
741				if(is_numeric($mins_output))
742				{
743					$uptime_counter += $mins_output * 60;
744				}
745			}
746
747			if(($time_split_pos = strpos($uptime_output, ':')) !== false)
748			{
749				$hours_output = substr($uptime_output, 0, $time_split_pos);
750				$hours_output = substr($hours_output, strrpos($hours_output, ' ') + 1);
751				$mins_output = substr($uptime_output, $time_split_pos + 1);
752				$mins_output = substr($mins_output, 0, strpos($mins_output, ' '));
753
754				if(is_numeric($hours_output))
755				{
756					$uptime_counter += $hours_output * 3600;
757				}
758				if(is_numeric($mins_output))
759				{
760					$uptime_counter += $mins_output * 60;
761				}
762			}
763
764			if(is_numeric($uptime_counter) && $uptime_counter > 0)
765			{
766				$uptime = $uptime_counter;
767			}
768		}
769
770		return intval($uptime);
771	}
772	public static function reboot()
773	{
774		$reboot_cmd = '';
775
776		if(phodevi::is_windows())
777		{
778			$reboot_cmd = 'shutdown /r';
779		}
780		else if(pts_client::executable_in_path('systemctl'))
781		{
782			$reboot_cmd = 'systemctl reboot';
783		}
784		else if(pts_client::executable_in_path('reboot'))
785		{
786			$reboot_cmd = 'reboot';
787		}
788		else if(pts_client::executable_in_path('shutdown'))
789		{
790			// macOS
791			$reboot_cmd = 'shutdown -r now';
792		}
793
794		if($reboot_cmd)
795		{
796			shell_exec($reboot_cmd);
797			// Buffer in case reboot isn't immediate
798			echo PHP_EOL . PHP_EOL . 'Waiting for reboot...' . PHP_EOL;
799			sleep(600);
800		}
801	}
802	public static function shutdown()
803	{
804		// some systems like systemctl poweroff, others just like poweroff, but not consistent one method for all systems in testing
805		if(pts_client::executable_in_path('systemctl') && rand(0, 1) == 1)
806		{
807			// Try systemd's poweroff method first
808			shell_exec('systemctl poweroff');
809			sleep(5);
810		}
811		if(pts_client::executable_in_path('poweroff'))
812		{
813			shell_exec('poweroff');
814			sleep(5);
815		}
816		if(phodevi::is_windows())
817		{
818			shell_exec('shutdown /s');
819			sleep(5);
820		}
821	}
822	public static function cpu_arch_compatible($check_against)
823	{
824		$compatible = true;
825		$this_arch = phodevi::read_property('system', 'kernel-architecture');
826		$check_against = pts_arrays::to_array($check_against);
827
828		if(isset($this_arch[2]) && substr($this_arch, -2) == '86')
829		{
830			$this_arch = 'x86';
831		}
832		if(!in_array($this_arch, $check_against))
833		{
834			$compatible = false;
835		}
836		if(phodevi::is_macos())
837		{
838			$compatible = true;
839		}
840
841		return $compatible;
842	}
843	public static function is_vendor_string($vendor)
844	{
845		return isset($vendor[2]) && pts_strings::string_only_contains($vendor, (pts_strings::CHAR_LETTER | pts_strings::CHAR_NUMERIC | pts_strings::CHAR_DECIMAL | pts_strings::CHAR_SPACE | pts_strings::CHAR_DASH)) && !pts_strings::has_in_istring($vendor, array('manufacturer', 'vendor', 'unknown', 'generic', 'warning')) && (!isset($vendor[7]) || strpos($vendor, ' ') !== false || pts_strings::times_occurred($vendor, pts_strings::CHAR_NUMERIC) == 0) && pts_strings::string_contains($vendor, pts_strings::CHAR_LETTER) && (isset($vendor[4]) || pts_strings::times_occurred($vendor, pts_strings::CHAR_LETTER) > 1) && substr($vendor, -1) != '-';
846	}
847	public static function is_product_string($product)
848	{
849		return phodevi::is_vendor_string($product) && !pts_strings::has_in_istring($product, array('VBOX', 'QEMU', 'Virtual', 'Family', '440BX', 'VMware', ' Gen', 'Core IGP'));
850	}
851	public static function opencl_support_detected()
852	{
853		$supported = true;
854
855		if(($clinfo = pts_client::executable_in_path('clinfo')))
856		{
857			$clinfo = shell_exec($clinfo);
858			if(strpos($clinfo, 'Number of platforms                               0') !== false)
859			{
860				$supported = false;
861			}
862		}
863
864		return $supported;
865	}
866	public static function vulkan_support_detected()
867	{
868		static $supported = -1;
869
870		if($supported !== -1)
871		{
872			return $supported;
873		}
874		$supported = true;
875
876		if(($vulkaninfo = pts_client::executable_in_path('vulkaninfo 2>&1')))
877		{
878			$vulkaninfo = shell_exec($vulkaninfo);
879			if(strpos($vulkaninfo, 'Cannot create Vulkan instance') !== false)
880			{
881				$supported = false;
882			}
883			else if(strpos($vulkaninfo, 'failed with ERROR_INITIALIZATION_FAILED') !== false)
884			{
885				$supported = false;
886			}
887		}
888
889		return $supported;
890	}
891	public static function is_fake_device($str)
892	{
893		$string_check = array(
894			'Logical Volume',
895			'QEMU',
896			' KVM',
897			'KVM ',
898			'Eng Sample',
899			'Unknown',
900			' virt',
901			'virtual',
902			'svga',
903			' VM',
904			'Storage ',
905			'Aspeed',
906			'440BX',
907			'Cirrus',
908			);
909
910		foreach($string_check as $check)
911		{
912			if(stripos($str, $check) !== false)
913			{
914				return true;
915			}
916		}
917
918		$without_multiplier = $str;
919		if(($x = strpos($without_multiplier, ' x ')) !== false)
920		{
921			$without_multiplier = substr($without_multiplier, $x + 3);
922		}
923		if(!preg_match('~[0-9]+~', $without_multiplier))
924		{
925			// No numbers at all in string so probably not a real device (e.g. some Virtual "Intel" CPU or similar components generic
926			return true;
927		}
928
929		return false;
930	}
931	public static function os_under_test($force_override = false, $force_value = null)
932	{
933		static $os_under_test = null;
934		if($force_override && !empty($force_value))
935		{
936			$os_under_test = $force_value;
937		}
938		else if($os_under_test == null)
939		{
940			$os_under_test = self::operating_system();
941		}
942
943		// The operating system under test
944		return $os_under_test;
945	}
946	public static function operating_system()
947	{
948		return self::$operating_system;
949	}
950	public static function is_linux()
951	{
952		return self::$operating_systems['linux'];
953	}
954	public static function is_minix()
955	{
956		return self::$operating_systems['minix'];
957	}
958	public static function is_solaris()
959	{
960		return self::$operating_systems['solaris'];
961	}
962	public static function is_bsd()
963	{
964		return self::$operating_systems['bsd'];
965	}
966	public static function is_macos()
967	{
968		return self::$operating_systems['macosx'];
969	}
970	public static function is_hurd()
971	{
972		return self::$operating_systems['hurd'];
973	}
974	public static function is_windows()
975	{
976		return self::$operating_systems['windows'];
977	}
978	public static function is_mesa_graphics()
979	{
980		self::detect_graphics();
981		return self::$graphics['mesa'];
982	}
983	public static function is_nvidia_graphics()
984	{
985		self::detect_graphics();
986		return self::$graphics['nvidia'];
987	}
988	public static function is_root()
989	{
990		return phodevi::read_property('system', 'username') == 'root' || is_writable('/root');
991	}
992	public static function is_display_server_active()
993	{
994		if(phodevi::read_property('system', 'system-layer') == 'Docker')
995		{
996			// hopefully temp workaround XXX TODO
997			return false;
998		}
999
1000		return pts_client::read_env('DISPLAY') != false || pts_client::read_env('WAYLAND_DISPLAY') != false || phodevi::is_windows() || phodevi::is_macos();
1001	}
1002}
1003
1004phodevi::create_vfs();
1005
1006if(PTS_IS_CLIENT)
1007{
1008	phodevi::initial_setup();
1009}
1010
1011?>
1012