Completed
Push — master ( 1e8374...7da87d )
by
unknown
37:58
created
lib/private/Files/SetupManager.php 1 patch
Indentation   +777 added lines, -777 removed lines patch added patch discarded remove patch
@@ -64,783 +64,783 @@
 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
-		$providersAreAuthoritative = true;
677
-		foreach ($providers as $provider) {
678
-			if (!(
679
-				is_a($provider, IAuthoritativeMountProvider::class, true)
680
-				|| is_a($provider, IRootMountProvider::class, true)
681
-				|| is_a($provider, IHomeMountProvider::class, true)
682
-			)) {
683
-				$providersAreAuthoritative = false;
684
-			}
685
-		}
686
-
687
-		if (!$providersAreAuthoritative && $this->fullSetupRequired($user)) {
688
-			$this->setupForUser($user);
689
-			return;
690
-		}
691
-
692
-		$this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
693
-
694
-		$this->oneTimeUserSetup($user);
695
-
696
-		// home providers are always used
697
-		$providers = array_filter($providers, function (string $provider) {
698
-			return !is_subclass_of($provider, IHomeMountProvider::class);
699
-		});
700
-
701
-		if (in_array('', $providers)) {
702
-			$this->setupForUser($user);
703
-			return;
704
-		}
705
-		$setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
706
-
707
-		$providers = array_diff($providers, $setupProviders);
708
-		if (count($providers) === 0) {
709
-			if (!$this->isSetupStarted($user)) {
710
-				$this->oneTimeUserSetup($user);
711
-			}
712
-			$this->eventLogger->end('fs:setup:user:providers');
713
-			return;
714
-		} else {
715
-			$this->dropPartialMountsForUser($user, $providers);
716
-			$this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
717
-			$mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
718
-		}
719
-
720
-		$this->registerMounts($user, $mounts, $providers);
721
-		$this->setupForUserWith($user, function () use ($mounts) {
722
-			array_walk($mounts, [$this->mountManager, 'addMount']);
723
-		});
724
-		$this->eventLogger->end('fs:setup:user:providers');
725
-	}
726
-
727
-	public function tearDown() {
728
-		$this->setupUsers = [];
729
-		$this->setupUsersComplete = [];
730
-		$this->setupUserMountProviders = [];
731
-		$this->setupMountProviderPaths = [];
732
-		$this->fullSetupRequired = [];
733
-		$this->rootSetup = false;
734
-		$this->mountManager->clear();
735
-		$this->userMountCache->clear();
736
-		$this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
737
-	}
738
-
739
-	/**
740
-	 * Get mounts from mount providers that are registered after setup
741
-	 */
742
-	private function listenForNewMountProviders() {
743
-		if (!$this->listeningForProviders) {
744
-			$this->listeningForProviders = true;
745
-			$this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
746
-				IMountProvider $provider,
747
-			) {
748
-				foreach ($this->setupUsers as $userId) {
749
-					$user = $this->userManager->get($userId);
750
-					if ($user) {
751
-						$mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
752
-						array_walk($mounts, [$this->mountManager, 'addMount']);
753
-					}
754
-				}
755
-			});
756
-		}
757
-	}
758
-
759
-	private function setupListeners() {
760
-		// note that this event handling is intentionally pessimistic
761
-		// clearing the cache to often is better than not enough
762
-
763
-		$this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
764
-			$this->cache->remove($event->getUser()->getUID());
765
-		});
766
-		$this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
767
-			$this->cache->remove($event->getUser()->getUID());
768
-		});
769
-		$this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
770
-			$this->cache->remove($event->getShare()->getSharedWith());
771
-		});
772
-		$this->eventDispatcher->addListener(BeforeNodeRenamedEvent::class, function (BeforeNodeRenamedEvent $event) {
773
-			// update cache information that is cached by mount point
774
-			$from = rtrim($event->getSource()->getPath(), '/') . '/';
775
-			$to = rtrim($event->getTarget()->getPath(), '/') . '/';
776
-			$existingMount = $this->setupMountProviderPaths[$from] ?? null;
777
-			if ($existingMount !== null) {
778
-				$this->setupMountProviderPaths[$to] = $this->setupMountProviderPaths[$from];
779
-				unset($this->setupMountProviderPaths[$from]);
780
-			}
781
-		});
782
-		$this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
783
-		) {
784
-			if ($user = $event->getUser()) {
785
-				$this->cache->remove($user->getUID());
786
-			} else {
787
-				$this->cache->clear();
788
-			}
789
-		});
790
-
791
-		$genericEvents = [
792
-			'OCA\Circles\Events\CreatingCircleEvent',
793
-			'OCA\Circles\Events\DestroyingCircleEvent',
794
-			'OCA\Circles\Events\AddingCircleMemberEvent',
795
-			'OCA\Circles\Events\RemovingCircleMemberEvent',
796
-		];
797
-
798
-		foreach ($genericEvents as $genericEvent) {
799
-			$this->eventDispatcher->addListener($genericEvent, function ($event) {
800
-				$this->cache->clear();
801
-			});
802
-		}
803
-	}
804
-
805
-	private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
806
-		if ($this->lockdownManager->canAccessFilesystem()) {
807
-			$this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
808
-		}
809
-	}
810
-
811
-	/**
812
-	 * Drops partially set-up mounts for the given user
813
-	 *
814
-	 * @param class-string<IMountProvider>[] $providers
815
-	 */
816
-	public function dropPartialMountsForUser(IUser $user, array $providers = []): void {
817
-		// mounts are cached by mount-point
818
-		$mounts = $this->mountManager->getAll();
819
-		$partialMounts = array_filter($this->setupMountProviderPaths,
820
-			static function (string $mountPoint) use (
821
-				$providers,
822
-				$user,
823
-				$mounts
824
-			) {
825
-				$isUserMount = str_starts_with($mountPoint, '/' . $user->getUID() . '/files');
826
-
827
-				if (!$isUserMount) {
828
-					return false;
829
-				}
830
-
831
-				$mountProvider = ($mounts[$mountPoint] ?? null)?->getMountProvider();
832
-
833
-				return empty($providers)
834
-					|| \in_array($mountProvider, $providers, true);
835
-			},
836
-			ARRAY_FILTER_USE_KEY);
837
-
838
-		if (!empty($partialMounts)) {
839
-			// remove partially set up mounts
840
-			foreach ($partialMounts as $mountPoint => $_mount) {
841
-				$this->mountManager->removeMount($mountPoint);
842
-				unset($this->setupMountProviderPaths[$mountPoint]);
843
-			}
844
-		}
845
-	}
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
+        $providersAreAuthoritative = true;
677
+        foreach ($providers as $provider) {
678
+            if (!(
679
+                is_a($provider, IAuthoritativeMountProvider::class, true)
680
+                || is_a($provider, IRootMountProvider::class, true)
681
+                || is_a($provider, IHomeMountProvider::class, true)
682
+            )) {
683
+                $providersAreAuthoritative = false;
684
+            }
685
+        }
686
+
687
+        if (!$providersAreAuthoritative && $this->fullSetupRequired($user)) {
688
+            $this->setupForUser($user);
689
+            return;
690
+        }
691
+
692
+        $this->eventLogger->start('fs:setup:user:providers', 'Setup filesystem for ' . implode(', ', $providers));
693
+
694
+        $this->oneTimeUserSetup($user);
695
+
696
+        // home providers are always used
697
+        $providers = array_filter($providers, function (string $provider) {
698
+            return !is_subclass_of($provider, IHomeMountProvider::class);
699
+        });
700
+
701
+        if (in_array('', $providers)) {
702
+            $this->setupForUser($user);
703
+            return;
704
+        }
705
+        $setupProviders = $this->setupUserMountProviders[$user->getUID()] ?? [];
706
+
707
+        $providers = array_diff($providers, $setupProviders);
708
+        if (count($providers) === 0) {
709
+            if (!$this->isSetupStarted($user)) {
710
+                $this->oneTimeUserSetup($user);
711
+            }
712
+            $this->eventLogger->end('fs:setup:user:providers');
713
+            return;
714
+        } else {
715
+            $this->dropPartialMountsForUser($user, $providers);
716
+            $this->setupUserMountProviders[$user->getUID()] = array_merge($setupProviders, $providers);
717
+            $mounts = $this->mountProviderCollection->getUserMountsForProviderClasses($user, $providers);
718
+        }
719
+
720
+        $this->registerMounts($user, $mounts, $providers);
721
+        $this->setupForUserWith($user, function () use ($mounts) {
722
+            array_walk($mounts, [$this->mountManager, 'addMount']);
723
+        });
724
+        $this->eventLogger->end('fs:setup:user:providers');
725
+    }
726
+
727
+    public function tearDown() {
728
+        $this->setupUsers = [];
729
+        $this->setupUsersComplete = [];
730
+        $this->setupUserMountProviders = [];
731
+        $this->setupMountProviderPaths = [];
732
+        $this->fullSetupRequired = [];
733
+        $this->rootSetup = false;
734
+        $this->mountManager->clear();
735
+        $this->userMountCache->clear();
736
+        $this->eventDispatcher->dispatchTyped(new FilesystemTornDownEvent());
737
+    }
738
+
739
+    /**
740
+     * Get mounts from mount providers that are registered after setup
741
+     */
742
+    private function listenForNewMountProviders() {
743
+        if (!$this->listeningForProviders) {
744
+            $this->listeningForProviders = true;
745
+            $this->mountProviderCollection->listen('\OC\Files\Config', 'registerMountProvider', function (
746
+                IMountProvider $provider,
747
+            ) {
748
+                foreach ($this->setupUsers as $userId) {
749
+                    $user = $this->userManager->get($userId);
750
+                    if ($user) {
751
+                        $mounts = $provider->getMountsForUser($user, Filesystem::getLoader());
752
+                        array_walk($mounts, [$this->mountManager, 'addMount']);
753
+                    }
754
+                }
755
+            });
756
+        }
757
+    }
758
+
759
+    private function setupListeners() {
760
+        // note that this event handling is intentionally pessimistic
761
+        // clearing the cache to often is better than not enough
762
+
763
+        $this->eventDispatcher->addListener(UserAddedEvent::class, function (UserAddedEvent $event) {
764
+            $this->cache->remove($event->getUser()->getUID());
765
+        });
766
+        $this->eventDispatcher->addListener(UserRemovedEvent::class, function (UserRemovedEvent $event) {
767
+            $this->cache->remove($event->getUser()->getUID());
768
+        });
769
+        $this->eventDispatcher->addListener(ShareCreatedEvent::class, function (ShareCreatedEvent $event) {
770
+            $this->cache->remove($event->getShare()->getSharedWith());
771
+        });
772
+        $this->eventDispatcher->addListener(BeforeNodeRenamedEvent::class, function (BeforeNodeRenamedEvent $event) {
773
+            // update cache information that is cached by mount point
774
+            $from = rtrim($event->getSource()->getPath(), '/') . '/';
775
+            $to = rtrim($event->getTarget()->getPath(), '/') . '/';
776
+            $existingMount = $this->setupMountProviderPaths[$from] ?? null;
777
+            if ($existingMount !== null) {
778
+                $this->setupMountProviderPaths[$to] = $this->setupMountProviderPaths[$from];
779
+                unset($this->setupMountProviderPaths[$from]);
780
+            }
781
+        });
782
+        $this->eventDispatcher->addListener(InvalidateMountCacheEvent::class, function (InvalidateMountCacheEvent $event,
783
+        ) {
784
+            if ($user = $event->getUser()) {
785
+                $this->cache->remove($user->getUID());
786
+            } else {
787
+                $this->cache->clear();
788
+            }
789
+        });
790
+
791
+        $genericEvents = [
792
+            'OCA\Circles\Events\CreatingCircleEvent',
793
+            'OCA\Circles\Events\DestroyingCircleEvent',
794
+            'OCA\Circles\Events\AddingCircleMemberEvent',
795
+            'OCA\Circles\Events\RemovingCircleMemberEvent',
796
+        ];
797
+
798
+        foreach ($genericEvents as $genericEvent) {
799
+            $this->eventDispatcher->addListener($genericEvent, function ($event) {
800
+                $this->cache->clear();
801
+            });
802
+        }
803
+    }
804
+
805
+    private function registerMounts(IUser $user, array $mounts, ?array $mountProviderClasses = null): void {
806
+        if ($this->lockdownManager->canAccessFilesystem()) {
807
+            $this->userMountCache->registerMounts($user, $mounts, $mountProviderClasses);
808
+        }
809
+    }
810
+
811
+    /**
812
+     * Drops partially set-up mounts for the given user
813
+     *
814
+     * @param class-string<IMountProvider>[] $providers
815
+     */
816
+    public function dropPartialMountsForUser(IUser $user, array $providers = []): void {
817
+        // mounts are cached by mount-point
818
+        $mounts = $this->mountManager->getAll();
819
+        $partialMounts = array_filter($this->setupMountProviderPaths,
820
+            static function (string $mountPoint) use (
821
+                $providers,
822
+                $user,
823
+                $mounts
824
+            ) {
825
+                $isUserMount = str_starts_with($mountPoint, '/' . $user->getUID() . '/files');
826
+
827
+                if (!$isUserMount) {
828
+                    return false;
829
+                }
830
+
831
+                $mountProvider = ($mounts[$mountPoint] ?? null)?->getMountProvider();
832
+
833
+                return empty($providers)
834
+                    || \in_array($mountProvider, $providers, true);
835
+            },
836
+            ARRAY_FILTER_USE_KEY);
837
+
838
+        if (!empty($partialMounts)) {
839
+            // remove partially set up mounts
840
+            foreach ($partialMounts as $mountPoint => $_mount) {
841
+                $this->mountManager->removeMount($mountPoint);
842
+                unset($this->setupMountProviderPaths[$mountPoint]);
843
+            }
844
+        }
845
+    }
846 846
 }
Please login to merge, or discard this patch.