From ec32409b8aab2045a911f563fb2b576c92a5a4c4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?J=C3=A9r=C3=A9my=20DECOOL?= <contact@jdecool.fr>
Date: Sun, 28 Jul 2024 14:19:14 +0200
Subject: [PATCH] [Lock] Add prefix argument to avoid collision with FlockStore

---
 .../FrameworkExtension.php                    |  2 +-
 src/Symfony/Component/Lock/CHANGELOG.md       |  5 ++++
 .../Component/Lock/Store/FlockStore.php       |  9 +++++--
 .../Component/Lock/Store/StoreFactory.php     |  5 +++-
 .../Lock/Tests/Store/FlockStoreTest.php       | 25 +++++++++++++++++--
 .../Lock/Tests/Store/StoreFactoryTest.php     |  1 +
 6 files changed, 41 insertions(+), 6 deletions(-)

diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
index 49821a043de57..70b48f208df70 100644
--- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
@@ -2009,7 +2009,7 @@ private function registerLockConfiguration(array $config, ContainerBuilder $cont
                 $storeDefinition = new Definition(PersistingStoreInterface::class);
                 $storeDefinition
                     ->setFactory([StoreFactory::class, 'createStore'])
-                    ->setArguments([$resourceStore])
+                    ->setArguments([$resourceStore, new Parameter('kernel.secret')])
                     ->addTag('lock.store');
 
                 $container->setDefinition($storeDefinitionId = '.lock.'.$resourceName.'.store.'.$container->hash($storeDsn), $storeDefinition);
diff --git a/src/Symfony/Component/Lock/CHANGELOG.md b/src/Symfony/Component/Lock/CHANGELOG.md
index 3c7f1b834bc83..3a71980b05b21 100644
--- a/src/Symfony/Component/Lock/CHANGELOG.md
+++ b/src/Symfony/Component/Lock/CHANGELOG.md
@@ -1,6 +1,11 @@
 CHANGELOG
 =========
 
+7.2
+---
+
+ * Add parameter `$prefix` to `FlockStore::__construct()`
+
 7.0
 ---
 
diff --git a/src/Symfony/Component/Lock/Store/FlockStore.php b/src/Symfony/Component/Lock/Store/FlockStore.php
index 403aede6c2bae..911a915981dc1 100644
--- a/src/Symfony/Component/Lock/Store/FlockStore.php
+++ b/src/Symfony/Component/Lock/Store/FlockStore.php
@@ -32,12 +32,15 @@ class FlockStore implements BlockingStoreInterface, SharedLockStoreInterface
 {
     private ?string $lockPath;
 
+    private ?string $prefix;
+
     /**
      * @param string|null $lockPath the directory to store the lock, defaults to the system's temporary directory
+     * @param string|null $prefix   a prefix to add to the lock filenames to avoid collision
      *
      * @throws LockStorageException If the lock directory doesn’t exist or is not writable
      */
-    public function __construct(?string $lockPath = null)
+    public function __construct(?string $lockPath = null, ?string $prefix = null)
     {
         if (!is_dir($lockPath ??= sys_get_temp_dir())) {
             if (false === @mkdir($lockPath, 0777, true) && !is_dir($lockPath)) {
@@ -48,6 +51,7 @@ public function __construct(?string $lockPath = null)
         }
 
         $this->lockPath = $lockPath;
+        $this->prefix = $prefix;
     }
 
     public function save(Key $key): void
@@ -83,8 +87,9 @@ private function lock(Key $key, bool $read, bool $blocking): void
         }
 
         if (!$handle) {
-            $fileName = \sprintf('%s/sf.%s.%s.lock',
+            $fileName = \sprintf('%s/sf.%s%s.%s.lock',
                 $this->lockPath,
+                $this->prefix ? $this->prefix.'.' : '',
                 substr(preg_replace('/[^a-z0-9\._-]+/i', '-', $key), 0, 50),
                 strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
             );
diff --git a/src/Symfony/Component/Lock/Store/StoreFactory.php b/src/Symfony/Component/Lock/Store/StoreFactory.php
index bc65182e45dde..9a3a0ad0fc282 100644
--- a/src/Symfony/Component/Lock/Store/StoreFactory.php
+++ b/src/Symfony/Component/Lock/Store/StoreFactory.php
@@ -24,7 +24,7 @@
  */
 class StoreFactory
 {
-    public static function createStore(#[\SensitiveParameter] object|string $connection): PersistingStoreInterface
+    public static function createStore(#[\SensitiveParameter] object|string $connection, #[\SensitiveParameter] ?string $secret = null): PersistingStoreInterface
     {
         switch (true) {
             case $connection instanceof \Redis:
@@ -54,6 +54,9 @@ public static function createStore(#[\SensitiveParameter] object|string $connect
             case 'flock' === $connection:
                 return new FlockStore();
 
+            case str_starts_with($connection, 'flock+exclusive://'):
+                return new FlockStore(substr($connection, 15), $secret);
+
             case str_starts_with($connection, 'flock://'):
                 return new FlockStore(substr($connection, 8));
 
diff --git a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php
index 910dd1800ec78..49e66c2c59e79 100644
--- a/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php
+++ b/src/Symfony/Component/Lock/Tests/Store/FlockStoreTest.php
@@ -25,9 +25,9 @@ class FlockStoreTest extends AbstractStoreTestCase
     use SharedLockStoreTestTrait;
     use UnserializableTestTrait;
 
-    protected function getStore(): PersistingStoreInterface
+    protected function getStore(?string $prefix = null): PersistingStoreInterface
     {
-        return new FlockStore();
+        return new FlockStore(null, $prefix);
     }
 
     public function testConstructWhenRepositoryCannotBeCreated()
@@ -101,4 +101,25 @@ public function testSaveSanitizeLongName()
 
         $store->delete($key);
     }
+
+    public function testSavePrefix()
+    {
+        $store = $this->getStore('FlockSecret');
+
+        $key = new Key(__CLASS__);
+
+        $file = \sprintf(
+            '%s/sf.FlockSecret.Symfony-Component-Lock-Tests-Store-FlockStoreTest.%s.lock',
+            sys_get_temp_dir(),
+            strtr(substr(base64_encode(hash('sha256', $key, true)), 0, 7), '/', '_')
+        );
+        // ensure the file does not exist before the store
+        @unlink($file);
+
+        $store->save($key);
+
+        $this->assertFileExists($file);
+
+        $store->delete($key);
+    }
 }
diff --git a/src/Symfony/Component/Lock/Tests/Store/StoreFactoryTest.php b/src/Symfony/Component/Lock/Tests/Store/StoreFactoryTest.php
index 6d2319c8d760e..09a88b5753639 100644
--- a/src/Symfony/Component/Lock/Tests/Store/StoreFactoryTest.php
+++ b/src/Symfony/Component/Lock/Tests/Store/StoreFactoryTest.php
@@ -92,5 +92,6 @@ public static function validConnections(): \Generator
 
         yield ['flock', FlockStore::class];
         yield ['flock://'.sys_get_temp_dir(), FlockStore::class];
+        yield ['flock+exclusive://'.sys_get_temp_dir(), FlockStore::class];
     }
 }