Skip to content

PluginAssetsTrait::_remove() does not remove symlink if it points to a non-existing path #19142

@mehov

Description

@mehov

Description

What I did

use Cake\Console\ConsoleIo;

class TestCommand extends \Cake\Command\Command
{

    use \Cake\Command\PluginAssetsTrait;

    public function execute(\Cake\Console\Arguments $args, ConsoleIo $io): ?int
    {
        // Fix '$io must not be accessed before initialization'
        $this->io = $io;

        $io->info('Symlink WWW_ROOT/img2 to WWW_ROOT/img which exists');
        $this->_createSymlink(WWW_ROOT . 'img', WWW_ROOT . 'img2');
        $io->info('Unlinking WWW_ROOT/img2');
        $this->_remove(['namespaced' => false, 'destDir' => WWW_ROOT, 'link' => 'img2']);
        $io->info(sprintf('Checking WWW_ROOT/img2 is_link: %d', is_link(WWW_ROOT . 'img2')));

        $io->info('');

        $io->info('Symlink WWW_ROOT/img3 to WWW_ROOT/gmi that does not exist');
        $this->_createSymlink(WWW_ROOT . 'gmi', WWW_ROOT . 'img3');
        $io->info('Unlinking WWW_ROOT/img3');
        $this->_remove(['namespaced' => false, 'destDir' => WWW_ROOT, 'link' => 'img3']);
        $io->info(sprintf('Checking WWW_ROOT/img3 is_link: %d', is_link(WWW_ROOT . 'img3')));

        return static::CODE_SUCCESS;
    }

}

What happened:

Symlink WWW_ROOT/img2 to WWW_ROOT/img which exists
Created symlink /var/www/cakephp-test/webroot/img2
Unlinking WWW_ROOT/img2
Unlinked /var/www/cakephp-test/webroot/img2
Checking WWW_ROOT/img2 is_link: 0

Symlink WWW_ROOT/img3 to WWW_ROOT/gmi that does not exist
Created symlink /var/www/cakephp-test/webroot/img3
Unlinking WWW_ROOT/img3
Checking WWW_ROOT/img3 is_link: 1

img3 was not deleted because file_exists(WWW_ROOT . 'img3') on line 174 below follows the symlink to a non-existing location WWW_ROOT/gmi and evaluates to false

What I expected to happen:

Checking WWW_ROOT/img3 is_link: 0

Reference:

/**
* Remove folder/symlink.
*
* @param array<string, mixed> $config Plugin config.
* @return bool
*/
protected function _remove(array $config): bool
{
if ($config['namespaced'] && !is_dir($config['destDir'])) {
$this->io->verbose(
$config['destDir'] . $config['link'] . ' does not exist',
1,
);
return false;
}
$dest = $config['destDir'] . $config['link'];
if (!file_exists($dest)) {
$this->io->verbose(
$dest . ' does not exist',
1,
);
return false;
}
if (is_link($dest)) {
// phpcs:ignore
$success = DIRECTORY_SEPARATOR === '\\' ? @rmdir($dest) : @unlink($dest);
if ($success) {
$this->io->out('Unlinked ' . $dest);
return true;
}
$this->io->err('Failed to unlink ' . $dest);
return false;
}
$fs = new Filesystem();
if ($fs->deleteDir($dest)) {
$this->io->out('Deleted ' . $dest);
return true;
}
$this->io->err('Failed to delete ' . $dest);
return false;
}

CakePHP Version

5.2.10

PHP Version

No response

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions