<?php

/*
 * This file is part of Linfo (c) 2010 Joseph Gillotti.
 * 
 * Linfo is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Linfo is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Linfo. If not, see <http://www.gnu.org/licenses/>.
 * 
*/


defined('IN_LINFO') or exit;

/*
 * NetBSD info class. Differs slightly from FreeBSD's
 * TODO: netbsd's /proc contains really useful info
 * possibly get some stuff from it if it exists
 */

class OS_NetBSD extends OS_BSD_Common {

	// Encapsulate these
	protected
		$settings,
		$exec,
		$error,
		$dmesg;

	// Start us off
	public function __construct($settings) {
		
		// Initiate parent
		parent::__construct($settings);
		
		// We search these folders for our commands
		$this->exec->setSearchPaths(array('/sbin', '/bin', '/usr/bin', '/usr/pkg/bin', '/usr/sbin'));

		// sysctl values we'll access below
		$this->GetSysCTL(array('kern.boottime', 'vm.loadavg'), false);
	}

	// Operating System
	public function getOS() {
		return 'NetBSD';
	}

	// Kernel version
	public function getKernel() {
		return php_uname('r');
	}

	// Host name
	public function getHostName() {
		return php_uname('n');
	}

	// Mounted file systems
	public function getMounts() {

		// Time it
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Mounted file systems');

		// Try getting mount command
		try {
			$res = $this->exec->exec('mount');
		}
		catch (CallExtException $e) {
			$this->error->add('Linfo Core', 'Error running `mount` command');
			return array();
		}

		// Match the file systems
		if(@preg_match_all('/^(\S+) on (\S+) type (\S+)/m', $res, $mount_match, PREG_SET_ORDER) == 0)
			return array();

		// Store them here
		$mounts = array();

		// Go through each
		foreach ($mount_match as $mount) {
			// Should we not show this?
			if (in_array($mount[1], $this->settings['hide']['storage_devices']) || in_array($mount[3], $this->settings['hide']['filesystems']))
				continue;
			
			// Get these
			$size = @disk_total_space($mount[2]);
			$free = @disk_free_space($mount[2]);
			$used = $size - $free;
			
			// Might be good, go for it
			$mounts[] = array(
				'device' => $mount[1],
				'mount' => $mount[2],
				'type' => $mount[3],
				'size' => $size ,
				'used' => $used,
				'free' => $free,
				'free_percent' => ((bool)$free != false && (bool)$size != false ? round($free / $size, 2) * 100 : false),
				'used_percent' => ((bool)$used != false && (bool)$size != false ? round($used / $size, 2) * 100 : false)
			);
		}
		
		// Give them
		return $mounts;
	}

	// Get system load
	public function getLoad() {

		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Load Averages');

		// Try using sysctl to get load average
		$res = $this->sysctl['vm.loadavg'];

		// Match it
		if (@preg_match('/([\d\.]+) ([\d\.]+) ([\d\.]+)$/', $res, $load_match))
			return array(
				'now' => $load_match[1],
				'5min' => $load_match[2],
				'15min' => $load_match[3]
			);

		// Match failed
		else
			return false;
	}

	// Get the always gloatable uptime
	public function getUpTime() {
		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Uptime');

		// Use sysctl
		$booted = strtotime($this->sysctl['kern.boottime']);

		// Give it
		return array(
			'text' => LinfoCommon::secondsConvert(time() - $booted),
			'bootedTimestamp' => $booted
		);
	}

	// Get network devices
	public function getNet() {
		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Network Devices');

		// Try using netstat
		try {
			$res = $this->exec->exec('netstat', '-nbdi');
		}
		catch(CallExtException $e) {
			$this->error->add('Linfo Core', 'Error using `netstat` to get network info');
			return array();
		}

		// Match the interfaces themselves
		if (preg_match_all('/^(\S+)\s+\d+\s+<Link>\s+[a-z0-9\:]+\s+(\d+)\s+(\d+)\s+\d+$/m', $res, $net_matches, PREG_SET_ORDER) == 0)
			return array();

		// Store statuses for each here
		$statuses = array();
	
		// Try using ifconfig to get statuses for each interface
		try {
			$ifconfig = $this->exec->exec('ifconfig', '-a');
			$current_nic = false;
			foreach ((array) explode("\n", $ifconfig) as $line) {
				if (preg_match('/^(\w+):/m', $line, $m) == 1)
					$current_nic = $m[1];
				elseif ($current_nic != false && preg_match('/^\s+status: (\w+)$/m', $line, $m) == 1) {
					$statuses[$current_nic] = $m[1];
					$current_nic = false;
				}
			}
		}
		catch(CallExtException $e) {}

		// Store interfaces here
		$nets = array();

		// Go through each
		foreach($net_matches as $net) {

			// See if we successfully found a status, and use it if so
			switch (array_key_exists($net[1], $statuses) ? $statuses[$net[1]] : 'unknown') {
				case 'active':
					$state = 'up';
				break;
				case 'inactive':
					$state = 'down';
				break;
				default:
					$state = 'unknown';
				break;
			}

			// Save this interface
			$nets[$net[1]] = array(
				'recieved' => array(
					'bytes' => $net[2],
				),
				'sent' => array(
					'bytes' => $net[3],
				),
				'state' => $state,
				'type' => 'Unknown' // TODO
			);
		}

		// Give it
		return $nets;
	}

