\documentclass{utart}

\usepackage{enumitem}
\usepackage{plantuml}
\utUseMinted

\author{jouyouyun}
\date{\today}
\title{deb 兼容方案}

\begin{document}
\utMakeTitle{}{1.0}{2022-01-24}
\utMakeTOC

\section{前言}
在原子更新中，用户的操作系统由操作系统厂商管理的核心 \texttt{rootfs} 和用户自定义安装的软件组成，这两部分都采用相同的包管理系统(\texttt{dpkg/apt})。

但核心的 \texttt{rootfs} 是由操作系统厂商管理，用户替换 \texttt{rootfs} 中的软件应在 \texttt{rootfs} 更新后继续保留，因此需要能够识别出用户替换的文件。

\section{方案设计}
为了识别用户的修改，主要有两个方向：
\begin{itemize}[leftmargin=4em]
\item 用户修改的文件存储目录与 \texttt{rootfs} 安装目录隔离，如用户安装或修改的文件全部存储在 \texttt{/vendor} 目录，或采用 \texttt{overlayfs} ；
\item \texttt{rootfs} 更新时，对比 \texttt{dpkg status} 来识别出变动的软件；
\end{itemize}

根据上述想法，分别设计了方案，详细如下。

\subsection{/vendor 结合 overlayfs}
\subsubsection{方案介绍}
本方案采用 \texttt{/vendor} 与 \texttt{overlayfs} 相结合的方案，用户安装的软件存储在  \texttt{/vendor} 目录。
在用户安装或卸载软件时，将 \texttt{/vendor} 以 \texttt{overlayfs} 的方式挂载到 \texttt{/usr} 上，操作完成后再卸载，并执行 \texttt{vendor} 与 \texttt{rootfs snapshot} 的合并。

\texttt{/usr} 默认需要只读挂载。

\subsubsection{关键技术}
\paragraph{apt/dpkg hook}
由于使用 \texttt{overlayfs} 会带来性能损耗，因此仅在 \texttt{deb} 安装时对 \texttt{/usr} 使用 \texttt{overlayfs} 。

\texttt{deb} 能够通过 \texttt{apt} 和 \texttt{dpkg} 进行安装，可使用其 \texttt{hook} 功能在 \texttt{deb} 安装之前挂载 \texttt{overlayfs} ，安装完成后卸载 \texttt{overlayfs} 。

\texttt{apt hook} 需要添加文件 \texttt{/etc/apt/apt.conf.d/99upgrader-hook.conf} ，内容如下：
\begin{minted}{shell}
  DPkg::Pre-Invoke {"mount -t overlay overlay -o lowerdir=/usr,upperdir=/vendor/usr,workdir=/vendor/overlay /usr || /bin/true";};
  DPkg::Post-Invoke {"umount /usr || /bin/true";};
\end{minted}

\texttt{dpkg hook} 需要添加文件 \texttt{/etc/dpkg/dpkg.cfg.d/upgrader-hook} ，内容如下：
\begin{minted}{shell}
  pre-invoke=sh -c "mount -t overlay overlay -o lowerdir=/usr,upperdir=/vendor/usr,workdir=/vendor/overlay /usr || /bin/true"
  post-invoke=sh -c "umount /usr || /bin/true"
\end{minted}

\paragraph{/usr 合并}
\texttt{deb} 在安装完成后，需要将 \texttt{/vendor/usr} 合并到 \texttt{/usr} 中才能让安装的 \texttt{deb} 生效。
合并使用软链的方式，将新安装的 \texttt{deb} 链接到 \texttt{/usr} 中，将卸载的 \texttt{deb} 从 \texttt{/usr} 中删除。

\subsubsection{关键流程}
\paragraph{apt 安装流程}
\begin{center}
  \begin{adjustbox}{scale=0.8}
    \begin{plantuml}
      @startuml
      :开始 apt install deb;
      :apt pre-invoke;
      :mount /usr overlayfs;
      :do install;
      :apt post-invoke;
      :umount /usr overlayfs;
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-1 apt 安装流程
\end{center}

