1. 程式人生 > >備份Jetson Nano系統

備份Jetson Nano系統

採用程式備份

#!/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()