\documentclass[a4paper,12pt,titlepage]{article}
\usepackage{CJKutf8}
\usepackage[hmargin=2cm,vmargin=1.5cm,includeheadfoot]{geometry}
\usepackage{fancyhdr}
\usepackage{verbatim}
\usepackage{color}
\usepackage{framed}
\usepackage{titlesec}
\setcounter{secnumdepth}{5}
\setcounter{tocdepth}{3}
\usepackage{indentfirst}
\usepackage{parskip}
\usepackage[unicode,colorlinks=false,pdfborder={0 0 0}, bookmarksnumbered=true,bookmarksopen=true,bookmarksopenlevel=3]{hyperref}

\usepackage[11pt]{moresize} % 字体
\usepackage[T1]{fontenc}

\usepackage{fancybox} % 支持文本加框

\usepackage{array} % 制表环境
\usepackage{longtable} % 长表格环境
\usepackage{multirow} % 纵向合并单元格

\usepackage{graphics} % 插图
\usepackage{graphicx}
\usepackage{here}

\usepackage{amsmath}
\usepackage{layout}
\usepackage{textcomp}
\usepackage{colortbl}
\usepackage{titlesec,titletoc}
\usepackage{layout}

\usepackage{tikz}

\renewcommand{\baselinestretch}{1.2} % 行距

\definecolor{shadecolor}{gray}{0.90}


\begin{document}
\begin{CJK*}{UTF8}{gbsn}
\CJKcaption{UTF8}
\CJKtilde

\pdfbookmark[-1]{“新”安装程序说明}{title}

\title{“新”安装程序说明}
\date{}
\author{李志}
\maketitle

\pdfbookmark[0]{目\quad录}{tableofcontents}

\tableofcontents % 目录
\listoftables
\listoffigures
\clearpage
\CJKindent % 首行缩进

\section{简介}
安装程序是完全由我们公司研发部开发的。这项工作是研发部花费时间比较长的一项工作。在2008年之前，安装程序就已经存在于我们的4.1版的安装光盘上了。在2008年9月到2009年2月，随着4.2版的开发，安装程序也在开发。这阶段工作主要是修改原来4.1版的bug，并且增加了一些功能：软件包分组、软件包可选安装、服务可选启动、支持制作raid磁盘分区等。从2009年2月到2009年8月，以春节为界，安装程序的工作主要是增加视窗界面功能（以前的安装程序界面是基于curses的，使用的工具是dialog）。自2009年8月到2010年6月，安装程序没有重大变化。

现在所提的“新”安装程序指的是从2010年6月到现在（2010年10月）这段时间做的工作。为什么在安装程序停止主要开发近一年之后要开发所谓“新”的安装程序呢？起因在2010年1月开始的安腾（Itanium）和PowerPC移植工作。2010年1月，电网采购招标，要求投标的操作系统可以在安腾（Itanium）和PowerPC平台上运行。于是我开始了操作系统移植工作。在工作中，对发现安装程序很难改动，这是其一。其二，我们的操作系统在惠普平台上很难被安装，安装程序是问题根源。其三，有很多安装程序的缺陷很难改彻底。深入检查后，发现安装程序有以下问题：

\begin{itemize}
\item 没有层次。

界面逻辑和安装逻辑混杂在一起。
\item 在不同界面中使用不同的逻辑。

最初的安装程序没有将界面显示相关的逻辑和安装操作的逻辑分开。在做视窗界面时，相关程序员延续了这一做法。为了实现两个界面，他简单的将大量原先的代码复制到一个新的为视窗界面专用的目录之中。在随后长时间的修改缺陷和开发过程中，出现了一种可笑的现象，某个缺陷在“字符界面”不出现，在“视窗界面”出现；或者相反。
\item 没有集中存储数据。

安装程序的每一个步骤都存储自己的数据到一个自己的文件。文件名不统一，文件格式也不统一。
\item 程序模块间关系复杂而混乱。

每一个安装步骤是一个脚本文件，执行的时候就是一个进程。按照执行的时间顺序，它们就是父子进程关系。但是当安装返回上一步时，原有进程并没有消失，而是产生了一个新的进程。
\item 代码罗嗦。
\item 不必要的复杂。
\item 对操作对象表达不清晰。

很难理解为什么安装程序能操作设备/dev/sda，但是不能操作/dev/cciss/xxxx。
\end{itemize}