	// Get drives
	public function getHD() {

		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('CPU');

		$drives = array();
		$curr_hd = false;

		// Parse dmesg
		foreach (explode("\n", $this->dmesg) as $dmesg_line) {
			
			// Beginning of a drive entry
			if (preg_match('/^([a-z]{2}\d) at [^:]+: <([^>]+)> (\w+)/', $dmesg_line, $init_match)) {

				// If it's a cdrom just stop here and save it.
				if ($init_match[3] == 'cdrom') {
					
					// Save entry
					$drives[] = array(
						'name' => preg_match('/^([^,]+)/', $init_match[2], $cd_match) ? $cd_match[1] : $init_match[2],
						'vendor' => false, // I don't know if this is possible
						'device' => '/dev/'.$init_match[1],
						
						// Not sure how to get the following:
						'size' => false,
						'reads' => false,
						'writes' => false
					);
				}
				
				// Otherwise prep for further info on a later line
				elseif ($init_match[3] == 'disk') {
					$curr_hd = array($init_match[1], $init_match[2], $init_match[3]);
				}

				// Don't go any farther with this line
				continue;
			}

			// A hard drive setting line, that has size and stuff
			elseif ($curr_hd != false && preg_match('/^'.preg_quote($curr_hd[0]).': (\d+) MB/', $dmesg_line, $drive_match)) {
				
				// Try getting vendor or name
				$make = preg_match('/^([^,]+), ([^,]+)/', $curr_hd[1], $v_match) ? array($v_match[1], $v_match[2]) : false;

				// Save entry
				$drives[] = array(
					'name' => $make ? $make[1] : $curr_hd[1],
					'vendor' => $make ? $make[0] : false,
					'device' => '/dev/'.$curr_hd[0],
					'size' => $drive_match[1] * 1048576,
					
					// Not sure how to get the following:
					'reads' => false, 
					'writes' => false 
				);

				// We're done with this drive
				$curr_hd = false;
				
				// Don't go any farther with this line
				continue;
			}
		}

		// Give drives
		return $drives;
	}

	// Get cpu's
	public function getCPU() {
		
		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('CPU');

		// Parse dmesg
		if (preg_match_all('/^cpu\d+ at [^:]+: (\S+) ([^,]+), (\d+)MHz/m', $this->dmesg, $cpu_matches, PREG_SET_ORDER) == 0)
			return array();

		// Store them here
		$cpus = array();

		// Store as many as possible
		foreach ($cpu_matches as $cpu_m)
			$cpus[] = array(
				'Model' => $cpu_m[2],
				'MHz' => $cpu_m[3],
				'Vendor' => $cpu_m[1]
			);

		// Give them
		return $cpus;
	}

