[tool] when writing to openssl as a part of macOS/iOS code-signing, flush the stdin stream before closing it (#150120)
Fixes https://github.com/flutter/flutter/issues/100584. Might help https://github.com/flutter/flutter/issues/137184.
This commit is contained in:
parent
651a17db54
commit
28ff59513b
@ -244,31 +244,83 @@ abstract class ProcessUtils {
|
||||
/// ```
|
||||
///
|
||||
/// However it did not catch a [SocketException] on Linux.
|
||||
///
|
||||
/// As part of making sure errors are caught, this function will call [flush]
|
||||
/// on [stdin] to ensure that [line] is written to the pipe before this
|
||||
/// function returns. This means completion will be blocked if the kernel
|
||||
/// buffer of the pipe is full.
|
||||
static Future<void> writelnToStdinGuarded({
|
||||
required IOSink stdin,
|
||||
required String line,
|
||||
required void Function(Object, StackTrace) onError,
|
||||
}) async {
|
||||
await _writeToStdinGuarded(
|
||||
stdin: stdin,
|
||||
content: line,
|
||||
onError: onError,
|
||||
isLine: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Please see [writelnToStdinGuarded].
|
||||
///
|
||||
/// This calls `stdin.write` instead of `stdin.writeln`.
|
||||
static Future<void> writeToStdinGuarded({
|
||||
required IOSink stdin,
|
||||
required String content,
|
||||
required void Function(Object, StackTrace) onError,
|
||||
}) async {
|
||||
await _writeToStdinGuarded(
|
||||
stdin: stdin,
|
||||
content: content,
|
||||
onError: onError,
|
||||
isLine: false,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _writeToStdinGuarded({
|
||||
required IOSink stdin,
|
||||
required String content,
|
||||
required void Function(Object, StackTrace) onError,
|
||||
required bool isLine,
|
||||
}) async {
|
||||
final Completer<void> completer = Completer<void>();
|
||||
|
||||
void handleError(Object error, StackTrace stackTrace) {
|
||||
try {
|
||||
onError(error, stackTrace);
|
||||
completer.complete();
|
||||
} on Exception catch (e) {
|
||||
completer.completeError(e);
|
||||
}
|
||||
}
|
||||
|
||||
void writeFlushAndComplete() {
|
||||
stdin.writeln(line);
|
||||
stdin.flush().whenComplete(() {
|
||||
if (!completer.isCompleted) {
|
||||
if (isLine) {
|
||||
stdin.writeln(content);
|
||||
} else {
|
||||
stdin.write(content);
|
||||
}
|
||||
stdin.flush().then(
|
||||
(_) {
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: handleError,
|
||||
);
|
||||
}
|
||||
|
||||
runZonedGuarded(
|
||||
writeFlushAndComplete,
|
||||
(Object error, StackTrace stackTrace) {
|
||||
onError(error, stackTrace);
|
||||
handleError(error, stackTrace);
|
||||
|
||||
// We may have already completed with an error in `handleError`.
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
}
|
||||
|
@ -231,7 +231,15 @@ Future<String?> _getCodeSigningIdentityDevelopmentTeam({
|
||||
|
||||
final Process opensslProcess = await processUtils.start(
|
||||
const <String>['openssl', 'x509', '-subject']);
|
||||
await (opensslProcess.stdin..write(signingCertificateStdout)).close();
|
||||
|
||||
await ProcessUtils.writeToStdinGuarded(
|
||||
stdin: opensslProcess.stdin,
|
||||
content: signingCertificateStdout,
|
||||
onError: (Object? error, _) {
|
||||
throw Exception('Unexpected error when writing to openssl: $error');
|
||||
},
|
||||
);
|
||||
await opensslProcess.stdin.close();
|
||||
|
||||
final String opensslOutput = await utf8.decodeStream(opensslProcess.stdout);
|
||||
// Fire and forget discard of the stderr stream so we don't hold onto resources.
|
||||
|
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
@ -444,4 +446,37 @@ void main() {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('writeToStdinGuarded', () {
|
||||
testWithoutContext('handles any error thrown by stdin.flush', () async {
|
||||
final _ThrowsOnFlushIOSink stdin = _ThrowsOnFlushIOSink();
|
||||
Object? errorPassedToCallback;
|
||||
|
||||
await ProcessUtils.writeToStdinGuarded(
|
||||
stdin: stdin,
|
||||
content: 'message to stdin',
|
||||
onError: (Object error, StackTrace stackTrace) {
|
||||
errorPassedToCallback = error;
|
||||
},
|
||||
);
|
||||
|
||||
expect(
|
||||
errorPassedToCallback,
|
||||
isNotNull,
|
||||
reason: 'onError callback should have been invoked.',
|
||||
);
|
||||
|
||||
expect(errorPassedToCallback, const TypeMatcher<SocketException>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class _ThrowsOnFlushIOSink extends MemoryIOSink {
|
||||
@override
|
||||
Future<Object?> flush() async {
|
||||
throw const SocketException(
|
||||
'Write failed',
|
||||
osError: OSError('Broken pipe', 32),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import 'package:flutter_tools/src/ios/code_signing.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/fake_process_manager.dart';
|
||||
import '../../src/fakes.dart';
|
||||
|
||||
const String kCertificates = '''
|
||||
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
|
||||
@ -581,6 +582,59 @@ void main() {
|
||||
expect(developmentTeam, isNull);
|
||||
expect(processManager, hasNoRemainingExpectations);
|
||||
});
|
||||
|
||||
testWithoutContext('handles stdin pipe breaking on openssl process', () async {
|
||||
final StreamSink<List<int>> stdinSink = ClosedStdinController();
|
||||
|
||||
final Completer<void> completer = Completer<void>();
|
||||
const String certificates = '''
|
||||
1) 86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 "iPhone Developer: Profile 1 (1111AAAA11)"
|
||||
1 valid identities found''';
|
||||
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['which', 'security'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['which', 'openssl'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['security', 'find-identity', '-p', 'codesigning', '-v'],
|
||||
stdout: certificates,
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['security', 'find-certificate', '-c', '1111AAAA11', '-p'],
|
||||
stdout: 'This is a fake certificate',
|
||||
),
|
||||
FakeCommand(
|
||||
command: const <String>['openssl', 'x509', '-subject'],
|
||||
stdin: IOSink(stdinSink),
|
||||
stdout: 'subject= /CN=iPhone Developer: Profile 1 (1111AAAA11)/OU=3333CCCC33/O=My Team/C=US',
|
||||
completer: completer,
|
||||
),
|
||||
]);
|
||||
|
||||
Future<Map<String, String>?> getCodeSigningIdentities() => getCodeSigningIdentityDevelopmentTeamBuildSetting(
|
||||
buildSettings: const <String, String>{
|
||||
'bogus': 'bogus',
|
||||
},
|
||||
platform: macosPlatform,
|
||||
processManager: processManager,
|
||||
logger: logger,
|
||||
config: testConfig,
|
||||
terminal: testTerminal,
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
() => getCodeSigningIdentities(),
|
||||
throwsA(
|
||||
const TypeMatcher<Exception>().having(
|
||||
(Exception e) => e.toString(),
|
||||
'message',
|
||||
equals('Exception: Unexpected error when writing to openssl: SocketException: Bad pipe'),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -737,3 +737,13 @@ class FakeDevtoolsLauncher extends Fake implements DevtoolsLauncher {
|
||||
closed = true;
|
||||
}
|
||||
}
|
||||
|
||||
class ClosedStdinController extends Fake implements StreamSink<List<int>> {
|
||||
@override
|
||||
Future<Object?> addStream(Stream<List<int>> stream) async => throw const SocketException('Bad pipe');
|
||||
|
||||
@override
|
||||
Future<Object?> close() async {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user