所以从2010年6月起，我开始了安装程序的重构工作。称之为“新”是因为它从架构上根本改变了旧安装程序。
\section{设计思想}
“新”安装程序设计的根本是两个理念：分层和以数据为中心。代码可以分成三层：界面层、数据层、和操作层。界面层负责界面的显示。数据层负责安装操作信息的存储。操作层负责执行安装操作。运行逻辑是界面层显示界面给用户，并根据用户输入存储信息到数据层，操作层根据数据层记录的信息执行具体的安装操作。由于有了数据层，模块间关系变得清晰，界面层只负责将数据写入数据层，至于之后这些数据会被如何使用，它并不知道；操作层根据数据层记录的信息执行操作，至于数据如何产生对它并无影响。这就给调试和开发带来了方便。

同样基于以数据为中心，在界面层我也将数据和逻辑分开。我用一个数据文件记录界面信息，比如窗口有几个按钮，它们又是如何摆放的，它们的回调函数是什么。我再在其它文件中实现窗口处理的一般逻辑，比如如何显示按钮，和那些回调函数。

\section{模块划分}
首先所有的python文件都以“ri”作为前缀，代表“Rocky install”。
\begin{itemize}
\item ri\_cmd.py: 包含三个一般性的函数：quit、 previous、 和next。
\item ri\_data.py: 包含一系列数据结构，以python class定义，这些数据结构与install.xml中的数据一一对应。
\item ri\_dep.py:  用来处理软件包依赖关系。调用者依次调用~“construct\_depending()”，~“resolve\_recursive\_depending()”，~“construct\_depended()”，~“get\_extra\_depending(pkg\_list)”~就可以得到因依赖关系而引出的额外软件包。
\item ri\_func\_dep.py: 用来检查额外的功能依赖。比如某分区设置为xfs，需要xfsprogs软件包。
\item ri\_inst\_cli.py: 为安装模块（ri\_install.py）提供cli(Command Line Interface)形式接口。
\item ri\_inst\_tk.py: 为安装模块（ri\_install.py）提供视窗接口（通过调用Tk函数实现）。
\item ri\_install.py: 安装模块，执行安装操作的主控程序。
\item ri\_main.py: 主控模块。
\item ri\_oper.py: 包含一系列python class用以记录安装操作，这些安装操作的执行是通过启动子进程（调用相对应的shell脚本）并以管道与子进程交互。ri\_oper.py被ri\_install.py调用，ri\_oper.py调用ri\_install\_tk.py或ri\_install\_cli.py。
\item ri\_tk.py: 处理与tk有关的操作。
\item ri\_tk\_cmd.py: 定义interface.xml中使用的函数。命名为ri\_tk\_cmd.py是因为这些函数在interface.xml中使用时并没有限制必须用tk实现。将来也许我可以定义一个模块ri\_ncurses\_cmd.py用来实现在ncurses上的界面，定义一个ri\_gtk\_cmd.py来实现在gtk上的界面，或者更多。
\item ri\_widget.py: 包含一系列数据结构，以python class定义，这些数据结构与interface.xml中的数据一一对应。
\end{itemize}
模块间关系如图\ref{f1}：



\section{数据格式说明}
数据格式是xml。xml的模板定义语言是RelaxNG。数据文件包括：
\begin{itemize}
\item interface.xml: 定义界面
\item interface\_ng.xml: interface.xml的模板定义
\item install.xml: 定义安装数据
\item install\_ng.xml: install.xml的模板定义
\end{itemize}


\subsection{界面数据格式}
下面我来介绍一下界面数据的格式。interface.xml的语法是由interface\_ng.xml定义的，以下详细描述interface\_ng.xml中各个数据域的含义：

\begin{shaded}
\begin{tabular}{p{2.5cm}p{3cm}p{3cm}}
元素 & 属性 & 子元素 \\
interface  & base\_widget  & widget+ \\
& sequence & message\_box+ \\
& & top\_window* \\
& & text* \\
& & sequence+
\end{tabular}
\end{shaded}

\begin{itemize}
 \item base\_widget：基础控件，在安装过程中的大部分时间里都被显示。每个安装步骤只是在它上面显示与安装步骤对应的控件。举例，图xxx
 \item sequence：安装序列，控制安装界面的顺序。
\end{itemize}

\subsubsection{widget}
\begin{shaded}
\begin{tabular}{p{2.5cm}p{4cm}p{4cm}}
widget & type & geometry\_management？ \\
& name？ & geometry\_location？ \\
& construct？ & variable* \\
& widget\_attribute？ & action？ \\
& & binding* \\
& & widget*
\end{tabular}
\end{shaded}

