blob: 6834e1f841baf667f731b96430fa038fa5a33ef8 [file] [log] [blame]
<?php
/**
* File contains the ezcArchiveEntry class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
* @package Archive
* @version //autogentag//
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
*/
/**
* The ezcArchiveEntry class provides system-independent file information.
*
* ezcArchiveEntry provides file information about the file path, it's access rights and whether the file is an
* directory, symbolic link, hard link, block-file, etc. The owner name, the group name, the last access time
* are also available. ezcArchiveEntry can be used to get the file information directly from the file-system or
* from an archive.
*
* The main purpose of ezcArchiveEntry is to provide information about:
* - Files on the file-system that should be appended to the archive.
* - Files currently in the archive that can be extracted to the file-system.
*
* Use the {@link getEntryFromFile()} to create an ezcArchiveEntry from a file in the filesystem.
* Important is that the prefix is set.
* This specifies which part of the path should be stripped, before the entry is appended to the archive.
* See also {@link ezcArchive::append()} or {@link ezcArchive::appendToCurrent()}.
*
* When the ezcArchiveEntry is an entry in the archive, the {@link getPath()} method contains always an
* relative path, and the prefix is not set.
*
* @package Archive
* @version //autogentag//
*/
class ezcArchiveEntry
{
/**
* Is a regular file.
*/
const IS_FILE = 0;
/**
* Is a hard link.
*/
const IS_LINK = 1;
/**
* Is a symbolic link.
*/
const IS_SYMBOLIC_LINK = 2;
/**
* Is a character device.
*/
const IS_CHARACTER_DEVICE = 3;
/**
* Is a block device.
*/
const IS_BLOCK_DEVICE = 4;
/**
* Is a directory.
*/
const IS_DIRECTORY = 5;
/**
* Is a FIFO.
*/
const IS_FIFO = 6;
/**
* Not used, is Tar specific?
*/
const IS_RESERVED = 7;
/**
* Contains the file information.
*
* @var ezcArchiveFileStructure
*/
protected $fileStructure;
/**
* The prefix of the file that may be removed from the path.
*
* @var string
*/
protected $prefix;
/**
* Constructs an archiveEntry from the {@link ezcArchiveFileStructure}.
*
* The $struct parameter contains the raw file information.
* This class encapsulates the file information structure and provides convenient methods to retrieve
* the information.
*
* @param ezcArchiveFileStructure $struct
*/
public function __construct( ezcArchiveFileStructure $struct )
{
$this->fileStructure = $struct;
}
/**
* Returns true when this entry represents a directory.
*
* @return bool
*/
public function isDirectory()
{
return ( $this->fileStructure->type == self::IS_DIRECTORY );
}
/**
* Returns true when this entry represents a file.
*
* @return bool
*/
public function isFile()
{
return ( $this->fileStructure->type == self::IS_FILE );
}
/**
* Returns true when this entry represents a hard link.
*
* @return bool
*/
public function isHardLink()
{
return ( $this->fileStructure->type == self::IS_LINK );
}
/**
* Returns true when this entry represents a symbolic link.
*
* @return bool
*/
public function isSymLink()
{
return ( $this->fileStructure->type == self::IS_SYMBOLIC_LINK );
}
/**
* Returns true when this entry represents a symbolic or a hard link.
*
* @return bool
*/
public function isLink()
{
return ( $this->isHardLink() || $this->isSymLink() );
}
/**
* Returns type of the entry.
*
* @return int Possible values are: {@link IS_LINK}, {@link IS_SYMBOLIC_LINK}, {@link IS_CHARACTER_DEVICE}, {@link IS_BLOCK_DEVICE},
* {@link IS_DIRECTORY}, or {@link IS_FIFO}.
*/
public function getType()
{
return $this->fileStructure->type;
}
/**
* Returns the user ID of the entry.
*
* @return int
*/
public function getUserId()
{
return $this->fileStructure->uid;
}
/**
* Returns the group ID of the entry.
*
* @return int
*/
public function getGroupId()
{
return $this->fileStructure->gid;
}
/**
* Returns the complete path or path without the prefix.
*
* By default the full path is returned, unless the $withPrefix is set
* to true.
*
* @param bool $withPrefix
*
* @return string
*/
public function getPath( $withPrefix = true )
{
if ( $withPrefix )
{
return $this->fileStructure->path;
}
else
{
return $this->getPathWithoutPrefix( $this->fileStructure->path, $this->prefix );
}
}
/**
* Returns the path without the prefix.
*
* The path without the prefix is returned.
* If the prefix doesn't match with the complete path, the whole path is returned.
*
* @param string $completePath
* @param string $prefix
* @return string
*/
private function getPathWithoutPrefix( $completePath, $prefix )
{
$prefixLength = strlen( $prefix );
if ( strcmp( substr( $completePath, 0, $prefixLength ), $prefix ) == 0 )
{
$i = 0;
// Check next character
while ( $completePath[$i + $prefixLength] == "/" )
{
$i++;
}
$result = substr( $completePath, $prefixLength + $i );
return $result;
}
else
{
// No match.
return $completePath;
}
}
/**
* Removes the prefix from the path and clears the prefix.
*
* This method is useful when it comes to adding a entry to an archive
* and the complete path to the file is no longer needed.
*/
public function removePrefixFromPath()
{
$this->fileStructure->path = $this->getPathWithoutPrefix( $this->fileStructure->path, $this->prefix );
$this->prefix = "";
}
/**
* Returns the link with or without prefix.
*
* This method is similar to {@link getPath()}, but returns the link instead of the path.
* If the current does not represents a link, an empty string is returned.
* Use the {@link isLink()} method to see if the current entry is a link.
*
* @param bool $withPrefix
*/
public function getLink( $withPrefix = true )
{
if ( $withPrefix )
{
return $this->fileStructure->link;
}
else
{
return $this->getPathWithoutPrefix( $this->fileStructure->link, $this->prefix );
}
}
/**
* Returns a bit mask representing the permissions of this entry.
*
* It returns the permissions in octal numbers as a string, for example:
* "0000755"
*
* @return string
*/
public function getPermissions()
{
return $this->fileStructure->mode;
}
/**
* Returns the file size.
*
* @return int
*/
public function getSize()
{
return $this->fileStructure->size;
}
/**
* Returns the modification time as a timestamp.
*
* @return int
*/
public function getModificationTime()
{
return $this->fileStructure->mtime;
}
/**
* Returns the last access time as a timestamp.
*
* @return int
*/
public function getAccessTime()
{
return $this->fileStructure->atime;
}
/**
* Returns the inode.
*
* @return int
*/
public function getInode()
{
return $this->fileStructure->ino;
}
/**
* Returns the major device number.
*
* @return int
*/
public function getMajor()
{
return $this->fileStructure->major;
}
/**
* Returns the minor device number.
*
* @return int
*/
public function getMinor()
{
return $this->fileStructure->minor;
}
/**
* Returns the device.
*
* FIXME DEPRECATED?
*
* @return int
*/
public function getDevice()
{
return $this->fileStructure->device;
}
/**
* Returns the permissions as a string.
*
* If the entry has all the permissions, it will return: "rwxrwx" Where the first three letters
* represent the group permissions and the last three letters the user permissions.
*
* @return string
*/
public function getPermissionsString()
{
$out = "";
$perm = octdec( $this->getPermissions() );
for ( $i = 6; $i >= 0; $i -= 3 )
{
$part = ( $perm >> $i );
if ( $part & 4 )
{
$out .= "r";
}
else
{
$out .= "-";
}
if ( $part & 2 )
{
$out .= "w";
}
else
{
$out .= "-";
}
if ( $part & 1 )
{
$out .= "x";
}
else
{
$out .= "-";
}
}
return $out;
}
/**
* Returns the type string for the current type of the entry.
*
* Returns a type string for the current entry. If the entry is a:
* - directory: "d".
* - file: "-".
* - symbolic link: "l".
* - hard link: "h".
*
* @return string
*/
public function getTypeString()
{
switch ( $this->fileStructure->type )
{
case self::IS_DIRECTORY:
return "d";
case self::IS_FILE:
return "-";
case self::IS_SYMBOLIC_LINK:
return "l";
case self::IS_LINK:
return "h";
default:
return "Z";
}
}
/**
* Sets the prefix.
*
* @param string $prefix
*/
public function setPrefix( $prefix )
{
$this->prefix = $prefix;
}
/**
* Returns the prefix.
*
* @return string
*/
public function getPrefix( )
{
return $this->prefix;
}
/**
* Returns a string representing the current entry.
*
* @return string
*/
public function __toString()
{
$out = $this->getTypeString();
$out .= $this->getPermissionsString();
$out .= " ";
$out .= $this->getUserId() . " ";
$out .= $this->getGroupId() . " ";
$out .= str_pad( $this->getSize(), 7, " ", STR_PAD_LEFT ) . " ";
$out .= date( "Y-m-d H:i:s ", $this->getModificationTime() );
$out .= $this->getPath();
$out .= ( $this->isLink() ? " -> " . $this->getLink() : "" );
return $out;
}
/**
* Create a file structure from a $file in the file system.
*
* @param string $file
* @return ezcArchiveFileStructure
*/
protected static function getFileStructureFromFile( $file )
{
clearstatcache();
$stat = ( is_link( $file ) ? lstat( $file ) : stat( $file ) );
$lstat = lstat( $file );
// Set the file information.
$struct = new ezcArchiveFileStructure();
$struct->path = $file;
$struct->gid = $stat["gid"];
$struct->uid = $stat["uid"];
$struct->mtime = $stat["mtime"];
$struct->atime = $stat["atime"];
$struct->mode = decoct( $stat["mode"] & ezcArchiveStatMode::S_PERM_MASK ); // First bits describe the type.
$struct->ino = $stat["ino"];
$struct->type = self::getLinkType( $stat );
if ( $struct->type == ezcArchiveEntry::IS_FILE )
{
$struct->size = $stat["size"];
}
else
{
$struct->size = 0;
}
if ( $struct->type == ezcArchiveEntry::IS_SYMBOLIC_LINK )
{
$struct->link = readlink( $file );
}
$rdev = $stat["rdev"];
if ( $rdev == -1 )
{
if ( $struct->type == ezcArchiveEntry::IS_BLOCK_DEVICE || $struct->type == ezcArchiveEntry::IS_CHARACTER_DEVICE)
{
throw new ezcArchiveException( "Cannot add a device to the TAR because the device type cannot be determined. Your system / PHP version does not support 'st_blksize'." );
}
$rdev = 0;
}
$struct->major = ( int ) ( $rdev / 256 );
$struct->minor = ( int ) ( $rdev % 256 );
return $struct;
}
/**
* Returns one or an array of ezcArchiveEntry's from one or multiple files in the file system.
*
* One or multiple ezcArchiveEntry's are created upon the given files.
* The prefix will directly set for the ezcArchiveEntry with $prefix.
*
* If $files contains a path to a file, then one ezcArchiveEntry will be created and returned.
* If $files is an array of paths, then all those ezcArchiveEntry's will be created and returned in an array.
*
* When multiple files are given in an array, this method will search for hard links among other files in the array.
*
* @throws ezcArchiveEntryPrefixException if the prefix is invalid.
*
* @param string|array(string) $files
* @param string $prefix
* @return ezcArchiveEntry
*/
public static function getEntryFromFile( $files, $prefix )
{
$isArray = true;
if ( !is_array( $files ) )
{
$isArray = false;
$files = array( $files );
}
$inodes = array();
$i = 0;
foreach ( $files as $file )
{
if ( !file_exists( $file ) && !is_link( $file ) )
return false;
$struct = self::getFileStructureFromFile( $file );
// Check if it's a hardlink if the OS supports it, and handle it.
if ( ezcBaseFeatures::supportsLink() )
{
if ( isset( $inodes[ $struct->ino ] ) )
{
// Yes, it's a hardlink.
$struct->type = ezcArchiveEntry::IS_LINK;
$struct->size = 0;
$struct->link = $inodes[ $struct->ino ];
}
else
{
$inodes[ $struct->ino ] = $struct->path;
}
}
$entry[$i] = new ezcArchiveEntry( $struct );
$entry[$i]->setPrefix( $prefix );
if ( isset( $prefix ) && strlen( $prefix ) > 0 )
{
if ( strlen( $entry[$i]->getPath() ) == strlen( $entry[$i]->getPath( false ) ) )
{
throw new ezcArchiveEntryPrefixException( $prefix, $file );
}
}
$i++;
}
return ( $isArray ? $entry : $entry[0] );
}
/**
* Returns an ezcArchiveEntry-type that corresponds to the ezcArchiveStatMode-type
*
* @param ezcArchiveStatMode $stat Possible values are: {@link ezcArchiveStatMode::S_IFIFO}, {@link ezcArchiveStatMode::S_IFCHR},
* {@link ezcArchiveStatMode::S_IFDIR}, {@link ezcArchiveStatMode::S_IFBLK},
* {@link ezcArchiveStatMode::S_IFREG}, or {@link ezcArchiveStatMode::S_IFLNK}.
*
* @return int Possible values are: {@link IS_LINK}, {@link IS_SYMBOLIC_LINK}, {@link IS_CHARACTER_DEVICE},
* {@link IS_BLOCK_DEVICE}, {@link IS_DIRECTORY}, or {@link IS_FIFO}.
*/
protected static function getLinkType( $stat )
{
switch ( $stat["mode"] & ezcArchiveStatMode::S_IFMT )
{
case ezcArchiveStatMode::S_IFIFO:
return ezcArchiveEntry::IS_FIFO;
case ezcArchiveStatMode::S_IFCHR:
return ezcArchiveEntry::IS_CHARACTER_DEVICE;
case ezcArchiveStatMode::S_IFDIR:
return ezcArchiveEntry::IS_DIRECTORY;
case ezcArchiveStatMode::S_IFBLK:
return ezcArchiveEntry::IS_BLOCK_DEVICE;
case ezcArchiveStatMode::S_IFREG:
return ezcArchiveEntry::IS_FILE;
case ezcArchiveStatMode::S_IFLNK:
return ezcArchiveEntry::IS_SYMBOLIC_LINK;
// Hardlinks are not resolved here. FIXME?
}
return false;
}
}
?>