	// Get ram usage
	public function getRam() {
		
		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Memory');

		// Start us off at zilch
		$return = array();
		$return['type'] = 'Virtual';
		$return['total'] = 0;
		$return['free'] = 0;
		$return['swapTotal'] = 0;
		$return['swapFree'] = 0;
		$return['swapInfo'] = array();

		// Get virtual memory usage with vmstat
		try {
			// Get result of vmstat
			$vmstat = $this->exec->exec('vmstat', '-s');

			// Get bytes per page
			preg_match('/^\s+(\d+) bytes per page$/m', $vmstat, $bytes_per_page);

			// Did we?
			if (!is_numeric($bytes_per_page[1]) || $bytes_per_page[1] < 0)
				throw new Exception('Error parsing page size out of `vmstat`');
			else
				list(, $bytes_per_page) = $bytes_per_page;

			// Get available ram
			preg_match('/^\s+(\d+) pages managed$/m', $vmstat, $available_ram);
			
			// Did we?
			if (!is_numeric($available_ram[1]))
				throw new Exception('Error parsing managed pages out of `vmstat`');
			else
				list(, $available_ram) = $available_ram;

			// Get free ram
			preg_match('/^\s+(\d+) pages free$/m', $vmstat, $free_ram);
			
			// Did we?
			if (!is_numeric($free_ram[1]))
				throw new Exception('Error parsing free pages out of `vmstat`');
			else
				list(, $free_ram) = $free_ram;

			// Okay, cool. Total them up
			$return['total'] = $available_ram * $bytes_per_page;
			$return['free'] = $free_ram * $bytes_per_page;
		}
		catch (CallExtException $e) {
			$this->error->add('Linfo Core', 'Error using `vmstat` to get memory usage');
		}
		catch (Exception $e) {
			$this->error->add('Linfo Core', $e->getMessage());
		}

		// Get swap
		try {
			$swapinfo = $this->exec->exec('swapctl', '-l');
			@preg_match_all('/^(\S+)\s+(\d+)\s+(\d+)\s+(\d+)/m', $swapinfo, $sm, PREG_SET_ORDER);
			foreach ($sm as $swap) {
				$return['swapTotal'] += $swap[2]*1024;
				$return['swapFree'] += (($swap[2] - $swap[3])*1024);
				$ft = is_file ($ft) ? @filetype($swap[1]) : 'Unknown'; // TODO: I'd rather it be Partition or File
				$return['swapInfo'][] = array(
					'device' => $swap[1],
					'size' => $swap[2]*1024,
					'used' => $swap[3]*1024,
					'type' => ucfirst($ft) 
				);
			}
		}
		catch (CallExtException $e) {
			$this->error->add('Linfo Core', 'Error using `swapctl` to get swap usage');
		}

		// Give it off
		return $return;
	}
	
	// Get devices
	public function getDevs() {

		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Hardware Devices');
		
		// Get them
		if(preg_match_all('/^([a-z]+\d+) at ([a-z]+)\d+[^:]+:(.+)/m', $this->dmesg, $devices_match, PREG_SET_ORDER) == 0)
			return array();		
		
		// Keep them here
		$devices = array();

		// Store the type column for each key
		$sort_type = array();
		
		// Stuff it
		foreach ($devices_match as $device) {

			if ($device[2] == 'ppb' || strpos($device[3], 'vendor') !== false)
				continue;

			// Only call this once
			$type = strtoupper($device[2]);

			// Stuff entry
			$devices[] = array(
				'vendor' => false, // Maybe todo? 
				'device' => $device[3],
				'type' => $type
			);

			// For the sorting of this entry
			$sort_type[] = $type;
		}
		
		// Sort
		array_multisort($devices, SORT_STRING, $sort_type);

		// Give them
		return $devices;
	}
	
	// Get stats on processes
	public function getProcessStats() {
		
		// Time?
		if (!empty($this->settings['timer']))
			$t = new LinfoTimerStart('Process Stats');

		// We'll return this after stuffing it with useful info
		$result = array(
			'exists' => true, 
			'totals' => array(
				'running' => 0,
				'zombie' => 0,
				'sleeping' => 0,
				'stopped' => 0,
			),
			'proc_total' => 0,
			'threads' => false // I'm not sure how to get this
		);

		// Use ps
		try {
			// Get it
			$ps = $this->exec->exec('ps', 'ax');

			// Match them
			preg_match_all('/^\s*\d+\s+[\w?]+\s+([A-Z])\S*\s+.+$/m', $ps, $processes, PREG_SET_ORDER);
			
			// Get total
			$result['proc_total'] = count($processes);
			
			// Go through
			foreach ($processes as $process) {
				switch ($process[1]) {
					case 'S':
					case 'I':
						$result['totals']['sleeping']++;
					break;
					case 'Z':
						$result['totals']['zombie']++;
					break;
					case 'R':
					case 'D':
					case 'O':
						$result['totals']['running']++;
					break;
					case 'T':
						$result['totals']['stopped']++;
					break;
				}
			}
		}
		catch (CallExtException $e) {
			$this->error->add('Linfo Core', 'Error using `ps` to get process info');
		}

		// Give
		return $result;
	}
	
	// TODO:
	public function getRAID() {}
	public function getBattery() {}
	public function getTemps() {}
}