\begin{itemize}
 \item type：记录widget的类型：Button，List，Text......
 \item name：名字用于将来索引此widget。
 \item construct：记录一个函数，在ri\_widget.py构造此widget时使用，请参考xxx
 \item widget\_attribute：Tk widget的属性，程序在显示tk控件的时候会把这里记录的数据直接传递给tk控件的构造函数。
\end{itemize}

\vspace{.5cm}
\begin{enumerate}
 \item geometry\_management是一个choice，目前只有一个选择：grid\_management。geometry management意思是位置管理在Tk中有三种，在我的程序中只用到了一个。

\begin{shaded}
\begin{tabular}{p{4cm}p{2cm}p{3cm}}
grid\_management & rows & configure+ \\
& & columns
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}}
configure & row \\
& column \\
& weight
\end{tabular}
\end{shaded}

row和column二选一。
\vspace{.5cm}
 \item geometry\_location也是一个choice，目前只有一个选择：grid\_location。

\begin{shaded}
\begin{tabular}{p{4cm}p{2cm}}
grid\_location & row \\
& column \\
& columnspan？ \\
& rowspan？
\end{tabular}
\end{shaded}

全部是传给Tk相关函数的参数。
\vspace{.5cm}
 \item variable

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
variable & name \\
& type \\
& value？
\end{tabular}
\end{shaded}

用来定义Tk中的变量，在程序中只用到了StringVar。
\vspace{.5cm}
 \item action

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
action & init？ & scroll* \\
& quit？ & 
\end{tabular}
\end{shaded}

\begin{itemize}
 \item init声明在widget显示（创建）时要额外执行的函数。
 \item quit声明在widget隐藏（销毁）时要额外执行的函数。
\end{itemize}

\vspace{.5cm}
scroll与滚动条有关，Tk对滚动条的设计和其它窗口系统不同，滚动条是一个单独的widget需要和相关widget进行关联。

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
scroll & scrolling \\
& scrolled
\end{tabular}
\end{shaded}

scrolling是一个滚动条，scrolled是被滚动的widget，比如一个Listbox。
\vspace{.5cm}
 \item binding声明与某一个窗口事件（sequence）关联的函数。

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
binding & sequence \\
& function
\end{tabular}
\end{shaded}
\end{enumerate}

\subsubsection{message\_box}
\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}}
message\_box & name \\
& type \\
& title \\
& message \\
\end{tabular}
\end{shaded}

type是以下值，它们正是Tk显示不同message\_box的函数：askokcancel, askquestion, askretrycancel, askyesno, showerror, showinfo, showwarning。

\subsubsection{top\_window}
\begin{shaded}
\begin{tabular}{p{3cm}p{4cm}p{4cm}}
top\_window & name & geometry\_management？ \\
& widget\_attribute？ & widget* \\
& construct？ & 
\end{tabular}
\end{shaded}

top\_window的定义是widget的子集。我去除了top\_window用不到的属性和子元素。关于top\_window，我们可以简单地把它理解为弹出窗口，而message\_box是一些特殊的top\_window。

\subsubsection{text}
\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
text & key？ & English \\
& & Chinese
\end{tabular}
\end{shaded}

我定义text元素是为了解决中英文（并且可以扩展到更多语言）问题。key是可选项，当它不存在时English元素内容起Key的作用。在程序和Interface.xml中我还有一个约定，key中存储的内容以‘\#’开头。

\subsubsection{sequence}
\begin{shaded}
\begin{tabular}{p{2.5cm}p{2cm}p{2cm}}
sequence & name & widget+
\end{tabular}
\end{shaded}

注意这里的sequence与binding中的sequence意义不同，那里是直接使用Tk中的词汇，这里的指的是安装序列。

name的引入是最初设计的一个想法，安装程序支持多个序列。虽然最终安装程序只用到了一个序列，但是这个设计被保留下来。

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
widget & name
\end{tabular}
\end{shaded}

此widget亦非彼widget。这里只是为了用到它唯一的属性：name。

\subsection{安装数据格式}
\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{3cm}}
install & none & serial-number \\
& & partitions \\
& & raids \\
& & mount-points \\
& & network \\
& & groups \\
& & services
\end{tabular}
\end{shaded}