\paragraph{dpkg 安装流程}
\begin{center}
  \begin{adjustbox}{scale=0.8}
    \begin{plantuml}
      @startuml
      :开始 dpkg install deb;
      :dpkg pre-invoke;
      :mount /usr overlayfs;
      :do install;
      :dpkg post-invoke;
      :umount /usr overlayfs;
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-2 dpkg 安装流程
\end{center}

\paragraph{/usr 合并流程}
\begin{center}
  \begin{adjustbox}{scale=0.85}
    \begin{plantuml}
      @startuml
      :开始合并 /usr and /vendor/usr;
      :checkout active version snapshot;
      :开始合并 snapshot and /vendor/usr to /usr-tmp;
      while (遍历 snapshot)
      :硬链接文件到 /usr-tmp;
      endwhile
      while (遍历 /vendor/usr)
      if (文件是存在于 /usr-tmp ?) then (Y)
      if (/usr-tmp 中的文件是否是指向 /vendor/usr 的软链?) then (Y)
      :continue;
      else (N)
      :删除 /usr-tmp 中的文件;
      :创建文件在 /usr-tmp 的软链;
      endif
      else (N)
      :创建文件在 /usr-tmp 的软链;
      endif
      endwhile
      :rename /usr to /usr-bak;
      :rename /usr-tmp to /usr;
      :rm /usr-bak;
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-3 /usr 合并流程
\end{center}

\subsection{dpkg status 差异合并}
此方案利用 \texttt{dpkg status} 文件保存了已安装软件信息的特点，对比当前 \texttt{snapshot} 和当前系统得到变更的软件列表。

合成新的 \texttt{/usr} 时，根据变更的软件列表将这些软件对应的文件从当前 \texttt{/usr} 中硬链接到新 \texttt{/usr} 目录。

\subsubsection{关键技术}
\paragraph{dpkg status 差异生成}
\texttt{dpkg status} 文件记录了已安装软件的信息，有特定的格式，不同的软件以空行进行分割。

生成差异时，首先解析当前的 \texttt{dpkg status} 文件和当前 \texttt{snapshot} 的 \texttt{dpkg status} 文件，然后生成想对于 \texttt{snapshot} 的差异。

接着再将差异内容中软件对应的文件从当前 \texttt{/usr} 目录硬链接到新 \texttt{/usr} 中。

\subsubsection{关键流程}
\paragraph{dpkg status 差异生成流程}
\begin{center}
  \begin{adjustbox}{scale=0.85}
    \begin{plantuml}
      @startuml
      :开始解析 dpkg status 文件;
      :解析当前 /usr 中的文件，标记为 usrList;
      :解析当前 snapshot 中的文件，标记为 snapList;
      while (遍历 usrList)
      if (软件在 snapList 是否存在?) then (Y)
      if (对比两者的信息是否一致?) then (N)
      :continue;
      else (N)
      :将软件加入到差异列表;
      endif
      else (N)
      :将软件加入到差异列表;
      endif
      endwhile
      :返回差异列表，标记为 diffList;
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-4 status 差异生成
\end{center}

\paragraph{差异内容合并流程}
\begin{center}
  \begin{adjustbox}{scale=0.85}
    \begin{plantuml}
      @startuml
      :开始合并差异软件内容;
      while (遍历 diffList)
      :获取软件的安装文件列表
      (/var/lib/dpkg/info/<package>.list);
      while (遍历文件列表)
      if (文件是否存在于新 /usr ?) then (Y)
      :删除存在的文件;
      endif
      :硬链接文件到新的 /usr 目录;
      endwhile
      endwhile
      :合并完成;
      @enduml
    \end{plantuml}
  \end{adjustbox}

  图 2-5 差异内容合入
\end{center}

\section{总结}
\texttt{/vendor overlayfs} 的方案经过测试， \texttt{/usr} 通过 \texttt{overlayfs} 挂载后无法卸载，因为其目录中有文件正在使用。

而 \texttt{dpkg status} 差异合并的方案经过测试，能够满足要求，因此最终采用此方案。

\end{document}
