From 788c8b8ad4b0a667e001389c97a9f4469644831f Mon Sep 17 00:00:00 2001 From: Christopher Fujino Date: Thu, 23 Jun 2022 14:44:09 -0700 Subject: [PATCH] [flutter_tools] tool exit access denied during symlinking (#106213) --- .../lib/src/flutter_plugins.dart | 46 ++++++++++----- .../test/general.shard/plugins_test.dart | 57 +++++++++++++++++-- 2 files changed, 84 insertions(+), 19 deletions(-) diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 24211438e2ba..89503641437c 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -1004,20 +1004,32 @@ void createPluginSymlinks(FlutterProject project, {bool force = false, @visibleF void handleSymlinkException(FileSystemException e, { required Platform platform, required OperatingSystemUtils os, + required String destination, + required String source, }) { - if (platform.isWindows && (e.osError?.errorCode ?? 0) == 1314) { - final String? versionString = RegExp(r'[\d.]+').firstMatch(os.name)?.group(0); - final Version? version = Version.parse(versionString); - // Windows 10 14972 is the oldest version that allows creating symlinks - // just by enabling developer mode; before that it requires running the - // terminal as Administrator. - // https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/ - final String instructions = (version != null && version >= Version(10, 0, 14972)) - ? 'Please enable Developer Mode in your system settings. Run\n' - ' start ms-settings:developers\n' - 'to open settings.' - : 'You must build from a terminal run as administrator.'; - throwToolExit('Building with plugins requires symlink support.\n\n$instructions'); + if (platform.isWindows) { + // ERROR_ACCESS_DENIED + if (e.osError?.errorCode == 5) { + throwToolExit( + 'ERROR_ACCESS_DENIED file system exception thrown while trying to ' + 'create a symlink from $source to $destination', + ); + } + // ERROR_PRIVILEGE_NOT_HELD, user cannot symlink + if (e.osError?.errorCode == 1314) { + final String? versionString = RegExp(r'[\d.]+').firstMatch(os.name)?.group(0); + final Version? version = Version.parse(versionString); + // Windows 10 14972 is the oldest version that allows creating symlinks + // just by enabling developer mode; before that it requires running the + // terminal as Administrator. + // https://blogs.windows.com/windowsdeveloper/2016/12/02/symlinks-windows-10/ + final String instructions = (version != null && version >= Version(10, 0, 14972)) + ? 'Please enable Developer Mode in your system settings. Run\n' + ' start ms-settings:developers\n' + 'to open settings.' + : 'You must build from a terminal run as administrator.'; + throwToolExit('Building with plugins requires symlink support.\n\n$instructions'); + } } } @@ -1043,7 +1055,13 @@ void _createPlatformPluginSymlinks(Directory symlinkDirectory, List? pl try { link.createSync(path); } on FileSystemException catch (e) { - handleSymlinkException(e, platform: globals.platform, os: globals.os); + handleSymlinkException( + e, + platform: globals.platform, + os: globals.os, + destination: 'dest', + source: 'source', + ); rethrow; } } diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart index 71c5fb21b54a..7de3ab487131 100644 --- a/packages/flutter_tools/test/general.shard/plugins_test.dart +++ b/packages/flutter_tools/test/general.shard/plugins_test.dart @@ -84,6 +84,8 @@ void main() { // using it instead of fs must re-run any necessary setup (e.g., // setUpProject). late FileSystem fsWindows; + const String pubCachePath = '/path/to/.pub-cache/hosted/pub.dartlang.org/foo-1.2.3'; + const String ephemeralPackagePath = '/path/to/app/linux/flutter/ephemeral/foo-1.2.3'; // Adds basic properties to the flutterProject and its subprojects. void setUpProject(FileSystem fileSystem) { @@ -1612,8 +1614,36 @@ flutter: const FileSystemException e = FileSystemException('', '', OSError('', 1314)); - expect(() => handleSymlinkException(e, platform: platform, os: os), - throwsToolExit(message: 'start ms-settings:developers')); + expect( + () => handleSymlinkException( + e, + platform: platform, + os: os, + source: pubCachePath, + destination: ephemeralPackagePath, + ), + throwsToolExit(message: 'start ms-settings:developers'), + ); + }); + + testWithoutContext('Symlink ERROR_ACCESS_DENIED failures show developers paths that were used', () async { + final Platform platform = FakePlatform(operatingSystem: 'windows'); + final FakeOperatingSystemUtils os = FakeOperatingSystemUtils('Microsoft Windows [Version 10.0.14972.1]'); + + const FileSystemException e = FileSystemException('', '', OSError('', 5)); + + expect( + () => handleSymlinkException( + e, + platform: platform, + os: os, + source: pubCachePath, + destination: ephemeralPackagePath, + ), + throwsToolExit( + message: 'ERROR_ACCESS_DENIED file system exception thrown while trying to create a symlink from $pubCachePath to $ephemeralPackagePath', + ), + ); }); testWithoutContext('Symlink failures instruct developers to run as administrator on older versions of Windows', () async { @@ -1622,8 +1652,16 @@ flutter: const FileSystemException e = FileSystemException('', '', OSError('', 1314)); - expect(() => handleSymlinkException(e, platform: platform, os: os), - throwsToolExit(message: 'administrator')); + expect( + () => handleSymlinkException( + e, + platform: platform, + os: os, + source: pubCachePath, + destination: ephemeralPackagePath, + ), + throwsToolExit(message: 'administrator'), + ); }); testWithoutContext('Symlink failures only give instructions for specific errors', () async { @@ -1632,7 +1670,16 @@ flutter: const FileSystemException e = FileSystemException('', '', OSError('', 999)); - expect(() => handleSymlinkException(e, platform: platform, os: os), returnsNormally); + expect( + () => handleSymlinkException( + e, + platform: platform, + os: os, + source: pubCachePath, + destination: ephemeralPackagePath, + ), + returnsNormally, + ); }); }); }