\subsubsection{serial-number}
\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}p{2cm}}
serial-number & none & 文本
\end{tabular}
\end{shaded}

\subsubsection{partitions}
\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}p{3cm}}
partitions & unit & partition+ \\
& label & 
\end{tabular}
\end{shaded}

\begin{itemize}
 \item unit是磁盘大小的单位：sector, MB, GB。
 \item label是一个choice，目前两个选择：msdos，gpt。这里的设计有问题，label规定了所有partition，但是有可能一块硬盘是msdos，另一块是gpt。解决办法应该在partions和~partition之间增加一层。
\end{itemize}

\begin{shaded}
\begin{tabular}{p{3cm}p{3cm}}
partition & device \\
& start \\
& size \\
& from\_os \\
& file-system \\
& flags \\
& type
\end{tabular}
\end{shaded}

from\_os用以标识这个partition元素是由安装程序制造的还是原先就存在的。现在安装程序还没有制作分区的能力，所以from\_os总是‘yes’。

其余几个属性与parted显示的数据列一一对应。

\subsubsection{raids}
\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
raids & none & raid
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
raid & device \\
& level \\
& from\_os \\
& active \\
& spare？
\end{tabular}
\end{shaded}

\begin{itemize}
 \item device是raid设备名，如md0。
 \item level是raid级别，目前只支持：0，1，5。
 \item from\_os用来标识此元素是安装程序制作的还是原来就存在的。
 \item active标识活动的raid部件设备名。
 \item spare标识空闲的raid部件设备名。
\end{itemize}

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{3cm}}
active & none & component+ \\
spare & none & component+
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}p{2cm}}
component & none & 文本
\end{tabular}
\end{shaded}

\subsubsection{mount-points}
\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}p{3cm}}
mount-points & none & mount-point+
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{3cm}p{3cm}}
mount-point & device \\
& directory \\
& file-system \\
& format \\
\end{tabular}
\end{shaded}

\begin{itemize}
 \item device： 设备名
 \item directory：目录名（挂载点）
 \item file-system：文件系统
 \item format：是否格式化
\end{itemize}

\subsubsection{network}
\begin{shaded}
\begin{tabular}{p{2cm}p{3cm}}
network & hostname \\
& configuration \\
& ip? \\
& mask? \\
& gateway? \\
& primary\_dns? \\
& secondary\_dns? \\
& domain?
\end{tabular}
\end{shaded}

configuration是一个choice，两个值：dynamic,static。

\subsubsection{groups}
\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
groups & none & group+
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{3cm}}
group & name & description? \\
& install & mandatory? \\
& & optional?
\end{tabular}
\end{shaded}

install是一个choice：all, yes, no

\vspace{.5cm}
\begin{enumerate}
 \item description

\begin{shaded}
\begin{tabular}{p{3cm}p{2cm}p{2cm}}
description & none & 文本
\end{tabular}
\end{shaded}
 \item mandatory

\begin{shaded}
mandatory = package+
\end{shaded}
 \item optional

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
optional & selection & package+
\end{tabular}
\end{shaded}

selection是一个choice：all, none, manual

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
package & name \\
& install?
\end{tabular}
\end{shaded}

install是一个choice：yes, no。
\end{enumerate}

\subsubsection{services}
\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}p{2cm}}
services & none & service*
\end{tabular}
\end{shaded}

\begin{shaded}
\begin{tabular}{p{2cm}p{2cm}}
service & name \\
& number \\
& script \\
& start \\
& package
\end{tabular}
\end{shaded}

start是一个choice：disable, yes, no。


\section{代码逻辑说明}

\subsection{界面相关逻辑}

\begin{figure}[H]
\centering\fontsize{8.5pt}{\baselineskip}\selectfont\input{Diagram1}
\caption{界面逻辑图}\label{f1}
\end{figure}


\subsection{安装相关逻辑}
\subsubsection{格式化分区}
swap，linux(ext3、ext2、jfs、xfs、reiserfs)，fat

\verb=format_partition.sh=
\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{enumerate}
 \item partition：分区
 \item filesystem：文件系统类型
\end{enumerate}
\end{itemize}
 \item 输出：
\begin{itemize}
 \item success:

\verb=@ format $devname success=
\end{itemize}
 \item 返回值：
\begin{description}
 \item[1] argument error
 \item[2] deivce node doesn't exist
 \item[3] filesystem no implement
 \item[127] format partition utils not found
