pymobiledevice3 4.14.6__py3-none-any.whl → 7.0.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- misc/plist_sniffer.py +15 -15
- misc/remotexpc_sniffer.py +29 -28
- misc/understanding_idevice_protocol_layers.md +15 -10
- pymobiledevice3/__main__.py +317 -127
- pymobiledevice3/_version.py +22 -4
- pymobiledevice3/bonjour.py +358 -113
- pymobiledevice3/ca.py +253 -16
- pymobiledevice3/cli/activation.py +31 -23
- pymobiledevice3/cli/afc.py +49 -40
- pymobiledevice3/cli/amfi.py +16 -21
- pymobiledevice3/cli/apps.py +87 -42
- pymobiledevice3/cli/backup.py +160 -90
- pymobiledevice3/cli/bonjour.py +44 -40
- pymobiledevice3/cli/cli_common.py +204 -198
- pymobiledevice3/cli/companion_proxy.py +14 -14
- pymobiledevice3/cli/crash.py +105 -56
- pymobiledevice3/cli/developer/__init__.py +62 -0
- pymobiledevice3/cli/developer/accessibility/__init__.py +65 -0
- pymobiledevice3/cli/developer/accessibility/settings.py +43 -0
- pymobiledevice3/cli/developer/arbitration.py +50 -0
- pymobiledevice3/cli/developer/condition.py +33 -0
- pymobiledevice3/cli/developer/core_device.py +294 -0
- pymobiledevice3/cli/developer/debugserver.py +244 -0
- pymobiledevice3/cli/developer/dvt/__init__.py +438 -0
- pymobiledevice3/cli/developer/dvt/core_profile_session.py +295 -0
- pymobiledevice3/cli/developer/dvt/simulate_location.py +56 -0
- pymobiledevice3/cli/developer/dvt/sysmon/__init__.py +69 -0
- pymobiledevice3/cli/developer/dvt/sysmon/process.py +188 -0
- pymobiledevice3/cli/developer/fetch_symbols.py +108 -0
- pymobiledevice3/cli/developer/simulate_location.py +51 -0
- pymobiledevice3/cli/diagnostics/__init__.py +75 -0
- pymobiledevice3/cli/diagnostics/battery.py +47 -0
- pymobiledevice3/cli/idam.py +42 -0
- pymobiledevice3/cli/lockdown.py +108 -103
- pymobiledevice3/cli/mounter.py +158 -99
- pymobiledevice3/cli/notification.py +38 -26
- pymobiledevice3/cli/pcap.py +45 -24
- pymobiledevice3/cli/power_assertion.py +18 -17
- pymobiledevice3/cli/processes.py +17 -23
- pymobiledevice3/cli/profile.py +165 -109
- pymobiledevice3/cli/provision.py +35 -34
- pymobiledevice3/cli/remote.py +217 -129
- pymobiledevice3/cli/restore.py +159 -143
- pymobiledevice3/cli/springboard.py +63 -53
- pymobiledevice3/cli/syslog.py +193 -86
- pymobiledevice3/cli/usbmux.py +73 -33
- pymobiledevice3/cli/version.py +5 -7
- pymobiledevice3/cli/webinspector.py +376 -214
- pymobiledevice3/common.py +3 -1
- pymobiledevice3/exceptions.py +182 -58
- pymobiledevice3/irecv.py +52 -53
- pymobiledevice3/irecv_devices.py +1489 -464
- pymobiledevice3/lockdown.py +473 -275
- pymobiledevice3/lockdown_service_provider.py +15 -8
- pymobiledevice3/osu/os_utils.py +27 -9
- pymobiledevice3/osu/posix_util.py +34 -15
- pymobiledevice3/osu/win_util.py +14 -8
- pymobiledevice3/pair_records.py +102 -21
- pymobiledevice3/remote/common.py +8 -4
- pymobiledevice3/remote/core_device/app_service.py +94 -67
- pymobiledevice3/remote/core_device/core_device_service.py +17 -14
- pymobiledevice3/remote/core_device/device_info.py +5 -5
- pymobiledevice3/remote/core_device/diagnostics_service.py +19 -4
- pymobiledevice3/remote/core_device/file_service.py +53 -23
- pymobiledevice3/remote/remote_service_discovery.py +79 -45
- pymobiledevice3/remote/remotexpc.py +73 -44
- pymobiledevice3/remote/tunnel_service.py +442 -317
- pymobiledevice3/remote/utils.py +14 -13
- pymobiledevice3/remote/xpc_message.py +145 -125
- pymobiledevice3/resources/dsc_uuid_map.py +19 -19
- pymobiledevice3/resources/firmware_notifications.py +20 -16
- pymobiledevice3/resources/notifications.txt +144 -0
- pymobiledevice3/restore/asr.py +27 -27
- pymobiledevice3/restore/base_restore.py +110 -21
- pymobiledevice3/restore/consts.py +87 -66
- pymobiledevice3/restore/device.py +59 -12
- pymobiledevice3/restore/fdr.py +46 -48
- pymobiledevice3/restore/ftab.py +19 -19
- pymobiledevice3/restore/img4.py +163 -0
- pymobiledevice3/restore/mbn.py +587 -0
- pymobiledevice3/restore/recovery.py +151 -151
- pymobiledevice3/restore/restore.py +562 -544
- pymobiledevice3/restore/restore_options.py +131 -110
- pymobiledevice3/restore/restored_client.py +51 -31
- pymobiledevice3/restore/tss.py +385 -267
- pymobiledevice3/service_connection.py +252 -59
- pymobiledevice3/services/accessibilityaudit.py +202 -120
- pymobiledevice3/services/afc.py +962 -365
- pymobiledevice3/services/amfi.py +24 -30
- pymobiledevice3/services/companion.py +23 -19
- pymobiledevice3/services/crash_reports.py +71 -47
- pymobiledevice3/services/debugserver_applist.py +3 -3
- pymobiledevice3/services/device_arbitration.py +8 -8
- pymobiledevice3/services/device_link.py +101 -79
- pymobiledevice3/services/diagnostics.py +973 -967
- pymobiledevice3/services/dtfetchsymbols.py +8 -8
- pymobiledevice3/services/dvt/dvt_secure_socket_proxy.py +4 -4
- pymobiledevice3/services/dvt/dvt_testmanaged_proxy.py +4 -4
- pymobiledevice3/services/dvt/instruments/activity_trace_tap.py +85 -74
- pymobiledevice3/services/dvt/instruments/application_listing.py +2 -3
- pymobiledevice3/services/dvt/instruments/condition_inducer.py +7 -6
- pymobiledevice3/services/dvt/instruments/core_profile_session_tap.py +466 -384
- pymobiledevice3/services/dvt/instruments/device_info.py +20 -11
- pymobiledevice3/services/dvt/instruments/energy_monitor.py +1 -1
- pymobiledevice3/services/dvt/instruments/graphics.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation.py +1 -1
- pymobiledevice3/services/dvt/instruments/location_simulation_base.py +10 -10
- pymobiledevice3/services/dvt/instruments/network_monitor.py +17 -17
- pymobiledevice3/services/dvt/instruments/notifications.py +1 -1
- pymobiledevice3/services/dvt/instruments/process_control.py +35 -10
- pymobiledevice3/services/dvt/instruments/screenshot.py +2 -2
- pymobiledevice3/services/dvt/instruments/sysmontap.py +15 -15
- pymobiledevice3/services/dvt/testmanaged/xcuitest.py +42 -52
- pymobiledevice3/services/file_relay.py +10 -10
- pymobiledevice3/services/heartbeat.py +9 -8
- pymobiledevice3/services/house_arrest.py +16 -15
- pymobiledevice3/services/idam.py +20 -0
- pymobiledevice3/services/installation_proxy.py +173 -81
- pymobiledevice3/services/lockdown_service.py +20 -10
- pymobiledevice3/services/misagent.py +22 -19
- pymobiledevice3/services/mobile_activation.py +147 -64
- pymobiledevice3/services/mobile_config.py +331 -294
- pymobiledevice3/services/mobile_image_mounter.py +141 -113
- pymobiledevice3/services/mobilebackup2.py +203 -145
- pymobiledevice3/services/notification_proxy.py +11 -11
- pymobiledevice3/services/os_trace.py +134 -74
- pymobiledevice3/services/pcapd.py +314 -302
- pymobiledevice3/services/power_assertion.py +10 -9
- pymobiledevice3/services/preboard.py +4 -4
- pymobiledevice3/services/remote_fetch_symbols.py +21 -14
- pymobiledevice3/services/remote_server.py +176 -146
- pymobiledevice3/services/restore_service.py +16 -16
- pymobiledevice3/services/screenshot.py +15 -12
- pymobiledevice3/services/simulate_location.py +7 -7
- pymobiledevice3/services/springboard.py +15 -15
- pymobiledevice3/services/syslog.py +5 -5
- pymobiledevice3/services/web_protocol/alert.py +11 -11
- pymobiledevice3/services/web_protocol/automation_session.py +251 -239
- pymobiledevice3/services/web_protocol/cdp_screencast.py +46 -37
- pymobiledevice3/services/web_protocol/cdp_server.py +19 -19
- pymobiledevice3/services/web_protocol/cdp_target.py +411 -373
- pymobiledevice3/services/web_protocol/driver.py +114 -111
- pymobiledevice3/services/web_protocol/element.py +124 -111
- pymobiledevice3/services/web_protocol/inspector_session.py +106 -102
- pymobiledevice3/services/web_protocol/selenium_api.py +49 -49
- pymobiledevice3/services/web_protocol/session_protocol.py +18 -12
- pymobiledevice3/services/web_protocol/switch_to.py +30 -27
- pymobiledevice3/services/webinspector.py +189 -155
- pymobiledevice3/tcp_forwarder.py +87 -69
- pymobiledevice3/tunneld/__init__.py +0 -0
- pymobiledevice3/tunneld/api.py +63 -0
- pymobiledevice3/tunneld/server.py +603 -0
- pymobiledevice3/usbmux.py +198 -147
- pymobiledevice3/utils.py +14 -11
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/METADATA +55 -28
- pymobiledevice3-7.0.6.dist-info/RECORD +188 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/WHEEL +1 -1
- pymobiledevice3/cli/developer.py +0 -1215
- pymobiledevice3/cli/diagnostics.py +0 -99
- pymobiledevice3/tunneld.py +0 -524
- pymobiledevice3-4.14.6.dist-info/RECORD +0 -168
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/entry_points.txt +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info/licenses}/LICENSE +0 -0
- {pymobiledevice3-4.14.6.dist-info → pymobiledevice3-7.0.6.dist-info}/top_level.txt +0 -0
pymobiledevice3/services/amfi.py
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import logging
|
|
3
3
|
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
DeviceHasPasscodeSetError,
|
|
8
|
-
|
|
4
|
+
from pymobiledevice3.exceptions import (
|
|
5
|
+
AmfiError,
|
|
6
|
+
DeveloperModeError,
|
|
7
|
+
DeviceHasPasscodeSetError,
|
|
8
|
+
PyMobileDevice3Exception,
|
|
9
|
+
)
|
|
10
|
+
from pymobiledevice3.lockdown import LockdownClient, retry_create_using_usbmux
|
|
9
11
|
from pymobiledevice3.services.heartbeat import HeartbeatService
|
|
10
12
|
|
|
11
13
|
|
|
12
14
|
class AmfiService:
|
|
13
|
-
|
|
14
15
|
DEVELOPER_MODE_REVEAL = 0
|
|
15
16
|
DEVELOPER_MODE_ENABLE = 1
|
|
16
17
|
DEVELOPER_MODE_ACCEPT = 2
|
|
17
18
|
|
|
18
|
-
SERVICE_NAME =
|
|
19
|
+
SERVICE_NAME = "com.apple.amfi.lockdown"
|
|
19
20
|
|
|
20
21
|
def __init__(self, lockdown: LockdownClient):
|
|
21
22
|
self._lockdown = lockdown
|
|
22
23
|
self._logger = logging.getLogger(self.__module__)
|
|
23
24
|
|
|
24
25
|
def reveal_developer_mode_option_in_ui(self):
|
|
25
|
-
"""
|
|
26
|
+
"""create an empty file at AMFIShowOverridePath"""
|
|
26
27
|
service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
|
|
27
|
-
resp = service.send_recv_plist({
|
|
28
|
-
if not resp.get(
|
|
29
|
-
raise PyMobileDevice3Exception(f
|
|
28
|
+
resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_REVEAL})
|
|
29
|
+
if not resp.get("success"):
|
|
30
|
+
raise PyMobileDevice3Exception(f"create_AMFIShowOverridePath() failed with: {resp}")
|
|
30
31
|
|
|
31
32
|
def enable_developer_mode(self, enable_post_restart=True):
|
|
32
33
|
"""
|
|
@@ -35,38 +36,31 @@ class AmfiService:
|
|
|
35
36
|
with "yes"
|
|
36
37
|
"""
|
|
37
38
|
service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
|
|
38
|
-
resp = service.send_recv_plist({
|
|
39
|
-
error = resp.get(
|
|
39
|
+
resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_ENABLE})
|
|
40
|
+
error = resp.get("Error")
|
|
40
41
|
|
|
41
42
|
if error is not None:
|
|
42
|
-
if error ==
|
|
43
|
+
if error == "Device has a passcode set":
|
|
43
44
|
raise DeviceHasPasscodeSetError()
|
|
44
45
|
raise AmfiError(error)
|
|
45
46
|
|
|
46
|
-
if not resp.get(
|
|
47
|
-
raise DeveloperModeError(f
|
|
47
|
+
if not resp.get("success"):
|
|
48
|
+
raise DeveloperModeError(f"enable_developer_mode(): {resp}")
|
|
48
49
|
|
|
49
50
|
if not enable_post_restart:
|
|
50
51
|
return
|
|
51
52
|
|
|
52
53
|
try:
|
|
53
54
|
HeartbeatService(self._lockdown).start()
|
|
54
|
-
except ConnectionAbortedError:
|
|
55
|
-
self._logger.debug(
|
|
56
|
-
|
|
57
|
-
while True:
|
|
58
|
-
try:
|
|
59
|
-
self._lockdown = create_using_usbmux(self._lockdown.udid)
|
|
60
|
-
break
|
|
61
|
-
except (NoDeviceConnectedError, ConnectionFailedError, BadDevError, OSError, construct.core.StreamError,
|
|
62
|
-
DeviceNotFoundError):
|
|
63
|
-
pass
|
|
55
|
+
except (ConnectionAbortedError, BrokenPipeError):
|
|
56
|
+
self._logger.debug("device disconnected, awaiting reconnect")
|
|
64
57
|
|
|
58
|
+
self._lockdown = retry_create_using_usbmux(None, serial=self._lockdown.udid)
|
|
65
59
|
self.enable_developer_mode_post_restart()
|
|
66
60
|
|
|
67
61
|
def enable_developer_mode_post_restart(self):
|
|
68
|
-
"""
|
|
62
|
+
"""answer the prompt that appears after the restart with "yes" """
|
|
69
63
|
service = self._lockdown.start_lockdown_service(self.SERVICE_NAME)
|
|
70
|
-
resp = service.send_recv_plist({
|
|
71
|
-
if not resp.get(
|
|
72
|
-
raise DeveloperModeError(f
|
|
64
|
+
resp = service.send_recv_plist({"action": self.DEVELOPER_MODE_ACCEPT})
|
|
65
|
+
if not resp.get("success"):
|
|
66
|
+
raise DeveloperModeError(f"enable_developer_mode_post_restart() failed: {resp}")
|
|
@@ -8,8 +8,8 @@ from pymobiledevice3.services.lockdown_service import LockdownService
|
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class CompanionProxyService(LockdownService):
|
|
11
|
-
SERVICE_NAME =
|
|
12
|
-
RSD_SERVICE_NAME =
|
|
11
|
+
SERVICE_NAME = "com.apple.companion_proxy"
|
|
12
|
+
RSD_SERVICE_NAME = "com.apple.companion_proxy.shim.remote"
|
|
13
13
|
|
|
14
14
|
def __init__(self, lockdown: LockdownServiceProvider):
|
|
15
15
|
if isinstance(lockdown, LockdownClient):
|
|
@@ -19,48 +19,52 @@ class CompanionProxyService(LockdownService):
|
|
|
19
19
|
|
|
20
20
|
def list(self):
|
|
21
21
|
service = self.lockdown.start_lockdown_service(self.service_name)
|
|
22
|
-
return service.send_recv_plist({
|
|
22
|
+
return service.send_recv_plist({"Command": "GetDeviceRegistry"}).get("PairedDevicesArray", [])
|
|
23
23
|
|
|
24
24
|
def listen_for_devices(self):
|
|
25
25
|
service = self.lockdown.start_lockdown_service(self.service_name)
|
|
26
|
-
service.send_plist({
|
|
26
|
+
service.send_plist({"Command": "StartListeningForDevices"})
|
|
27
27
|
while True:
|
|
28
28
|
yield service.recv_plist()
|
|
29
29
|
|
|
30
30
|
def get_value(self, udid: str, key: str):
|
|
31
31
|
service = self.lockdown.start_lockdown_service(self.service_name)
|
|
32
|
-
response = service.send_recv_plist({
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
response = service.send_recv_plist({
|
|
33
|
+
"Command": "GetValueFromRegistry",
|
|
34
|
+
"GetValueGizmoUDIDKey": udid,
|
|
35
|
+
"GetValueKeyKey": key,
|
|
36
|
+
})
|
|
35
37
|
|
|
36
|
-
value = response.get(
|
|
38
|
+
value = response.get("RetrievedValueDictionary")
|
|
37
39
|
if value is not None:
|
|
38
40
|
return value
|
|
39
41
|
|
|
40
|
-
error = response.get(
|
|
42
|
+
error = response.get("Error")
|
|
41
43
|
raise PyMobileDevice3Exception(error)
|
|
42
44
|
|
|
43
|
-
def start_forwarding_service_port(
|
|
44
|
-
|
|
45
|
+
def start_forwarding_service_port(
|
|
46
|
+
self, remote_port: int, service_name: Optional[str] = None, options: Optional[dict] = None
|
|
47
|
+
):
|
|
45
48
|
service = self.lockdown.start_lockdown_service(self.service_name)
|
|
46
49
|
|
|
47
|
-
request = {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
request = {
|
|
51
|
+
"Command": "StartForwardingServicePort",
|
|
52
|
+
"GizmoRemotePortNumber": remote_port,
|
|
53
|
+
"IsServiceLowPriority": False,
|
|
54
|
+
"PreferWifi": False,
|
|
55
|
+
}
|
|
51
56
|
|
|
52
57
|
if service_name is not None:
|
|
53
|
-
request[
|
|
58
|
+
request["ForwardedServiceName"] = service_name
|
|
54
59
|
|
|
55
60
|
if options is not None:
|
|
56
61
|
request.update(options)
|
|
57
62
|
|
|
58
|
-
return service.send_recv_plist(request).get(
|
|
63
|
+
return service.send_recv_plist(request).get("CompanionProxyServicePort")
|
|
59
64
|
|
|
60
65
|
def stop_forwarding_service_port(self, remote_port: int):
|
|
61
66
|
service = self.lockdown.start_lockdown_service(self.service_name)
|
|
62
67
|
|
|
63
|
-
request = {
|
|
64
|
-
'GizmoRemotePortNumber': remote_port}
|
|
68
|
+
request = {"Command": "StopForwardingServicePort", "GizmoRemotePortNumber": remote_port}
|
|
65
69
|
|
|
66
70
|
return service.send_recv_plist(request)
|
|
@@ -3,21 +3,27 @@ import posixpath
|
|
|
3
3
|
import re
|
|
4
4
|
import time
|
|
5
5
|
from collections.abc import Generator
|
|
6
|
-
from
|
|
6
|
+
from json import JSONDecodeError
|
|
7
|
+
from typing import Callable, ClassVar, Optional
|
|
7
8
|
|
|
8
9
|
from pycrashreport.crash_report import get_crash_report_from_buf
|
|
9
10
|
from xonsh.built_ins import XSH
|
|
10
11
|
from xonsh.cli_utils import Annotated, Arg
|
|
11
12
|
|
|
12
|
-
from pymobiledevice3.exceptions import
|
|
13
|
+
from pymobiledevice3.exceptions import (
|
|
14
|
+
AfcException,
|
|
15
|
+
AfcFileNotFoundError,
|
|
16
|
+
NotificationTimeoutError,
|
|
17
|
+
SysdiagnoseTimeoutError,
|
|
18
|
+
)
|
|
13
19
|
from pymobiledevice3.lockdown import LockdownClient
|
|
14
20
|
from pymobiledevice3.lockdown_service_provider import LockdownServiceProvider
|
|
15
21
|
from pymobiledevice3.services.afc import AfcService, AfcShell, path_completer
|
|
16
22
|
from pymobiledevice3.services.notification_proxy import NotificationProxyService
|
|
17
23
|
from pymobiledevice3.services.os_trace import OsTraceService
|
|
18
24
|
|
|
19
|
-
SYSDIAGNOSE_PROCESS_NAMES = (
|
|
20
|
-
SYSDIAGNOSE_DIR =
|
|
25
|
+
SYSDIAGNOSE_PROCESS_NAMES = ("sysdiagnose", "sysdiagnosed")
|
|
26
|
+
SYSDIAGNOSE_DIR = "DiagnosticLogs/sysdiagnose"
|
|
21
27
|
SYSDIAGNOSE_IN_PROGRESS_MAX_TTL_SECS = 600
|
|
22
28
|
|
|
23
29
|
# on iOS17, we need to wait for a moment before trying to fetch the sysdiagnose archive
|
|
@@ -25,14 +31,14 @@ IOS17_SYSDIAGNOSE_DELAY = 3
|
|
|
25
31
|
|
|
26
32
|
|
|
27
33
|
class CrashReportsManager:
|
|
28
|
-
COPY_MOBILE_NAME =
|
|
29
|
-
RSD_COPY_MOBILE_NAME =
|
|
34
|
+
COPY_MOBILE_NAME = "com.apple.crashreportcopymobile"
|
|
35
|
+
RSD_COPY_MOBILE_NAME = "com.apple.crashreportcopymobile.shim.remote"
|
|
30
36
|
|
|
31
|
-
CRASH_MOVER_NAME =
|
|
32
|
-
RSD_CRASH_MOVER_NAME =
|
|
37
|
+
CRASH_MOVER_NAME = "com.apple.crashreportmover"
|
|
38
|
+
RSD_CRASH_MOVER_NAME = "com.apple.crashreportmover.shim.remote"
|
|
33
39
|
|
|
34
|
-
APPSTORED_PATH =
|
|
35
|
-
IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS = [
|
|
40
|
+
APPSTORED_PATH = "/com.apple.appstored"
|
|
41
|
+
IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS: ClassVar = [".tmp", ".tar.gz"]
|
|
36
42
|
|
|
37
43
|
def __init__(self, lockdown: LockdownServiceProvider):
|
|
38
44
|
self.logger = logging.getLogger(__name__)
|
|
@@ -61,16 +67,16 @@ class CrashReportsManager:
|
|
|
61
67
|
Clear all crash reports.
|
|
62
68
|
"""
|
|
63
69
|
undeleted_items = []
|
|
64
|
-
for filename in self.ls(
|
|
70
|
+
for filename in self.ls("/"):
|
|
65
71
|
undeleted_items.extend(self.afc.rm(filename, force=True))
|
|
66
72
|
|
|
67
73
|
for item in undeleted_items:
|
|
68
74
|
# special case of file that sometimes created automatically right after delete,
|
|
69
75
|
# and then we can't delete the folder because it's not empty
|
|
70
76
|
if item != self.APPSTORED_PATH:
|
|
71
|
-
raise AfcException(f
|
|
77
|
+
raise AfcException(f"failed to clear crash reports directory, undeleted items: {undeleted_items}", None)
|
|
72
78
|
|
|
73
|
-
def ls(self, path: str =
|
|
79
|
+
def ls(self, path: str = "/", depth: int = 1) -> list[str]:
|
|
74
80
|
"""
|
|
75
81
|
List file and folder in the crash report's directory.
|
|
76
82
|
:param path: Path to list, relative to the crash report's directory.
|
|
@@ -79,30 +85,32 @@ class CrashReportsManager:
|
|
|
79
85
|
"""
|
|
80
86
|
return list(self.afc.dirlist(path, depth))[1:] # skip the root path '/'
|
|
81
87
|
|
|
82
|
-
def pull(
|
|
88
|
+
def pull(
|
|
89
|
+
self, out: str, entry: str = "/", erase: bool = False, match: Optional[str] = None, progress_bar: bool = True
|
|
90
|
+
) -> None:
|
|
83
91
|
"""
|
|
84
92
|
Pull crash reports from the device.
|
|
85
93
|
:param out: Directory to pull crash reports to.
|
|
86
94
|
:param entry: File or Folder to pull.
|
|
87
95
|
:param erase: Whether to erase the original file from the CrashReports directory.
|
|
88
96
|
:param match: Regex to match against file and directory names to pull.
|
|
97
|
+
:param progress_bar: Whether to show a progress bar when pulling large files.
|
|
89
98
|
"""
|
|
90
99
|
|
|
91
100
|
def log(src: str, dst: str) -> None:
|
|
92
|
-
self.logger.info(f
|
|
93
|
-
if erase:
|
|
94
|
-
|
|
95
|
-
self.afc.rm_single(src, force=True)
|
|
101
|
+
self.logger.info(f"{src} --> {dst}")
|
|
102
|
+
if erase and not self.afc.isdir(src):
|
|
103
|
+
self.afc.rm_single(src, force=True)
|
|
96
104
|
|
|
97
105
|
match = None if match is None else re.compile(match)
|
|
98
|
-
self.afc.pull(entry, out, match, callback=log)
|
|
106
|
+
self.afc.pull(entry, out, match, callback=log, progress_bar=progress_bar, ignore_errors=True)
|
|
99
107
|
|
|
100
108
|
def flush(self) -> None:
|
|
101
|
-
"""
|
|
102
|
-
ack = b
|
|
109
|
+
"""Trigger com.apple.crashreportmover to flush all products into CrashReports directory"""
|
|
110
|
+
ack = b"ping\x00"
|
|
103
111
|
assert ack == self.lockdown.start_lockdown_service(self.crash_mover_service_name).recvall(len(ack))
|
|
104
112
|
|
|
105
|
-
def watch(self, name: str = None, raw: bool = False) -> Generator[str, None, None]:
|
|
113
|
+
def watch(self, name: Optional[str] = None, raw: bool = False) -> Generator[str, None, None]:
|
|
106
114
|
"""
|
|
107
115
|
Monitor creation of new crash reports for a given process name.
|
|
108
116
|
|
|
@@ -110,20 +118,28 @@ class CrashReportsManager:
|
|
|
110
118
|
representation for the crash.
|
|
111
119
|
"""
|
|
112
120
|
for syslog_entry in OsTraceService(lockdown=self.lockdown).syslog():
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
if (
|
|
122
|
+
(posixpath.basename(syslog_entry.filename) != "osanalyticshelper")
|
|
123
|
+
or (posixpath.basename(syslog_entry.image_name) != "OSAnalytics")
|
|
124
|
+
or not syslog_entry.message.startswith("Saved type ")
|
|
125
|
+
):
|
|
116
126
|
# skip non-ips creation syslog lines
|
|
117
127
|
continue
|
|
118
128
|
|
|
119
129
|
filename = posixpath.basename(syslog_entry.message.split()[-1])
|
|
120
|
-
self.logger.debug(f
|
|
130
|
+
self.logger.debug(f"crash report: {filename}")
|
|
121
131
|
|
|
122
|
-
if posixpath.splitext(filename)[-1] not in (
|
|
132
|
+
if posixpath.splitext(filename)[-1] not in (".ips", ".panic"):
|
|
123
133
|
continue
|
|
124
134
|
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
while True:
|
|
136
|
+
try:
|
|
137
|
+
crash_report_raw = self.afc.get_file_contents(filename).decode()
|
|
138
|
+
crash_report = get_crash_report_from_buf(crash_report_raw, filename=filename)
|
|
139
|
+
break
|
|
140
|
+
except (AfcFileNotFoundError, JSONDecodeError):
|
|
141
|
+
# Sometimes we have to wait for the file to be readable
|
|
142
|
+
pass
|
|
127
143
|
|
|
128
144
|
if name is None or crash_report.name == name:
|
|
129
145
|
if raw:
|
|
@@ -131,8 +147,14 @@ class CrashReportsManager:
|
|
|
131
147
|
else:
|
|
132
148
|
yield crash_report
|
|
133
149
|
|
|
134
|
-
def get_new_sysdiagnose(
|
|
135
|
-
|
|
150
|
+
def get_new_sysdiagnose(
|
|
151
|
+
self,
|
|
152
|
+
out: str,
|
|
153
|
+
erase: bool = True,
|
|
154
|
+
*,
|
|
155
|
+
timeout: Optional[float] = None,
|
|
156
|
+
callback: Optional[Callable[[float], None]] = None,
|
|
157
|
+
) -> None:
|
|
136
158
|
"""
|
|
137
159
|
Monitor the creation of a newly created sysdiagnose archive and pull it
|
|
138
160
|
:param out: filename
|
|
@@ -150,7 +172,7 @@ class CrashReportsManager:
|
|
|
150
172
|
if callback is not None:
|
|
151
173
|
callback(time.monotonic() - start_time)
|
|
152
174
|
|
|
153
|
-
self.logger.info(
|
|
175
|
+
self.logger.info("sysdiagnose tarball creation has been started")
|
|
154
176
|
self._wait_for_sysdiagnose_to_finish(timeout)
|
|
155
177
|
|
|
156
178
|
if callback is not None:
|
|
@@ -163,17 +185,17 @@ class CrashReportsManager:
|
|
|
163
185
|
|
|
164
186
|
def _wait_for_sysdiagnose_to_finish(self, end_time: Optional[float] = None) -> None:
|
|
165
187
|
with NotificationProxyService(self.lockdown, timeout=end_time) as service:
|
|
166
|
-
stop_notification =
|
|
188
|
+
stop_notification = "com.apple.sysdiagnose.sysdiagnoseStopped"
|
|
167
189
|
service.notify_register_dispatch(stop_notification)
|
|
168
190
|
try:
|
|
169
191
|
for event in service.receive_notification():
|
|
170
|
-
if event[
|
|
192
|
+
if event["Name"] != stop_notification:
|
|
171
193
|
continue
|
|
172
|
-
self.logger.debug(f
|
|
194
|
+
self.logger.debug(f"Received {event}")
|
|
173
195
|
time.sleep(IOS17_SYSDIAGNOSE_DELAY)
|
|
174
196
|
break
|
|
175
197
|
except NotificationTimeoutError as e:
|
|
176
|
-
raise SysdiagnoseTimeoutError(
|
|
198
|
+
raise SysdiagnoseTimeoutError("Timeout waiting for sysdiagnose completion") from e
|
|
177
199
|
|
|
178
200
|
def _get_new_sysdiagnose_filename(self, end_time: Optional[float] = None) -> str:
|
|
179
201
|
sysdiagnose_filename = None
|
|
@@ -183,17 +205,19 @@ class CrashReportsManager:
|
|
|
183
205
|
try:
|
|
184
206
|
for filename in self.afc.listdir(SYSDIAGNOSE_DIR):
|
|
185
207
|
# search for an IN_PROGRESS archive
|
|
186
|
-
if filename not in excluded_temp_files and
|
|
208
|
+
if filename not in excluded_temp_files and "IN_PROGRESS_" in filename:
|
|
187
209
|
for ext in self.IN_PROGRESS_SYSDIAGNOSE_EXTENSIONS:
|
|
188
210
|
if filename.endswith(ext):
|
|
189
|
-
delta =
|
|
190
|
-
|
|
211
|
+
delta = (
|
|
212
|
+
self.lockdown.date
|
|
213
|
+
- self.afc.stat(posixpath.join(SYSDIAGNOSE_DIR, filename))["st_mtime"]
|
|
214
|
+
)
|
|
191
215
|
# Ignores IN_PROGRESS sysdiagnose files older than the defined time to live
|
|
192
216
|
if delta.total_seconds() < SYSDIAGNOSE_IN_PROGRESS_MAX_TTL_SECS:
|
|
193
|
-
self.logger.debug(f
|
|
217
|
+
self.logger.debug(f"Detected in progress sysdiagnose {filename}")
|
|
194
218
|
sysdiagnose_filename = filename.rsplit(ext)[0]
|
|
195
|
-
sysdiagnose_filename = sysdiagnose_filename.replace(
|
|
196
|
-
sysdiagnose_filename = f
|
|
219
|
+
sysdiagnose_filename = sysdiagnose_filename.replace("IN_PROGRESS_", "")
|
|
220
|
+
sysdiagnose_filename = f"{sysdiagnose_filename}.tar.gz"
|
|
197
221
|
return posixpath.join(SYSDIAGNOSE_DIR, sysdiagnose_filename)
|
|
198
222
|
else:
|
|
199
223
|
self.logger.warning(f"Old sysdiagnose temp file ignored {filename}")
|
|
@@ -202,7 +226,7 @@ class CrashReportsManager:
|
|
|
202
226
|
pass
|
|
203
227
|
|
|
204
228
|
if self._check_timeout(end_time):
|
|
205
|
-
raise SysdiagnoseTimeoutError(
|
|
229
|
+
raise SysdiagnoseTimeoutError("Timeout finding in-progress sysdiagnose filename")
|
|
206
230
|
|
|
207
231
|
def _check_timeout(self, end_time: Optional[float] = None) -> bool:
|
|
208
232
|
return end_time is not None and time.monotonic() > end_time
|
|
@@ -212,16 +236,16 @@ class CrashReportsShell(AfcShell):
|
|
|
212
236
|
@classmethod
|
|
213
237
|
def create(cls, service_provider: LockdownServiceProvider, **kwargs):
|
|
214
238
|
manager = CrashReportsManager(service_provider)
|
|
215
|
-
XSH.ctx[
|
|
239
|
+
XSH.ctx["_manager"] = manager
|
|
216
240
|
super(CrashReportsShell, CrashReportsShell).create(service_provider, service=manager.afc)
|
|
217
241
|
|
|
218
242
|
def _setup_shell_commands(self):
|
|
219
243
|
super()._setup_shell_commands()
|
|
220
|
-
self._register_arg_parse_alias(
|
|
221
|
-
self._register_arg_parse_alias(
|
|
244
|
+
self._register_arg_parse_alias("parse", self._do_parse)
|
|
245
|
+
self._register_arg_parse_alias("clear", self._do_clear)
|
|
222
246
|
|
|
223
247
|
def _do_parse(self, filename: Annotated[str, Arg(completer=path_completer)]) -> None:
|
|
224
248
|
print(get_crash_report_from_buf(self.afc.get_file_contents(filename).decode(), filename=filename))
|
|
225
249
|
|
|
226
250
|
def _do_clear(self) -> None:
|
|
227
|
-
XSH.ctx[
|
|
251
|
+
XSH.ctx["_manager"].clear()
|
|
@@ -7,14 +7,14 @@ CHUNK_SIZE = 200
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
class DebugServerAppList(LockdownService):
|
|
10
|
-
SERVICE_NAME =
|
|
10
|
+
SERVICE_NAME = "com.apple.debugserver.DVTSecureSocketProxy.applist"
|
|
11
11
|
|
|
12
12
|
def __init__(self, lockdown: LockdownClient):
|
|
13
13
|
super().__init__(lockdown, self.SERVICE_NAME)
|
|
14
14
|
|
|
15
15
|
def get(self) -> dict:
|
|
16
|
-
buf = b
|
|
17
|
-
while b
|
|
16
|
+
buf = b""
|
|
17
|
+
while b"</plist>" not in buf:
|
|
18
18
|
buf += self.service.recv(CHUNK_SIZE)
|
|
19
19
|
|
|
20
20
|
return plistlib.loads(buf)
|
|
@@ -4,25 +4,25 @@ from pymobiledevice3.services.lockdown_service import LockdownService
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class DtDeviceArbitration(LockdownService):
|
|
7
|
-
SERVICE_NAME =
|
|
7
|
+
SERVICE_NAME = "com.apple.dt.devicearbitration"
|
|
8
8
|
|
|
9
9
|
def __init__(self, lockdown: LockdownClient):
|
|
10
10
|
super().__init__(lockdown, self.SERVICE_NAME, is_developer_service=True)
|
|
11
11
|
|
|
12
12
|
@property
|
|
13
13
|
def version(self) -> dict:
|
|
14
|
-
return self.service.send_recv_plist({
|
|
14
|
+
return self.service.send_recv_plist({"command": "version"})
|
|
15
15
|
|
|
16
16
|
def check_in(self, hostname: str, force: bool = False):
|
|
17
|
-
request = {
|
|
17
|
+
request = {"command": "check-in", "hostname": hostname}
|
|
18
18
|
if force:
|
|
19
|
-
request[
|
|
19
|
+
request["command"] = "force-check-in"
|
|
20
20
|
response = self.service.send_recv_plist(request)
|
|
21
|
-
if response.get(
|
|
21
|
+
if response.get("result") != "success":
|
|
22
22
|
raise DeviceAlreadyInUseError(response)
|
|
23
23
|
|
|
24
24
|
def check_out(self):
|
|
25
|
-
request = {
|
|
25
|
+
request = {"command": "check-out"}
|
|
26
26
|
response = self.service.send_recv_plist(request)
|
|
27
|
-
if response.get(
|
|
28
|
-
raise ArbitrationError(f
|
|
27
|
+
if response.get("result") != "success":
|
|
28
|
+
raise ArbitrationError(f"failed with: {response}")
|