[flutter_tools] check for permission issues when copying file (#63540)
diff --git a/packages/flutter_tools/lib/src/base/error_handling_file_system.dart b/packages/flutter_tools/lib/src/base/error_handling_file_system.dart index c039adc..2b29c74 100644 --- a/packages/flutter_tools/lib/src/base/error_handling_file_system.dart +++ b/packages/flutter_tools/lib/src/base/error_handling_file_system.dart
@@ -195,6 +195,17 @@ } @override + RandomAccessFile openSync({FileMode mode = FileMode.read}) { + return _runSync<RandomAccessFile>( + () => delegate.openSync( + mode: mode, + ), + platform: _platform, + failureMessage: 'Flutter failed to open a file at "${delegate.path}"', + ); + } + + @override String toString() => delegate.toString(); } @@ -385,9 +396,17 @@ // https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes const int kDeviceFull = 112; const int kUserMappedSectionOpened = 1224; + const int kAccessDenied = 5; final int errorCode = e.osError?.errorCode ?? 0; // Catch errors and bail when: switch (errorCode) { + case kAccessDenied: + throwToolExit( + '$message. The flutter tool cannot access the file.\n' + 'Please ensure that the SDK and/or project is installed in a location ' + 'that has read/write permissions for the current user.' + ); + break; case kDeviceFull: throwToolExit( '$message. The target device is full.'
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index e81d3d4..ab9d31c 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -32,18 +32,19 @@ import '../template.dart'; const List<String> _kAvailablePlatforms = <String>[ - 'ios', - 'android', - 'windows', - 'linux', - 'macos', - 'web', - ]; + 'ios', + 'android', + 'windows', + 'linux', + 'macos', + 'web', +]; const String _kNoPlatformsErrorMessage = ''' The plugin project was generated without specifying the `--platforms` flag, no new platforms are added. To add platforms, run `flutter create -t plugin --platforms <platforms> .` under the same -directory. You can also find detailed instructions on how to add platforms in the `pubspec.yaml` at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. +directory. You can also find detailed instructions on how to add platforms in the `pubspec.yaml` +at https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin-platforms. '''; class CreateCommand extends FlutterCommand { @@ -790,7 +791,10 @@ final Template template = await Template.fromName( templateName, fileSystem: globals.fs, + logger: globals.logger, + templateRenderer: globals.templateRenderer, templateManifest: templateManifest, + pub: pub, ); return template.render(directory, context, overwriteExisting: overwrite); }
diff --git a/packages/flutter_tools/lib/src/commands/ide_config.dart b/packages/flutter_tools/lib/src/commands/ide_config.dart index af891d6..a5844fe 100644 --- a/packages/flutter_tools/lib/src/commands/ide_config.dart +++ b/packages/flutter_tools/lib/src/commands/ide_config.dart
@@ -254,6 +254,8 @@ null, fileSystem: globals.fs, templateManifest: null, + logger: globals.logger, + templateRenderer: globals.templateRenderer, ); return template.render( globals.fs.directory(dirPath),
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index fad3e5c..c91dfa6 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart
@@ -15,6 +15,7 @@ import 'base/logger.dart'; import 'build_info.dart'; import 'bundle.dart' as bundle; +import 'dart/pub.dart'; import 'features.dart'; import 'flutter_manifest.dart'; import 'globals.dart' as globals; @@ -657,7 +658,14 @@ } Future<void> _overwriteFromTemplate(String path, Directory target) async { - final Template template = await Template.fromName(path, fileSystem: globals.fs, templateManifest: null); + final Template template = await Template.fromName( + path, + fileSystem: globals.fs, + templateManifest: null, + logger: globals.logger, + templateRenderer: globals.templateRenderer, + pub: pub, + ); template.render( target, <String, dynamic>{ @@ -810,7 +818,14 @@ } Future<void> _overwriteFromTemplate(String path, Directory target) async { - final Template template = await Template.fromName(path, fileSystem: globals.fs, templateManifest: null); + final Template template = await Template.fromName( + path, + fileSystem: globals.fs, + templateManifest: null, + logger: globals.logger, + templateRenderer: globals.templateRenderer, + pub: pub, + ); template.render( target, <String, dynamic>{
diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index 54d87fb..094321b 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart
@@ -8,10 +8,11 @@ import 'base/common.dart'; import 'base/file_system.dart'; +import 'base/logger.dart'; +import 'base/template.dart'; import 'cache.dart'; import 'dart/package_map.dart'; import 'dart/pub.dart'; -import 'globals.dart' as globals hide fs; /// Expands templates in a directory to a destination. All files that must /// undergo template expansion should end with the '.tmpl' extension. All files @@ -32,8 +33,12 @@ class Template { Template(Directory templateSource, Directory baseDir, this.imageSourceDir, { @required FileSystem fileSystem, + @required Logger logger, + @required TemplateRenderer templateRenderer, @required Set<Uri> templateManifest, }) : _fileSystem = fileSystem, + _logger = logger, + _templateRenderer = templateRenderer, _templateManifest = templateManifest { _templateFilePaths = <String, String>{}; @@ -42,14 +47,9 @@ } final List<FileSystemEntity> templateFiles = templateSource.listSync(recursive: true); - - for (final FileSystemEntity entity in templateFiles) { - if (entity is! File) { - // We are only interesting in template *file* URIs. - continue; - } + for (final FileSystemEntity entity in templateFiles.whereType<File>()) { if (_templateManifest != null && !_templateManifest.contains(Uri.file(entity.absolute.path))) { - globals.logger.printTrace('Skipping ${entity.absolute.path}, missing from the template manifest.'); + _logger.printTrace('Skipping ${entity.absolute.path}, missing from the template manifest.'); // Skip stale files in the flutter_tools directory. continue; } @@ -68,20 +68,28 @@ static Future<Template> fromName(String name, { @required FileSystem fileSystem, @required Set<Uri> templateManifest, + @required Logger logger, + @required TemplateRenderer templateRenderer, + @required Pub pub, }) async { // All named templates are placed in the 'templates' directory final Directory templateDir = _templateDirectoryInPackage(name, fileSystem); - final Directory imageDir = await _templateImageDirectory(name, fileSystem); + final Directory imageDir = await _templateImageDirectory(name, fileSystem, logger, pub); return Template( templateDir, templateDir, imageDir, fileSystem: fileSystem, + logger: logger, + templateRenderer: templateRenderer, templateManifest: templateManifest, ); } final FileSystem _fileSystem; + final Logger _logger; final Set<Uri> _templateManifest; + final TemplateRenderer _templateRenderer; + static const String templateExtension = '.tmpl'; static const String copyTemplateExtension = '.copy.tmpl'; static const String imageTemplateExtension = '.img.tmpl'; @@ -102,7 +110,7 @@ try { destination.createSync(recursive: true); } on FileSystemException catch (err) { - globals.printError(err.toString()); + _logger.printError(err.toString()); throwToolExit('Failed to flutter create at ${destination.path}.'); return 0; } @@ -198,22 +206,22 @@ if (overwriteExisting) { finalDestinationFile.deleteSync(recursive: true); if (printStatusWhenWriting) { - globals.printStatus(' $relativePathForLogging (overwritten)'); + _logger.printStatus(' $relativePathForLogging (overwritten)'); } } else { // The file exists but we cannot overwrite it, move on. if (printStatusWhenWriting) { - globals.printTrace(' $relativePathForLogging (existing - skipped)'); + _logger.printTrace(' $relativePathForLogging (existing - skipped)'); } return; } } else { if (printStatusWhenWriting) { - globals.printStatus(' $relativePathForLogging (created)'); + _logger.printStatus(' $relativePathForLogging (created)'); } } - fileCount++; + fileCount += 1; finalDestinationFile.createSync(recursive: true); final File sourceFile = _fileSystem.file(absoluteSourcePath); @@ -222,6 +230,7 @@ // not need mustache rendering but needs to be directly copied. if (sourceFile.path.endsWith(copyTemplateExtension)) { + _validateReadPermissions(sourceFile); sourceFile.copySync(finalDestinationFile.path); return; @@ -233,6 +242,7 @@ if (sourceFile.path.endsWith(imageTemplateExtension)) { final File imageSourceFile = _fileSystem.file(_fileSystem.path.join( imageSourceDir.path, relativeDestinationPath.replaceAll(imageTemplateExtension, ''))); + _validateReadPermissions(imageSourceFile); imageSourceFile.copySync(finalDestinationFile.path); return; @@ -242,8 +252,9 @@ // rendering via mustache. if (sourceFile.path.endsWith(templateExtension)) { + _validateReadPermissions(sourceFile); final String templateContents = sourceFile.readAsStringSync(); - final String renderedContents = globals.templateRenderer.renderString(templateContents, context); + final String renderedContents = _templateRenderer.renderString(templateContents, context); finalDestinationFile.writeAsStringSync(renderedContents); @@ -252,12 +263,20 @@ // Step 5: This file does not end in .tmpl but is in a directory that // does. Directly copy the file to the destination. - + _validateReadPermissions(sourceFile); sourceFile.copySync(finalDestinationFile.path); }); return fileCount; } + + /// Attempt open/close the file to ensure that read permissions are correct. + /// + /// If this fails with a certain error code, the [ErrorHandlingFileSystem] will + /// trigger a tool exit with a better message. + void _validateReadPermissions(File file) { + file.openSync().closeSync(); + } } Directory _templateDirectoryInPackage(String name, FileSystem fileSystem) { @@ -268,25 +287,25 @@ // Returns the directory containing the 'name' template directory in // flutter_template_images, to resolve image placeholder against. -Future<Directory> _templateImageDirectory(String name, FileSystem fileSystem) async { +Future<Directory> _templateImageDirectory(String name, FileSystem fileSystem, Logger logger, Pub pub) async { final String toolPackagePath = fileSystem.path.join( Cache.flutterRoot, 'packages', 'flutter_tools'); final String packageFilePath = fileSystem.path.join(toolPackagePath, kPackagesFileName); // Ensure that .packgaes is present. if (!fileSystem.file(packageFilePath).existsSync()) { - await _ensurePackageDependencies(toolPackagePath); + await _ensurePackageDependencies(toolPackagePath, pub); } PackageConfig packageConfig = await loadPackageConfigWithLogging( fileSystem.file(packageFilePath), - logger: globals.logger, + logger: logger, ); Uri imagePackageLibDir = packageConfig['flutter_template_images']?.packageUriRoot; // Ensure that the template image package is present. if (imagePackageLibDir == null || !fileSystem.directory(imagePackageLibDir).existsSync()) { - await _ensurePackageDependencies(toolPackagePath); + await _ensurePackageDependencies(toolPackagePath, pub); packageConfig = await loadPackageConfigWithLogging( fileSystem.file(packageFilePath), - logger: globals.logger, + logger: logger, ); imagePackageLibDir = packageConfig['flutter_template_images']?.packageUriRoot; } @@ -298,7 +317,7 @@ // Runs 'pub get' for the given path to ensure that .packages is created and // all dependencies are present. -Future<void> _ensurePackageDependencies(String packagePath) async { +Future<void> _ensurePackageDependencies(String packagePath, Pub pub) async { await pub.get( context: PubContext.pubGet, directory: packagePath,