\end{description}
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "/dev/sda1 ext2" | ./format_partition.sh=
 \item 批量处理：文件格式和单个处理一致
\end{itemize}
 \item 说明：需要处理swap分区。
\end{itemize}

\subsubsection{挂载分区}
\verb=mount_partition.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{enumerate}
 \item partition：块设备
 \item mountpoint：挂载点
 \item filesystem：文件系统类型
 \item filsystem options：文件系统选项（这个为可选参数，不传这个参数会有默认的选项：文件系统是ext2|ext3|reiserfs，会以acl的选项挂载，其他则为defaults）
\end{enumerate}
\end{itemize}
 \item 输出：
\begin{itemize}
 \item success:	

\verb=@ mount $devname success=
\end{itemize}
 \item 返回值：
\begin{description}
 \item[1] argument error
 \item[2] device node doesn't exist
 \item[32] mount failure
\end{description}
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "/dev/sda1 / ext3" |./mount_partition.sh=
 \item 批量处理：文件格式和单个处理一致
\end{itemize}
 \item 说明：不处理swap分区。
\end{itemize}

\subsubsection{安装软件包}
\verb=install_pkg.sh=
\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{itemize}
 \item pkgname
\end{itemize}
\item argument:
\begin{itemize}
 \item \verb=[--help]     =\qquad show usage
 \item \verb=[-s|--source]=\qquad package source directory, if not specify, default is /Rocky/packages.
\end{itemize}
\end{itemize}
 \item 输出：
\begin{itemize}
 \item sucess:

\verb=@ install $pkgname success.=
\end{itemize}
 \item 返回值：
\begin{description}
 \item[1] argument error
 \item[2] source directory \$PKG\_SOURCE\_DIR doesn't exist
 \item[3] pkgname doesn't exist in source directory
\end{description}
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "acl" |./install_pkg.sh -s /Rocky/packages=
 \item 批量处理：文件格式和单个处理一致
\end{itemize}
\item 说明：
\begin{enumerate}
 \item 由安装程序生成/var/lib/pkg/db的空文件；
 \item 默认用/Rocky/packages下面的软件包，如果不指定-s参数。
\end{enumerate}
\end{itemize}

\subsubsection{配置fstab}
\verb=configure_fstab.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{enumerate}
 \item block devices：块设备
 \item mount point：挂载点
 \item filesystem type：文件系统类型

以下参数为可选参数，如果不指定，会有默认的配置，如果指定5)那么4）也需要指定，依次类推
 \item fs\_mntops： ext2|ext3|reiserfs的默认配置是acl，其他都是defaults。
 \item fs\_freq：默认配置：swap是0，其他都是1
 \item fs\_passno：默认配置：swap是0，其他都是1
\end{enumerate}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item [1] argument error
 \item [2] fstab doesn't exist
\end{description}
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "/dev/hda2 /home ext3"|./configure_fstab.sh=
 \item 批量处理：文件格式和单个处理一致
\end{itemize}
 \item 说明：
\begin{enumerate}
 \item 需要处理swap;
 \item 块设备：如果是swap则转成by-id，其他都转成by-uuid，这个是因为udev-117的udevinfo 不能查出查出raid的by-id，也不能查出swap的uuid。
\end{enumerate}
\end{itemize}

\subsubsection{生成issue文件}
\verb=generate_issue.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item argument
\begin{itemize}
 \item \verb=[--help]      =\qquad show usage
 \item \verb=[-v|--version]=\qquad version of OS
 \item \verb=[-a|--arch]   =\qquad architecture of OS
 \item \verb=[-r|--release]=\qquad release of OS(base or security)
 \item \verb=[-d|--date]   =\qquad date of generate OS
\end{itemize}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item [1] argument error
\end{description}
 \item 说明：
\begin{enumerate}
 \item 生成\verb=$TARGET/etc/issue=文件，一般\verb+TARGET=/mnt+；
 \item 可以只给一个参数，但是这样其他参数则为空值。
\end{enumerate}
\end{itemize}

\subsubsection{配置网络}
\verb=configure_network.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item argument：

\verb=<-t|--type> "static" or "dynamic"=
\begin{itemize}
 \item static argument:
