diff --git a/src/VirtualFileSystem/Container.php b/src/VirtualFileSystem/Container.php index 3b1865b..fc093f3 100644 --- a/src/VirtualFileSystem/Container.php +++ b/src/VirtualFileSystem/Container.php @@ -156,6 +156,35 @@ public function createDir($path, $recursive = false, $mode = null) return $newDirectory; } + /** + * Creates link at given path + * + * @param string $path + * @param $destination + * + * @return Structure\File + * + */ + public function createLink($path, $destination) + { + + $destination = $this->fileAt($destination); + + try { + $file = $this->fileAt($path); + throw new \RuntimeException(sprintf('%s already exists', $path)); + } catch (NotFoundException $e) { + + } + + $parent = $this->fileAt(dirname($path)); + + $parent->addLink($newLink = $this->factory()->getLink(basename($path), $destination)); + + return $newLink; + + } + /** * Creates file at given path * diff --git a/src/VirtualFileSystem/Factory.php b/src/VirtualFileSystem/Factory.php index 9e4230d..1ce9161 100644 --- a/src/VirtualFileSystem/Factory.php +++ b/src/VirtualFileSystem/Factory.php @@ -14,6 +14,7 @@ use VirtualFileSystem\Structure\Node; use VirtualFileSystem\Structure\Root; use VirtualFileSystem\Structure\File; +use VirtualFileSystem\Structure\Link; /** * Factory class to encapsulate object creation. @@ -116,4 +117,17 @@ public function getFile($basename) { return $this->updateMetadata(new File($basename)); } + + /** + * Creates Link object. + * + * @param string $basename + * @param Structure\Node $destination + * + * @return Link + */ + public function getLink($basename, Node $destination) + { + return $this->updateMetadata(new Link($basename, $destination)); + } } diff --git a/src/VirtualFileSystem/FileSystem.php b/src/VirtualFileSystem/FileSystem.php index ebbbd36..7b005d0 100644 --- a/src/VirtualFileSystem/FileSystem.php +++ b/src/VirtualFileSystem/FileSystem.php @@ -161,4 +161,17 @@ public function createStructure(array $structure) { $this->container()->createStructure($structure); } + + /** + * Creates and returns a link + * + * @param string $path + * @param $destinationPath + * + * @return Link + */ + public function createLink($path, $destinationPath) + { + return $this->container()->createLink($path, $destinationPath); + } } diff --git a/src/VirtualFileSystem/Structure/Directory.php b/src/VirtualFileSystem/Structure/Directory.php index f496922..5f0a65d 100644 --- a/src/VirtualFileSystem/Structure/Directory.php +++ b/src/VirtualFileSystem/Structure/Directory.php @@ -63,6 +63,16 @@ public function addFile(File $file) $this->addNode($file); } + /** + * Adds child Link. + * + * @param Link $link + */ + public function addLink(Link $link) + { + $this->addNode($link); + } + /** * Adds child Node. * diff --git a/src/VirtualFileSystem/Structure/Link.php b/src/VirtualFileSystem/Structure/Link.php new file mode 100644 index 0000000..9a034df --- /dev/null +++ b/src/VirtualFileSystem/Structure/Link.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace VirtualFileSystem\Structure; + +/** + * Object representation of a Link. + * + * @author Michael Donat + * @package php-vfs + */ +class Link extends Node +{ + /** + * @see http://man7.org/linux/man-pages/man2/lstat.2.html + */ + const S_IFTYPE = 0120000; + + /** + * @var Node + */ + protected $destination; + + /** + * Class constructor. + * + * @param string $basename + */ + public function __construct($basename, Node $destination) + { + parent::__construct($basename); + $this->destination = $destination; + } + + /** + * Returns Link size. + * + * The size is the length of the destination path + * + * @return mixed + */ + public function size() + { + return $this->destination->size(); + } + + /** + * @return Node + */ + public function getDestination() + { + return $this->destination; + } +} diff --git a/src/VirtualFileSystem/Wrapper.php b/src/VirtualFileSystem/Wrapper.php index 0f4d95a..c9ca898 100644 --- a/src/VirtualFileSystem/Wrapper.php +++ b/src/VirtualFileSystem/Wrapper.php @@ -12,6 +12,7 @@ use VirtualFileSystem\Structure\Directory; use VirtualFileSystem\Structure\File; +use VirtualFileSystem\Structure\Link; use VirtualFileSystem\Structure\Root; use VirtualFileSystem\Wrapper\FileHandler; use VirtualFileSystem\Wrapper\DirectoryHandler; @@ -160,6 +161,10 @@ public function stream_open($path, $mode, $options, &$opened_path) $file = $container->fileAt($path); + if($file instanceof Link) { + $file = $file->getDestination(); + } + if (($extended || $writeMode || $appendMode) && $file instanceof Directory) { if ($options & STREAM_REPORT_ERRORS) { trigger_error(sprintf('fopen(%s): failed to open stream: Is a directory', $path), E_USER_WARNING); diff --git a/tests/VirtualFileSystem/ContainerTest.php b/tests/VirtualFileSystem/ContainerTest.php index 6e2af12..a8348b2 100644 --- a/tests/VirtualFileSystem/ContainerTest.php +++ b/tests/VirtualFileSystem/ContainerTest.php @@ -237,4 +237,27 @@ public function testRemoveThrowsWhenDeletingDirectoriesWithRecursiveFlag() $container->remove('/dir'); } + + public function testLinkCreation() + { + $container = new Container(new Factory()); + $container->createFile('/file'); + $container->createLink('/link', '/file'); + + $this->assertInstanceOf('\VirtualFileSystem\Structure\Link', $container->fileAt('/link')); + + } + + public function testLinkCreationThrowsWhenTryingToOverride() + { + $container = new Container(new Factory()); + + $container->createFile('/file'); + $container->createLink('/link', '/file'); + + $this->setExpectedException('\RuntimeException'); + + $container->createLink('/link', '/file'); + + } } diff --git a/tests/VirtualFileSystem/Structure/LinkTest.php b/tests/VirtualFileSystem/Structure/LinkTest.php new file mode 100644 index 0000000..cfcc9fd --- /dev/null +++ b/tests/VirtualFileSystem/Structure/LinkTest.php @@ -0,0 +1,26 @@ +setData('12345'); + + $link = new Link('link', $node); + + $this->assertEquals($node->size(), $link->size()); + + $dir = new Directory('/d'); + + $link = new Link('link', $dir); + + $this->assertEquals($dir->size(), $dir->size()); + + $dir->addFile($node); + + $this->assertEquals($dir->size(), $dir->size()); + } +} diff --git a/tests/VirtualFileSystem/WrapperTest.php b/tests/VirtualFileSystem/WrapperTest.php index e8687e8..cb13519 100644 --- a/tests/VirtualFileSystem/WrapperTest.php +++ b/tests/VirtualFileSystem/WrapperTest.php @@ -4,6 +4,7 @@ use VirtualFileSystem\Structure\Directory; use VirtualFileSystem\Structure\File; +use VirtualFileSystem\Structure\Link; class WrapperTest extends \PHPUnit_Framework_TestCase { @@ -67,6 +68,15 @@ public function testIsDir() } + public function testIsLink() + { + $fs = new FileSystem(); + $fs->root()->addDirectory($d = new Directory('dir')); + $d->addLink(new Link('link', $d)); + + $this->assertTrue(is_link($fs->path('/dir/link'))); + } + public function testIsFile() { $fs = new FileSystem(); @@ -1210,6 +1220,97 @@ public function testTouchNotAllowedIfNotOwnerOrNotWritable() ); } + public function testLchown() { + + if ($this->uid == 0) { + $this->markTestSkipped( + 'No point testing if user is already root. \Php unit shouldn\'t be run as root user. (Unless you are a windows user!)' + ); + } + + $fs = new FileSystem(); + $directory = $fs->createDirectory('/dir'); + $link = new Link('link', $directory); + $directory->addLink($link); + + $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root + + lchown($fs->path('/dir/link'), 'root'); + $this->assertEquals('root', posix_getpwuid(fileowner($fs->path('/dir/link')))['name']); + + } + + public function testLchgrp() + { + if ($this->uid == 0) { + $this->markTestSkipped( + 'No point testing if group is already root. Php unit shouldn\'t be run as root group. (Unless you are on Windows - then we skip)' + ); + } + + $fs = new FileSystem(); + $directory = $fs->createDirectory('/dir'); + $link = new Link('link', $directory); + $directory->addLink($link); + + $fs->container()->setPermissionHelper(new Wrapper\PermissionHelper(0, 0)); //forcing user to root + + //lets workout available group + //this is needed to find string name of group root belongs to + $group = posix_getgrgid(posix_getpwuid(0)['gid'])['name']; + + chgrp($fs->path('/dir/link'), $group); + + $this->assertEquals($group, posix_getgrgid(filegroup($fs->path('/dir/link')))['name']); + } + + public function testFileCopy() { + + $fs = new FileSystem(); + $fs->createFile('/file', 'data'); + + copy($fs->path('/file'), $fs->path('/file2')); + + $this->assertTrue(file_exists($fs->path('/file2'))); + + $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); + + } + + public function testLinkCopyCreatesHardCopyOfFile() { + + $fs = new FileSystem(); + $fs->createFile('/file', 'data'); + $fs->createLink('/link', '/file'); + + copy($fs->path('/link'), $fs->path('/file2')); + + $this->assertTrue(file_exists($fs->path('/file2'))); + $this->assertEquals('data', $fs->container()->fileAt('/file2')->data()); + + } + + public function testLinkReading() { + + $fs = new FileSystem(); + $fs->createFile('/file', 'data'); + $fs->createLink('/link', '/file'); + + $this->assertEquals('data', file_get_contents($fs->path('/link'))); + } + + public function tetsLinkWriting() { + + $fs = new FileSystem(); + $fs->createFile('/file', 'ubots!'); + $fs->createLink('/link', '/file'); + + file_put_contents($fs->path('/link'), 'data'); + + $this->assertEquals('data', file_get_contents($fs->path('/link'))); + + } + public function testIsExecutableReturnsCorrectly() { $fs = new FileSystem();