2011-09-28 22:48:17 +00:00
< ? php
/*
* This file is part of Composer .
*
* ( c ) Nils Adermann < naderman @ naderman . de >
* Jordi Boggiano < j . boggiano @ seld . be >
*
* For the full copyright and license information , please view the LICENSE
* file that was distributed with this source code .
*/
2012-02-09 17:45:28 +00:00
namespace Composer\Util ;
2012-01-03 16:03:17 +00:00
2012-11-19 09:29:32 +00:00
use RecursiveDirectoryIterator ;
use RecursiveIteratorIterator ;
2016-01-27 23:56:02 +00:00
use Symfony\Component\Filesystem\Exception\IOException ;
2014-06-09 11:12:42 +00:00
use Symfony\Component\Finder\Finder ;
2012-11-19 09:29:32 +00:00
2011-09-28 22:48:17 +00:00
/**
* @ author Jordi Boggiano < j . boggiano @ seld . be >
2012-07-23 15:30:11 +00:00
* @ author Johannes M . Schmitt < schmittjoh @ gmail . com >
2011-09-28 22:48:17 +00:00
*/
class Filesystem
{
2012-08-10 08:14:02 +00:00
private $processExecutor ;
public function __construct ( ProcessExecutor $executor = null )
{
$this -> processExecutor = $executor ? : new ProcessExecutor ();
}
2012-09-07 18:25:07 +00:00
public function remove ( $file )
{
if ( is_dir ( $file )) {
return $this -> removeDirectory ( $file );
}
if ( file_exists ( $file )) {
2014-06-29 16:49:45 +00:00
return $this -> unlink ( $file );
2012-09-07 18:25:07 +00:00
}
return false ;
}
2013-09-26 09:38:33 +00:00
/**
* Checks if a directory is empty
*
2013-10-11 23:12:02 +00:00
* @ param string $dir
2013-09-26 09:38:33 +00:00
* @ return bool
*/
public function isDirEmpty ( $dir )
{
2014-06-09 11:12:42 +00:00
$finder = Finder :: create ()
-> ignoreVCS ( false )
-> ignoreDotFiles ( false )
-> depth ( 0 )
-> in ( $dir );
2013-09-26 09:38:33 +00:00
2014-06-09 11:12:42 +00:00
return count ( $finder ) === 0 ;
2014-06-04 12:20:36 +00:00
}
public function emptyDirectory ( $dir , $ensureDirectoryExists = true )
{
2014-06-29 10:49:11 +00:00
if ( file_exists ( $dir ) && is_link ( $dir )) {
2014-06-29 16:49:45 +00:00
$this -> unlink ( $dir );
2014-06-29 10:49:11 +00:00
}
2014-06-04 12:20:36 +00:00
if ( $ensureDirectoryExists ) {
$this -> ensureDirectoryExists ( $dir );
}
if ( is_dir ( $dir )) {
2014-06-09 11:12:42 +00:00
$finder = Finder :: create ()
-> ignoreVCS ( false )
-> ignoreDotFiles ( false )
-> depth ( 0 )
-> in ( $dir );
foreach ( $finder as $path ) {
$this -> remove (( string ) $path );
2014-06-04 12:20:36 +00:00
}
}
2013-09-26 09:38:33 +00:00
}
2012-11-19 09:29:32 +00:00
/**
* Recursively remove a directory
*
* Uses the process component if proc_open is enabled on the PHP
* installation .
*
2015-09-28 09:51:14 +00:00
* @ param string $directory
2014-07-16 13:17:38 +00:00
* @ throws \RuntimeException
2015-09-28 09:51:14 +00:00
* @ return bool
2012-11-19 09:29:32 +00:00
*/
2011-12-03 14:39:06 +00:00
public function removeDirectory ( $directory )
2011-09-28 22:48:17 +00:00
{
2014-07-24 13:08:25 +00:00
if ( $this -> isSymlinkedDirectory ( $directory )) {
return $this -> unlinkSymlinkedDirectory ( $directory );
2014-06-29 10:49:11 +00:00
}
2016-01-27 23:33:11 +00:00
if ( $this -> isJunction ( $directory )) {
return $this -> removeJunction ( $directory );
}
2014-06-29 16:49:45 +00:00
if ( ! file_exists ( $directory ) || ! is_dir ( $directory )) {
2012-03-07 23:12:38 +00:00
return true ;
}
2013-11-14 20:21:08 +00:00
if ( preg_match ( '{^(?:[a-z]:)?[/\\\\]+$}i' , $directory )) {
throw new \RuntimeException ( 'Aborting an attempted deletion of ' . $directory . ', this was probably not intended, if it is a real use case please report it.' );
}
2012-11-19 09:29:32 +00:00
if ( ! function_exists ( 'proc_open' )) {
return $this -> removeDirectoryPhp ( $directory );
}
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows ()) {
2014-09-24 16:30:12 +00:00
$cmd = sprintf ( 'rmdir /S /Q %s' , ProcessExecutor :: escape ( realpath ( $directory )));
2011-09-28 22:48:17 +00:00
} else {
2014-09-24 16:30:12 +00:00
$cmd = sprintf ( 'rm -rf %s' , ProcessExecutor :: escape ( $directory ));
2011-09-28 22:48:17 +00:00
}
2012-01-18 07:56:35 +00:00
2013-02-11 21:52:06 +00:00
$result = $this -> getProcess () -> execute ( $cmd , $output ) === 0 ;
2012-03-07 23:12:38 +00:00
2014-06-29 16:49:45 +00:00
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
2012-03-07 23:12:38 +00:00
2014-06-29 16:49:45 +00:00
if ( $result && ! file_exists ( $directory )) {
return true ;
2014-05-12 21:12:57 +00:00
}
return $this -> removeDirectoryPhp ( $directory );
2011-09-28 22:48:17 +00:00
}
2011-12-03 14:39:06 +00:00
2012-11-19 09:29:32 +00:00
/**
* Recursively delete directory using PHP iterators .
*
* Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
* before directories , creating a single non - recursive loop
* to delete files / directories in the correct order .
*
2013-01-05 19:01:58 +00:00
* @ param string $directory
2012-11-19 09:29:32 +00:00
* @ return bool
*/
public function removeDirectoryPhp ( $directory )
{
2016-02-28 14:06:36 +00:00
try {
$it = new RecursiveDirectoryIterator ( $directory , RecursiveDirectoryIterator :: SKIP_DOTS );
} catch ( \UnexpectedValueException $e ) {
// re-try once after clearing the stat cache if it failed as it
// sometimes fails without apparent reason, see https://github.com/composer/composer/issues/4009
clearstatcache ();
usleep ( 100000 );
2016-02-28 14:08:43 +00:00
if ( ! is_dir ( $directory )) {
return true ;
}
2016-02-28 14:06:36 +00:00
$it = new RecursiveDirectoryIterator ( $directory , RecursiveDirectoryIterator :: SKIP_DOTS );
}
2012-11-19 09:29:32 +00:00
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: CHILD_FIRST );
foreach ( $ri as $file ) {
if ( $file -> isDir ()) {
2014-06-29 16:49:45 +00:00
$this -> rmdir ( $file -> getPathname ());
2012-11-19 09:29:32 +00:00
} else {
2014-06-29 16:49:45 +00:00
$this -> unlink ( $file -> getPathname ());
2012-11-19 09:29:32 +00:00
}
}
2014-06-29 16:49:45 +00:00
return $this -> rmdir ( $directory );
2012-11-19 09:29:32 +00:00
}
2011-12-03 14:39:06 +00:00
public function ensureDirectoryExists ( $directory )
{
if ( ! is_dir ( $directory )) {
if ( file_exists ( $directory )) {
throw new \RuntimeException (
$directory . ' exists and is not a directory.'
);
}
2013-02-20 13:51:15 +00:00
if ( !@ mkdir ( $directory , 0777 , true )) {
2011-12-03 14:39:06 +00:00
throw new \RuntimeException (
$directory . ' does not exist and could not be created.'
);
}
}
}
2011-12-03 19:44:00 +00:00
2014-06-29 16:49:45 +00:00
/**
* Attempts to unlink a file and in case of failure retries after 350 ms on windows
*
2015-09-28 09:51:14 +00:00
* @ param string $path
2014-07-16 13:17:38 +00:00
* @ throws \RuntimeException
2015-09-28 09:51:14 +00:00
* @ return bool
2014-06-29 16:49:45 +00:00
*/
public function unlink ( $path )
{
2014-07-28 21:42:53 +00:00
if ( !@ $this -> unlinkImplementation ( $path )) {
2014-06-29 16:49:45 +00:00
// retry after a bit on windows since it tends to be touchy with mass removals
2016-02-03 21:39:16 +00:00
if ( ! Platform :: isWindows () || ( usleep ( 350000 ) && !@ $this -> unlinkImplementation ( $path ))) {
2014-06-29 16:49:45 +00:00
$error = error_get_last ();
$message = 'Could not delete ' . $path . ': ' . @ $error [ 'message' ];
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows ()) {
2014-06-29 16:49:45 +00:00
$message .= " \n This can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed " ;
}
throw new \RuntimeException ( $message );
}
}
return true ;
}
/**
* Attempts to rmdir a file and in case of failure retries after 350 ms on windows
*
2015-09-28 09:51:14 +00:00
* @ param string $path
2014-07-16 13:17:38 +00:00
* @ throws \RuntimeException
2015-09-28 09:51:14 +00:00
* @ return bool
2014-06-29 16:49:45 +00:00
*/
public function rmdir ( $path )
{
if ( !@ rmdir ( $path )) {
// retry after a bit on windows since it tends to be touchy with mass removals
2016-02-03 21:39:16 +00:00
if ( ! Platform :: isWindows () || ( usleep ( 350000 ) && !@ rmdir ( $path ))) {
2014-06-29 16:49:45 +00:00
$error = error_get_last ();
$message = 'Could not delete ' . $path . ': ' . @ $error [ 'message' ];
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows ()) {
2014-06-29 16:49:45 +00:00
$message .= " \n This can be due to an antivirus or the Windows Search Indexer locking the file while they are analyzed " ;
}
throw new \RuntimeException ( $message );
}
}
return true ;
}
2012-11-19 10:21:41 +00:00
/**
* Copy then delete is a non - atomic version of { @ link rename } .
*
2013-01-26 18:43:01 +00:00
* Some systems can 't rename and also don' t have proc_open ,
2012-11-19 10:21:41 +00:00
* which requires this solution .
*
* @ param string $source
* @ param string $target
*/
public function copyThenRemove ( $source , $target )
{
2014-09-22 14:09:55 +00:00
if ( ! is_dir ( $source )) {
copy ( $source , $target );
$this -> unlink ( $source );
return ;
}
2012-11-19 10:21:41 +00:00
$it = new RecursiveDirectoryIterator ( $source , RecursiveDirectoryIterator :: SKIP_DOTS );
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: SELF_FIRST );
2013-09-20 03:02:06 +00:00
$this -> ensureDirectoryExists ( $target );
2012-11-19 10:21:41 +00:00
foreach ( $ri as $file ) {
$targetPath = $target . DIRECTORY_SEPARATOR . $ri -> getSubPathName ();
if ( $file -> isDir ()) {
2013-09-20 03:02:06 +00:00
$this -> ensureDirectoryExists ( $targetPath );
2012-11-19 10:21:41 +00:00
} else {
copy ( $file -> getPathname (), $targetPath );
}
}
$this -> removeDirectoryPhp ( $source );
}
2012-07-23 15:30:11 +00:00
public function rename ( $source , $target )
{
2012-09-07 18:25:07 +00:00
if ( true === @ rename ( $source , $target )) {
2012-07-23 15:30:11 +00:00
return ;
}
2012-11-19 12:51:24 +00:00
if ( ! function_exists ( 'proc_open' )) {
return $this -> copyThenRemove ( $source , $target );
}
2012-11-19 10:21:41 +00:00
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows ()) {
2012-09-07 18:25:07 +00:00
// Try to copy & delete - this is a workaround for random "Access denied" errors.
2015-11-04 15:46:27 +00:00
$command = sprintf ( 'xcopy %s %s /E /I /Q /Y' , ProcessExecutor :: escape ( $source ), ProcessExecutor :: escape ( $target ));
2013-03-21 14:21:54 +00:00
$result = $this -> processExecutor -> execute ( $command , $output );
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
if ( 0 === $result ) {
2012-09-07 18:25:07 +00:00
$this -> remove ( $source );
return ;
}
} else {
// We do not use PHP's "rename" function here since it does not support
// the case where $source, and $target are located on different partitions.
2014-09-24 16:30:12 +00:00
$command = sprintf ( 'mv %s %s' , ProcessExecutor :: escape ( $source ), ProcessExecutor :: escape ( $target ));
2013-03-21 14:21:54 +00:00
$result = $this -> processExecutor -> execute ( $command , $output );
// clear stat cache because external processes aren't tracked by the php stat cache
clearstatcache ();
if ( 0 === $result ) {
2012-08-10 08:14:02 +00:00
return ;
}
2012-07-23 15:30:11 +00:00
}
2012-09-07 18:25:07 +00:00
2013-07-25 16:02:01 +00:00
return $this -> copyThenRemove ( $source , $target );
2012-07-23 15:30:11 +00:00
}
2011-12-03 19:44:00 +00:00
/**
* Returns the shortest path from $from to $to
*
2013-06-13 11:28:24 +00:00
* @ param string $from
* @ param string $to
* @ param bool $directories if true , the source / target are considered to be directories
2013-06-13 00:05:44 +00:00
* @ throws \InvalidArgumentException
2011-12-03 19:44:00 +00:00
* @ return string
*/
2012-03-23 11:49:29 +00:00
public function findShortestPath ( $from , $to , $directories = false )
2011-12-03 19:44:00 +00:00
{
if ( ! $this -> isAbsolutePath ( $from ) || ! $this -> isAbsolutePath ( $to )) {
2012-07-23 19:58:47 +00:00
throw new \InvalidArgumentException ( sprintf ( '$from (%s) and $to (%s) must be absolute paths.' , $from , $to ));
2011-12-03 19:44:00 +00:00
}
2013-03-13 21:13:32 +00:00
$from = lcfirst ( $this -> normalizePath ( $from ));
$to = lcfirst ( $this -> normalizePath ( $to ));
2012-03-10 01:14:40 +00:00
2012-03-23 11:49:29 +00:00
if ( $directories ) {
2015-06-04 17:44:09 +00:00
$from = rtrim ( $from , '/' ) . '/dummy_file' ;
2012-03-22 09:11:48 +00:00
}
2011-12-03 19:44:00 +00:00
if ( dirname ( $from ) === dirname ( $to )) {
return './' . basename ( $to );
}
2013-09-26 12:34:41 +00:00
$commonPath = $to ;
2013-10-28 10:01:17 +00:00
while ( strpos ( $from . '/' , $commonPath . '/' ) !== 0 && '/' !== $commonPath && ! preg_match ( '{^[a-z]:/?$}i' , $commonPath )) {
$commonPath = strtr ( dirname ( $commonPath ), '\\' , '/' );
2011-12-03 19:44:00 +00:00
}
2013-10-28 10:01:17 +00:00
if ( 0 !== strpos ( $from , $commonPath ) || '/' === $commonPath ) {
2011-12-03 19:44:00 +00:00
return $to ;
}
$commonPath = rtrim ( $commonPath , '/' ) . '/' ;
$sourcePathDepth = substr_count ( substr ( $from , strlen ( $commonPath )), '/' );
$commonPathCode = str_repeat ( '../' , $sourcePathDepth );
2012-05-22 10:07:08 +00:00
2011-12-04 21:40:30 +00:00
return ( $commonPathCode . substr ( $to , strlen ( $commonPath ))) ? : './' ;
2011-12-03 19:44:00 +00:00
}
/**
* Returns PHP code that , when executed in $from , will return the path to $to
*
2013-06-13 11:28:24 +00:00
* @ param string $from
* @ param string $to
* @ param bool $directories if true , the source / target are considered to be directories
2016-04-11 10:08:27 +00:00
* @ param bool $staticCode
2013-06-13 00:05:44 +00:00
* @ throws \InvalidArgumentException
2011-12-03 19:44:00 +00:00
* @ return string
*/
2016-04-11 10:08:27 +00:00
public function findShortestPathCode ( $from , $to , $directories = false , $staticCode = false )
2011-12-03 19:44:00 +00:00
{
if ( ! $this -> isAbsolutePath ( $from ) || ! $this -> isAbsolutePath ( $to )) {
2012-07-23 19:58:47 +00:00
throw new \InvalidArgumentException ( sprintf ( '$from (%s) and $to (%s) must be absolute paths.' , $from , $to ));
2011-12-03 19:44:00 +00:00
}
2013-03-13 21:13:32 +00:00
$from = lcfirst ( $this -> normalizePath ( $from ));
$to = lcfirst ( $this -> normalizePath ( $to ));
2012-03-10 01:14:40 +00:00
2011-12-03 19:44:00 +00:00
if ( $from === $to ) {
2011-12-03 22:21:10 +00:00
return $directories ? '__DIR__' : '__FILE__' ;
2011-12-03 19:44:00 +00:00
}
2013-09-26 12:34:41 +00:00
$commonPath = $to ;
2013-10-17 09:10:16 +00:00
while ( strpos ( $from . '/' , $commonPath . '/' ) !== 0 && '/' !== $commonPath && ! preg_match ( '{^[a-z]:/?$}i' , $commonPath ) && '.' !== $commonPath ) {
2013-10-28 10:01:17 +00:00
$commonPath = strtr ( dirname ( $commonPath ), '\\' , '/' );
2011-12-03 19:44:00 +00:00
}
2013-10-17 09:10:16 +00:00
if ( 0 !== strpos ( $from , $commonPath ) || '/' === $commonPath || '.' === $commonPath ) {
2011-12-03 19:44:00 +00:00
return var_export ( $to , true );
}
$commonPath = rtrim ( $commonPath , '/' ) . '/' ;
2011-12-04 21:40:30 +00:00
if ( strpos ( $to , $from . '/' ) === 0 ) {
2011-12-03 22:21:10 +00:00
return '__DIR__ . ' . var_export ( substr ( $to , strlen ( $from )), true );
}
$sourcePathDepth = substr_count ( substr ( $from , strlen ( $commonPath )), '/' ) + $directories ;
2016-04-11 10:08:27 +00:00
if ( $staticCode ) {
$commonPathCode = " __DIR__ . ' " . str_repeat ( '/..' , $sourcePathDepth ) . " ' " ;
} else {
$commonPathCode = str_repeat ( 'dirname(' , $sourcePathDepth ) . '__DIR__' . str_repeat ( ')' , $sourcePathDepth );
}
2011-12-04 21:40:30 +00:00
$relTarget = substr ( $to , strlen ( $commonPath ));
2012-05-22 10:07:08 +00:00
2011-12-04 21:40:30 +00:00
return $commonPathCode . ( strlen ( $relTarget ) ? '.' . var_export ( '/' . $relTarget , true ) : '' );
2011-12-03 19:44:00 +00:00
}
/**
* Checks if the given path is absolute
*
2012-06-23 09:58:18 +00:00
* @ param string $path
* @ return bool
2011-12-03 19:44:00 +00:00
*/
public function isAbsolutePath ( $path )
{
return substr ( $path , 0 , 1 ) === '/' || substr ( $path , 1 , 1 ) === ':' ;
}
2012-01-18 07:56:35 +00:00
2012-12-16 19:04:39 +00:00
/**
* Returns size of a file or directory specified by path . If a directory is
* given , it ' s size will be computed recursively .
*
2013-06-13 11:28:24 +00:00
* @ param string $path Path to the file or directory
2013-06-13 00:05:44 +00:00
* @ throws \RuntimeException
2012-12-16 19:04:39 +00:00
* @ return int
*/
public function size ( $path )
{
if ( ! file_exists ( $path )) {
throw new \RuntimeException ( " $path does not exist. " );
}
if ( is_dir ( $path )) {
return $this -> directorySize ( $path );
}
2013-01-05 19:01:58 +00:00
2012-12-16 19:04:39 +00:00
return filesize ( $path );
}
2013-03-13 21:13:32 +00:00
/**
* Normalize a path . This replaces backslashes with slashes , removes ending
* slash and collapses redundant separators and up - level references .
*
2013-06-13 11:28:24 +00:00
* @ param string $path Path to the file or directory
2013-03-13 21:13:32 +00:00
* @ return string
*/
public function normalizePath ( $path )
{
$parts = array ();
$path = strtr ( $path , '\\' , '/' );
$prefix = '' ;
2013-04-05 04:41:14 +00:00
$absolute = false ;
2013-03-13 21:13:32 +00:00
2013-04-05 04:41:14 +00:00
if ( preg_match ( '{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i' , $path , $match )) {
2013-03-13 21:13:32 +00:00
$prefix = $match [ 1 ];
$path = substr ( $path , strlen ( $prefix ));
}
2013-04-05 04:41:14 +00:00
if ( substr ( $path , 0 , 1 ) === '/' ) {
$absolute = true ;
$path = substr ( $path , 1 );
}
$up = false ;
2013-03-13 21:13:32 +00:00
foreach ( explode ( '/' , $path ) as $chunk ) {
2013-04-05 04:41:14 +00:00
if ( '..' === $chunk && ( $absolute || $up )) {
2013-03-13 21:13:32 +00:00
array_pop ( $parts );
2013-04-05 04:41:14 +00:00
$up = ! ( empty ( $parts ) || '..' === end ( $parts ));
2013-03-13 21:13:32 +00:00
} elseif ( '.' !== $chunk && '' !== $chunk ) {
$parts [] = $chunk ;
2013-04-05 04:41:14 +00:00
$up = '..' !== $chunk ;
2013-03-13 21:13:32 +00:00
}
}
2013-04-05 04:41:14 +00:00
return $prefix . ( $absolute ? '/' : '' ) . implode ( '/' , $parts );
2013-03-13 21:13:32 +00:00
}
2014-05-31 19:36:09 +00:00
/**
* Return if the given path is local
*
2014-06-10 14:02:44 +00:00
* @ param string $path
2014-05-31 19:36:09 +00:00
* @ return bool
*/
public static function isLocalPath ( $path )
{
return ( bool ) preg_match ( '{^(file://|/|[a-z]:[\\\\/]|\.\.[\\\\/]|[a-z0-9_.-]+[\\\\/])}i' , $path );
}
2014-09-24 18:13:05 +00:00
public static function getPlatformPath ( $path )
{
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows ()) {
2014-09-24 18:13:05 +00:00
$path = preg_replace ( '{^(?:file:///([a-z])/)}i' , 'file://$1:/' , $path );
}
return preg_replace ( '{^file://}i' , '' , $path );
}
2012-12-16 19:04:39 +00:00
protected function directorySize ( $directory )
{
$it = new RecursiveDirectoryIterator ( $directory , RecursiveDirectoryIterator :: SKIP_DOTS );
$ri = new RecursiveIteratorIterator ( $it , RecursiveIteratorIterator :: CHILD_FIRST );
$size = 0 ;
foreach ( $ri as $file ) {
if ( $file -> isFile ()) {
$size += $file -> getSize ();
}
}
2013-01-05 19:01:58 +00:00
2012-12-16 19:04:39 +00:00
return $size ;
}
2012-01-18 07:56:35 +00:00
protected function getProcess ()
{
return new ProcessExecutor ;
}
2014-07-28 21:42:53 +00:00
/**
* delete symbolic link implementation ( commonly known as " unlink() " )
*
* symbolic links on windows which link to directories need rmdir instead of unlink
*
* @ param string $path
*
* @ return bool
*/
private function unlinkImplementation ( $path )
{
2016-02-03 21:39:16 +00:00
if ( Platform :: isWindows () && is_dir ( $path ) && is_link ( $path )) {
2014-07-28 21:42:53 +00:00
return rmdir ( $path );
}
return unlink ( $path );
}
2014-07-24 13:08:25 +00:00
2015-10-27 15:36:12 +00:00
/**
* Creates a relative symlink from $link to $target
*
2015-11-21 19:28:10 +00:00
* @ param string $target The path of the binary file to be symlinked
* @ param string $link The path where the symlink should be created
2015-10-27 15:36:12 +00:00
* @ return bool
*/
public function relativeSymlink ( $target , $link )
{
$cwd = getcwd ();
2015-10-28 00:04:33 +00:00
$relativePath = $this -> findShortestPath ( $link , $target );
2015-10-27 15:36:12 +00:00
chdir ( dirname ( $link ));
$result = @ symlink ( $relativePath , $link );
chdir ( $cwd );
return ( bool ) $result ;
}
2015-08-18 13:40:48 +00:00
/**
* return true if that directory is a symlink .
*
* @ param string $directory
*
* @ return bool
*/
public function isSymlinkedDirectory ( $directory )
2014-07-24 13:08:25 +00:00
{
if ( ! is_dir ( $directory )) {
return false ;
}
$resolved = $this -> resolveSymlinkedDirectorySymlink ( $directory );
return is_link ( $resolved );
}
/**
* @ param string $directory
*
* @ return bool
*/
private function unlinkSymlinkedDirectory ( $directory )
{
$resolved = $this -> resolveSymlinkedDirectorySymlink ( $directory );
return $this -> unlink ( $resolved );
}
/**
* resolve pathname to symbolic link of a directory
*
* @ param string $pathname directory path to resolve
*
* @ return string resolved path to symbolic link or original pathname ( unresolved )
*/
private function resolveSymlinkedDirectorySymlink ( $pathname )
{
if ( ! is_dir ( $pathname )) {
return $pathname ;
}
$resolved = rtrim ( $pathname , '/' );
if ( ! strlen ( $resolved )) {
return $pathname ;
}
return $resolved ;
}
2016-01-27 23:33:11 +00:00
2016-01-27 23:56:02 +00:00
/**
* Creates an NTFS junction .
*
2016-02-02 22:44:01 +00:00
* @ param string $target
* @ param string $junction
2016-01-27 23:56:02 +00:00
*/
2016-02-02 22:44:01 +00:00
public function junction ( $target , $junction )
2016-01-27 23:56:02 +00:00
{
2016-02-05 10:27:41 +00:00
if ( ! Platform :: isWindows ()) {
2016-02-02 22:44:01 +00:00
throw new \LogicException ( sprintf ( 'Function %s is not available on non-Windows platform' , __CLASS__ ));
}
if ( ! is_dir ( $target )) {
throw new IOException ( sprintf ( 'Cannot junction to "%s" as it is not a directory.' , $target ), 0 , null , $target );
}
$cmd = sprintf ( 'mklink /J %s %s' ,
ProcessExecutor :: escape ( str_replace ( '/' , DIRECTORY_SEPARATOR , $junction )),
ProcessExecutor :: escape ( realpath ( $target )));
if ( $this -> getProcess () -> execute ( $cmd , $output ) !== 0 ) {
throw new IOException ( sprintf ( 'Failed to create junction to "%s" at "%s".' , $target , $junction ), 0 , null , $target );
2016-01-27 23:56:02 +00:00
}
2016-03-14 11:27:41 +00:00
clearstatcache ( true , $junction );
2016-01-27 23:56:02 +00:00
}
2016-01-27 23:33:11 +00:00
/**
* Returns whether the target directory is a Windows NTFS Junction .
*
2016-02-25 10:04:44 +00:00
* @ param string $junction Path to check .
2016-01-27 23:33:11 +00:00
* @ return bool
*/
public function isJunction ( $junction )
{
2016-02-05 10:27:41 +00:00
if ( ! Platform :: isWindows ()) {
2016-01-27 23:33:11 +00:00
return false ;
}
2016-02-02 22:44:01 +00:00
if ( ! is_dir ( $junction ) || is_link ( $junction )) {
return false ;
}
2016-02-25 21:16:38 +00:00
/**
* According to MSDN at https :// msdn . microsoft . com / en - us / library / 14 h5k7ff . aspx we can detect a junction now
* using the 'mode' value from stat : " The _S_IFDIR bit is set if path specifies a directory; the _S_IFREG bit
* is set if path specifies an ordinary file or a device . " We have just tested for a directory above, so if
* we have a directory that isn ' t one according to lstat ( ... ) we must have a junction .
*
* #define _S_IFDIR 0x4000
* #define _S_IFREG 0x8000
2016-03-14 11:27:41 +00:00
*
* Stat cache should be cleared before to avoid accidentally reading wrong information from previous installs .
2016-02-25 21:16:38 +00:00
*/
2016-03-14 11:27:41 +00:00
clearstatcache ( true , $junction );
2016-02-02 22:44:01 +00:00
$stat = lstat ( $junction );
2016-02-29 15:04:05 +00:00
2016-02-25 21:16:38 +00:00
return ! ( $stat [ 'mode' ] & 0xC000 );
2016-01-27 23:33:11 +00:00
}
/**
* Removes a Windows NTFS junction .
*
2016-02-25 10:04:44 +00:00
* @ param string $junction
2016-01-27 23:33:11 +00:00
* @ return bool
*/
public function removeJunction ( $junction )
{
2016-02-05 10:27:41 +00:00
if ( ! Platform :: isWindows ()) {
2016-01-27 23:33:11 +00:00
return false ;
}
$junction = rtrim ( str_replace ( '/' , DIRECTORY_SEPARATOR , $junction ), DIRECTORY_SEPARATOR );
2016-02-02 22:44:01 +00:00
if ( ! $this -> isJunction ( $junction )) {
throw new IOException ( sprintf ( '%s is not a junction and thus cannot be removed as one' , $junction ));
}
2016-01-27 23:33:11 +00:00
$cmd = sprintf ( 'rmdir /S /Q %s' , ProcessExecutor :: escape ( $junction ));
2016-03-14 11:27:41 +00:00
clearstatcache ( true , $junction );
2016-02-25 10:04:44 +00:00
2016-02-05 10:27:41 +00:00
return ( $this -> getProcess () -> execute ( $cmd , $output ) === 0 );
2016-01-27 23:33:11 +00:00
}
2012-03-22 09:11:48 +00:00
}