\documentclass{utart}

\usepackage{enumitem}
\usepackage{plantuml}

\title{deepin 原子更新概要设计文档}
\author{jouyouyun}

\setUTClassify{C级商密}

% 设置文档编号
\setUTIndex{UT-YZGX20220224T_SYS023}

% 设置拟制人信息
\setUTFiction{闫博文}{2022-02-22}

% 设置审核人信息
\setUTReview{闫博文}{2022-02-22}

% 设置批准人信息
\setUTApprove{闫博文}{2022-02-22}

\begin{document}
\utMakeTitle{}{1.0.0}{2022-02-22}
\utMakeChangeLog{
  1.0.0 & 创建 & 闫博文 & 2022-02-22 \\
  \hline
}
\utMakeTOC

\section{概述}
\subsection{目的}
本文档是针对 deepin 原子更新程序给出的系统概要设计文档，在本文档中，将给出 deepin 原子更新程序的设计原则、关键静态结构设计、关键动态流程设计、非功能性设计等内容。
\par
deepin 系统更新程序的设计与实现是基于系统更新的需求分析，总体上将结合结构化设计的方法与文字描述，给出程序结构化的概要设计，与需求分析内容相对应，以保证系统设计的严谨性与可实现性。
在结构化部分，本文档将主要采取 UML 语言的包图、类图、序列图等进行程序设计。
\par
本文档的适用读者为 deepin 原子更新程序的产品经理、设计人员、开发人员、测试人员以及后续维护人员。

\subsection{术语说明}
\begin{itemize}[leftmargin=4em]
\item ostree：是一个用于对Linux操作系统进行版本更新的系统，它可以被视为 "面向操作系统二进制文件的git" 。通常用来做操作系统项目的持续交付；
\item hardlink：硬链接，将文件系统中的文件与名称相关联，允许同一文件使用多个硬链接，不支持跨分区或目录的链接；
\item snapshot：快照，是系统在某一时刻状态的副本;
\item 原子操作：是指操作一旦开始，便不会被中断，直至结束。在多线程中，是指操作从开始到结束，只在同一线程，中间不会出现线程切换。
\end{itemize}

