*/ class nfs_exception extends Exception {} /** * nfs module * * @author Joe Gillotti */ class nfs implements modules { private /** * Paths to NFS related files and commands */ $_paths = array( 'exports' => '/etc/exports', 'rmtab' => '/var/lib/nfs/rmtab', 'etab' => '/var/lib/nfs/etab', 'exportfs' => '/usr/sbin/exportfs' ); /** * Some checks as we start up * * @access public */ public function __construct() { // Make sure we're root $my_uid = posix_getuid(); if ($my_uid !== 0) { $my_user = posix_getpwuid($my_uid); throw new nfs_exception('This must be ran as root; not '.$my_user['name'].' ('.$my_uid.')'); } } /** * View exported directories in the exports file * * @access public * @return array the exports */ public function view_exports() { // Get contents $contents = is_readable($this->_paths['exports']) ? file_get_contents($this->_paths['exports']) : ''; // Attempt matching for non-commented working exports. Return empty list on failure. if (!preg_match_all('/^(?!\#)(\S+)((?:\s+[\w\.\*]+\([a-z,_]+\))+)/m', $contents, $matches, PREG_SET_ORDER)) return array(); // Store matches prettily here $exports = array(); // Populate it for ($i = 0, $num = count($matches); $i < $num; $i++) { // Store clients for this export here $clients = array(); // Find them if (preg_match_all('/\s+([\w\.\*]+)\(([a-z,_]+)\)/', $matches[$i][2], $c_matches, PREG_SET_ORDER)) for ($c_i = 0, $c_num = count($c_matches); $c_i < $c_num; $c_i++) $clients[] = array( 'host' => $c_matches[$c_i][1], 'options' => explode(',', $c_matches[$c_i][2]) ); // Append export to our list $exports[] = array( 'directory' => $matches[$i][1], 'clients' => $clients ); } // Give list return $exports; } /** * View remotely mounted exports listed in rmtab * * @access public * @return array the mounted exports */ public function view_mounted_exports() { // Get contents $contents = is_readable($this->_paths['rmtab']) ? file_get_contents($this->_paths['rmtab']) : ''; // Try matching if (!preg_match_all('/([\w\.]+):([^:]+)/m', $contents, $matches, PREG_SET_ORDER)) return array(); // Keep mounts here $mounts = array(); // Populate it for ($i = 0, $num = count($matches); $i < $num; $i++) $mounts[] = array( 'client' => $matches[$i][1], 'share' => $matches[$i][2] ); // Give it return $mounts; } public function add_exports($share, $clients) { // Store the second part of the exports line here $access = ''; // Populate that with each client and its settings for ($i = 0, $num = count($clients); $i < $num; $i++) $access .= ' '.$clients[$i]['host'].'('.$clients[$i]['options'].')'; // Try saving it return file_put_contents($this->_paths['exports'], "$share$access", FILE_APPEND); } /** * Replace specific lines in exports file * * @param mixed $replacements * @access public * @return bool */ public function replace_exports($replacements) { // Make sure that's an array $replacements = (array) $replacements; // Get lines currently in file if (!($lines = @file($this->_paths['exports'], FILE_IGNORE_NEW_LINES))) return false; // Go through each line, checking if its one we want to replace foreach ($lines as $k => $line) { // Potential fixing $line = trim($line); // Is it in our list of replacements? if (array_key_exists(trim($line), $replacements)) $lines[$k] = $replacements[$line]; } // Try saving it return file_put_contents($this->_paths['exports'], implode("\n", $lines)); } /** * Delete specific lines in exports file * * @param array $kill_lines * @access public * @return bool */ public function delete_exports($kill_lines) { // Make sure this is an array so we can kill multiple ones $kill_lines = (array) $kill_lines; // Get lines currently in file if (!($lines = @file($this->_paths['exports'], FILE_IGNORE_NEW_LINES))) return false; // Go through each line, checking if its one we want to kill foreach ($lines as $k => $line) if (in_array(trim($line), $kill_lines)) unset($lines[$k]); // Try saving it return file_put_contents($this->_paths['exports'], implode("\n", $lines)); } /** * Export a directory immediately, but don't save it * * @param string $client * @param string $share * @param string $options * @access public * @return int */ public function tmp_export($client, $share, $options) { // The return code may not be passed; default to NULL if it isn't $code = null; // Run the command exec("{$this->_paths['exportfs']} -o ".implode(',', $options)." $client:$share", &$out, &$code); // If the exit code is zero, it should have succeeded. return $code !== null ? $code === 0 : null; } /** * Reparse the exports file and share appropriately * * @access public * @return int */ public function reload() { // The return code may not be passed; default to NULL if it isn't $code = null; // Run the command exec("{$this->_paths['exportfs']} -ra", &$out, &$code); // If the exit code is zero, it should have succeeded. return $code !== null ? $code === 0 : null; } /** * Show exports that are active and real, by parsing etab * * @access public * @return void */ public function view_live_exports() { // Get exports with this command. Might be really unreliable. $contents = is_readable($this->_paths['etab']) ? file_get_contents($this->_paths['etab']) : ''; // Try matching the exports out of it if (preg_match_all('/^(\S+)\s+([\d\.\/\*]+)\(([^\)]+)\)$/m', $contents, $matches, PREG_SET_ORDER) == 0) return array(); // Store exports here $exports = array(); // Populate it for ($i = 0, $num = count($matches); $i < $num; $i++) $exports[] = array( 'share' => $matches[$i][1], 'host' => $matches[$i][2], 'options' => explode(',', $matches[$i][3]) ); // Give exports list return $exports; } }