備份Jetson Nano系統
阿新 • • 發佈:2019-07-01
採用程式備份
#!/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()