\begin{itemize}
 \item \verb=<-h|--hostname>  =\qquad hostname
 \item \verb=<-i|--ip>        =\qquad ip address
 \item \verb=<-n|--netmask>   =\qquad netmask
 \item \verb=<-g|--gateway>   =\qquad gateway
 \item \verb=<-d|--domainname>=\qquad domainname
 \item \verb=[-p|--pdns]      =\qquad priamaryDNS
 \item \verb=[-s|--sdns]      =\qquad secondaryDNS
 \item\verb= [-e|--device]    =\qquad network device. if no configure, default is eth0.
\end{itemize}
 \item dynamic argument:
\begin{itemize}
 \item \verb=<-h|--hostname>  =\qquad hostname
 \item \verb=[-e|--device]    =\qquad network device. if no configure, default is eth0.
\end{itemize}
\end{itemize}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item [1] argument error
 \item [2] ip/netmask/gateway address incorrect
 \item [3] hosts/resolv.conf/ifcfg-eth0/network doesn't exist
\end{description}
 \item Usage：

\begin{verbatim}
./configure_network.sh -t static -h QinBo -d in.linx -i 192.168.1.110 
-n 255.255.255.0 -g 192.168.1.254 -p 172.16.0.254
\end{verbatim}

 \item 说明：
\begin{enumerate}
 \item type必须为static或者dynamic；
 \item 不管哪种type，hostname是必须的；
 \item type=static的时候IP、域名、子网掩码、网关是必须的；
 \item device：多网卡的时候，可以指定用哪个网卡，默认用的是eth0。
\end{enumerate}
\end{itemize}
 
\subsubsection{做自启服务}
\verb=mk_serv_autoboot.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{enumerate}
 \item package name：软件包名字（自启动服务所在软件包）
 \item boot script：启动脚本
 \item boot service：需要启动的服务
 \item boot number：启动号
\end{enumerate}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item[1] argument error
 \item[2] boot script  doesn't exist
\end{description}
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "netkit-base inetd rsh S310"|./mk_serv_autoboot.sh=
 \item 批量处理：文件格式和单个处理一致
\end{itemize}
 \item 说明：默认在\verb=/etc/rc.d/{rc3.d,rc5.d}=建立链接。
\end{itemize}


\subsubsection{拷贝kernel、modules和生成initrd}
\verb=copy_kernels.sh=

\begin{itemize}
 \item 输入：无
 \item 输出：无
 \item 返回值：
\begin{description}
 \item[1] kernel directory/modules directory/initrd.gz/makeinitrd doesn't exist
\end{description}
 \item 说明：
\begin{enumerate}
 \item 此脚本从/Rocky/kernels和/Rocky/modules目录下拷贝kernel和modules；
 \item 把/Rocky/initrd/initrd.gz，拷贝到硬盘上的/boot/initrd.gz.old；
 \item 生成initrd调用原来的makeinitrd脚本生成。
\end{enumerate}
\end{itemize}

\subsubsection{配置bootloader配置文件}
\verb=configure_bootloader_cnf.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item arguments
\begin{itemize}
 \item \verb=[--help]         =\qquad show this message
 \item \verb=<-t|--type>      =\qquad boot loader type:grub,etc.
 \item \verb=<-r|--root>      =\qquad partition mount by root
 \item \verb=[-b|--boot]      =\qquad partition mount by boot (optional,if the boot is not mount by stand alone)
 \item \verb=[-k|--kparameter]=\qquad kernel parameter
 \item \verb=[-o|--osversion] =\qquad OS version
\end{itemize}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item [1] argument error
 \item [2] bootloader type doesn't specify
 \item [3] bootloader no implement
 \item [4] bootloader configuration file doesn't exist
\end{description}
 \item 说明：对grub软件包里面的grub.conf.sample(\$TARGET/boot/grub/)进行相应的修改，产生新的文件：\$TARGET/boot/grub/menu.lst。
\end{itemize}

\subsubsection{安装bootloader}
\verb=install_bootloader.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item arguments
\begin{itemize}
 \item \verb=[--help]   =\qquad show this message
 \item \verb=<-t|--type>=\qquad boot loader type:grub,etc.
\end{itemize}
\end{itemize}
 \item 输出：无
 \item 返回值：
\begin{description}
 \item[1] argument error
 \item[2] bootloader type doesn't specify
 \item[3] bootloader no implement
 \item[127] bootloader utils not found
\end{description}
 \item 说明：此脚本把grub装在第一块硬盘上面，因为bios默认会从第一块硬盘启动，这样bios就能找到我们的装上去的gurb，从而正确引导系统。
\end{itemize}

\subsubsection{安装完成执行的脚本}
\verb=exec_finish_install.sh=