\subsection{参考资料}
\begin{itemize}[leftmargin=4em]
\item \href{https://refspecs.linuxfoundation.org/FHS\_3.0/fhs/index.html}{Filesystem Hierarchy Standard.}
\item \href{https://ostree.readthedocs.io/en/latest/}{ostree}
\item \href{https://github.com/linuxdeepin/deepin-styleguide}{deepin 编码规范}
\end{itemize}

\section{系统设计}
deepin 系统更新程序最初的目标是将系统状态的变更操作，如软件的安装、更新或卸载，放入到 chroot 中操作；并提供系统快照的管理功能，如快照生成和快照回滚。
即通过快照构建隔离于当前系统运行环境的 chroot 环境，如此对系统的变更操作，便不会对当前的系统产生影响，仅在变更操作成功后才改变当前系统的状态。

当前阶段，基于时间和 deb 兼容的考虑，系统的变更仍在当前运行环境中进行，不构建 chroot 环境，但在系统变更前生成当前系统的快照。

\subsection{设计原则}
deepin 更新管理程序基于其需求，实现时需遵循以下原则：
\begin{itemize}[leftmargin=4em]
\item 更新程序应采用管道/过滤器与面向对象相结合的设计风格，流程控制主要采用管道/过滤器风格，功能模块主要采用面向对象风格；
\item 功能模块之间应避免相互调用，降低模块耦合度；
\item 模块应遵循最小化职责原则;
\item 实现时应尽可能只用系统库，禁止引入第三方库，因为需要集成进 \texttt{initrd.img} 中；
\item 执行时应实现失败处理逻辑，尽可能降低对现有系统的影响；
\item 应检查每个流程的产物，确认执行成功；
\item 应遵守 deepin 编码规范；
\end{itemize}

\subsection{子系统设计}
\subsubsection{结构设计}
deepin 更新程序被设计为一个单文件程序，通过命令行参数来执行不同的流程。
其中 init、commit、rollback、clean 等操作，应进行单例检查，不允许并行执行。

整体结构如下：
\begin{center}
  \begin{adjustbox}{scale=0.75}
    \begin{plantuml}
      @startuml
      package "deepin-atomic-upgrade" {
        package action {
          [init] -[hidden]r-> [list]
          [list] -[hidden]r-> [commit]
          [commit] -[hidden]r-> [rollback]
          [rollback] -[hidden]r-> [clean]

          note top of [init]
          初始化
          end note
          note top of [list]
          版本展示
          end note
          note top of [commit]
          版本提交
          end note
          note top of [rollback]
          版本回滚
          end note
          note top of [clean]
          版本清理
          end note
        }

        package "modules" {
          [upgrader]

          note left of [upgrader]
          更新管理模块，实现初始化、版本查询、
          版本提交、版本回滚等接口
          end note
        }

        package "objects" {
          [init] -d-> [upgrader]
          [list] -d-> [upgrader]
          [commit] -d-> [upgrader]
          [rollback] -d-> [upgrader]
          [clean] -d-> [upgrader]

          [repo] -[hidden]r-> [mountpoint]
          [mountpoint] -[hidden]r-> [mountinfo]
          [mountinfo] -[hidden]r-> [util]

          [upgrader] -d-> [repo]
          [upgrader] -d-> [mountpoint]
          [upgrader] -d-> [mountinfo]
          [upgrader] -d-> [util]

          note bottom of [repo]
          本地 repo 管理模块，
          提供 repo 管理的抽象接口。
          ostree、btrfs、zfs 实现这些接口
          end note
          note bottom of [mountpoint]
          挂载点信息模块，实现挂载点
          的挂载与卸载接口
          end note
          note bottom of [mountinfo]
          /proc/self/mounts 解析模块，
          并实现查询功能
          end note
          note bottom of [util]
          通用功能接口，包括文件复制、
          属主权限修改、字符设备创建
          等功能
          end note
        }
      }
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-1 更新程序结构
\end{center}

\subsubsection{目录组织设计}
deepin 更新程序需要对操作系统目录结构进行修改，主要是将数据分区挂载到 \texttt{/persistent} 。

目录结构如下：
\begin{center}
  \begin{adjustbox}{scale=0.65}
    \begin{plantuml}
      @startuml
      package 硬盘分区 {
        [系统分区] -[hidden]r-> [数据分区]
      }

      package 挂载点 {
        [/usr] -[hidden]r-> [/etc]
        [/etc] -[hidden]r-> [/boot]
        [/boot] -[hidden]r-> [/persistent]

        [/etc] -u-> [系统分区]
        [/boot] -u-> [系统分区]
        [/usr] -u-> [系统分区]
        [/persistent] -u-> [数据分区]
      }

      package 软链接 {
        [/bin] -[hidden]d-> [/sbin]
        [/sbin] -[hidden]d-> [/lib]
        [/lib] -[hidden]d-> [/lib<qual>]

        [/bin] .r.> [/usr]
        [/sbin] .r.> [/usr]
        [/lib] .u.> [/usr]
        [/lib<qual>] .u.> [/usr]
      }

      package Bind {
        [/var] -[hidden]d-> [/opt]
        [/opt] -[hidden]d-> [/root]
        [/root] -[hidden]d-> [/home]
        [/home] -[hidden]d-> [/vendor]

        [/var] -r-> [/persistent]
        [/opt] -r-> [/persistent]
        [/root] -r-> [/persistent]
        [/home] -l-> [/persistent]
        [/vendor] -l-> [/persistent]
      }
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-2 目录组织结构
\end{center}

同时，deepin 更新程序对系统文件的组成同样进行了重新设计，系统文件由快照和 \texttt{/vendor} 组合组成，结构如下：
\begin{center}
  \begin{adjustbox}{scale=0.75}
    \begin{plantuml}
      @startuml
      package Snapshot {
        [version1] -[hidden]r-> [version2]
        [version2] -[hidden]r-> [versionX]
      }

      [Repo] -u-> [version1]
      [Repo] -u-> [version2]
      [Repo] -u-> [versionX]

      [ActiveVersion] -d-> [versionX]
      [ActiveVersion] -[hidden]r-> [Vendor]
      [OS] <-d- [ActiveVersion]
      [OS] <-d- [Vendor]

      note right of [Repo]
      位于 /persistent/osroot/repo
      end note
      note bottom of Snapshot
      位于 /persistent/osroot/snapshot
      end note
      note right of [Vendor]
      Bind于 /persistent/vendor
      end note
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-3 OS 组成
\end{center}

\subsubsection{repo 设计}
repo 是 deepin 更新程序中最核心的对象，repo 采用了面向接口的设计方法，定义了 repo 必需的接口，并由具体的 repo 管理技术实现。

repo 设计上需要支持 ostree、btrfs、zfs 三种技术，可在配置中指定是要哪种技术管理本地仓库，通过 repo 提供的 \texttt{NewRepo} 函数进行创建。

repo 的组成结构如下：
\begin{center}
  \begin{adjustbox}{scale=0.55}
    \begin{plantuml}
      @startuml
      package Repo {
        [Interface]

        [ostree] -[hidden]r-> [btrfs]
        [btrfs] -[hidden]r-> [zfs]

        [Interface] <-d- [ostree]
        [Interface] <-d- [btrfs]
        [Interface] <-d- [zfs]

        NewRepo - [Interface]
      }
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-4 repo 结构
\end{center}

\subsection{关键流程设计}
\subsubsection{repo 初始化}
deepin 更新程序的 repo 应该在系统安装完成后进行初始化，但考虑到已有版本的兼容，则需要能够支持已有系统的 repo 创建。
因而 deepin 更新程序实现了 init 流程，来完成 repo 初始化工作，init 流程如下：
\begin{center}
  \begin{adjustbox}{scale=0.65}
    \begin{plantuml}
      @startuml
      :传入 init 参数调用更新程序;
      if (是否已有更新程序运行) then (Y)
      :显示错误信息;
      else
      if (检查配置文件是否正确) then (N)
      :显示错误信息;
      else
      if (检查 repo 是否存在) then (N)
      :显示错误信息;
      else
      :创建空 repo;
      :创建临时 rootfs 目录;
      :hardlink 当前系统文件到 rootfs 目录;
      :生成版本号;
      :提交 rootfs 目录中的数据;
      :显示已提交的版本号;
      endif
      endif
      endif
      stop
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-5 repo init
\end{center}

在完成数据提交后，应执行引导更新操作，以生成包含可回滚到此新版本的引导项。

\subsubsection{version 提交}
数据的提交流程在 repo 初始化中已经描述，这里就不再赘述。

\subsubsection{version 回滚}
\begin{center}
  \begin{adjustbox}{scale=0.75}
    \begin{plantuml}
      @startuml
      :传入 rollback 参数调用更新程序;
      if (是否已有更新程序运行) then (Y)
      :显示错误信息;
      else
      if (检查配置文件是否正确) then (N)
      :显示错误信息;
      else
      if (检查 repo 是否存在) then (N)
      :显示错误信息;
      else
      if (传入的 version 是否存在) then (N)
      :显示错误信息;
      else
      if (传入的 version 是否是当前版本) then (Y)
      :显示提示信息;
      else
      :挂载 fstab 中的分区;
      :生成 version 的快照数据;
      while (编历目录订阅列表)
      :查询目录对应的当前系统的分区挂载信息;
      :创建临时目录;
      :将分区挂载信息挂载到临时目录;
      :使用快照目录替换临时目录;
      endwhile
      endif
      endif
      endif
      endif
      endif
      stop
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-6 repo version rollback
\end{center}

回滚时必需保证临时目录的分区挂载信息与系统中对应目录的分区挂载信息一致，否则会导致分区中的数据未被回滚。

\subsubsection{version 清理}
\begin{center}
  \begin{adjustbox}{scale=0.65}
    \begin{plantuml}
      @startuml
      :传入 clean 参数调用更新程序;
      if (是否已有更新程序运行) then (Y)
      :显示错误信息;
      else
      :列出所有 version;
      if (version 个数是否大于最大保留数量) then (N)
      :显示提示信息;
      else
      :生成待清理版本列表;
      while (遍历待清理版本列表)
      :获取版本对应的提交列表;
      :删除版本;
      while (遍历提交列表)
      :删除提交;
      endwhile
      endwhile
      endif
      endif
      stop
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-7 repo version clean
\end{center}

ostree 中一个 version 对应一个 branch ，删除 branch 时并不会删除 branch 关联的 commit ，但数据是与 commit 关联的，因此需要将 branch 的 commit 都删掉，才能清理掉无用的数据。

\subsection{关键数据结构设计}
\subsubsection{版本号格式}
本地仓库的版本号由 \texttt{<distribution>.<major>.<minor>.<date>} 组成。
\texttt{distribution} 是系统大版本，只允许包含字母和数字，字母全小写，与 \texttt{debian} 中的含义保持一致；
\texttt{major} 和 \texttt{minor} 是无符号整数，\texttt{major} 在 \texttt{date} 发生变化时自增，并将 \texttt{minor} 重置；
\texttt{minor} 则是在 \texttt{date} 不变时自增， \texttt{major} 则保持不变；
\texttt{date} 是当前的时间，由 \texttt{YearMonthDay}组成，如 \texttt{20220222} 。

这里用下面的例子说明版本号的变更方式：

\begin{quote}
假设 \texttt{distribution} 为 \texttt{v23} ，\texttt{date} 为 \texttt{20220222} 。

则初始化本地仓库时，版本号则为 \texttt{v23.0.0.20220222} ，在日期不变时，对系统做了一些修改，再次提交时，版本则变为 \texttt{v23.0.1.20220222} 。

而在一天后，再次提交时，版本号则为 \texttt{v23.1.0.20220223}。
\end{quote}

\subsubsection{Repo}
Repo 中定义了仓库管理的接口，OSTree 进行实现，数据结构如下：
\begin{center}
  \begin{adjustbox}{scale=0.75}
    \begin{plantuml}
      @startuml
      package Repo {
        interface Repository {
          Init() error
          Exist(string version)
          Last() (string, error)
          List() ([]string, error)
          Snapshot(error, snapDir string) error
          Commit(version, subject, dataDir string) error
          Diff(baseVersion, targetVersion, dstFile string) error
          Cat(version, filepath, dstFile string) error
          Previous(version string) (string, error)
        }
        class OSTree implements Repository {
          - repoDir string
        }
      }
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-8 repo object
\end{center}

\section{非功能性设计}
\subsection{性能}
deepin 更新程序在数据提交与回滚时，会进行大量的文件复制操作，是主要的耗时点。更新程序中使用以下措施消减影响：
\begin{itemize}[leftmargin=4em]
\item 计算数据的差异，每次提交或回滚根据差异数据生成待提交或回滚的数据；
\item 保留系统文件的 hardlink 缓存，已减少生成系统时的文件操作；
\end{itemize}

\subsection{安全性}
TODO

\subsection{可靠性}
TODO

\subsection{兼容性}
\begin{itemize}[leftmargin=4em]
\item V20 升级兼容；
\item V20 回滚兼容；
\end{itemize}

\section{部署与实施}
ISO 预装，安装器初始化。

\end{document}
