<?php
/*
* FILE INFORMATION:
* $HeadURL: https://cs-content.svn.sourceforge.net/svnroot/cs-content/releases/0.10/cs_fileSystemClass.php $
* $Id: cs_fileSystemClass.php 298 2008-07-15 20:49:56Z crazedsanity $
* $LastChangedDate: 2008-07-15 15:49:56 -0500 (Tue, 15 Jul 2008) $
* $LastChangedBy: crazedsanity $
* $LastChangedRevision: 298 $
*/
require_once(dirname(__FILE__) ."/cs_globalFunctions.php");
require_once(dirname(__FILE__) ."/cs_versionAbstract.class.php");
class cs_fileSystemClass extends cs_versionAbstract {
public $root; //actual root directory.
public $cwd; //current directory; relative to $this->root
public $realcwd; //$this->root .'/'. $this->cwd
public $dh; //directory handle.
public $fh; //file handle.
public $filename; //filename currently being used.
public $gf; //cs_globalFunctions{} object.
public $lineNum = NULL;
//========================================================================================
/**
* The constructor.
*/
public function __construct($rootDir=NULL, $cwd=NULL, $initialMode=NULL) {
//set the root directory that we'll be using; this is considered just like "/" in
// linux. Directories above it are considered non-existent.
if(($rootDir) AND (is_dir($rootDir))) {
// yup... use it.
$this->root = $rootDir;
} elseif(($GLOBALS['SITE_ROOT']) AND (is_dir($GLOBALS['SITE_ROOT']))) {
//not set, but SITE_ROOT is... use it.
$this->root = $GLOBALS['SITE_ROOT'];
} else {
//nothing useable... die.
exit("UNUSEABLE ROOT: $rootDir");
}
$this->gf = new cs_globalFunctions();
$this->root = $this->resolve_path_with_dots($this->root);
//set the CURRENT working directory... this should be a RELATIVE path to $this->root.
if(($cwd) AND (is_dir($rootDir .'/'. $cwd)) AND (!ereg($this->root, $cwd))) {
//looks good. Use it.
$this->cwd = $cwd;
$this->realcwd = $this->root .'/'. $cwd;
} else {
//no dice. Use the root.
$this->cwd = '/';
$this->realcwd = $this->root ;
}
chdir($this->realcwd);
//check for the initialMode...
$useableModes = array('r', 'r+', 'w', 'w+', 'a', 'a+', 'x', 'x+');
if(($initialMode) AND (in_array($initialMode, $useableModes))) {
//
$this->mode = $initialMode;
} else {
//define the DEFAULT mode.
$this->mode = "r+";
}
}//end __construct()
//========================================================================================
//========================================================================================
public function cdup() {
$retval = FALSE;
//easy way to go "up" a directory (just like doing "cd .." in linux)
if(strlen($this->cwd) > 1) {
$myCwd = preg_replace('/\/$/', '', $this->cwd);
if(!preg_match('/^\//', $myCwd)) {
$myCwd = '/'. $myCwd;
}
$myParts = explode('/', $myCwd);
array_pop($myParts);
$myCwd = $this->gf->string_from_array($myParts, NULL, '/');
$realCwd = $this->gf->create_list($this->root, $myCwd, '/');
if(file_exists($realCwd)) {
$retval = TRUE;
$this->realcwd = $realCwd;
$this->cwd = '/'. $myCwd;
}
}
return($retval);
}//end cdup()
//========================================================================================
//========================================================================================
/**
* Think of the linux version of "cd": we're changing the current directory.
*
* @param $newDir (str) dir to change to, either absolutte or relative.
*
* @return 0 (FAIL) unable to change directory
* @return 1 (PASS) success.
*/
public function cd($newDir) {
//check to see if it's a relative path or not...
//NOTE: non-relative paths should NOT include $this->root.
if((preg_match('/^\//', $newDir)) AND (is_dir($this->root .'/'. $newDir))) {
//not a relative path.
$this->cwd = '/'. $newDir;
$this->realcwd = $this->root . $newDir;
$retval = 1;
} elseif(is_dir($this->realcwd .'/'. $newDir)) {
//relative path...
$this->cwd = $this->gf->create_list($this->cwd, $newDir, '/');
$this->realcwd .= '/'. $newDir;
$retval = 1;
} else {
//bad.
$retval = 0;
}
$this->cwd = preg_replace('/\/\//', '/', $this->cwd);
$this->realcwd = preg_replace('/\/\//', '/', $this->realcwd);
return($retval);
}//end cd()
//========================================================================================
//========================================================================================
/**
* Just like the linux version of the 'ls' command.
*/
public function ls($filename=NULL, $args=NULL) {
clearstatcache();
//open the directory for reading.
$this->dh = opendir($this->realcwd);
clearstatcache();
if(is_string($filename)) {
//check to make sure the file exists.
$tFile=$this->filename2absolute($filename);
if(file_exists($tFile)) {
//it's there... get info about it.
$info = $this->get_fileinfo($tFile);
if($info['type'] == 'dir') {
$oldCwd = $this->cwd;
$oldRealCwd = $this->realcwd;
$this->cd($filename);
$retval = $this->ls();
$this->cwd = $oldCwd;
$this->realcwd = $oldRealCwd;
}
else {
$retval[$filename] = $info;
}
} else {
//stupid!
$retval[$filename] = "FILE NOT FOUND.";
}
} else {
//array if file/directory names to ignore if matched exactly.
$ignoreArr = array("CVS", ".svn", ".", "..");
while (($file = readdir($this->dh)) !== false) {
if(!in_array($file, $ignoreArr)) {
$tFile = $this->realcwd .'/'. $file;
$tType = filetype($tFile);
$retval[$file] = $this->get_fileinfo($tFile);
if(!$tType) {
debug_print("FILE: $tFile || TYPE: $tType || is_file(): ". is_file($tFile) ."is_dir(): ". is_dir($tFile));
exit;
}
#debug_print("FILE: $file || $dir". $file);
unset($tType);
}
}
}
#debug_print($retval);
#debug_print(readdir($this->dh));
return($retval);
}//end ls()
//========================================================================================
//========================================================================================
/**
* Grabs an array of information for a given file.
*/
public function get_fileinfo($tFile) {
$retval = array(
"size" => filesize($tFile),
"type" => @filetype($tFile),
"accessed" => fileatime($tFile),
"modified" => filemtime($tFile),
"owner" => $this->my_getuser_group(fileowner($tFile), 'uid'),
"uid" => fileowner($tFile),
"group" => $this->my_getuser_group(filegroup($tFile), 'gid'),
"gid" => filegroup($tFile),
"perms" => $this->translate_perms(fileperms($tFile)),
"perms_num" => substr(sprintf('%o', fileperms($tFile)), -4)
);
return($retval);
}//end get_fileinfo()
//========================================================================================
//========================================================================================
/**
* Gets the username/groupname of the given uid/gid.
*
* @param $int (int) uid/gid to check.
* @param $type (str) is it a uid or a gid?
*
* @return (string) groupname/username
*/
private function my_getuser_group($int, $type='uid') {
if($type == 'uid') {
$func = 'posix_getpwuid';
} elseif($type == 'gid') {
$func = 'posix_getgrgid';
} else {
$retval = $int;
}
if(!function_exists($func)) {
throw new exception(__METHOD__ .": required function missing (". $func .")");
}
$t = $func($int);
return($t['name']);
}//end my_getpwuid()
//========================================================================================
//========================================================================================
/**
* Translates the permissions string (like "0700") into a *nix-style permissions
* string (i.e. "rwx------").
*
* @param $in_Perms (int) permission number string
*
* @return (string) permissions string.
*
* NOTE: pretty sure I copied this from php.net, though I don't know the owner. If anybody
* can enlighten me, I'd be glad to give them credit.
*/
private function translate_perms($in_Perms) {
$sP = "";
$sP .= (($in_Perms & 0x0100) ? 'r' : '-') .
(($in_Perms & 0x0080) ? 'w' : '-') .
(($in_Perms & 0x0040) ? (($in_Perms & 0x0800) ? 's' : 'x' ) :
(($in_Perms & 0x0800) ? 'S' : '-'));
// group
$sP .= (($in_Perms & 0x0020) ? 'r' : '-') .
(($in_Perms & 0x0010) ? 'w' : '-') .
(($in_Perms & 0x0008) ? (($in_Perms & 0x0400) ? 's' : 'x' ) :
(($in_Perms & 0x0400) ? 'S' : '-'));
// world
$sP .= (($in_Perms & 0x0004) ? 'r' : '-') .
(($in_Perms & 0x0002) ? 'w' : '-') .
(($in_Perms & 0x0001) ? (($in_Perms & 0x0200) ? 't' : 'x' ) :
(($in_Perms & 0x0200) ? 'T' : '-'));
return($sP);
}//end translate_perms()
//========================================================================================
//========================================================================================
/**
* Creates an empty file... think of the linux command "touch".
*
* @param $filename (string) filename to create.
*
* @return 0 (FAIL) unable to create file.
* @return 1 (PASS) file created successfully.
*/
public function create_file($filename, $truncateFile=FALSE) {
$retval = 0;
$filename = $this->filename2absolute($filename);
$filename = $this->resolve_path_with_dots($filename);
$this->filename = $filename;
//check to see if the file exists...
if(!file_exists($filename)) {
if($this->is_writable(dirname($filename))) {
//no file. Create it.
$createFileRes = touch($filename);
if($createFileRes) {
$retval = 1;
}
else {
throw new exception(__METHOD__ .": invalid return from touch(". $filename ."), return was (". $createFileRes .")");
}
}
else {
throw new exception(__METHOD__ .": directory (". dirname($filename) .") is not writable");
}
}
elseif($truncateFile === TRUE) {
$this->truncate_file($filename);
}
else {
throw new exception(__METHOD__ .": file (". $filename .") exists and truncate not set");
}
return($retval);
}//end create_file()
//========================================================================================
//========================================================================================
/**
* Opens a stream/resource handle to use for doing file i/o.
*
* @param $filename (string) filename to open: should be relative to the current dir.
* @param $mode (string) mode to use: consult PHP.net's info for fopen().
*
* @return 0 (FAIL) unable to open file.
* @return 1 (PASS) file opened successfully.
*/
public function openFile($filename=NULL, $mode="r+") {
clearstatcache();
if(!strlen($filename) || is_null($filename)) {
$filename = $this->filename;
}
$filename = $this->filename2absolute($filename);
$this->filename = $filename;
if($this->is_readable($filename)) {
//make sure we've got a mode to use.
if(!is_string($mode)) {
$mode = "r+";
}
$this->mode = $mode;
if(in_array($this->mode, array("r+", "w", "w+", "a", "a+", "x", "x+")) && !$this->is_writable($filename)) {
throw new exception(__METHOD__ .": file is not writable (". $filename .") (". $this->is_writable($filename) ."), mode=(". $this->mode .")");
}
//attempt to open a stream to a file...
$this->fh = fopen($this->filename, $this->mode);
if(is_resource($this->fh)) {
//looks like we opened successfully.
$retval = 1;
$this->lineNum = 0;
} else {
//something bad happened.
$retval = 0;
}
}
else {
throw new exception(__METHOD__ .": file is unreadable (". $filename .")");
}
return($retval);
}//end openFile()
//========================================================================================
//========================================================================================
/**
* Write the given contents into the current file or the filename given.
*
* @param $content (str) Content to write into the file.
* @param $filename (str,optional) filename to use. If none given, the current one is used.
*
* @return 0 (FAIL) unable to write content to the file.
* @return (n) (PASS) wrote (n) bytes.
*/
public function write($content, $filename=NULL) {
//open the file for writing.
if(!$filename) {
$filename= $this->filename;
}
$this->filename = $filename;
//open the file...
$openResult = $this->openFile($this->filename, $this->mode);
//looks like we made it...
$retval = fwrite($this->fh, $content, strlen($content));
//done... return the result.
return($retval);
}//end write()
//========================================================================================
//========================================================================================
/**
* Takes the given filename & returns the ABSOLUTE pathname: checks to see if the given
* string already has the absolute path in it.
*/
private function filename2absolute($filename) {
clearstatcache();
$filename = $this->resolve_path_with_dots($filename);
//see if it starts with a "/"...
if(preg_match("/^\//", $filename)) {
$retval = $filename;
} else {
$retval=$this->realcwd .'/'. $filename;
$retval = $this->resolve_path_with_dots($retval);
#debug_print(__METHOD__ .": realcwd=(". $this->realcwd .")");
#$this->resolve_path_with_dots($retval);
}
if(!$this->check_chroot($retval, FALSE)) {
$this->gf->debug_print(func_get_args());
throw new exception(__METHOD__ .": file is outside of allowed directory (". $retval .")");
}
return($retval);
}//end filename2absolute()
//========================================================================================
//========================================================================================
/**
* Reads-in the contents of an entire file.
*/
function read($filename, $returnArray=FALSE) {
$myFile = $this->filename2absolute($filename);
if(!file_exists($myFile)) {
throw new exception(__METHOD__ .": file doesn't exist (". $myFile .")");
}
elseif($this->is_readable($myFile)) {
if($returnArray) {
$data = file($myFile);
}
else {
$data = file_get_contents($myFile);
}
if($data === FALSE) {
throw new exception(__METHOD__. ": file_get_contents() returned FALSE");
}
}
else {
throw new exception(__METHOD__. ": File isn't readable (". $myFile .")");
}
return($data);
}//end read()
//========================================================================================
//========================================================================================
public function rm($filename) {
$filename = $this->filename2absolute($filename);
return(unlink($filename));
}//end rm()
//========================================================================================
//========================================================================================
/**
* Return the next line for a file.
*
* When the end of the file is found, this method returns FALSE (returning NULL might be
* misconstrued as a blank line).
*/
public function get_next_line($maxLength=NULL, $trimLine=TRUE) {
if(is_resource($this->fh) && get_resource_type($this->fh) == 'stream') {
if(feof($this->fh)) {
$retval = FALSE;
}
else {
if(!is_numeric($maxLength)) {
$retval = @fgets($this->fh);
}
else {
$retval = fgets($this->fh, $maxLength);
}
if($trimLine) {
$retval = trim($retval);
}
if(is_null($this->lineNum) || !is_numeric($this->lineNum) || $this->lineNum < 0) {
throw new exception(__METHOD__ .": invalid data for lineNum (". $this->lineNum .")");
}
$this->lineNum++;
}
}
else {
throw new exception(__METHOD__ .": invalid filehandle");
}
return($retval);
}//end get_next_line()
//========================================================================================
//========================================================================================
public function append_to_file($data, $eolChar="\n") {
$retval = FALSE;
if(is_resource($this->fh)) {
$result = fwrite($this->fh, $data . $eolChar);
if($result === FALSE) {
throw new exception(__METHOD__ .": failed to write data to file");
}
else {
$this->lineNum++;
$retval = $result;
}
}
else {
throw new exception(__METHOD__ .": invalid filehandle");
}
return($retval);
}//end append_to_file()
//========================================================================================
//========================================================================================
public function closeFile() {
$retval = FALSE;
if(is_resource($this->fh)) {
fclose($this->fh);
$retval = TRUE;
}
//reset internal pointers.
$this->filename = NULL;
$this->lineNum = NULL;
return($retval);
}//end closeFile()
//========================================================================================
//========================================================================================
/**
* Compare the given filename to the open filename to see if they match (using this allows
* giving a filename instead of comparing the whole path).
*/
public function compare_open_filename($compareToFilename) {
if(!strlen($compareToFilename) || is_null($compareToFilename)) {
throw new exception(__METHOD__ .": invalid filename to compare");
}
elseif(!strlen($this->filename)) {
$retval = FALSE;
}
else {
$internalFilename = $this->filename2absolute($this->filename);
$compareToFilename = $this->filename2absolute($compareToFilename);
if($internalFilename == $compareToFilename) {
$retval = TRUE;
}
else {
$retval = FALSE;
}
}
return($retval);
}//end compare_open_filename()
//========================================================================================
//========================================================================================
/**
* Give a file a new name.
*
* TODO: check to make sure both files exist within our root.
*/
public function rename($currentFilename, $newFilename) {
if($newFilename == $currentFilename) {
$this->gf->debug_print(func_get_args());
throw new exception(__METHOD__ .": renaming file to same name");
}
if($this->compare_open_filename($currentFilename)) {
$this->closeFile();
}
if($this->compare_open_filename($newFilename)) {
//renaming a different file to our currently open file...
$this->gf->debug_print(func_get_args());
throw new exception(__METHOD__ .": renaming another file (". $currentFilename .") to the currently open filename (". $newFilename .")");
}
else {
$currentFilename = $this->filename2absolute($currentFilename);
$newFilename = $this->filename2absolute($newFilename);
if(!$this->is_writable(dirname($newFilename))) {
throw new exception(__METHOD__ .": directory isn't writable... ");
}
$retval = rename($currentFilename, $newFilename);
if($retval !== TRUE) {
throw new exception(__METHOD__ .": failed to rename file (". $retval .")");
}
}
return($retval);
}//end rename()
//========================================================================================
//========================================================================================
/**
* Check if the given filename is executable.
*/
public function is_executable($filename) {
$filename = $this->filename2absolute($filename);
$retval = FALSE;
if(strlen($filename)) {
$retval = is_executable($filename);
}
return($retval);
}//end is_executable()
//========================================================================================
//========================================================================================
/**
* Check if the given filename is readable.
*/
public function is_readable($filename) {
$filename = $this->filename2absolute($filename);
$retval = FALSE;
if(strlen($filename)) {
$retval = is_readable($filename);
}
return($retval);
}//end is_readable()
//========================================================================================
//========================================================================================
/**
* Check if the given filename/path is writable
*/
public function is_writable($filenameOrPath) {
$filenameOrPath = $this->filename2absolute($filenameOrPath);
$retval = FALSE;
if(strlen($filenameOrPath)) {
$retval = is_writable($filenameOrPath);
}
return($retval);
}//end is_writable()
//========================================================================================
//========================================================================================
/**
* Determines how many lines are left in the current file.
*/
public function count_remaining_lines() {
if(is_resource($this->fh) && get_resource_type($this->fh) == 'stream') {
$originalLineNum = $this->lineNum;
$myFilename = $this->filename;
$myNextLine = $this->get_next_line();
$retval = 0;
while($myNextLine !== FALSE) {
$retval++;
$myNextLine = $this->get_next_line();
}
$this->closeFile();
$this->openFile($myFilename, $this->mode);
if($originalLineNum > 0) {
while($originalLineNum > $this->lineNum) {
$this->get_next_line();
}
}
if($this->lineNum !== $originalLineNum) {
throw new exception(__METHOD__ .": failed to match-up old linenum (". $originalLineNum .") with the current one (". $this->lineNum .")");
}
}
else {
throw new exception(__METHOD__ .": Invalid filehandle, can't count remaining lines");
}
return($retval);
}//end count_remaining_files()
//========================================================================================
//========================================================================================
/**
* Moves the cursor to the given line number.
*
* NOTE: remember if you're trying to get line #1 (literally), then you'll
* want to go to line #0, then call get_next_line() to retrieve it... this
* is the traditional logical vs. programatic numbering issue. A.k.a. the
* "off-by-one problem".
*/
public function go_to_line($lineNum) {
$retval = FALSE;
if(is_resource($this->fh) && get_resource_type($this->fh) == 'stream') {
if($this->lineNum > $lineNum) {
//gotta "rewind" the cursor back to the beginning.
rewind($this->fh);
$this->lineNum=0;
}
if($lineNum == $this->lineNum) {
$retval = TRUE;
}
elseif($this->lineNum < $lineNum) {
while($this->lineNum < $lineNum) {
//don't grab any data, just move the cursor...
$this->get_next_line();
}
if($this->lineNum == $lineNum) {
$retval = TRUE;
}
else {
throw new exception(__METHOD__ .": couldn't reach the line (". $lineNum ."), failed at (". $this->lineNum .")");
}
}
else {
throw new exception(__METHOD__ .": internal lineNum (". $this->lineNum .") couldn't be retrieved or reset to (". $lineNum .")");
}
}
return($retval);
}//end go_to_line()
//========================================================================================
//========================================================================================
/**
* Fix a path that contains "../".
*
* EXAMPLE: changes "/home/user/blah/blah/../../test" into "/home/user/test"
*/
public function resolve_path_with_dots($path) {
while(preg_match('/\/\//', $path)) {
$path = preg_replace('/\/\//', '/', $path);
}
$retval = $path;
if(strlen($path) && preg_match('/\./', $path)) {
$isAbsolute = FALSE;
if(preg_match('/^\//', $path)) {
$isAbsolute = TRUE;
$path = preg_replace('/^\//', '', $path);
}
$pieces = explode('/', $path);
$finalPieces = array();
for($i=0; $i < count($pieces); $i++) {
$dirName = $pieces[$i];
if($dirName == '.') {
//do nothing; don't bother appending.
}
elseif($dirName == '..') {
$rippedIndex = array_pop($finalPieces);
}
else {
$finalPieces[] = $dirName;
}
}
$retval = $this->gf->string_from_array($finalPieces, NULL, '/');
if($isAbsolute) {
$retval = '/'. $retval;
}
}
return($retval);
}//end resolve_path_with_dots()
//========================================================================================
//========================================================================================
private function check_chroot($path, $translatePath=TRUE) {
if($translatePath === TRUE) {
$path = $this->filename2absolute($path);
}
//now, let's go through the root directory structure, & make sure $path is within that.
$rootPieces = explode('/', $this->root);
$pathPieces = explode('/', $path);
if($rootPieces[0] == '') {
array_shift($rootPieces);
}
if($rootPieces[(count($rootPieces) -1)] == '') {
array_pop($rootPieces);
}
if($pathPieces[0] == '') {
array_shift($pathPieces);
}
$retval = TRUE;
$tmp = '';
foreach($rootPieces as $index=>$dirName) {
$pathDir = $pathPieces[$index];
if($pathDir != $dirName) {
$retval = FALSE;
$this->gf->debug_print(__METHOD__ .": failed... tmp=(". $tmp ."), dirName=(". $dirName .")");
break;
}
$tmp = $this->gf->create_list($tmp, $dirName, '/');
}
return($retval);
}//end check_chroot()
//========================================================================================
//========================================================================================
public function copy_file($filename, $destination) {
$retval = FALSE;
if($this->openFile($filename)) {
if($this->check_chroot($destination)) {
//okay, try to copy.
$retval = copy($this->fh, $destination);
}
else {
throw new exception(__METHOD__ .':: destination is not in the directory path');
}
}
return($retval);
}//end copy_file()
//========================================================================================
//========================================================================================
public function move_file($filename, $destination) {
$retval = FALSE;
if($this->is_readable($filename)) {
if($this->check_chroot($destination)) {
//do the move.
$retval = rename($filename, $destination);
}
else {
$this->gf->debug_print(__METHOD__ .":: ". $this->check_chroot($destination),1);
throw new exception(__METHOD__ .':: destination is not in the directory path (from=['. $filename .'], to=['. $destination .']');
}
}
return($retval);
}//end move_file()
//========================================================================================
//========================================================================================
public function mkdir($name, $mode=0777) {
if(!is_numeric($mode) || strlen($mode) != 4) {
$mode = 0777;
}
$retval = NULL;
if(!is_null($name) && strlen($name)) {
$name = $this->filename2absolute($name);
if($this->check_chroot($name)) {
$retval = mkdir($name, $mode);
chmod($name, $mode);
}
else {
throw new exception(__METHOD__ .': ('. $name .') isn\'t within chroot');
}
}
else {
throw new exception(__METHOD__ .': invalid data: ('. $name .')');
}
return($retval);
}//end mkdir()
//========================================================================================
//========================================================================================
public function truncate_file($filename) {
if($this->is_writable($filename)) {
if($this->openFile($filename)) {
$retval = ftruncate($this->fh,0);
$this->closeFile();
}
else {
throw new exception(__METHOD__ .": unable to open specified file (". $filename .")");
}
}
else {
throw new exception(__METHOD__ .": Cannot truncate, file (". $filename .") is not writable");
}
return($retval);
}//end truncate_file()
//========================================================================================
//========================================================================================
public function go_to_last_line() {
if(is_resource($this->fh) && get_resource_type($this->fh) == 'stream') {
if(feof($this->fh)) {
$retval = TRUE;
}
else {
//NOTE::: fseek() doesn't update the file pointer in $this->fh, so we have to use fgets(), which seems faster anyway.
while(!feof($this->fh)) {
fgets($this->fh);
$this->lineNum++;
}
$retval = TRUE;
}
}
else {
throw new exception(__METHOD__ .": invalid filehandle");
}
return($retval);
}//end go_to_last_line()
//========================================================================================
}//end cs_filesystemClass{}
?>