\begin{itemize}
 \item 输入：
\begin{itemize}
 \item stdin
\begin{itemize}
 \item 脚本的名字
\end{itemize}
\end{itemize}
 \item 输出：无
 \item 返回值：根据具体执行脚本的返回值来确定，0是成功。
 \item Usage：
\begin{itemize}
 \item 单个处理：\verb=echo "99finish_install.sh"| ./exec_finish_install.sh=
 \item 批量处理：文件格式和单个处理一致	
\end{itemize}
 \item 说明：此脚本把99finish\_install.sh拷贝到/var/finish\_install目录下，然后根据 /var/finish\_install 目录下面的脚本名字前面的数字顺序，从小到大依次拷贝到硬盘系统里面执行。

99finish\_install.sh 即原来的firstboot.sh。
\end{itemize}

\subsection{接口逻辑}
\subsubsection{界面层和操作层}
界面层和操作层在进程关系上是两个进程，界面层进程负责根据用户输入写入配置文件install.xml。操作层读入install.xml，根据其中信息执行相应操作。

\subsubsection{操作层：控制程序、操作、和界面}
操作层有一个总控程序：ri\_install.py；操作程序：ri\_oper.py及大量shell scripts；以及界面程序：ri\_install\_tk.py和ri\_install\_cli.py。它们的关系如图\ref{f2}：

\begin{figure}[H]
\centering\fontsize{8.5pt}{\baselineskip}\selectfont\input{Diagram2}
\caption{接口逻辑图}\label{f2}
\end{figure}


\section{特殊情况处理}

\subsection{软件包显示界面}
有以下几个特殊之处需要处理：
\begin{itemize}
\item 软件组个数不固定，无法在interface.xml中声明
\item 显示每个软件组信息的top\_window，如何激活
\end{itemize}
为了解决第一个问题，在interface.xml和interface\_ng.xml中为widget和top\_window增加了一个属性：construct，记录一个函数名。在ri\_widget.py对widget或top\_window构造时会调用这个函数（如果有construct属性的话），在这个函数中，会对widget的结构（属性、子widget等）进行添加、修改、和删除操作。
为了解决第二个问题，首先在ri\_tk.py中定义了一个class:SoftwarePackageWindow，我认为也可以通过interface.xml定义再加上一些辅助函数，也可以达到同样效果；其次在ri\_tk\_cmd.py中，我定义了class GroupButton和GroupCheck，它们都定义了\_\_call\_\_()，于是它们的实例可以作为函数使用。第三，在construct属性所指向的“构造”函数中，我为每个软件组建立了一个Button或者CheckButton，它们的回调函数就是GroupButton或者GroupCheck的实例。
第二个问题的难点在于，每个软件组需要显示不同的必选软件包和可选软件包，显示逻辑本身又是一致的。比较容易想到的是，定义一个函数，函数有一个输入参数。但是Button的回调函数又要求是没有参数的，所以引入了“函数对象”。

\subsection{安装界面}
安装界面被设计成一个单独的进程，在interface.xml中没有它的定义。实际安装操作在大量的shell scripts中实现，这些shell scripts每一个都是独立的程序，互相没有调用关系。ri\_oper.py为每一个shell script定义一个class，这些class负责调用shell script并提供调用参数，还要通过管道提供输入，读取输出。安装界面有一个特殊之处，它提供了两种界面：tk和cli。我把涉及界面显示部分一一抽出来做成函数，比如显示目前安装步骤，显示安装进行了百分之多少......在ri\_install\_tk.py和ri\_install\_cli.py中都定义了这些的函数。最后，ri\_install.py根据输入参数，决定语言环境和显示界面，为众多ri\_oper实例赋予安装权重，调用ri\_oper实例的install函数。

\subsection{RAID~操作}
mkraid.sh与其它脚本在调用参数和标准输入上有些差别，为了降低程序复杂度，专为它提供了mkraid\_wrapper.sh。目的是规范成与其它脚本相同格式的调用参数、标准输入、和标准输出。

\section{没有做的事}
\section{安装程序使用}
\subsection{几种“非主流”安装方法}
\subsubsection{远程安装}
\subsubsection{利用配置好的xml安装}
\subsubsection{纯手工安装}
\subsection{安装过程中易碰到的问题}
\appendix
\section{Python \& TK}

\section{Xml \& RelaxNG}



\clearpage
\end{CJK*}
\end{document}
