diff --git a/src/Oro/Bundle/InstallerBundle/Composer/PermissionsHandler.php b/src/Oro/Bundle/InstallerBundle/Composer/PermissionsHandler.php index 18d2ccaec97..53841ec35a8 100644 --- a/src/Oro/Bundle/InstallerBundle/Composer/PermissionsHandler.php +++ b/src/Oro/Bundle/InstallerBundle/Composer/PermissionsHandler.php @@ -27,15 +27,115 @@ class PermissionsHandler */ public function setPermissions($directory) { + // Check ACL support before attempting to use it + if (!$this->isAclSupported()) { + return $this->setPermissionsTraditional($directory); + } + try { $this->setPermissionsSetfacl($directory); $this->setPermissionsChmod($directory); return true; } catch (ProcessFailedException $exception) { + // Fall back to traditional permissions if ACL fails + return $this->setPermissionsTraditional($directory); + } + } + + /** + * Check if ACL is supported on the filesystem + * + * @return bool + */ + protected function isAclSupported(): bool + { + static $aclSupported = null; + + if ($aclSupported !== null) { + return $aclSupported; + } + + $fs = new Filesystem(); + $testDir = 'var/cache'; + if (!$fs->exists($testDir)) { + $fs->mkdir($testDir, 0755); + } + + $testFile = $testDir . '/.acl_test_' . uniqid(); + + try { + $fs->touch($testFile); + $user = trim(shell_exec('whoami') ?: 'www-data'); + + $testCmd = sprintf('setfacl -m "u:%s:rw" %s 2>&1', escapeshellarg($user), escapeshellarg($testFile)); + $process = $this->getProcess($testCmd); + $process->setTimeout(2); + $process->run(); + + if ($process->isSuccessful()) { + $aclSupported = true; + } else { + $output = $process->getErrorOutput() . $process->getOutput(); + $aclSupported = stripos($output, 'Operation not supported') === false + && stripos($output, 'not supported') === false; + } + + if ($fs->exists($testFile)) { + $fs->remove($testFile); + } + } catch (\Exception $e) { + $aclSupported = false; + if ($fs->exists($testFile)) { + try { + $fs->remove($testFile); + } catch (\Exception $e2) { + // Ignore cleanup errors + } + } + } + + return $aclSupported; + } + + /** + * Set permissions using traditional Unix chmod/chown when ACL is not supported + * + * @param string $directory + * @return bool + */ + protected function setPermissionsTraditional($directory): bool + { + $fs = new Filesystem(); + if (!$fs->exists($directory)) { + $fs->mkdir($directory); } - return false; + try { + $user = trim(shell_exec('whoami') ?: 'www-data'); + $group = $user; + + $webServerUser = $this->runProcessQuiet(self::PS_AUX); + if ($webServerUser) { + $group = $webServerUser; + } + + $chownCmd = sprintf('chown -R %s:%s %s', escapeshellarg($user), escapeshellarg($group), escapeshellarg($directory)); + $process = $this->getProcess($chownCmd); + $process->run(); + + $chmodCmd = sprintf('find %s -type d -exec chmod 775 {} +', escapeshellarg($directory)); + $process = $this->getProcess($chmodCmd); + $process->run(); + + $chmodCmd = sprintf('find %s -type f -exec chmod 664 {} +', escapeshellarg($directory)); + $process = $this->getProcess($chmodCmd); + $process->run(); + + return true; + } catch (\Exception $e) { + return false; + } } /** @@ -44,6 +144,10 @@ public function setPermissions($directory) */ public function setPermissionsSetfacl($path) { + if (!$this->isAclSupported()) { + throw new ProcessFailedException(new Process(['setfacl'], null, null, null, 0)); + } + $fs = new Filesystem(); if (!$fs->exists($path)) { $fs->mkdir($path); @@ -68,9 +172,14 @@ public function setPermissionsChmod($path) } foreach ($this->getUsers() as $user) { - $this->runProcess( - str_replace([self::VAR_USER, self::VAR_PATH], [$user, $path], self::CHMOD) - ); + try { + $this->runProcess( + str_replace([self::VAR_USER, self::VAR_PATH], [$user, $path], self::CHMOD) + ); + } catch (ProcessFailedException $e) { + // chmod +a might not be supported, skip it + continue; + } } } @@ -104,6 +213,21 @@ protected function runProcess($commandline) return trim($process->getOutput()); } + /** + * Run process without throwing exception on failure + * + * @param string $commandline + * @return string|null + */ + protected function runProcessQuiet($commandline) + { + try { + return $this->runProcess($commandline); + } catch (ProcessFailedException $e) { + return null; + } + } + protected function getProcess(string $commandline): Process { if (method_exists(Process::class, 'fromShellCommandline')) { @@ -113,3 +237,4 @@ protected function getProcess(string $commandline): Process } } } +