Completed
Push — master ( d56b24...3950ef )
by
unknown
29:50
created
lib/private/Files/SetupManager.php 1 patch
Indentation   +766 added lines, -766 removed lines patch added patch discarded remove patch
@@ -64,772 +64,772 @@
 block discarded – undo
64 64
 use function in_array;
65 65
 
66 66
 class SetupManager {
67
-	private bool $rootSetup = false;
68
-	// List of users for which at least one mount is setup
69
-	private array $setupUsers = [];
70
-	// List of users for which all mounts are setup
71
-	private array $setupUsersComplete = [];
72
-	/**
73
-	 * An array of provider classes that have been set up, indexed by UserUID.
74
-	 *
75
-	 * @var array<string, class-string<IMountProvider>[]>
76
-	 */
77
-	private array $setupUserMountProviders = [];
78
-	/**
79
-	 * An array of paths that have already been set up
80
-	 *
81
-	 * @var array<string, int>
82
-	 */
83
-	private array $setupMountProviderPaths = [];
84
-	private ICache $cache;
85
-	private bool $listeningForProviders;
86
-	private array $fullSetupRequired = [];
87
-	private bool $setupBuiltinWrappersDone = false;
88
-	private bool $forceFullSetup;
89
-	private bool $optimizeAuthoritativeProviders;
90
-	private const SETUP_WITH_CHILDREN = 1;
91
-	private const SETUP_WITHOUT_CHILDREN = 0;
92
-
93
-	public function __construct(
94
-		private IEventLogger $eventLogger,
95
-		private MountProviderCollection $mountProviderCollection,
96
-		private IMountManager $mountManager,
97
-		private IUserManager $userManager,
98
-		private IEventDispatcher $eventDispatcher,
99
-		private IUserMountCache $userMountCache,
100
-		private ILockdownManager $lockdownManager,
101
-		private IUserSession $userSession,
102
-		ICacheFactory $cacheFactory,
103
-		private LoggerInterface $logger,
104
-		private IConfig $config,
105
-		private ShareDisableChecker $shareDisableChecker,
106
-		private IAppManager $appManager,
107
-		private FileAccess $fileAccess,
108
-	) {
109
-		$this->cache = $cacheFactory->createDistributed('setupmanager::');
110
-		$this->listeningForProviders = false;
111
-		$this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
112
-		$this->optimizeAuthoritativeProviders = $this->config->getSystemValueBool('debug.optimize-authoritative-providers', true);
113
-
114
-		$this->setupListeners();
115
-	}
116
-
117
-	private function isSetupStarted(IUser $user): bool {
118
-		return in_array($user->getUID(), $this->setupUsers, true);
119
-	}
120
-
121
-	public function isSetupComplete(IUser $user): bool {
122
-		return in_array($user->getUID(), $this->setupUsersComplete, true);
123
-	}
124
-
125
-	/**
126
-	 * Checks if a path has been cached either directly or through a full setup
127
-	 * of one of its parents.
128
-	 */
129
-	private function isPathSetup(string $path): bool {
130
-		// if the exact path was already setup with or without children
131
-		if (array_key_exists($path, $this->setupMountProviderPaths)) {
132
-			return true;
133
-		}
134
-
135
-		// or if any of the ancestors was fully setup
136
-		while (($path = dirname($path)) !== '/') {
137
-			$setupPath = $this->setupMountProviderPaths[$path . '/'] ?? null;
138
-			if ($setupPath === self::SETUP_WITH_CHILDREN) {
139
-				return true;
140
-			}
141
-		}
142
-
143
-		return false;
144
-	}
145
-
146
-	private function setupBuiltinWrappers() {
147
-		if ($this->setupBuiltinWrappersDone) {
148
-			return;
149
-		}
150
-		$this->setupBuiltinWrappersDone = true;
151
-
152
-		// load all filesystem apps before, so no setup-hook gets lost
153
-		$this->appManager->loadApps(['filesystem']);
154
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
155
-
156
-		Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
157
-			if ($storage->instanceOfStorage(Common::class)) {
158
-				$options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
159
-				$storage->setMountOptions($options);
160
-			}
161
-			return $storage;
162
-		});
163
-
164
-		$reSharingEnabled = Share::isResharingAllowed();
165
-		$user = $this->userSession->getUser();
166
-		$sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
167
-		Filesystem::addStorageWrapper(
168
-			'sharing_mask',
169
-			function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
170
-				$sharingEnabledForMount = $mount->getOption('enable_sharing', true);
171
-				$isShared = $mount instanceof ISharedMountPoint;
172
-				if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
173
-					return new PermissionsMask([
174
-						'storage' => $storage,
175
-						'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
176
-					]);
177
-				}
178
-				return $storage;
179
-			}
180
-		);
181
-
182
-		// install storage availability wrapper, before most other wrappers
183
-		Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
184
-			$externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
185
-			if ($externalMount && !$storage->isLocal()) {
186
-				return new Availability(['storage' => $storage]);
187
-			}
188
-			return $storage;
189
-		});
190
-
191
-		Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
192
-			if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
193
-				return new Encoding(['storage' => $storage]);
194
-			}
195
-			return $storage;
196
-		});
197
-
198
-		$quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
199
-		Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
200
-			// set up quota for home storages, even for other users
201
-			// which can happen when using sharing
202
-			if ($mount instanceof HomeMountPoint) {
203
-				$user = $mount->getUser();
204
-				return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
205
-					return $user->getQuotaBytes();
206
-				}, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
207
-			}
208
-
209
-			return $storage;
210
-		});
211
-
212
-		Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
213
-			/*
67
+    private bool $rootSetup = false;
68
+    // List of users for which at least one mount is setup
69
+    private array $setupUsers = [];
70
+    // List of users for which all mounts are setup
71
+    private array $setupUsersComplete = [];
72
+    /**
73
+     * An array of provider classes that have been set up, indexed by UserUID.
74
+     *
75
+     * @var array<string, class-string<IMountProvider>[]>
76
+     */
77
+    private array $setupUserMountProviders = [];
78
+    /**
79
+     * An array of paths that have already been set up
80
+     *
81
+     * @var array<string, int>
82
+     */
83
+    private array $setupMountProviderPaths = [];
84
+    private ICache $cache;
85
+    private bool $listeningForProviders;
86
+    private array $fullSetupRequired = [];
87
+    private bool $setupBuiltinWrappersDone = false;
88
+    private bool $forceFullSetup;
89
+    private bool $optimizeAuthoritativeProviders;
90
+    private const SETUP_WITH_CHILDREN = 1;
91
+    private const SETUP_WITHOUT_CHILDREN = 0;
92
+
93
+    public function __construct(
94
+        private IEventLogger $eventLogger,
95
+        private MountProviderCollection $mountProviderCollection,
96
+        private IMountManager $mountManager,
97
+        private IUserManager $userManager,
98
+        private IEventDispatcher $eventDispatcher,
99
+        private IUserMountCache $userMountCache,
100
+        private ILockdownManager $lockdownManager,
101
+        private IUserSession $userSession,
102
+        ICacheFactory $cacheFactory,
103
+        private LoggerInterface $logger,
104
+        private IConfig $config,
105
+        private ShareDisableChecker $shareDisableChecker,
106
+        private IAppManager $appManager,
107
+        private FileAccess $fileAccess,
108
+    ) {
109
+        $this->cache = $cacheFactory->createDistributed('setupmanager::');
110
+        $this->listeningForProviders = false;
111
+        $this->forceFullSetup = $this->config->getSystemValueBool('debug.force-full-fs-setup');
112
+        $this->optimizeAuthoritativeProviders = $this->config->getSystemValueBool('debug.optimize-authoritative-providers', true);
113
+
114
+        $this->setupListeners();
115
+    }
116
+
117
+    private function isSetupStarted(IUser $user): bool {
118
+        return in_array($user->getUID(), $this->setupUsers, true);
119
+    }
120
+
121
+    public function isSetupComplete(IUser $user): bool {
122
+        return in_array($user->getUID(), $this->setupUsersComplete, true);
123
+    }
124
+
125
+    /**
126
+     * Checks if a path has been cached either directly or through a full setup
127
+     * of one of its parents.
128
+     */
129
+    private function isPathSetup(string $path): bool {
130
+        // if the exact path was already setup with or without children
131
+        if (array_key_exists($path, $this->setupMountProviderPaths)) {
132
+            return true;
133
+        }
134
+
135
+        // or if any of the ancestors was fully setup
136
+        while (($path = dirname($path)) !== '/') {
137
+            $setupPath = $this->setupMountProviderPaths[$path . '/'] ?? null;
138
+            if ($setupPath === self::SETUP_WITH_CHILDREN) {
139
+                return true;
140
+            }
141
+        }
142
+
143
+        return false;
144
+    }
145
+
146
+    private function setupBuiltinWrappers() {
147
+        if ($this->setupBuiltinWrappersDone) {
148
+            return;
149
+        }
150
+        $this->setupBuiltinWrappersDone = true;
151
+
152
+        // load all filesystem apps before, so no setup-hook gets lost
153
+        $this->appManager->loadApps(['filesystem']);
154
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
155
+
156
+        Filesystem::addStorageWrapper('mount_options', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
157
+            if ($storage->instanceOfStorage(Common::class)) {
158
+                $options = array_merge($mount->getOptions(), ['mount_point' => $mountPoint]);
159
+                $storage->setMountOptions($options);
160
+            }
161
+            return $storage;
162
+        });
163
+
164
+        $reSharingEnabled = Share::isResharingAllowed();
165
+        $user = $this->userSession->getUser();
166
+        $sharingEnabledForUser = $user ? !$this->shareDisableChecker->sharingDisabledForUser($user->getUID()) : true;
167
+        Filesystem::addStorageWrapper(
168
+            'sharing_mask',
169
+            function ($mountPoint, IStorage $storage, IMountPoint $mount) use ($reSharingEnabled, $sharingEnabledForUser) {
170
+                $sharingEnabledForMount = $mount->getOption('enable_sharing', true);
171
+                $isShared = $mount instanceof ISharedMountPoint;
172
+                if (!$sharingEnabledForMount || !$sharingEnabledForUser || (!$reSharingEnabled && $isShared)) {
173
+                    return new PermissionsMask([
174
+                        'storage' => $storage,
175
+                        'mask' => Constants::PERMISSION_ALL - Constants::PERMISSION_SHARE,
176
+                    ]);
177
+                }
178
+                return $storage;
179
+            }
180
+        );
181
+
182
+        // install storage availability wrapper, before most other wrappers
183
+        Filesystem::addStorageWrapper('oc_availability', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
184
+            $externalMount = $mount instanceof ExternalMountPoint || $mount instanceof Mount;
185
+            if ($externalMount && !$storage->isLocal()) {
186
+                return new Availability(['storage' => $storage]);
187
+            }
188
+            return $storage;
189
+        });
190
+
191
+        Filesystem::addStorageWrapper('oc_encoding', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
192
+            if ($mount->getOption('encoding_compatibility', false) && !$mount instanceof SharedMount) {
193
+                return new Encoding(['storage' => $storage]);
194
+            }
195
+            return $storage;
196
+        });
197
+
198
+        $quotaIncludeExternal = $this->config->getSystemValue('quota_include_external_storage', false);
199
+        Filesystem::addStorageWrapper('oc_quota', function ($mountPoint, $storage, IMountPoint $mount) use ($quotaIncludeExternal) {
200
+            // set up quota for home storages, even for other users
201
+            // which can happen when using sharing
202
+            if ($mount instanceof HomeMountPoint) {
203
+                $user = $mount->getUser();
204
+                return new Quota(['storage' => $storage, 'quotaCallback' => function () use ($user) {
205
+                    return $user->getQuotaBytes();
206
+                }, 'root' => 'files', 'include_external_storage' => $quotaIncludeExternal]);
207
+            }
208
+
209
+            return $storage;
210
+        });
211
+
212
+        Filesystem::addStorageWrapper('readonly', function ($mountPoint, IStorage $storage, IMountPoint $mount) {
213
+            /*
214 214
 			 * Do not allow any operations that modify the storage
215 215
 			 */
216
-			if ($mount->getOption('readonly', false)) {
217
-				return new PermissionsMask([
218
-					'storage' => $storage,
219
-					'mask' => Constants::PERMISSION_ALL & ~(
220
-						Constants::PERMISSION_UPDATE
221
-						| Constants::PERMISSION_CREATE
222
-						| Constants::PERMISSION_DELETE
223
-					),
224
-				]);
225
-			}
226
-			return $storage;
227
-		});
228
-
229
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
230
-	}
231
-
232
-	/**
233
-	 * Update the cached mounts for all non-authoritative mount providers for a user.
234
-	 */
235
-	private function updateNonAuthoritativeProviders(IUser $user): void {
236
-		// prevent recursion loop from when getting mounts from providers ends up setting up the filesystem
237
-		static $updatingProviders = false;
238
-		if ($updatingProviders) {
239
-			return;
240
-		}
241
-		$updatingProviders = true;
242
-
243
-		$providers = $this->mountProviderCollection->getProviders();
244
-		$nonAuthoritativeProviders = array_filter(
245
-			$providers,
246
-			fn (IMountProvider $provider) => !(
247
-				$provider instanceof IAuthoritativeMountProvider
248
-				|| $provider instanceof IRootMountProvider
249
-				|| $provider instanceof IHomeMountProvider
250
-			)
251
-		);
252
-		$providerNames = array_map(fn (IMountProvider $provider) => get_class($provider), $nonAuthoritativeProviders);
253
-		$mount = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providerNames);
254
-		$this->userMountCache->registerMounts($user, $mount, $providerNames);
255
-
256
-		$updatingProviders = false;
257
-	}
258
-
259
-	/**
260
-	 * Setup the full filesystem for the specified user
261
-	 */
262
-	public function setupForUser(IUser $user): void {
263
-		if ($this->isSetupComplete($user)) {
264
-			return;
265
-		}
266
-		$this->setupUsersComplete[] = $user->getUID();
267
-
268
-		$this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
269
-
270
-		$this->dropPartialMountsForUser($user);
271
-
272
-		$this->setupUserMountProviders[$user->getUID()] ??= [];
273
-		$previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
274
-
275
-		$this->setupForUserWith($user, function () use ($user) {
276
-			$this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
277
-				string $providerClass,
278
-			) use ($user) {
279
-				return !in_array($providerClass, $this->setupUserMountProviders[$user->getUID()]);
280
-			});
281
-		});
282
-		$this->afterUserFullySetup($user, $previouslySetupProviders);
283
-		$this->eventLogger->end('fs:setup:user:full');
284
-	}
285
-
286
-	/**
287
-	 * Part of the user setup that is run only once per user.
288
-	 */
289
-	private function oneTimeUserSetup(IUser $user) {
290
-		if ($this->isSetupStarted($user)) {
291
-			return;
292
-		}
293
-		$this->setupUsers[] = $user->getUID();
294
-
295
-		$this->setupRoot();
296
-
297
-		$this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
298
-
299
-		$this->setupBuiltinWrappers();
300
-
301
-		$prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
302
-
303
-		// TODO remove hook
304
-		OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
305
-
306
-		$event = new BeforeFileSystemSetupEvent($user);
307
-		$this->eventDispatcher->dispatchTyped($event);
308
-
309
-		Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
310
-
311
-		$userDir = '/' . $user->getUID() . '/files';
312
-
313
-		Filesystem::initInternal($userDir);
314
-
315
-		if ($this->lockdownManager->canAccessFilesystem()) {
316
-			$this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
317
-			// home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
318
-			$homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
319
-			$this->mountManager->addMount($homeMount);
320
-
321
-			if ($homeMount->getStorageRootId() === -1) {
322
-				$this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
323
-				$homeMount->getStorage()->mkdir('');
324
-				$homeMount->getStorage()->getScanner()->scan('');
325
-				$this->eventLogger->end('fs:setup:user:home:scan');
326
-			}
327
-			$this->eventLogger->end('fs:setup:user:home');
328
-		} else {
329
-			$this->mountManager->addMount(new MountPoint(
330
-				new NullStorage([]),
331
-				'/' . $user->getUID()
332
-			));
333
-			$this->mountManager->addMount(new MountPoint(
334
-				new NullStorage([]),
335
-				'/' . $user->getUID() . '/files'
336
-			));
337
-			$this->setupUsersComplete[] = $user->getUID();
338
-		}
339
-
340
-		$this->listenForNewMountProviders();
341
-
342
-		$this->eventLogger->end('fs:setup:user:onetime');
343
-	}
344
-
345
-	/**
346
-	 * Final housekeeping after a user has been fully setup
347
-	 */
348
-	private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
349
-		$this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
350
-		$userRoot = '/' . $user->getUID() . '/';
351
-		$mounts = $this->mountManager->getAll();
352
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
353
-			return str_starts_with($mount->getMountPoint(), $userRoot);
354
-		});
355
-		$allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
356
-			return get_class($provider);
357
-		}, array_merge(
358
-			$this->mountProviderCollection->getProviders(),
359
-			$this->mountProviderCollection->getHomeProviders(),
360
-			$this->mountProviderCollection->getRootProviders(),
361
-		));
362
-		$newProviders = array_diff($allProviders, $previouslySetupProviders);
363
-		$mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
364
-			return !in_array($mount->getMountProvider(), $previouslySetupProviders);
365
-		});
366
-		$this->registerMounts($user, $mounts, $newProviders);
367
-
368
-		$this->markUserMountsCached($user);
369
-		$this->eventLogger->end('fs:setup:user:full:post');
370
-	}
371
-
372
-	private function markUserMountsCached(IUser $user): void {
373
-		$cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
374
-		if ($cacheDuration > 0) {
375
-			$this->cache->set($user->getUID(), true, $cacheDuration);
376
-			$this->fullSetupRequired[$user->getUID()] = false;
377
-		}
378
-	}
379
-
380
-	/**
381
-	 * Executes the one-time user setup and, if the user can access the
382
-	 * filesystem, executes $mountCallback.
383
-	 *
384
-	 * @param IUser $user
385
-	 * @param IMountPoint $mounts
386
-	 * @return void
387
-	 * @throws \OCP\HintException
388
-	 * @throws \OC\ServerNotAvailableException
389
-	 * @see self::oneTimeUserSetup()
390
-	 *
391
-	 */
392
-	private function setupForUserWith(IUser $user, callable $mountCallback): void {
393
-		$this->oneTimeUserSetup($user);
394
-
395
-		if ($this->lockdownManager->canAccessFilesystem()) {
396
-			$mountCallback();
397
-		}
398
-		$this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
399
-		\OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
400
-		$this->eventLogger->end('fs:setup:user:post-init-mountpoint');
401
-
402
-		$userDir = '/' . $user->getUID() . '/files';
403
-		$this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
404
-		OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
405
-		$this->eventLogger->end('fs:setup:user:setup-hook');
406
-	}
407
-
408
-	/**
409
-	 * Set up the root filesystem
410
-	 */
411
-	public function setupRoot(): void {
412
-		//setting up the filesystem twice can only lead to trouble
413
-		if ($this->rootSetup) {
414
-			return;
415
-		}
416
-
417
-		$this->setupBuiltinWrappers();
418
-
419
-		$this->rootSetup = true;
420
-
421
-		$this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
422
-
423
-		$rootMounts = $this->mountProviderCollection->getRootMounts();
424
-		foreach ($rootMounts as $rootMountProvider) {
425
-			$this->mountManager->addMount($rootMountProvider);
426
-		}
427
-
428
-		$this->eventLogger->end('fs:setup:root');
429
-	}
430
-
431
-	/**
432
-	 * Get the user to setup for a path or `null` if the root needs to be setup
433
-	 *
434
-	 * @param string $path
435
-	 * @return IUser|null
436
-	 */
437
-	private function getUserForPath(string $path): ?IUser {
438
-		if ($path === '' || $path === '/') {
439
-			return null;
440
-		} elseif (str_starts_with($path, '/__groupfolders')) {
441
-			return null;
442
-		} elseif (substr_count($path, '/') < 2) {
443
-			if ($user = $this->userSession->getUser()) {
444
-				return $user;
445
-			} else {
446
-				return null;
447
-			}
448
-		} elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
449
-			return null;
450
-		} else {
451
-			[, $userId] = explode('/', $path);
452
-		}
453
-
454
-		return $this->userManager->get($userId);
455
-	}
456
-
457
-	/**
458
-	 * Set up the filesystem for the specified path, optionally including all
459
-	 * children mounts.
460
-	 */
461
-	public function setupForPath(string $path, bool $includeChildren = false): void {
462
-		$user = $this->getUserForPath($path);
463
-		if (!$user) {
464
-			$this->setupRoot();
465
-			return;
466
-		}
467
-
468
-		if ($this->isSetupComplete($user)) {
469
-			return;
470
-		}
471
-
472
-		if ($this->fullSetupRequired($user)) {
473
-			if ($this->optimizeAuthoritativeProviders) {
474
-				$this->updateNonAuthoritativeProviders($user);
475
-				$this->markUserMountsCached($user);
476
-			} else {
477
-				$this->setupForUser($user);
478
-				return;
479
-			}
480
-		}
481
-
482
-		// for the user's home folder, and includes children we need everything always
483
-		if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
484
-			$this->setupForUser($user);
485
-			return;
486
-		}
487
-
488
-		if (!isset($this->setupUserMountProviders[$user->getUID()])) {
489
-			$this->setupUserMountProviders[$user->getUID()] = [];
490
-		}
491
-		$setupProviders = &$this->setupUserMountProviders[$user->getUID()];
492
-		$currentProviders = [];
493
-
494
-		try {
495
-			$cachedMount = $this->userMountCache->getMountForPath($user, $path);
496
-		} catch (NotFoundException $e) {
497
-			$this->setupForUser($user);
498
-			return;
499
-		}
500
-
501
-		$this->oneTimeUserSetup($user);
502
-
503
-		$this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
504
-		$this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
505
-
506
-		$fullProviderMounts = [];
507
-		$authoritativeMounts = [];
508
-
509
-		$mountProvider = $cachedMount->getMountProvider();
510
-		$mountPoint = $cachedMount->getMountPoint();
511
-		$isMountProviderSetup = in_array($mountProvider, $setupProviders);
512
-		$isPathSetupAsAuthoritative = $this->isPathSetup($mountPoint);
513
-		if (!$isMountProviderSetup && !$isPathSetupAsAuthoritative) {
514
-			if ($mountProvider === '') {
515
-				$this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
516
-				$this->eventLogger->end('fs:setup:user:path:find');
517
-				$this->setupForUser($user);
518
-				$this->eventLogger->end('fs:setup:user:path');
519
-				return;
520
-			}
521
-
522
-			if (is_a($mountProvider, IPartialMountProvider::class, true)) {
523
-				$rootId = $cachedMount->getRootId();
524
-				$rootMetadata = $this->fileAccess->getByFileId($rootId);
525
-				if (!$rootMetadata) {
526
-					$this->setupForUser($user);
527
-					return;
528
-				}
529
-				$providerArgs = new MountProviderArgs($cachedMount, $rootMetadata);
530
-				// mark the path as cached (without children for now...)
531
-				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITHOUT_CHILDREN;
532
-				$authoritativeMounts[] = array_values(
533
-					$this->mountProviderCollection->getUserMountsFromProviderByPath(
534
-						$mountProvider,
535
-						$path,
536
-						false,
537
-						[$providerArgs]
538
-					)
539
-				);
540
-			} else {
541
-				$currentProviders[] = $mountProvider;
542
-				$setupProviders[] = $mountProvider;
543
-				$fullProviderMounts[] = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
544
-			}
545
-		}
546
-
547
-		if ($includeChildren) {
548
-			$subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
549
-			$this->eventLogger->end('fs:setup:user:path:find');
550
-
551
-			$needsFullSetup = array_any(
552
-				$subCachedMounts,
553
-				fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
554
-			);
555
-
556
-			if ($needsFullSetup) {
557
-				$this->logger->debug('mount has no provider set, performing full setup');
558
-				$this->setupForUser($user);
559
-				$this->eventLogger->end('fs:setup:user:path');
560
-				return;
561
-			}
562
-
563
-			/** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
564
-			$authoritativeCachedMounts = [];
565
-			foreach ($subCachedMounts as $cachedMount) {
566
-				/** @var class-string<IMountProvider> $mountProvider */
567
-				$mountProvider = $cachedMount->getMountProvider();
568
-
569
-				// skip setup for already set up providers
570
-				if (in_array($mountProvider, $setupProviders)) {
571
-					continue;
572
-				}
573
-
574
-				if (is_a($mountProvider, IPartialMountProvider::class, true)) {
575
-					// skip setup if path was set up as authoritative before
576
-					if ($this->isPathSetup($cachedMount->getMountPoint())) {
577
-						continue;
578
-					}
579
-					// collect cached mount points for authoritative providers
580
-					$authoritativeCachedMounts[$mountProvider] ??= [];
581
-					$authoritativeCachedMounts[$mountProvider][] = $cachedMount;
582
-					continue;
583
-				}
584
-
585
-				$currentProviders[] = $mountProvider;
586
-				$setupProviders[] = $mountProvider;
587
-				$fullProviderMounts[] = $this->mountProviderCollection->getUserMountsForProviderClasses(
588
-					$user,
589
-					[$mountProvider]
590
-				);
591
-			}
592
-
593
-			if (!empty($authoritativeCachedMounts)) {
594
-				$rootIds = array_map(
595
-					fn (ICachedMountInfo $mount) => $mount->getRootId(),
596
-					array_merge(...array_values($authoritativeCachedMounts)),
597
-				);
598
-
599
-				$rootsMetadata = [];
600
-				foreach (array_chunk($rootIds, 1000) as $chunk) {
601
-					foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
602
-						$rootsMetadata[$id] = $fileMetadata;
603
-					}
604
-				}
605
-				$this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
606
-				foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
607
-					$providerArgs = array_values(array_filter(array_map(
608
-						static function (ICachedMountInfo $info) use ($rootsMetadata) {
609
-							$rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
610
-
611
-							return $rootMetadata
612
-								? new MountProviderArgs($info, $rootMetadata)
613
-								: null;
614
-						},
615
-						$cachedMounts
616
-					)));
617
-					$authoritativeMounts[] = $this->mountProviderCollection->getUserMountsFromProviderByPath(
618
-						$providerClass,
619
-						$path,
620
-						true,
621
-						$providerArgs,
622
-					);
623
-				}
624
-			}
625
-		} else {
626
-			$this->eventLogger->end('fs:setup:user:path:find');
627
-		}
628
-
629
-		$fullProviderMounts = array_merge(...$fullProviderMounts);
630
-		$authoritativeMounts = array_merge(...$authoritativeMounts);
631
-
632
-		if (count($fullProviderMounts) || count($authoritativeMounts)) {
633
-			if (count($fullProviderMounts)) {
634
-				$this->registerMounts($user, $fullProviderMounts, $currentProviders);
635
-			}
636
-
637
-			$this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
638
-				$allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
639
-				array_walk($allMounts, $this->mountManager->addMount(...));
640
-			});
641
-		} elseif (!$this->isSetupStarted($user)) {
642
-			$this->oneTimeUserSetup($user);
643
-		}
644
-		$this->eventLogger->end('fs:setup:user:path');
645
-	}
646
-
647
-	private function fullSetupRequired(IUser $user): bool {
648
-		if ($this->forceFullSetup) {
649
-			return true;
650
-		}
651
-
652
-		// we perform a "cached" setup only after having done the full setup recently
653
-		// this is also used to trigger a full setup after handling events that are likely
654
-		// to change the available mounts
655
-		if (!isset($this->fullSetupRequired[$user->getUID()])) {
656
-			$this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
657
-		}
658
-		return $this->fullSetupRequired[$user->getUID()];
659
-	}
660
-
661
-	/**
662
-	 * @param string $path
663
-	 * @param string[] $providers
664
-	 */
665
-	public function setupForProvider(string $path, array $providers): void {
666
-		$user = $this->getUserForPath($path);
667
-		if (!$user) {
668
-			$this->setupRoot();
669
-			return;
670
-		}
671
-
672
-		if ($this->isSetupComplete($user)) {
673
-			return;
674
-		}
675
-
676
-		if ($this->fullSetupRequired($user)) {
677
-			$this->setupForUser($user);
678
-			return;
679
-		}
680
-
681
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
682
-
683
-		$this->oneTimeUserSetup($user);
684
-
685
-		// home providers are always used
686
-		$providers = array_filter($providers, function (string $provider) {
687
-			return !is_subclass_of($provider, IHomeMountProvider::class);
688
-		});
689
-
690
-		if (in_array('', $providers)) {
691
-			$this->setupForUser($user);
692
-			return;
693
-		}
694
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
695
-
696
-		$providers = array_diff($providers, $setupProviders);
697
-		if (count($providers) === 0) {
698
-			if (!$this->isSetupStarted($user)) {
699
-				$this->oneTimeUserSetup($user);
700
-			}
701
-			$this->eventLogger->end('fs:setup:user:providers');
702
-			return;
703
-		} else {
704
-			$this->dropPartialMountsForUser($user, $providers);
705
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
706
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
707
-		}
708
-
709
-		$this->registerMounts($user, $mounts, $providers);
710
-		$this->setupForUserWith($user, function () use ($mounts) {
711
-			array_walk($mounts, [$this->mountManager, 'addMount']);
712
-		});
713
-		$this->eventLogger->end('fs:setup:user:providers');
714
-	}
715
-
716
-	public function tearDown() {
717
-		$this->setupUsers = [];
718
-		$this->setupUsersComplete = [];
719
-		$this->setupUserMountProviders = [];
720
-		$this->setupMountProviderPaths = [];
721
-		$this->fullSetupRequired = [];
722
-		$this->rootSetup = false;
723
-		$this->mountManager->clear();
724
-		$this->userMountCache->clear();
725
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
726
-	}
727
-
728
-	/**
729
-	 * Get mounts from mount providers that are registered after setup
730
-	 */
731
-	private function listenForNewMountProviders() {
732
-		if (!$this->listeningForProviders) {
733
-			$this->listeningForProviders = true;
734
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
735
-				IMountProvider $provider,
736
-			) {
737
-				foreach ($this->setupUsers as $userId) {
738
-					$user = $this->userManager->get($userId);
739
-					if ($user) {
740
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
741
-						array_walk($mounts, [$this->mountManager, 'addMount']);
742
-					}
743
-				}
744
-			});
745
-		}
746
-	}
747
-
748
-	private function setupListeners() {
749
-		// note that this event handling is intentionally pessimistic
750
-		// clearing the cache to often is better than not enough
751
-
752
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
753
-			$this->cache->remove($event->getUser()->getUID());
754
-		});
755
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
756
-			$this->cache->remove($event->getUser()->getUID());
757
-		});
758
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
759
-			$this->cache->remove($event->getShare()->getSharedWith());
760
-		});
761
-		$this->eventDispatcher->addListener(BeforeNodeRenamedEvent::class, function (BeforeNodeRenamedEvent $event) {
762
-			// update cache information that is cached by mount point
763
-			$from = rtrim($event->getSource()->getPath(), '/') . '/';
764
-			$to = rtrim($event->getTarget()->getPath(), '/') . '/';
765
-			$existingMount = $this->setupMountProviderPaths[$from] ?? null;
766
-			if ($existingMount !== null) {
767
-				$this->setupMountProviderPaths[$to] = $this->setupMountProviderPaths[$from];
768
-				unset($this->setupMountProviderPaths[$from]);
769
-			}
770
-		});
771
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
772
-		) {
773
-			if ($user = $event->getUser()) {
774
-				$this->cache->remove($user->getUID());
775
-			} else {
776
-				$this->cache->clear();
777
-			}
778
-		});
779
-
780
-		$genericEvents = [
781
-			'OCA\Circles\Events\CreatingCircleEvent',
782
-			'OCA\Circles\Events\DestroyingCircleEvent',
783
-			'OCA\Circles\Events\AddingCircleMemberEvent',
784
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
785
-		];
786
-
787
-		foreach ($genericEvents as $genericEvent) {
788
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
789
-				$this->cache->clear();
790
-			});
791
-		}
792
-	}
793
-
794
-	private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
795
-		if ($this->lockdownManager->canAccessFilesystem()) {
796
-			$this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
797
-		}
798
-	}
799
-
800
-	/**
801
-	 * Drops partially set-up mounts for the given user
802
-	 *
803
-	 * @param class-string<IMountProvider>[] $providers
804
-	 */
805
-	public function dropPartialMountsForUser(IUser $user, array $providers = []): void {
806
-		// mounts are cached by mount-point
807
-		$mounts = $this->mountManager->getAll();
808
-		$partialMounts = array_filter($this->setupMountProviderPaths,
809
-			static function (string $mountPoint) use (
810
-				$providers,
811
-				$user,
812
-				$mounts
813
-			) {
814
-				$isUserMount = str_starts_with($mountPoint, '/' . $user->getUID() . '/files');
815
-
816
-				if (!$isUserMount) {
817
-					return false;
818
-				}
819
-
820
-				$mountProvider = ($mounts[$mountPoint] ?? null)?->getMountProvider();
821
-
822
-				return empty($providers)
823
-					|| \in_array($mountProvider, $providers, true);
824
-			},
825
-			ARRAY_FILTER_USE_KEY);
826
-
827
-		if (!empty($partialMounts)) {
828
-			// remove partially set up mounts
829
-			foreach ($partialMounts as $mountPoint => $_mount) {
830
-				$this->mountManager->removeMount($mountPoint);
831
-				unset($this->setupMountProviderPaths[$mountPoint]);
832
-			}
833
-		}
834
-	}
216
+            if ($mount->getOption('readonly', false)) {
217
+                return new PermissionsMask([
218
+                    'storage' => $storage,
219
+                    'mask' => Constants::PERMISSION_ALL & ~(
220
+                        Constants::PERMISSION_UPDATE
221
+                        | Constants::PERMISSION_CREATE
222
+                        | Constants::PERMISSION_DELETE
223
+                    ),
224
+                ]);
225
+            }
226
+            return $storage;
227
+        });
228
+
229
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
230
+    }
231
+
232
+    /**
233
+     * Update the cached mounts for all non-authoritative mount providers for a user.
234
+     */
235
+    private function updateNonAuthoritativeProviders(IUser $user): void {
236
+        // prevent recursion loop from when getting mounts from providers ends up setting up the filesystem
237
+        static $updatingProviders = false;
238
+        if ($updatingProviders) {
239
+            return;
240
+        }
241
+        $updatingProviders = true;
242
+
243
+        $providers = $this->mountProviderCollection->getProviders();
244
+        $nonAuthoritativeProviders = array_filter(
245
+            $providers,
246
+            fn (IMountProvider $provider) => !(
247
+                $provider instanceof IAuthoritativeMountProvider
248
+                || $provider instanceof IRootMountProvider
249
+                || $provider instanceof IHomeMountProvider
250
+            )
251
+        );
252
+        $providerNames = array_map(fn (IMountProvider $provider) => get_class($provider), $nonAuthoritativeProviders);
253
+        $mount = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providerNames);
254
+        $this->userMountCache->registerMounts($user, $mount, $providerNames);
255
+
256
+        $updatingProviders = false;
257
+    }
258
+
259
+    /**
260
+     * Setup the full filesystem for the specified user
261
+     */
262
+    public function setupForUser(IUser $user): void {
263
+        if ($this->isSetupComplete($user)) {
264
+            return;
265
+        }
266
+        $this->setupUsersComplete[] = $user->getUID();
267
+
268
+        $this->eventLogger->start('fs:setup:user:full', 'Setup full filesystem for user');
269
+
270
+        $this->dropPartialMountsForUser($user);
271
+
272
+        $this->setupUserMountProviders[$user->getUID()] ??= [];
273
+        $previouslySetupProviders = $this->setupUserMountProviders[$user->getUID()];
274
+
275
+        $this->setupForUserWith($user, function () use ($user) {
276
+            $this->mountProviderCollection->addMountForUser($user, $this->mountManager, function (
277
+                string $providerClass,
278
+            ) use ($user) {
279
+                return !in_array($providerClass, $this->setupUserMountProviders[$user->getUID()]);
280
+            });
281
+        });
282
+        $this->afterUserFullySetup($user, $previouslySetupProviders);
283
+        $this->eventLogger->end('fs:setup:user:full');
284
+    }
285
+
286
+    /**
287
+     * Part of the user setup that is run only once per user.
288
+     */
289
+    private function oneTimeUserSetup(IUser $user) {
290
+        if ($this->isSetupStarted($user)) {
291
+            return;
292
+        }
293
+        $this->setupUsers[] = $user->getUID();
294
+
295
+        $this->setupRoot();
296
+
297
+        $this->eventLogger->start('fs:setup:user:onetime', 'Onetime filesystem for user');
298
+
299
+        $this->setupBuiltinWrappers();
300
+
301
+        $prevLogging = Filesystem::logWarningWhenAddingStorageWrapper(false);
302
+
303
+        // TODO remove hook
304
+        OC_Hook::emit('OC_Filesystem', 'preSetup', ['user' => $user->getUID()]);
305
+
306
+        $event = new BeforeFileSystemSetupEvent($user);
307
+        $this->eventDispatcher->dispatchTyped($event);
308
+
309
+        Filesystem::logWarningWhenAddingStorageWrapper($prevLogging);
310
+
311
+        $userDir = '/' . $user->getUID() . '/files';
312
+
313
+        Filesystem::initInternal($userDir);
314
+
315
+        if ($this->lockdownManager->canAccessFilesystem()) {
316
+            $this->eventLogger->start('fs:setup:user:home', 'Setup home filesystem for user');
317
+            // home mounts are handled separate since we need to ensure this is mounted before we call the other mount providers
318
+            $homeMount = $this->mountProviderCollection->getHomeMountForUser($user);
319
+            $this->mountManager->addMount($homeMount);
320
+
321
+            if ($homeMount->getStorageRootId() === -1) {
322
+                $this->eventLogger->start('fs:setup:user:home:scan', 'Scan home filesystem for user');
323
+                $homeMount->getStorage()->mkdir('');
324
+                $homeMount->getStorage()->getScanner()->scan('');
325
+                $this->eventLogger->end('fs:setup:user:home:scan');
326
+            }
327
+            $this->eventLogger->end('fs:setup:user:home');
328
+        } else {
329
+            $this->mountManager->addMount(new MountPoint(
330
+                new NullStorage([]),
331
+                '/' . $user->getUID()
332
+            ));
333
+            $this->mountManager->addMount(new MountPoint(
334
+                new NullStorage([]),
335
+                '/' . $user->getUID() . '/files'
336
+            ));
337
+            $this->setupUsersComplete[] = $user->getUID();
338
+        }
339
+
340
+        $this->listenForNewMountProviders();
341
+
342
+        $this->eventLogger->end('fs:setup:user:onetime');
343
+    }
344
+
345
+    /**
346
+     * Final housekeeping after a user has been fully setup
347
+     */
348
+    private function afterUserFullySetup(IUser $user, array $previouslySetupProviders): void {
349
+        $this->eventLogger->start('fs:setup:user:full:post', 'Housekeeping after user is setup');
350
+        $userRoot = '/' . $user->getUID() . '/';
351
+        $mounts = $this->mountManager->getAll();
352
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($userRoot) {
353
+            return str_starts_with($mount->getMountPoint(), $userRoot);
354
+        });
355
+        $allProviders = array_map(function (IMountProvider|IHomeMountProvider|IRootMountProvider $provider) {
356
+            return get_class($provider);
357
+        }, array_merge(
358
+            $this->mountProviderCollection->getProviders(),
359
+            $this->mountProviderCollection->getHomeProviders(),
360
+            $this->mountProviderCollection->getRootProviders(),
361
+        ));
362
+        $newProviders = array_diff($allProviders, $previouslySetupProviders);
363
+        $mounts = array_filter($mounts, function (IMountPoint $mount) use ($previouslySetupProviders) {
364
+            return !in_array($mount->getMountProvider(), $previouslySetupProviders);
365
+        });
366
+        $this->registerMounts($user, $mounts, $newProviders);
367
+
368
+        $this->markUserMountsCached($user);
369
+        $this->eventLogger->end('fs:setup:user:full:post');
370
+    }
371
+
372
+    private function markUserMountsCached(IUser $user): void {
373
+        $cacheDuration = $this->config->getSystemValueInt('fs_mount_cache_duration', 5 * 60);
374
+        if ($cacheDuration > 0) {
375
+            $this->cache->set($user->getUID(), true, $cacheDuration);
376
+            $this->fullSetupRequired[$user->getUID()] = false;
377
+        }
378
+    }
379
+
380
+    /**
381
+     * Executes the one-time user setup and, if the user can access the
382
+     * filesystem, executes $mountCallback.
383
+     *
384
+     * @param IUser $user
385
+     * @param IMountPoint $mounts
386
+     * @return void
387
+     * @throws \OCP\HintException
388
+     * @throws \OC\ServerNotAvailableException
389
+     * @see self::oneTimeUserSetup()
390
+     *
391
+     */
392
+    private function setupForUserWith(IUser $user, callable $mountCallback): void {
393
+        $this->oneTimeUserSetup($user);
394
+
395
+        if ($this->lockdownManager->canAccessFilesystem()) {
396
+            $mountCallback();
397
+        }
398
+        $this->eventLogger->start('fs:setup:user:post-init-mountpoint', 'post_initMountPoints legacy hook');
399
+        \OC_Hook::emit('OC_Filesystem', 'post_initMountPoints', ['user' => $user->getUID()]);
400
+        $this->eventLogger->end('fs:setup:user:post-init-mountpoint');
401
+
402
+        $userDir = '/' . $user->getUID() . '/files';
403
+        $this->eventLogger->start('fs:setup:user:setup-hook', 'setup legacy hook');
404
+        OC_Hook::emit('OC_Filesystem', 'setup', ['user' => $user->getUID(), 'user_dir' => $userDir]);
405
+        $this->eventLogger->end('fs:setup:user:setup-hook');
406
+    }
407
+
408
+    /**
409
+     * Set up the root filesystem
410
+     */
411
+    public function setupRoot(): void {
412
+        //setting up the filesystem twice can only lead to trouble
413
+        if ($this->rootSetup) {
414
+            return;
415
+        }
416
+
417
+        $this->setupBuiltinWrappers();
418
+
419
+        $this->rootSetup = true;
420
+
421
+        $this->eventLogger->start('fs:setup:root', 'Setup root filesystem');
422
+
423
+        $rootMounts = $this->mountProviderCollection->getRootMounts();
424
+        foreach ($rootMounts as $rootMountProvider) {
425
+            $this->mountManager->addMount($rootMountProvider);
426
+        }
427
+
428
+        $this->eventLogger->end('fs:setup:root');
429
+    }
430
+
431
+    /**
432
+     * Get the user to setup for a path or `null` if the root needs to be setup
433
+     *
434
+     * @param string $path
435
+     * @return IUser|null
436
+     */
437
+    private function getUserForPath(string $path): ?IUser {
438
+        if ($path === '' || $path === '/') {
439
+            return null;
440
+        } elseif (str_starts_with($path, '/__groupfolders')) {
441
+            return null;
442
+        } elseif (substr_count($path, '/') < 2) {
443
+            if ($user = $this->userSession->getUser()) {
444
+                return $user;
445
+            } else {
446
+                return null;
447
+            }
448
+        } elseif (str_starts_with($path, '/appdata_' . \OC_Util::getInstanceId()) || str_starts_with($path, '/files_external/')) {
449
+            return null;
450
+        } else {
451
+            [, $userId] = explode('/', $path);
452
+        }
453
+
454
+        return $this->userManager->get($userId);
455
+    }
456
+
457
+    /**
458
+     * Set up the filesystem for the specified path, optionally including all
459
+     * children mounts.
460
+     */
461
+    public function setupForPath(string $path, bool $includeChildren = false): void {
462
+        $user = $this->getUserForPath($path);
463
+        if (!$user) {
464
+            $this->setupRoot();
465
+            return;
466
+        }
467
+
468
+        if ($this->isSetupComplete($user)) {
469
+            return;
470
+        }
471
+
472
+        if ($this->fullSetupRequired($user)) {
473
+            if ($this->optimizeAuthoritativeProviders) {
474
+                $this->updateNonAuthoritativeProviders($user);
475
+                $this->markUserMountsCached($user);
476
+            } else {
477
+                $this->setupForUser($user);
478
+                return;
479
+            }
480
+        }
481
+
482
+        // for the user's home folder, and includes children we need everything always
483
+        if (rtrim($path) === '/' . $user->getUID() . '/files' && $includeChildren) {
484
+            $this->setupForUser($user);
485
+            return;
486
+        }
487
+
488
+        if (!isset($this->setupUserMountProviders[$user->getUID()])) {
489
+            $this->setupUserMountProviders[$user->getUID()] = [];
490
+        }
491
+        $setupProviders = &$this->setupUserMountProviders[$user->getUID()];
492
+        $currentProviders = [];
493
+
494
+        try {
495
+            $cachedMount = $this->userMountCache->getMountForPath($user, $path);
496
+        } catch (NotFoundException $e) {
497
+            $this->setupForUser($user);
498
+            return;
499
+        }
500
+
501
+        $this->oneTimeUserSetup($user);
502
+
503
+        $this->eventLogger->start('fs:setup:user:path', "Setup $path filesystem for user");
504
+        $this->eventLogger->start('fs:setup:user:path:find', "Find mountpoint for $path");
505
+
506
+        $fullProviderMounts = [];
507
+        $authoritativeMounts = [];
508
+
509
+        $mountProvider = $cachedMount->getMountProvider();
510
+        $mountPoint = $cachedMount->getMountPoint();
511
+        $isMountProviderSetup = in_array($mountProvider, $setupProviders);
512
+        $isPathSetupAsAuthoritative = $this->isPathSetup($mountPoint);
513
+        if (!$isMountProviderSetup && !$isPathSetupAsAuthoritative) {
514
+            if ($mountProvider === '') {
515
+                $this->logger->debug('mount at ' . $cachedMount->getMountPoint() . ' has no provider set, performing full setup');
516
+                $this->eventLogger->end('fs:setup:user:path:find');
517
+                $this->setupForUser($user);
518
+                $this->eventLogger->end('fs:setup:user:path');
519
+                return;
520
+            }
521
+
522
+            if (is_a($mountProvider, IPartialMountProvider::class, true)) {
523
+                $rootId = $cachedMount->getRootId();
524
+                $rootMetadata = $this->fileAccess->getByFileId($rootId);
525
+                if (!$rootMetadata) {
526
+                    $this->setupForUser($user);
527
+                    return;
528
+                }
529
+                $providerArgs = new MountProviderArgs($cachedMount, $rootMetadata);
530
+                // mark the path as cached (without children for now...)
531
+                $this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITHOUT_CHILDREN;
532
+                $authoritativeMounts[] = array_values(
533
+                    $this->mountProviderCollection->getUserMountsFromProviderByPath(
534
+                        $mountProvider,
535
+                        $path,
536
+                        false,
537
+                        [$providerArgs]
538
+                    )
539
+                );
540
+            } else {
541
+                $currentProviders[] = $mountProvider;
542
+                $setupProviders[] = $mountProvider;
543
+                $fullProviderMounts[] = $this->mountProviderCollection->getUserMountsForProviderClasses($user, [$mountProvider]);
544
+            }
545
+        }
546
+
547
+        if ($includeChildren) {
548
+            $subCachedMounts = $this->userMountCache->getMountsInPath($user, $path);
549
+            $this->eventLogger->end('fs:setup:user:path:find');
550
+
551
+            $needsFullSetup = array_any(
552
+                $subCachedMounts,
553
+                fn (ICachedMountInfo $info) => $info->getMountProvider() === ''
554
+            );
555
+
556
+            if ($needsFullSetup) {
557
+                $this->logger->debug('mount has no provider set, performing full setup');
558
+                $this->setupForUser($user);
559
+                $this->eventLogger->end('fs:setup:user:path');
560
+                return;
561
+            }
562
+
563
+            /** @var array<class-string<IMountProvider>, ICachedMountInfo[]> $authoritativeCachedMounts */
564
+            $authoritativeCachedMounts = [];
565
+            foreach ($subCachedMounts as $cachedMount) {
566
+                /** @var class-string<IMountProvider> $mountProvider */
567
+                $mountProvider = $cachedMount->getMountProvider();
568
+
569
+                // skip setup for already set up providers
570
+                if (in_array($mountProvider, $setupProviders)) {
571
+                    continue;
572
+                }
573
+
574
+                if (is_a($mountProvider, IPartialMountProvider::class, true)) {
575
+                    // skip setup if path was set up as authoritative before
576
+                    if ($this->isPathSetup($cachedMount->getMountPoint())) {
577
+                        continue;
578
+                    }
579
+                    // collect cached mount points for authoritative providers
580
+                    $authoritativeCachedMounts[$mountProvider] ??= [];
581
+                    $authoritativeCachedMounts[$mountProvider][] = $cachedMount;
582
+                    continue;
583
+                }
584
+
585
+                $currentProviders[] = $mountProvider;
586
+                $setupProviders[] = $mountProvider;
587
+                $fullProviderMounts[] = $this->mountProviderCollection->getUserMountsForProviderClasses(
588
+                    $user,
589
+                    [$mountProvider]
590
+                );
591
+            }
592
+
593
+            if (!empty($authoritativeCachedMounts)) {
594
+                $rootIds = array_map(
595
+                    fn (ICachedMountInfo $mount) => $mount->getRootId(),
596
+                    array_merge(...array_values($authoritativeCachedMounts)),
597
+                );
598
+
599
+                $rootsMetadata = [];
600
+                foreach (array_chunk($rootIds, 1000) as $chunk) {
601
+                    foreach ($this->fileAccess->getByFileIds($chunk) as $id => $fileMetadata) {
602
+                        $rootsMetadata[$id] = $fileMetadata;
603
+                    }
604
+                }
605
+                $this->setupMountProviderPaths[$mountPoint] = self::SETUP_WITH_CHILDREN;
606
+                foreach ($authoritativeCachedMounts as $providerClass => $cachedMounts) {
607
+                    $providerArgs = array_values(array_filter(array_map(
608
+                        static function (ICachedMountInfo $info) use ($rootsMetadata) {
609
+                            $rootMetadata = $rootsMetadata[$info->getRootId()] ?? null;
610
+
611
+                            return $rootMetadata
612
+                                ? new MountProviderArgs($info, $rootMetadata)
613
+                                : null;
614
+                        },
615
+                        $cachedMounts
616
+                    )));
617
+                    $authoritativeMounts[] = $this->mountProviderCollection->getUserMountsFromProviderByPath(
618
+                        $providerClass,
619
+                        $path,
620
+                        true,
621
+                        $providerArgs,
622
+                    );
623
+                }
624
+            }
625
+        } else {
626
+            $this->eventLogger->end('fs:setup:user:path:find');
627
+        }
628
+
629
+        $fullProviderMounts = array_merge(...$fullProviderMounts);
630
+        $authoritativeMounts = array_merge(...$authoritativeMounts);
631
+
632
+        if (count($fullProviderMounts) || count($authoritativeMounts)) {
633
+            if (count($fullProviderMounts)) {
634
+                $this->registerMounts($user, $fullProviderMounts, $currentProviders);
635
+            }
636
+
637
+            $this->setupForUserWith($user, function () use ($fullProviderMounts, $authoritativeMounts) {
638
+                $allMounts = [...$fullProviderMounts, ...$authoritativeMounts];
639
+                array_walk($allMounts, $this->mountManager->addMount(...));
640
+            });
641
+        } elseif (!$this->isSetupStarted($user)) {
642
+            $this->oneTimeUserSetup($user);
643
+        }
644
+        $this->eventLogger->end('fs:setup:user:path');
645
+    }
646
+
647
+    private function fullSetupRequired(IUser $user): bool {
648
+        if ($this->forceFullSetup) {
649
+            return true;
650
+        }
651
+
652
+        // we perform a "cached" setup only after having done the full setup recently
653
+        // this is also used to trigger a full setup after handling events that are likely
654
+        // to change the available mounts
655
+        if (!isset($this->fullSetupRequired[$user->getUID()])) {
656
+            $this->fullSetupRequired[$user->getUID()] = !$this->cache->get($user->getUID());
657
+        }
658
+        return $this->fullSetupRequired[$user->getUID()];
659
+    }
660
+
661
+    /**
662
+     * @param string $path
663
+     * @param string[] $providers
664
+     */
665
+    public function setupForProvider(string $path, array $providers): void {
666
+        $user = $this->getUserForPath($path);
667
+        if (!$user) {
668
+            $this->setupRoot();
669
+            return;
670
+        }
671
+
672
+        if ($this->isSetupComplete($user)) {
673
+            return;
674
+        }
675
+
676
+        if ($this->fullSetupRequired($user)) {
677
+            $this->setupForUser($user);
678
+            return;
679
+        }
680
+
681
+        $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
682
+
683
+        $this->oneTimeUserSetup($user);
684
+
685
+        // home providers are always used
686
+        $providers = array_filter($providers, function (string $provider) {
687
+            return !is_subclass_of($provider, IHomeMountProvider::class);
688
+        });
689
+
690
+        if (in_array('', $providers)) {
691
+            $this->setupForUser($user);
692
+            return;
693
+        }
694
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
695
+
696
+        $providers = array_diff($providers, $setupProviders);
697
+        if (count($providers) === 0) {
698
+            if (!$this->isSetupStarted($user)) {
699
+                $this->oneTimeUserSetup($user);
700
+            }
701
+            $this->eventLogger->end('fs:setup:user:providers');
702
+            return;
703
+        } else {
704
+            $this->dropPartialMountsForUser($user, $providers);
705
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
706
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
707
+        }
708
+
709
+        $this->registerMounts($user, $mounts, $providers);
710
+        $this->setupForUserWith($user, function () use ($mounts) {
711
+            array_walk($mounts, [$this->mountManager, 'addMount']);
712
+        });
713
+        $this->eventLogger->end('fs:setup:user:providers');
714
+    }
715
+
716
+    public function tearDown() {
717
+        $this->setupUsers = [];
718
+        $this->setupUsersComplete = [];
719
+        $this->setupUserMountProviders = [];
720
+        $this->setupMountProviderPaths = [];
721
+        $this->fullSetupRequired = [];
722
+        $this->rootSetup = false;
723
+        $this->mountManager->clear();
724
+        $this->userMountCache->clear();
725
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
726
+    }
727
+
728
+    /**
729
+     * Get mounts from mount providers that are registered after setup
730
+     */
731
+    private function listenForNewMountProviders() {
732
+        if (!$this->listeningForProviders) {
733
+            $this->listeningForProviders = true;
734
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
735
+                IMountProvider $provider,
736
+            ) {
737
+                foreach ($this->setupUsers as $userId) {
738
+                    $user = $this->userManager->get($userId);
739
+                    if ($user) {
740
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
741
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
742
+                    }
743
+                }
744
+            });
745
+        }
746
+    }
747
+
748
+    private function setupListeners() {
749
+        // note that this event handling is intentionally pessimistic
750
+        // clearing the cache to often is better than not enough
751
+
752
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
753
+            $this->cache->remove($event->getUser()->getUID());
754
+        });
755
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
756
+            $this->cache->remove($event->getUser()->getUID());
757
+        });
758
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
759
+            $this->cache->remove($event->getShare()->getSharedWith());
760
+        });
761
+        $this->eventDispatcher->addListener(BeforeNodeRenamedEvent::class, function (BeforeNodeRenamedEvent $event) {
762
+            // update cache information that is cached by mount point
763
+            $from = rtrim($event->getSource()->getPath(), '/') . '/';
764
+            $to = rtrim($event->getTarget()->getPath(), '/') . '/';
765
+            $existingMount = $this->setupMountProviderPaths[$from] ?? null;
766
+            if ($existingMount !== null) {
767
+                $this->setupMountProviderPaths[$to] = $this->setupMountProviderPaths[$from];
768
+                unset($this->setupMountProviderPaths[$from]);
769
+            }
770
+        });
771
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
772
+        ) {
773
+            if ($user = $event->getUser()) {
774
+                $this->cache->remove($user->getUID());
775
+            } else {
776
+                $this->cache->clear();
777
+            }
778
+        });
779
+
780
+        $genericEvents = [
781
+            'OCA\Circles\Events\CreatingCircleEvent',
782
+            'OCA\Circles\Events\DestroyingCircleEvent',
783
+            'OCA\Circles\Events\AddingCircleMemberEvent',
784
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
785
+        ];
786
+
787
+        foreach ($genericEvents as $genericEvent) {
788
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
789
+                $this->cache->clear();
790
+            });
791
+        }
792
+    }
793
+
794
+    private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
795
+        if ($this->lockdownManager->canAccessFilesystem()) {
796
+            $this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
797
+        }
798
+    }
799
+
800
+    /**
801
+     * Drops partially set-up mounts for the given user
802
+     *
803
+     * @param class-string<IMountProvider>[] $providers
804
+     */
805
+    public function dropPartialMountsForUser(IUser $user, array $providers = []): void {
806
+        // mounts are cached by mount-point
807
+        $mounts = $this->mountManager->getAll();
808
+        $partialMounts = array_filter($this->setupMountProviderPaths,
809
+            static function (string $mountPoint) use (
810
+                $providers,
811
+                $user,
812
+                $mounts
813
+            ) {
814
+                $isUserMount = str_starts_with($mountPoint, '/' . $user->getUID() . '/files');
815
+
816
+                if (!$isUserMount) {
817
+                    return false;
818
+                }
819
+
820
+                $mountProvider = ($mounts[$mountPoint] ?? null)?->getMountProvider();
821
+
822
+                return empty($providers)
823
+                    || \in_array($mountProvider, $providers, true);
824
+            },
825
+            ARRAY_FILTER_USE_KEY);
826
+
827
+        if (!empty($partialMounts)) {
828
+            // remove partially set up mounts
829
+            foreach ($partialMounts as $mountPoint => $_mount) {
830
+                $this->mountManager->removeMount($mountPoint);
831
+                unset($this->setupMountProviderPaths[$mountPoint]);
832
+            }
833
+        }
834
+    }
835 835
 }
Please login to merge, or discard this patch.