参考备份Jetson Nano系统(该程序不完备,制作的image不可用,仅供)
#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""
Description: Ubuntu 19.04 Python 3.7.3
FileName: back_jetson.py
cmd:
sudo umount /dev/mapper/loop0p
sudo kpartx -dv /dev/loop0
sudo losetup -d /dev/loop0
sudo dump -0uaf - /media/fzyzm/S_TOOLS/BaiduNetdiskDownload|sudo restore -rf -
dump -0 -f /media/fzyzm/S_LINUX_OTHER/my_temp/bd.dump /home/fzyzm/下载/aarch64/EFI/BOOT/
sudo restore -rf /media/fzyzm/S_LINUX_OTHER/my_temp/bd.dump -D /media/fzyzm/S_LINUX_OTHER/my_temp/temp2/
run:
sudo python3 /home/fzyzm/PycharmProjects/my_tools/back_sys_image/back_jetson.py
"""
import os
import sys
import time
import subprocess
import chardet
from collections import OrderedDict
def get_coding(org_bytes):
encoding = chardet.detect(org_bytes)
if not encoding:
return "utf-8"
if not encoding['encoding']:
return "utf-8"
return encoding['encoding']
def exec_shell_cmd(cmd_list):
"""
执行shell命令
:param cmd_list:
:return:
"""
if not isinstance(cmd_list, (str, tuple, list)):
return ""
if isinstance(cmd_list, str):
cmd_list = cmd_list.split()
print("")
cmd_prompt = "exec cmd:" + " ".join(cmd_list)
print(cmd_prompt)
print("=" * len(cmd_prompt))
# 必须加上close_fds=True,否则子进程会一直存在 , shell=True会造成parted一直执行超时,
child = subprocess.Popen(cmd_list, close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdoutdata, stderrdata = child.communicate()
if child.returncode != 0:
stderrdata = stderrdata.decode(get_coding(stderrdata))
print("error info", child.returncode, stderrdata)
return ""
stdoutdata = stdoutdata.decode(get_coding(stdoutdata))
print(stdoutdata)
return stdoutdata
def exec_pip_cmd(*args, **kwargs):
"""
执行管道式命令
:param args:
:param kwargs:
:return:
"""
if not args:
return ""
for cmd_list in args:
if not isinstance(cmd_list, (str, tuple, list)):
return ""
cwd = None
if kwargs and "cwd" in kwargs:
cwd = kwargs["cwd"]
child_list = []
for cmd_list in args:
if isinstance(cmd_list, str):
cmd_list = cmd_list.split()
print("")
cmd_prompt = "exec cmd:" + " ".join(cmd_list)
print(cmd_prompt)
print("=" * len(cmd_prompt))
if child_list:
child = subprocess.Popen(cmd_list, close_fds=True, stdin=child_list[-1].stdout,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
else:
child = subprocess.Popen(cmd_list, close_fds=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd)
child_list.append(child)
if not child_list:
return ""
stdoutdata, stderrdata = child_list[-1].communicate()
if child_list[-1].returncode != 0:
stderrdata = stderrdata.decode(get_coding(stderrdata))
print("error info", child_list[-1].returncode, stderrdata)
return stderrdata
stdoutdata = stdoutdata.decode(get_coding(stdoutdata))
print(stdoutdata)
return stdoutdata
def get_part_used(first_part_dev_name):
"""
得到分区的已经使用量
:param first_part_dev_name:
:return:
"""
# 挂载分区
data = exec_shell_cmd(f"df -k")
src_mount_path_name = None
for line in data.split("\n"):
line = line.strip()
if not line:
continue
line = line.split()
if not line:
continue
if line[0] == first_part_dev_name:
src_mount_path_name = line[-1].strip()
break
if not src_mount_path_name:
src_mount_path_name = "/mnt/mysrc_" + first_part_dev_name.replace("/", "_")
exec_shell_cmd(f"mkdir -p {src_mount_path_name}")
exec_shell_cmd(f"mount -t ext4 {first_part_dev_name} {src_mount_path_name}")
data = exec_shell_cmd(["df", "-k"])
if data is None:
return 0
line = ""
for line in data.split("\n"):
line = line.strip()
if line.find(first_part_dev_name) > -1:
break
if len(line) < len(first_part_dev_name):
return 0
line = line.split()
if len(line) < 4:
return 0
used_bytes = int(line[2])*1024
used_bytes = int(used_bytes * 1.2 + 32 * 1024 * 1024)
return used_bytes
def copy_sys_32m(dev_name, bak_part_image_name):
"""
拷贝磁盘的前32M
:param dev_name:
:param bak_part_image_name:
:return:
"""
return ["dd", f"if=/dev/{dev_name}", f"of={bak_part_image_name}", "bs=1M", "count=32"]
def get_src_part_info(disk_dev_name, disk_used_size):
"""
获得分区信息
:param disk_dev_name:
:param disk_used_size:
:return:
"""
# 分区
src_part_info = OrderedDict()
data = exec_shell_cmd(f"fdisk -l {disk_dev_name}")
first_part = True
for line in data.split("\n"):
line = line.strip()
if line.find(disk_dev_name) != 0:
continue
line = line.split()
if len(line) < 3:
continue
if first_part:
line[2] = (disk_used_size - 32 * 1024 * 1024) // 512
first_part = False
src_part_info[line[0]] = line[:3]
# 分区name
data = exec_shell_cmd(f"parted --script {disk_dev_name} print ")
need_process = False
for line in data.split("\n"):
line = line.strip()
if not need_process and line.find("End") > 0 and line.find("Name") > 0:
need_process = True
continue
if not need_process:
continue
line = line.split()
if len(line) < 5:
continue
part_info = src_part_info[disk_dev_name + line[0]]
part_info.append(line[0])
part_info.append(line[-1])
for info_id in range(1, len(part_info) - 1):
if isinstance(part_info[info_id], str):
part_info[info_id] = int(part_info[info_id])
return src_part_info
def create_new_image(out_img_name, disk_used_size, disk_part_info):
"""
创建一个新的镜像
:param out_img_name:
:param disk_used_size:
:param disk_part_info:
:return:
"""
new_disk_used_size = (int(disk_used_size) // 1024 + 1) // 1024 + 1
exec_shell_cmd(f"dd if=/dev/zero of={out_img_name} bs=1M count={new_disk_used_size}")
exec_shell_cmd(f"parted {out_img_name} --script -- mklabel GPT")
for _, part_info in disk_part_info.items():
if part_info[3] == "1":
cmd_str = f"parted --script {out_img_name} mkpart {part_info[4]} ext4 {part_info[1]}s {part_info[2]}s"
else:
cmd_str = f"parted --script {out_img_name} mkpart {part_info[4]} {part_info[1]}s {part_info[2]}s"
# print(cmd_str)
exec_shell_cmd(cmd_str)
return True
def copy_1m_32m(bak_part_image_name, new_image_name, disk_part_info):
"""
拷贝一个文件的1M到31M数据
:param bak_part_image_name:
:param new_image_name:
:param disk_part_info:
:return:
"""
min_part_start = min([part_info[1] for _, part_info in disk_part_info.items()]) * 512
# 将前几个分区的数据拷贝到新的镜像文件
space_24m = 24 * 1048576
print(f"copy {bak_part_image_name}/{min_part_start}:{space_24m} {new_image_name}/{min_part_start}:{space_24m}")
with open(bak_part_image_name, "rb") as src_image:
src_image.seek(min_part_start)
data = src_image.read(space_24m - min_part_start)
if not data:
return False
with open(new_image_name, "rb+") as dest_image:
dest_image.seek(min_part_start)
dest_image.write(data)
return True
def mount_data_part(src_part_dev_name, new_image_name):
"""
挂载img的APP分区
:param src_part_dev_name:
:param new_image_name:
:return:
"""
img_dev_name = exec_shell_cmd(f"losetup --show -f {new_image_name}").strip()
data = exec_shell_cmd(f"kpartx -av {img_dev_name}")
mapper_part_name = "/dev/mapper/" + data.split("\n")[0].strip().split()[2].strip() # /dev/mapper/loop2p1
exec_shell_cmd(f"mkfs.ext4 {mapper_part_name}")
dest_mount_path_name = "/mnt/mydest_" + mapper_part_name.replace("/", "_")
exec_shell_cmd(f"mkdir -p {dest_mount_path_name}")
exec_shell_cmd(f"mount -t ext4 {mapper_part_name} {dest_mount_path_name}")
data = exec_shell_cmd(f"df -k")
src_mount_path_name = None
for line in data.split("\n"):
line = line.strip()
if not line:
continue
line = line.split()
if not line:
continue
if line[0] == src_part_dev_name:
src_mount_path_name = line[-1].strip()
break
if not src_mount_path_name:
src_mount_path_name = "/mnt/mysrc_" + src_part_dev_name.replace("/", "_")
exec_shell_cmd(f"mkdir -p {src_mount_path_name}")
exec_shell_cmd(f"mount -t ext4 {src_part_dev_name} {src_mount_path_name}")
return img_dev_name, src_mount_path_name, dest_mount_path_name
def umount_data_part(img_dev_name, src_mount_path_name, dest_mount_path_name):
"""
卸载img的APP分区
:param img_dev_name:
:param src_mount_path_name:
:param dest_mount_path_name:
:return:
"""
exec_shell_cmd(f"umount {src_mount_path_name}")
exec_shell_cmd(f"umount {dest_mount_path_name}")
exec_shell_cmd(f"kpartx -dv {img_dev_name}")
exec_shell_cmd(f"losetup -d {img_dev_name}")
return True
def copy_app_part(src_part_dev_name="/dev/sdc1", new_image_name="/media/fzyzm/Y_LINUX_BAK/jetson_20190630_154532.img"):
"""
备份APP分区
:param src_part_dev_name:
:param new_image_name:
:return:
"""
img_dev_name, src_mount_path_name, dest_mount_path_name = mount_data_part(src_part_dev_name, new_image_name)
exec_pip_cmd(f"dump -0uaf - {src_mount_path_name}", f"restore -rf -", cwd=dest_mount_path_name)
# rm
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/proc/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/tmp/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/lost+found/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/media/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/mnt/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/run/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/dev/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/sys/*")
exec_shell_cmd(f"rm -fr {dest_mount_path_name}/var/*")
umount_data_part(img_dev_name, src_mount_path_name, dest_mount_path_name)
return True
def main():
# 设置变量初始值
disk_name = "sdc"
out_path = "/media/fzyzm/S_LINUX_OTHER/my_temp/"
now_time_str = time.strftime("%Y%m%d_%H%M%S", time.localtime())
# 从命令行获取参数
if len(sys.argv) >= 2:
disk_name = sys.argv[1]
if len(sys.argv) >= 3:
out_path = sys.argv[2]
# 备份磁盘的前32M
bak_part_image_name = os.path.join(out_path, f"{disk_name}_{now_time_str}.img")
exec_shell_cmd(copy_sys_32m(disk_name, bak_part_image_name))
# 磁盘设备序号处理
disk_dev_name = f"/dev/{disk_name}"
first_part_dev_name = f"{disk_dev_name}1"
# 新镜像名称处理
new_image_name = os.path.join(out_path, f"jetson_{now_time_str}.img")
# 获取磁盘已使用空间
disk_used_size = get_part_used(first_part_dev_name)
print("use disk space:", disk_used_size)
# 创建新分区
disk_part_info = get_src_part_info(disk_dev_name, disk_used_size)
create_new_image(new_image_name, disk_used_size, disk_part_info)
copy_1m_32m(bak_part_image_name, new_image_name, disk_part_info)
copy_app_part(first_part_dev_name, new_image_name)
return
if __name__ == "__main__":
if len(sys.argv) == 1:
print("usage: back_jetson.py disk_device_name backup_dest_path_name")
print("example: back_jetson.py sdc /media/fzyzm/S_LINUX_OTHER/my_temp/")
main()