PageRenderTime 60ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/class.OS_Linux.php

https://bitbucket.org/rmarefaty/linfo
PHP | 1430 lines | 760 code | 262 blank | 408 comment | 157 complexity | c860100fa2ff31ff3d3d07990f834e1f MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. * This file is part of Linfo (c) 2010 Joseph Gillotti.
  4. *
  5. * Linfo is free software: you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation, either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * Linfo is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU General Public License
  16. * along with Linfo. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. /**
  19. * Keep out hackers...
  20. */
  21. defined('IN_INFO') or exit;
  22. /**
  23. * Get info on a usual linux system
  24. * Works by exclusively looking around /proc and /sys
  25. * Totally ignores CallExt class, very deliberately
  26. * Also deliberately ignores trying to find out the distro.
  27. */
  28. class OS_Linux {
  29. // Keep these tucked away
  30. protected
  31. $settings, $error;
  32. /**
  33. * Constructor. Localizes settings
  34. *
  35. * @param array $settings of linfo settings
  36. * @access public
  37. */
  38. public function __construct($settings) {
  39. // Localize settings
  40. $this->settings = $settings;
  41. // Localize error handler
  42. $this->error = LinfoError::Fledging();
  43. // Make sure we have what we need
  44. if (!is_dir('/sys') || !is_dir('/proc'))
  45. throw new GetInfoException('This needs access to /proc and /sys to work.');
  46. }
  47. /**
  48. * getAll
  49. *
  50. * @access public
  51. * @return array the info
  52. */
  53. public function getAll() {
  54. // Return everything, whilst obeying display permissions
  55. return array(
  56. 'OS' => empty($this->settings['show']['os']) ? '' : $this->getOS(),
  57. 'Kernel' => empty($this->settings['show']['kernel']) ? '' : $this->getKernel(),
  58. 'Distro' => empty($this->settings['show']['distro']) ? '' : $this->getDistro(),
  59. 'RAM' => empty($this->settings['show']['ram']) ? array() : $this->getRam(),
  60. 'HD' => empty($this->settings['show']['hd']) ? '' : $this->getHD(),
  61. 'Mounts' => empty($this->settings['show']['mounts']) ? array() : $this->getMounts(),
  62. 'Load' => empty($this->settings['show']['load']) ? array() : $this->getLoad(),
  63. 'HostName' => empty($this->settings['show']['hostname']) ? '' : $this->getHostName(),
  64. 'UpTime' => empty($this->settings['show']['uptime']) ? '' : $this->getUpTime(),
  65. 'CPU' => empty($this->settings['show']['cpu']) ? array() : $this->getCPU(),
  66. 'CPUArchitecture' => empty($this->settings['show']['cpu']) ? array() : $this->getCPUArchitecture(),
  67. 'Network Devices' => empty($this->settings['show']['network']) ? array() : $this->getNet(),
  68. 'Devices' => empty($this->settings['show']['devices']) ? array() : $this->getDevs(),
  69. 'Temps' => empty($this->settings['show']['temps']) ? array(): $this->getTemps(),
  70. 'Battery' => empty($this->settings['show']['battery']) ? array(): $this->getBattery(),
  71. 'Raid' => empty($this->settings['show']['raid']) ? array(): $this->getRAID(),
  72. 'Wifi' => empty($this->settings['show']['wifi']) ? array(): $this->getWifi(),
  73. 'SoundCards' => empty($this->settings['show']['sound']) ? array(): $this->getSoundCards(),
  74. 'processStats' => empty($this->settings['show']['process_stats']) ? array() : $this->getProcessStats(),
  75. 'services' => empty($this->settings['show']['process_stats']) ? array() : $this->getServices(),
  76. 'numLoggedIn' => empty($this->settings['show']['numLoggedIn']) ? array() : $this->getNumLoggedIn()
  77. );
  78. }
  79. /**
  80. * getOS
  81. *
  82. * @access private
  83. * @return string Linux
  84. */
  85. private function getOS() {
  86. // Linux, obviously
  87. return 'Linux';
  88. }
  89. /**
  90. * getKernel
  91. *
  92. * @access private
  93. * @return string kernel version
  94. */
  95. private function getKernel() {
  96. // Time?
  97. if (!empty($this->settings['timer']))
  98. $t = new LinfoTimerStart('Kernel');
  99. // File containing info
  100. $file = '/proc/version';
  101. // Make sure we can use it
  102. if (!is_file($file) || !is_readable($file)) {
  103. $this->error->add('Linfo Core', '/proc/version not found');
  104. return 'Unknown';
  105. }
  106. // Get it
  107. $contents = getContents($file);
  108. // Parse it
  109. if (preg_match('/^Linux version (\S+).+$/', $contents, $match) != 1) {
  110. $this->error->add('Linfo Core', 'Error parsing /proc/version');
  111. return 'Unknown';
  112. }
  113. // Return it
  114. return $match[1];
  115. }
  116. /**
  117. * getHostName
  118. *
  119. * @access private
  120. * @return string the host name
  121. */
  122. private function getHostName() {
  123. // Time?
  124. if (!empty($this->settings['timer']))
  125. $t = new LinfoTimerStart('Hostname');
  126. // File containing info
  127. $file = '/proc/sys/kernel/hostname';
  128. // Get it
  129. $hostname = getContents($file, false);
  130. // Failed?
  131. if ($hostname === false) {
  132. $this->error->add('Linfo Core', 'Error getting /proc/sys/kernel/hostname');
  133. return 'Unknown';
  134. }
  135. else {
  136. // Didn't fail; return it
  137. return $hostname;
  138. }
  139. }
  140. /**
  141. * getRam
  142. *
  143. * @access private
  144. * @return array the memory information
  145. */
  146. private function getRam(){
  147. // Time?
  148. if (!empty($this->settings['timer']))
  149. $t = new LinfoTimerStart('Memory');
  150. // We'll return the contents of this
  151. $return = array();
  152. // Files containing juicy info
  153. $procFileSwap = '/proc/swaps';
  154. $procFileMem = '/proc/meminfo';
  155. // First off, these need to exist..
  156. if (!is_readable($procFileSwap) || !is_readable($procFileMem)) {
  157. $this->error->add('Linfo Core', '/proc/swaps and/or /proc/meminfo are not readable');
  158. return array();
  159. }
  160. // To hold their values
  161. $memVals = array();
  162. $swapVals = array();
  163. // Get memContents
  164. @preg_match_all('/^([^:]+)\:\s+(\d+)\s*(?:k[bB])?\s*/m', getContents($procFileMem), $matches, PREG_SET_ORDER);
  165. // Deal with it
  166. foreach ((array)$matches as $memInfo)
  167. $memVals[$memInfo[1]] = $memInfo[2];
  168. // Get swapContents
  169. @preg_match_all('/^(\S+)\s+(\S+)\s+(\d+)\s(\d+)[^$]*$/m', getContents($procFileSwap), $matches, PREG_SET_ORDER);
  170. foreach ((array)$matches as $swapDevice) {
  171. // Append each swap device
  172. $swapVals[] = array (
  173. 'device' => $swapDevice[1],
  174. 'type' => $swapDevice[2],
  175. 'size' => $swapDevice[3]*1024,
  176. 'used' => $swapDevice[4]*1024
  177. );
  178. }
  179. // Get individual vals
  180. $return['type'] = 'Physical';
  181. $return['total'] = $memVals['MemTotal']*1024;
  182. $return['free'] = $memVals['MemFree']*1024 + $memVals['Cached']*1024+ $memVals['Buffers']*1024;
  183. $return['swapTotal'] = $memVals['SwapTotal']*1024;
  184. $return['swapFree'] = $memVals['SwapFree']*1024 + $memVals['SwapCached']*1024;
  185. $return['swapCached'] = $memVals['SwapCached']*1024;
  186. $return['swapInfo'] = $swapVals;
  187. // Return it
  188. return $return;
  189. }
  190. /**
  191. * getCPU
  192. *
  193. * @access private
  194. * @return array of cpu info
  195. */
  196. private function getCPU() {
  197. // Time?
  198. if (!empty($this->settings['timer']))
  199. $t = new LinfoTimerStart('CPUs');
  200. // File that has it
  201. $file = '/proc/cpuinfo';
  202. // Not there?
  203. if (!is_file($file) || !is_readable($file)) {
  204. $this->error->add('Linfo Core', '/proc/cpuinfo not readable');
  205. return array();
  206. }
  207. /*
  208. * Get all info for all CPUs from the cpuinfo file
  209. */
  210. // Get contents
  211. $contents = trim(@file_get_contents($file));
  212. // Lines
  213. $lines = explode("\n", $contents);
  214. // Store CPUs here
  215. $cpus = array();
  216. // Holder for current CPU info
  217. $cur_cpu = array();
  218. // Go through lines in file
  219. $num_lines = count($lines);
  220. // We use the key of the first line to separate CPUs
  221. $first_line = substr($lines[0], 0, strpos($lines[0], ' '));
  222. for ($i = 0; $i < $num_lines; $i++) {
  223. // Approaching new CPU? Save current and start new info for this
  224. if (strpos($lines[$i], $first_line) === 0 && count($cur_cpu) > 0) {
  225. $cpus[] = $cur_cpu;
  226. $cur_cpu = array();
  227. // Default to unknown
  228. $cur_cpu['Model'] = 'Unknown';
  229. }
  230. // Info here
  231. $line = explode(':', $lines[$i], 2);
  232. if (!array_key_exists(1, $line))
  233. continue;
  234. $key = trim($line[0]);
  235. $value = trim($line[1]);
  236. // What we want are MHZ, Vendor, and Model.
  237. switch ($key) {
  238. // CPU model
  239. case 'model name':
  240. case 'cpu':
  241. case 'Processor':
  242. $cur_cpu['Model'] = $value;
  243. break;
  244. // Speed in MHz
  245. case 'cpu MHz':
  246. $cur_cpu['MHz'] = $value;
  247. break;
  248. case 'Cpu0ClkTck': // Old sun boxes
  249. $cur_cpu['MHz'] = hexdec($value) / 1000000;
  250. break;
  251. // Brand/vendor
  252. case 'vendor_id':
  253. $cur_cpu['Vendor'] = $value;
  254. break;
  255. }
  256. }
  257. // Save remaining one
  258. if (count($cur_cpu) > 0)
  259. $cpus[] = $cur_cpu;
  260. // Return them
  261. return $cpus;
  262. }
  263. // Famously interesting uptime
  264. private function getUpTime () {
  265. // Time?
  266. if (!empty($this->settings['timer']))
  267. $t = new LinfoTimerStart('Uptime');
  268. // Get contents
  269. $contents = getContents('/proc/uptime', false);
  270. // eh?
  271. if ($contents === false) {
  272. $this->error->add('Linfo Core', '/proc/uptime does not exist.');
  273. return 'Unknown';
  274. }
  275. // Seconds
  276. list($seconds) = explode(' ', $contents, 1);
  277. // Get it textual, as in days/minutes/hours/etc
  278. $uptime = seconds_convert(ceil($seconds));
  279. // Now find out when the system was booted
  280. $contents = getContents('/proc/stat', false);
  281. // Ugh
  282. if ($contents === false)
  283. return $uptime; // Settle for just uptime
  284. // Get date of boot
  285. if (preg_match('/^btime (\d+)$/m', $contents, $boot) != 1)
  286. return $uptime;
  287. // Okay?
  288. list(, $boot) = $boot;
  289. // Return
  290. return $uptime . '; booted '.date('m/d/y h:i A', $boot);
  291. }
  292. /**
  293. * getHD
  294. *
  295. * @access private
  296. * @return array the hard drive info
  297. */
  298. private function getHD() {
  299. // Time?
  300. if (!empty($this->settings['timer']))
  301. $t = new LinfoTimerStart('Drives');
  302. // Get partitions
  303. $partitions = array();
  304. $partitions_contents = getContents('/proc/partitions');
  305. if (@preg_match_all('/(\d+)\s+([a-z]{3})(\d+)$/m', $partitions_contents, $partitions_match, PREG_SET_ORDER) > 0) {
  306. // Go through each match
  307. $num_partitions = count($partitions_match);
  308. for ($i = 0; $i < $num_partitions; $i++) {
  309. $partition = $partitions_match[$i];
  310. $partitions[$partition[2]][] = array(
  311. 'size' => $partition[1] * 1024,
  312. 'number' => $partition[3]
  313. );
  314. }
  315. }
  316. // Store drives here
  317. $drives = array();
  318. // Get actual drives
  319. $drive_paths = (array) @glob('/sys/block/*/device/model', GLOB_NOSORT);
  320. $num_drives = count($drive_paths);
  321. for ($i = 0; $i < $num_drives; $i++) {
  322. // Path
  323. $path = $drive_paths[$i];
  324. // Dirname of the drive's sys entry
  325. $dirname = dirname(dirname($path));
  326. // Parts of the path
  327. $parts = explode('/', $path);
  328. // Attempt getting read/write stats
  329. if (preg_match('/^(\d+)\s+\d+\s+\d+\s+\d+\s+(\d+)\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+\s+\d+$/', getContents(dirname(dirname($path)).'/stat'), $statMatches) !== 1) {
  330. // Didn't get it
  331. $reads = false;
  332. $writes = false;
  333. }
  334. else
  335. // Got it, save it
  336. list(, $reads, $writes) = $statMatches;
  337. // Append this drive on
  338. $drives[] = array(
  339. 'name' => getContents($path, 'Unknown'),
  340. 'vendor' => getContents(dirname($path).'/vendor', 'Unknown'),
  341. 'device' => '/dev/'.$parts[3],
  342. 'reads' => $reads,
  343. 'writes' => $writes,
  344. 'size' => getContents(dirname(dirname($path)).'/size', 0) * 512,
  345. 'partitions' => array_key_exists($parts[3], $partitions) && is_array($partitions[$parts[3]]) ? $partitions[$parts[3]] : false
  346. );
  347. }
  348. // Return drives
  349. return $drives;
  350. }
  351. /**
  352. * getTemps
  353. *
  354. * @access private
  355. * @return array the temps
  356. */
  357. private function getTemps() {
  358. // Time?
  359. if (!empty($this->settings['timer']))
  360. $t = new LinfoTimerStart('Temperature');
  361. // Hold them here
  362. $return = array();
  363. // hddtemp?
  364. if (array_key_exists('hddtemp', (array)$this->settings['temps']) && !empty($this->settings['temps']['hddtemp'])) {
  365. try {
  366. // Initiate class
  367. $hddtemp = new GetHddTemp($this->settings);
  368. // Set mode, as in either daemon or syslog
  369. $hddtemp->setMode($this->settings['hddtemp']['mode']);
  370. // If we're daemon, save host and port
  371. if ($this->settings['hddtemp']['mode'] == 'daemon') {
  372. $hddtemp->setAddress(
  373. $this->settings['hddtemp']['address']['host'],
  374. $this->settings['hddtemp']['address']['port']);
  375. }
  376. // Result after working it
  377. $hddtemp_res = $hddtemp->work();
  378. // If it's an array, it worked
  379. if (is_array($hddtemp_res))
  380. // Save result
  381. $return = array_merge($return, $hddtemp_res);
  382. }
  383. // There was an issue
  384. catch (GetHddTempException $e) {
  385. $this->error->add('hddtemp parser', $e->getMessage());
  386. }
  387. }
  388. // mbmon?
  389. if (array_key_exists('mbmon', (array)$this->settings['temps']) && !empty($this->settings['temps']['mbmon'])) {
  390. try {
  391. // Initiate class
  392. $mbmon = new GetMbMon;
  393. // Set host and port
  394. $mbmon->setAddress(
  395. $this->settings['mbmon']['address']['host'],
  396. $this->settings['mbmon']['address']['port']);
  397. // Get result after working it
  398. $mbmon_res = $mbmon->work();
  399. // If it's an array, it worked
  400. if (is_array($mbmon_res))
  401. // Save result
  402. $return = array_merge($return, $mbmon_res);
  403. }
  404. catch (GetMbMonException $e) {
  405. $this->error->add('mbmon parser', $e->getMessage());
  406. }
  407. }
  408. // sensord? (part of lm-sensors)
  409. if (array_key_exists('sensord', (array)$this->settings['temps']) && !empty($this->settings['temps']['sensord'])) {
  410. try {
  411. // Iniatate class
  412. $sensord = new GetSensord;
  413. // Work it
  414. $sensord_res = $sensord->work();
  415. // If it's an array, it worked
  416. if (is_array($sensord_res))
  417. // Save result
  418. $return = array_merge($return, $sensord_res);
  419. }
  420. catch (GetSensordException $e) {
  421. $this->error->add('sensord parser', $e->getMessage());
  422. }
  423. }
  424. // hwmon? (probably the fastest of what's here)
  425. // too simple to be in its own class
  426. if (array_key_exists('hwmon', (array)$this->settings['temps']) && !empty($this->settings['temps']['hwmon'])) {
  427. // Store them here
  428. $hwmon_vals = array();
  429. // Wacky location
  430. $hwmon_paths = (array) @glob('/sys/class/hwmon/hwmon*/*_label', GLOB_NOSORT);
  431. $num_paths = count($hwmon_paths);
  432. for ($i = 0; $i < $num_paths; $i++) {
  433. // The path
  434. $path = $hwmon_paths[$i];
  435. // Get info here
  436. $section = rtrim($path, 'label');
  437. $filename = basename($path);
  438. $label = getContents($path);
  439. $value = getContents($section.'input');
  440. // Determine units and possibly fix values
  441. if (strpos($filename, 'fan') !== false)
  442. $unit = 'RPM';
  443. elseif (strpos($filename, 'temp') !== false) {
  444. $unit = 'C'; // Always seems to be in celsius
  445. $value = strlen($value) == 5 ? substr($value, 0, 2) : $value; // Pointless extra 0's
  446. }
  447. elseif (preg_match('/^in\d_label$/', $filename)) {
  448. $unit = 'v';
  449. }
  450. else
  451. $unit = ''; // Not sure if there's a temp
  452. // Append values
  453. $hwmon_vals[] = array(
  454. 'path' => 'N/A',
  455. 'name' => $label,
  456. 'temp' => $value,
  457. 'unit' => $unit
  458. );
  459. }
  460. // Save any if we have any
  461. if (count($hwmon_vals) > 0)
  462. $return = array_merge($return, $hwmon_vals);
  463. }
  464. // Additional weird bullshit? In this case, laptop backlight percentage. lolwtf, right?
  465. foreach ((array) @glob('/sys/devices/virtual/backlight/*/max_brightness', GLOB_NOSORT) as $bl) {
  466. $dir = dirname($bl);
  467. if (!is_file($dir.'/actual_brightness'))
  468. continue;
  469. $max = get_int_from_file($bl);
  470. $cur = get_int_from_file($dir.'/actual_brightness');
  471. if ($max < 0 || $cur < 0)
  472. continue;
  473. $return[] = array(
  474. 'name' => 'Backlight brightness',
  475. 'temp' => round($cur/$max, 2)*100,
  476. 'unit' => '%',
  477. 'path' => 'N/A',
  478. 'bar' => true
  479. );
  480. }
  481. // Done
  482. return $return;
  483. }
  484. /**
  485. * getMounts
  486. *
  487. * @access private
  488. * @return array the mounted the file systems
  489. */
  490. private function getMounts() {
  491. // Time?
  492. if (!empty($this->settings['timer']))
  493. $t = new LinfoTimerStart('Mounted file systems');
  494. // File
  495. $contents = getContents('/proc/mounts', false);
  496. // Can't?
  497. if ($contents == false)
  498. $this->error->add('Linfo Core', '/proc/mounts does not exist');
  499. // Parse
  500. if (@preg_match_all('/^(\S+) (\S+) (\S+) (.+) \d \d$/m', $contents, $match, PREG_SET_ORDER) === false)
  501. $this->error->add('Linfo Core', 'Error parsing /proc/mounts');
  502. // Return these
  503. $mounts = array();
  504. // Populate
  505. $num_matches = count($match);
  506. for ($i = 0; $i < $num_matches; $i++) {
  507. // This mount
  508. $mount = $match[$i];
  509. // Should we not show this?
  510. if (in_array($mount[1], $this->settings['hide']['storage_devices']) || in_array($mount[3], $this->settings['hide']['filesystems']))
  511. continue;
  512. // Spaces and other things in the mount path are escaped C style. Fix that.
  513. $mount[2] = stripcslashes($mount[2]);
  514. // Get these
  515. $size = @disk_total_space($mount[2]);
  516. $free = @disk_free_space($mount[2]);
  517. $used = $size != false && $free != false ? $size - $free : false;
  518. // If it's a symlink, find out where it really goes.
  519. // (using realpath instead of readlink because the former gives absolute paths)
  520. $symlink = is_link($mount[1]) ? realpath($mount[1]) : false;
  521. // Optionally get mount options
  522. if ($this->settings['show']['mounts_options'] && !in_array($mount[3], (array) $this->settings['hide']['fs_mount_options']))
  523. $mount_options = explode(',', $mount[4]);
  524. else
  525. $mount_options = array();
  526. // Might be good, go for it
  527. $mounts[] = array(
  528. 'device' => $symlink != false ? $symlink : $mount[1],
  529. 'mount' => $mount[2],
  530. 'type' => $mount[3],
  531. 'size' => $size,
  532. 'used' => $used,
  533. 'free' => $free,
  534. 'free_percent' => ((bool)$free != false && (bool)$size != false ? round($free / $size, 2) * 100 : false),
  535. 'used_percent' => ((bool)$used != false && (bool)$size != false ? round($used / $size, 2) * 100 : false),
  536. 'options' => $mount_options
  537. );
  538. }
  539. // Return
  540. return $mounts;
  541. }
  542. /**
  543. * getDevs
  544. *
  545. * @access private
  546. * @return array of devices
  547. */
  548. private function getDevs() {
  549. // Time?
  550. if (!empty($this->settings['timer']))
  551. $t = new LinfoTimerStart('Hardware Devices');
  552. // Location of useful paths
  553. $pci_ids = locate_actual_path(array(
  554. '/usr/share/misc/pci.ids', // debian/ubuntu
  555. '/usr/share/pci.ids', // opensuse
  556. '/usr/share/hwdata/pci.ids', // centos. maybe also redhat/fedora
  557. ));
  558. $usb_ids = locate_actual_path(array(
  559. '/usr/share/misc/usb.ids', // debian/ubuntu
  560. '/usr/share/usb.ids', // opensuse
  561. '/usr/share/hwdata/usb.ids', // centos. maybe also redhat/fedora
  562. ));
  563. // Did we not get them?
  564. $pci_ids || $this->error->add('Linux Device Finder', 'Cannot find pci.ids; ensure pciutils is installed.');
  565. $usb_ids || $this->error->add('Linux Device Finder', 'Cannot find usb.ids; ensure usbutils is installed.');
  566. // Class that does it
  567. $hw = new HW_IDS($usb_ids, $pci_ids);
  568. $hw->work('linux');
  569. return $hw->result();
  570. }
  571. /**
  572. * getRAID
  573. *
  574. * @access private
  575. * @return array of raid arrays
  576. */
  577. private function getRAID() {
  578. // Time?
  579. if (!empty($this->settings['timer']))
  580. $t = new LinfoTimerStart('RAID');
  581. // Store it here
  582. $raidinfo = array();
  583. // mdadm?
  584. if (array_key_exists('mdadm', (array)$this->settings['raid']) && !empty($this->settings['raid']['mdadm'])) {
  585. // Try getting contents
  586. $mdadm_contents = getContents('/proc/mdstat', false);
  587. // No?
  588. if ($mdadm_contents === false)
  589. $this->error->add('Linux softraid mdstat parser', '/proc/mdstat does not exist.');
  590. // Parse
  591. @preg_match_all('/(\S+)\s*:\s*(\w+)\s*raid(\d+)\s*([\w+\[\d+\] (\(\w\))?]+)\n\s+(\d+) blocks\s*(?:super \d\.\d\s*)?(level \d\, [\w\d]+ chunk\, algorithm \d\s*)?\[(\d\/\d)\] \[([U\_]+)\]/mi', (string) $mdadm_contents, $match, PREG_SET_ORDER);
  592. // Store them here
  593. $mdadm_arrays = array();
  594. // Deal with entries
  595. foreach ((array) $match as $array) {
  596. // Temporarily store drives here
  597. $drives = array();
  598. // Parse drives
  599. foreach (explode(' ', $array[4]) as $drive) {
  600. // Parse?
  601. if(preg_match('/([\w\d]+)\[\d+\](\(\w\))?/', $drive, $match_drive) == 1) {
  602. // Determine a status other than normal, like if it failed or is a spare
  603. if (array_key_exists(2, $match_drive)) {
  604. switch ($match_drive[2]) {
  605. case '(S)':
  606. $drive_state = 'spare';
  607. break;
  608. case '(F)':
  609. $drive_state = 'failed';
  610. break;
  611. case null:
  612. $drive_state = 'normal';
  613. break;
  614. // I'm not sure if there are status codes other than the above
  615. default:
  616. $drive_state = 'unknown';
  617. break;
  618. }
  619. }
  620. else
  621. $drive_state = 'normal';
  622. // Append this drive to the temp drives array
  623. $drives[] = array(
  624. 'drive' => '/dev/'.$match_drive[1],
  625. 'state' => $drive_state
  626. );
  627. }
  628. }
  629. // Add record of this array to arrays list
  630. $mdadm_arrays[] = array(
  631. 'device' => '/dev/'.$array[1],
  632. 'status' => $array[2],
  633. 'level' => $array[3],
  634. 'drives' => $drives,
  635. 'size' => byte_convert($array[5]*1024),
  636. 'algorithm' => $array[6],
  637. 'count' => $array[7],
  638. 'chart' => $array[8]
  639. );
  640. }
  641. // Append MD arrays to main raidinfo if it's good
  642. if (is_array($mdadm_arrays) && count($mdadm_arrays) > 0 )
  643. $raidinfo = array_merge($raidinfo, $mdadm_arrays);
  644. }
  645. // Return info
  646. return $raidinfo;
  647. }
  648. /**
  649. * getLoad
  650. *
  651. * @access private
  652. * @return array of current system load values
  653. */
  654. private function getLoad() {
  655. // Time?
  656. if (!empty($this->settings['timer']))
  657. $t = new LinfoTimerStart('Load Averages');
  658. // File that has it
  659. $file = '/proc/loadavg';
  660. // Get contents
  661. $contents = getContents($file, false);
  662. // ugh
  663. if ($contents === false) {
  664. $this->error->add('Linfo Core', '/proc/loadavg unreadable');
  665. return array();
  666. }
  667. // Parts
  668. $parts = explode(' ', $contents);
  669. // Return array of info
  670. return array(
  671. 'now' => $parts[0],
  672. '5min' => $parts[1],
  673. '15min' => $parts[2]
  674. );
  675. }
  676. /**
  677. * getNet
  678. *
  679. * @access private
  680. * @return array of network devices
  681. */
  682. private function getNet() {
  683. // Time?
  684. if (!empty($this->settings['timer']))
  685. $t = new LinfoTimerStart('Network Devices');
  686. // Hold our return values
  687. $return = array();
  688. // Use glob to get paths
  689. $nets = (array) @glob('/sys/class/net/*', GLOB_NOSORT);
  690. // Get values for each device
  691. $num_nets = count($nets);
  692. for ($i = 0; $i < $num_nets; $i++) {
  693. // Path
  694. $path = $nets[$i];
  695. // States
  696. $operstate_contents = getContents($path.'/operstate');
  697. switch ($operstate_contents) {
  698. case 'down':
  699. case 'up':
  700. case 'unknown':
  701. $state = $operstate_contents;
  702. break;
  703. default:
  704. $state = 'unknown';
  705. break;
  706. }
  707. // motherfucker
  708. if ($state = 'unknown' && file_exists($path.'/carrier')) {
  709. $carrier = getContents($path.'/carrier', false);
  710. if (!empty($carrier))
  711. $state = 'up';
  712. else
  713. $state = 'down';
  714. }
  715. // Type
  716. $type_contents = strtoupper(getContents($path.'/device/modalias'));
  717. list($type) = explode(':', $type_contents, 2);
  718. $type = $type != 'USB' && $type != 'PCI' ? 'N/A' : $type;
  719. // Save and get info for each
  720. $return[basename($path)] = array(
  721. // Stats are stored in simple files just containing the number
  722. 'recieved' => array(
  723. 'bytes' => get_int_from_file($path.'/statistics/rx_bytes'),
  724. 'errors' => get_int_from_file($path.'/statistics/rx_errors'),
  725. 'packets' => get_int_from_file($path.'/statistics/rx_packets')
  726. ),
  727. 'sent' => array(
  728. 'bytes' => get_int_from_file($path.'/statistics/tx_bytes'),
  729. 'errors' => get_int_from_file($path.'/statistics/tx_errors'),
  730. 'packets' => get_int_from_file($path.'/statistics/rx_packets')
  731. ),
  732. // These were determined above
  733. 'state' => $state,
  734. 'type' => $type
  735. );
  736. }
  737. // Return array of info
  738. return $return;
  739. }
  740. /**
  741. * getBattery
  742. *
  743. * @access private
  744. * @return array of battery status
  745. */
  746. private function getBattery() {
  747. // Time?
  748. if (!empty($this->settings['timer']))
  749. $t = new LinfoTimerStart('Batteries');
  750. // Return values
  751. $return = array();
  752. // Here they should be
  753. $bats = (array) @glob('/sys/class/power_supply/BAT*', GLOB_NOSORT);
  754. // Get vals for each battery
  755. foreach ($bats as $b) {
  756. // Fuck pointless cuntshit
  757. foreach(array($b.'/manufacturer', $b.'/status', $b.'/charge_now') as $f)
  758. if (!is_file($f))
  759. continue 2;
  760. // Get these from the simple text files
  761. $charge_full = is_file($b.'/charge_full_design') ? get_int_from_file($b.'/charge_full_design') : get_int_from_file($b.'/charge_full');
  762. $charge_now = get_int_from_file($b.'/charge_now');
  763. // Save result set
  764. $return[] = array(
  765. 'charge_full' => $charge_full,
  766. 'charge_now' => $charge_now,
  767. 'percentage' => ($charge_now != 0 && $charge_full != 0 ? (round($charge_now / $charge_full, 4) * 100) : '?').'%',
  768. 'device' => getContents($b.'/manufacturer') . ' ' . getContents($b.'/model_name', 'Unknown'),
  769. 'state' => getContents($b.'/status', 'Unknown')
  770. );
  771. }
  772. // Give it
  773. return $return;
  774. }
  775. /**
  776. * getWifi
  777. *
  778. * @access private
  779. * @return array of wifi devices
  780. */
  781. private function getWifi() {
  782. // Time?
  783. if (!empty($this->settings['timer']))
  784. $t = new LinfoTimerStart('Wifi');
  785. // Return these
  786. $return = array();
  787. // In here
  788. $contents = getContents('/proc/net/wireless');
  789. // Oi
  790. if ($contents == false) {
  791. $this->error->add('Linux WiFi info parser', '/proc/net/wireless does not exist');
  792. return $return;
  793. }
  794. // Parse
  795. @preg_match_all('/^ (\S+)\:\s*(\d+)\s*(\S+)\s*(\S+)\s*(\S+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*(\d+)\s*$/m', $contents, $match, PREG_SET_ORDER);
  796. // Match
  797. foreach ($match as $wlan) {
  798. $return[] = array(
  799. 'device' => $wlan[1],
  800. 'status' => $wlan[2],
  801. 'quality_link' => $wlan[3],
  802. 'quality_level' => $wlan[4],
  803. 'quality_noise' => $wlan[5],
  804. 'dis_nwid' => $wlan[6],
  805. 'dis_crypt' => $wlan[7],
  806. 'dis_frag' => $wlan[8],
  807. 'dis_retry' => $wlan[9],
  808. 'dis_misc' => $wlan[10],
  809. 'mis_beac' => $wlan[11]
  810. );
  811. }
  812. // Done
  813. return $return;
  814. }
  815. /**
  816. * getSoundCards
  817. *
  818. * @access private
  819. * @return array of soundcards
  820. */
  821. private function getSoundCards() {
  822. // Time?
  823. if (!empty($this->settings['timer']))
  824. $t = new LinfoTimerStart('Sound cards');
  825. // This should be it
  826. $file = '/proc/asound/cards';
  827. // eh?
  828. if (!is_file($file)) {
  829. $this->error->add('Linux sound card detector', '/proc/asound/cards does not exist');
  830. }
  831. // Get contents and parse
  832. $contents = getContents($file);
  833. // Parse
  834. if (preg_match_all('/^\s*(\d+)\s\[[\s\w]+\]:\s(.+)$/m', $contents, $matches, PREG_SET_ORDER) == 0)
  835. return array();
  836. // eh?
  837. $cards = array();
  838. // Deal with results
  839. foreach ($matches as $card)
  840. $cards[] = array(
  841. 'number' => $card[1],
  842. 'card' => $card[2],
  843. );
  844. // Give cards
  845. return $cards;
  846. }
  847. /**
  848. * getProcessStats
  849. *
  850. * @access private
  851. * @return array of process stats
  852. */
  853. private function getProcessStats() {
  854. // Time?
  855. if (!empty($this->settings['timer']))
  856. $t = new LinfoTimerStart('Process Stats');
  857. // We'll return this after stuffing it with useful info
  858. $result = array(
  859. 'exists' => true,
  860. 'totals' => array(
  861. 'running' => 0,
  862. 'zombie' => 0,
  863. 'sleeping' => 0,
  864. 'stopped' => 0,
  865. ),
  866. 'proc_total' => 0,
  867. 'threads' => 0
  868. );
  869. // Get all the paths to each process' status file
  870. $processes = (array) @glob('/proc/*/status', GLOB_NOSORT);
  871. // Total
  872. $result['proc_total'] = count($processes);
  873. // Go through each
  874. for ($i = 0; $i < $result['proc_total']; $i++) {
  875. // Don't waste time if we can't use it
  876. if (!is_readable($processes[$i]))
  877. continue;
  878. // Get that file's contents
  879. $status_contents = getContents($processes[$i]);
  880. // Try getting state
  881. @preg_match('/^State:\s+(\w)/m', $status_contents, $state_match);
  882. // Well? Determine state
  883. switch ($state_match[1]) {
  884. case 'D': // disk sleep? wtf?
  885. case 'S':
  886. $result['totals']['sleeping']++;
  887. break;
  888. case 'Z':
  889. $result['totals']['zombie']++;
  890. break;
  891. case 'R':
  892. $result['totals']['running']++;
  893. break;
  894. case 'T':
  895. $result['totals']['stopped']++;
  896. break;
  897. }
  898. // Try getting number of threads
  899. @preg_match('/^Threads:\s+(\d+)/m', $status_contents, $threads_match);
  900. // Well?
  901. if ($threads_match)
  902. list(, $threads) = $threads_match;
  903. // Append it on if it's good
  904. if (is_numeric($threads))
  905. $result['threads'] = $result['threads'] + $threads;
  906. }
  907. // Give off result
  908. return $result;
  909. }
  910. /**
  911. * getServices
  912. *
  913. * @access private
  914. * @return array the services
  915. */
  916. private function getServices() {
  917. // Time?
  918. if (!empty($this->settings['timer']))
  919. $t = new LinfoTimerStart('Services');
  920. // We allowed?
  921. if (!empty($settings['show']['services']) || !is_array($this->settings['services']) || count($this->settings['services']) == 0)
  922. return array();
  923. // Temporarily keep statuses here
  924. $statuses = array();
  925. // A bit of unfucking potential missing values in config file
  926. $this->settings['services']['executables'] = (array) $this->settings['services']['executables'];
  927. $this->settings['services']['pidFiles'] = (array) $this->settings['services']['pidFiles'];
  928. // Convert paths of executables to PID files
  929. $pids = array();
  930. $do_process_search = false;
  931. if (count($this->settings['services']['executables']) > 0) {
  932. $potential_paths = @glob('/proc/*/cmdline');
  933. if (is_array($potential_paths)) {
  934. $num_paths = count($potential_paths);
  935. $do_process_search = true;
  936. }
  937. }
  938. // Should we go ahead and do the PID search based on executables?
  939. if ($do_process_search) {
  940. // Precache all process cmdlines
  941. for ($i = 0; $i < $num_paths; $i++)
  942. $cmdline_cache[$i] = explode("\x00", getContents($potential_paths[$i]));
  943. // Go through the list of executables to search for
  944. foreach ($this->settings['services']['executables'] as $service => $exec) {
  945. // Go through pid file list. for loops are faster than foreach
  946. for ($i = 0; $i < $num_paths; $i++) {
  947. $cmdline = $cmdline_cache[$i];
  948. $match = false;
  949. if (is_array($exec)) {
  950. $match = true;
  951. foreach ($exec as $argn => $argv) {
  952. if($cmdline[$argn] != $argv)
  953. $match = false;
  954. }
  955. }
  956. else if ($cmdline[0] == $exec) {
  957. $match = true;
  958. }
  959. // If this one matches, stop here and save it
  960. if ($match) {
  961. // Get pid out of path to cmdline file
  962. $pids[$service] = substr($potential_paths[$i], 6 /*strlen('/proc/')*/,
  963. strpos($potential_paths[$i], '/', 7)-6);
  964. break;
  965. }
  966. }
  967. }
  968. }
  969. // PID files
  970. foreach ($this->settings['services']['pidFiles'] as $service => $file) {
  971. $pid = getContents($file, false);
  972. if ($pid != false && is_numeric($pid))
  973. $pids[$service] = $pid;
  974. }
  975. // Deal with PIDs
  976. foreach ($pids as $service => $pid) {
  977. $path = '/proc/'.$pid.'/status';
  978. $status_contents = getContents($path, false);
  979. if ($status_contents == false) {
  980. $statuses[$service] = array('state' => 'Down', 'threads' => 'N/A', 'pid' => $pid);
  981. continue;
  982. }
  983. // Attempt getting info out of it
  984. if (!preg_match_all('/^(\w+):\s+(\w+)/m', $status_contents, $status_matches, PREG_SET_ORDER))
  985. continue;
  986. // Initially set these as pointless
  987. $state = false;
  988. $threads = false;
  989. $mem = false;
  990. // Go through
  991. //foreach ($status_matches as $status_match) {
  992. for ($i = 0, $num = count($status_matches); $i < $num; $i++) {
  993. // What have we here?
  994. switch ($status_matches[$i][1]) {
  995. // State section
  996. case 'State':
  997. switch ($status_matches[$i][2]) {
  998. case 'D': // disk sleep? wtf?
  999. case 'S':
  1000. $state = 'Up (Sleeping)';
  1001. break;
  1002. case 'Z':
  1003. $state = 'Zombie';
  1004. break;
  1005. // running
  1006. case 'R':
  1007. $state = 'Up (Running)';
  1008. break;
  1009. // stopped
  1010. case 'T':
  1011. $state = 'Up (Stopped)';
  1012. break;
  1013. default:
  1014. continue;
  1015. break;
  1016. }
  1017. break;
  1018. // Mem usage
  1019. case 'VmRSS':
  1020. if (is_numeric($status_matches[$i][2]))
  1021. $mem = $status_matches[$i][2] * 1024; // Measured in kilobytes; we want bytes
  1022. break;
  1023. // Thread count
  1024. case 'Threads':
  1025. if (is_numeric($status_matches[$i][2]))
  1026. $threads = $status_matches[$i][2];
  1027. // Thread count should be last. Stop here to possibly save time assuming we have the other values
  1028. if ($state !== false && $mem !== false && $threads !== false)
  1029. break;
  1030. break;
  1031. }
  1032. }
  1033. // Save info
  1034. $statuses[$service] = array(
  1035. 'state' => $state ? $state : '?',
  1036. 'threads' => $threads,
  1037. 'pid' => $pid,
  1038. 'memory_usage' => $mem
  1039. );
  1040. }
  1041. return $statuses;
  1042. }
  1043. /**
  1044. * getDistro
  1045. *
  1046. * @access private
  1047. * @return array the distro,version or false
  1048. */
  1049. private function getDistro() {
  1050. // Time?
  1051. if (!empty($this->settings['timer']))
  1052. $t = new LinfoTimerStart('Determining Distrobution');
  1053. // Seems the best way of doing it, as opposed to calling 'lsb_release -a', parsing /etc/issue, or
  1054. // just checking if distro specific version files exist without actually parsing them:
  1055. // - Allows multiple files of the same name for different distros/versions of distros, provided each
  1056. // - uses different regular expression syntax.
  1057. // - Also permits files that contain only the distro release version and nothing else,
  1058. // - in which case passing false instead of a regex string snags the contents.
  1059. // - And even also supports empty files, and just uses said file to identify the distro and ignore version
  1060. // Store the distribution's files we check for, optional regex parsing string, and name of said distro here:
  1061. $distros = array(
  1062. // This snags ubuntu and other distros which use the lsb method of identifying themselves
  1063. array('/etc/lsb-release','/^DISTRIB_ID=([^$]+)$\n^DISTRIB_RELEASE=([^$]+)$\n^DISTRIB_CODENAME=([^$]+)$\n/m', false),
  1064. // These working snag versions
  1065. array('/etc/redhat-release', '/^CentOS release ([\d\.]+) \(([^)]+)\)$/', 'CentOS'),
  1066. array('/etc/redhat-release', '/^Red Hat.+release (\S+) \(([^)]+)\)$/', 'RedHat'),
  1067. array('/etc/fedora-release', '/^Fedora(?: Core)? release (\d+) \(([^)]+)\)$/', 'Fedora'),
  1068. array('/etc/gentoo-release', '/([\d\.]+)$/', 'Gentoo'),
  1069. array('/etc/SuSE-release', '/^VERSION = ([\d\.]+)$/m', 'openSUSE'),
  1070. array('/etc/slackware-version', '/([\d\.]+)$/', 'Slackware'),
  1071. // These don't because they're empty
  1072. array('/etc/arch-release', '', 'Arch'),
  1073. // I'm unaware of the structure of these files, so versions are not picked up
  1074. array('/etc/mklinux-release', '', 'MkLinux'),
  1075. array('/etc/tinysofa-release ', '', 'TinySofa'),
  1076. array('/etc/turbolinux-release ', '', 'TurboLinux'),
  1077. array('/etc/yellowdog-release ', '', 'YellowDog'),
  1078. array('/etc/annvix-release ', '', 'Annvix'),
  1079. array('/etc/arklinux-release ', '', 'Arklinux'),
  1080. array('/etc/aurox-release ', '', 'AuroxLinux'),
  1081. array('/etc/blackcat-release ', '', 'BlackCat'),
  1082. array('/etc/cobalt-release ', '', 'Cobalt'),
  1083. array('/etc/immunix-release ', '', 'Immunix'),
  1084. array('/etc/lfs-release ', '', 'Linux-From-Scratch'),
  1085. array('/etc/linuxppc-release ', '', 'Linux-PPC'),
  1086. array('/etc/mklinux-release ', '', 'MkLinux'),
  1087. array('/etc/nld-release ', '', 'NovellLinuxDesktop'),
  1088. // Leave this since debian derivitives might have it in addition to their own file
  1089. // If it's last it ensures nothing else has it and thus it should be normal debian
  1090. array('/etc/debian_version', false, 'Debian'),
  1091. );
  1092. // Hunt
  1093. foreach ($distros as $distro) {
  1094. // File we're checking for exists and is readable
  1095. if (file_exists($distro[0]) && is_readable($distro[0])) {
  1096. // Get it
  1097. $contents = $distro[1] !== '' ? getContents($distro[0], '') : '';
  1098. // Don't use regex, this is enough; say version is the file's contents
  1099. if ($distro[1] === false) {
  1100. return array(
  1101. 'name' => $distro[2],
  1102. 'version' => $contents == '' ? false : $contents
  1103. );
  1104. }
  1105. // No fucking idea what the version is. Don't use the file's contents for anything
  1106. elseif($distro[1] === '') {
  1107. return array(
  1108. 'name' => $distro[2],
  1109. 'version' => false
  1110. );
  1111. }
  1112. // Get the distro out of the regex as well?
  1113. elseif($distro[2] === false && preg_match($distro[1], $contents, $m)) {
  1114. return array(
  1115. 'name' => $m[1],
  1116. 'version' => $m[2] . (isset($m[3]) ? ' ('.$m[3].')' : '')
  1117. );
  1118. }
  1119. // Our regex match it?
  1120. elseif(preg_match($distro[1], $contents, $m)) {
  1121. return array(
  1122. 'name' => $distro[2],
  1123. 'version' => $m[1] . (isset($m[2]) ? ' ('.$m[2].')' : '')
  1124. );
  1125. }
  1126. }
  1127. }
  1128. // Return lack of result if we didn't find it
  1129. return false;
  1130. }
  1131. /**
  1132. * getCPUArchitecture
  1133. *
  1134. * @access private
  1135. * @return string the arch and bits
  1136. */
  1137. private function getCPUArchitecture() {
  1138. return php_uname('m');
  1139. }
  1140. /**
  1141. * getNumLoggedIn
  1142. *
  1143. * @access private
  1144. * @return number of logged in users with shells
  1145. */
  1146. private function getNumLoggedIn() {
  1147. // Snag command line of every process in system
  1148. $procs = glob('/proc/*/cmdline', GLOB_NOSORT);
  1149. // Store unqiue users here
  1150. $users = array();
  1151. // Each process
  1152. foreach ($procs as $proc) {
  1153. // Does the process match a popular shell, such as bash, csh, etc?
  1154. if (preg_match('/(?:bash|csh|zsh|ksh)$/', getContents($proc, ''))) {
  1155. // Who the fuck owns it, anyway?
  1156. $owner = fileowner(dirname($proc));
  1157. // Careful..
  1158. if (!is_numeric($owner))
  1159. continue;
  1160. // Have we not seen this user before?
  1161. if (!in_array($owner, $users))
  1162. $users[] = $owner;
  1163. }
  1164. }
  1165. // Give number of unique users with shells running
  1166. return count($users);
  1167. }
  1168. }