fix: added fallback for lsof

background sync didn't work reliably with `android:debuggable`.
AOSP `su` binary also behaved weirdly, and didn't want
to work properly, so I've opted into using MagiskSU - which
also didn't work reliably but it's much better than alternatives
(and is somewhat standard).

lsof will fallback to using `su` when output without it is empty
usually when app lacks `android:debuggable`, this change
makes checking which PID hold access to files during initializeAppConfigs.
This commit is contained in:
Czarek Nakamoto 2025-05-21 10:12:55 +02:00
parent 308a196fc6
commit e1216f8774
4 changed files with 62 additions and 41 deletions

View file

@ -30,6 +30,7 @@ import 'package:cake_wallet/themes/utils/theme_provider.dart';
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/view_model/dev/lsof_view_model.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/address_info.dart'; import 'package:cw_core/address_info.dart';
@ -207,29 +208,9 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey); final trades = await CakeHive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey); final orders = await CakeHive.openBox<Order>(Order.boxName, encryptionKey: ordersBoxKey);
var lsofProcess = await Process.start( printV("lsof (before): ${await LsofViewModelBase.fetchLsof()}", separateMultiline: true);
"/system/bin/lsof",
["walletinfo.hive", "walletinfo.lock"],
workingDirectory: (await getAppDir()).path,
runInShell: true,
);
printV("exitcode: ${await lsofProcess.exitCode}");
printV("__stderr: ${await lsofProcess.stderr.transform(utf8.decoder).join()}", separateMultiline: true);
printV("__stdout: ${await lsofProcess.stdout.transform(utf8.decoder).join()}", separateMultiline: true);
final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName); final walletInfoSource = await CakeHive.openBox<WalletInfo>(WalletInfo.boxName);
printV("WalletInfoSource length (initializeAppConfigs): ${walletInfoSource.length}"); printV("lsof ( after): ${await LsofViewModelBase.fetchLsof()}",separateMultiline: true);
lsofProcess = await Process.start(
"/system/bin/lsof",
["walletinfo.hive", "walletinfo.lock"],
workingDirectory: (await getAppDir()).path,
runInShell: true,
);
printV("exitcode: ${await lsofProcess.exitCode}");
printV("__stderr: ${await lsofProcess.stderr.transform(utf8.decoder).join()}", separateMultiline: true);
printV("__stdout: ${await lsofProcess.stdout.transform(utf8.decoder).join()}", separateMultiline: true);
final templates = await CakeHive.openBox<Template>(Template.boxName); final templates = await CakeHive.openBox<Template>(Template.boxName);
final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName); final exchangeTemplates = await CakeHive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);

View file

@ -51,7 +51,7 @@ class DevLsof extends BasePage {
children: [ children: [
SelectableText( SelectableText(
viewModel.logs??'', viewModel.logs??'',
style: TextStyle(fontSize: 8), style: TextStyle(fontSize: 6),
), ),
], ],
), ),

View file

@ -108,7 +108,7 @@ class OtherSettingsPage extends BasePage {
handler: (BuildContext context) => handler: (BuildContext context) =>
Navigator.of(context).pushNamed(Routes.devPrintVerbose), Navigator.of(context).pushNamed(Routes.devPrintVerbose),
), ),
if (kDebugMode && Platform.isAndroid) if (FeatureFlag.hasDevOptions && Platform.isAndroid)
SettingsCellWithArrow( SettingsCellWithArrow(
title: '[dev] lsof', title: '[dev] lsof',
handler: (BuildContext context) => handler: (BuildContext context) =>

View file

@ -1,8 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io' hide stderr, stdout;
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
import 'package:flutter_daemon/flutter_daemon.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'lsof_view_model.g.dart'; part 'lsof_view_model.g.dart';
@ -12,23 +11,64 @@ abstract class LsofViewModelBase with Store {
@observable @observable
String? logs = null; String? logs = null;
static Future<String> fetchLsof() async {
String? toret;
try {
final dir = await getAppDir();
final list = await dir.list(recursive: true);
final fList = await list.map((element) => element.path).toList();
var lsofProcess = await Process.start(
"lsof", fList,
workingDirectory: (await getAppDir()).path,
runInShell: false,
);
var stderr = (await lsofProcess.stderr.transform(utf8.decoder).join()).trim();
var stdout = (await lsofProcess.stdout.transform(utf8.decoder).join()).trim();
if (stdout.isEmpty || true) {
final suCheck = await Process.start("su", ["--help"]);
stderr = (await suCheck.stderr.transform(utf8.decoder).join()).trim();
stdout = (await suCheck.stdout.transform(utf8.decoder).join()).trim();
if (!stdout.contains('MagiskSU')) {
toret = """Unsupported (or none) su binary.
expected: MagiskSU
-- found
stderr:
${stderr}
stdout:
${stdout}
ec: ${await suCheck.exitCode}
""";
}
// Retry as root, lsof doesn't work reliably on release builds for some reason?
// magisk su command behaves weirdly - so special characters need to be escaped
final list = await dir.list(recursive: true);
final fList = await list.map((element) => element.path.replaceAll(" ", r"\ ")).toList();
final lsofProcess2 = await Process.start(
"su", ['-c', 'lsof' , ...fList],
workingDirectory: (await getAppDir()).path,
runInShell: false,
);
stderr = (await lsofProcess2.stderr.transform(utf8.decoder).join()).trim();
stdout = (await lsofProcess2.stdout.transform(utf8.decoder).join()).trim();
}
toret = '''stderr:
${stderr}
stdout:
${stdout}
''';
} catch (e) {
toret = e.toString();
rethrow;
}
return toret;
}
@action @action
Future<void> refresh() async { Future<void> refresh() async {
final dir = await getAppDir(); logs = await fetchLsof();
final list = await dir.list(recursive: true);
final fList = await list.map((element) => element.path).toList();
var lsofProcess = await Process.start(
"/system/bin/lsof", fList,
// ["walletinfo.hive", "walletinfo.lock"],
workingDirectory: (await getAppDir()).path,
runInShell: true,
);
logs = '''exitcode: ${await lsofProcess.exitCode}
stderr: ${await lsofProcess.stderr.transform(utf8.decoder).join()}
stdout: ${await lsofProcess.stdout.transform(utf8.decoder).join()}
''';
} }
} }