星期三, 一月 31, 2007

Apache 1.3 to 2.0 VirtualHost

原来的 Apache 1.3,其虚拟主机的设定如下:
NameVirtualHost *:80
"<"VirtualHost *">"
...
但把虚拟主机的那些配置拷贝到 Apache 2.0 中的配置文件之后,启动 Apache 得到报错"No VirtualHost found for NameVirtualHost *:80"。

排错,将所有的 VirtualHost * 替换为 VirtualHost *:80 即可。

星期二, 一月 30, 2007

"tar -T/-X" for my backup design

在我设计的备份方案中,应该根据 backup ID 有若干文件列表,备份程序在执行时应该是从这个列表中查找所有的目录和文件来打包。因为涉及到差分和增量备份的问题,所有不能直接利用这个列表来 tar,而是需要比较时间戳,因此还需要根据这个列表来查找更新的所有文件(可利用 find),并记录到一个新的列表中,随后的 tar 利用这个实际的列表来操作(当然这个过程都是自动完成的,也不一定要调用 find/tar,而可以编写 python 时使用 tarfile 等模块,但直接 wrap 可能比较方便)。

tar 可以使用 -T/-I 参数来读取列表,-X 读取排除列表,但列表生成以后,其中某些文件可能会被删除,例如 web 产生的一些 cache。这时候运行 tar -T 就会报错并退出,而后续的打包就不再进行。

可能需要忽略这一点,用 --ignore-failed-read 可以,问题就是他不会报告哪些被忽略了。我想可以在使用的使用 wrap 这个参数,由用户来进行选择,再传递给 tar 处理。

如果同时使用 -T i-files -X x-files 会怎样呢?
sh$ find `pwd`/lamp01 >i-files
sh$ echo "/tmp/temp/lamp01/profiles" >x-files
sh$ tar -c -z -f lamp01.tgz -T i-files -X x-files /tmp/temp/lamp01
sh$ tar tfz lamp01.tgz | grep profiles | sort
tmp/temp/lamp01/.svn/prop-base/profiles.svn-base
tmp/temp/lamp01/.svn/prop-base/profiles.svn-base
tmp/temp/lamp01/.svn/prop-base/profiles.svn-base
tmp/temp/lamp01/.svn/prop-base/profiles.svn-base
tmp/temp/lamp01/.svn/prop-base/profiles.svn-base
tmp/temp/lamp01/.svn/props/profiles.svn-work
tmp/temp/lamp01/.svn/props/profiles.svn-work
tmp/temp/lamp01/.svn/props/profiles.svn-work
tmp/temp/lamp01/.svn/props/profiles.svn-work
tmp/temp/lamp01/.svn/props/profiles.svn-work
tmp/temp/lamp01/.svn/text-base/profiles.svn-base
tmp/temp/lamp01/.svn/text-base/profiles.svn-base
tmp/temp/lamp01/.svn/text-base/profiles.svn-base
tmp/temp/lamp01/.svn/text-base/profiles.svn-base
tmp/temp/lamp01/.svn/text-base/profiles.svn-base
会发现很多重复的项,而且 tar 归档的大小是普通情况下的 3 倍,这意味着同时使用 -T/-X 选项会有问题。因此如果要排除某个目录,只能将希望放在 find 上了——如果使用 shell script 或在 python 中使用 os.popen() 直接调用这些命令来实现的时候。

例如可以使用如下命令:
find `pwd`/lamp01 -path `pwd`/lamp01/profiles -prune -o -print | grep profiles
这里最好使用绝对路径,在备份的这个脚本设计中也最好使用绝对路径,能够避免很多混乱的情况!可以使用 tar -P 参数。

如果要排除两个目录:
find `pwd`/lamp01 \( -path `pwd`/lamp01/profiles -o -path /tmp/temp/lamp01/confiles \) -prune -o -print | grep profiles
对一个目录也同样可以使用 \(\):
find `pwd`/lamp01 \( -path `pwd`/lamp01/profiles \) -prune -o -print
即便是有不相干的目录加入进来也没有关系
find `pwd`/lamp01 \( -path `pwd`/lamp01/profiles -o -path /etc \) -prune -o -print
不过可以认真考虑一下 backup list 应该以怎样的形式为好?

星期四, 一月 25, 2007

quota 磁盘配额

首先需要修改 /etc/fstab 对指定的文件系统实施磁盘配额:
/dev/VolGroup00/LogVol00 /                      ext3    defaults,usrquota,grpquota       1 1
然后重新挂载分区:
# mount -a -o remount
接着运行:
# quotacheck -cug /
-c,create, -u,user, -g,group
这不一定会成功,你可能会得到下面的错误提示:
quotacheck: Mountpoint (or device) / not found.
quotacheck: Can't find filesystem to check or filesystem not mounted with quota option.
这是因为指定的分区没有在 mount 时施加 usrquota,grpquota 选项,用 mount 或 cat /etc/mtab 应该可以看到:
/dev/mapper/VolGroup00-LogVol00 on / type ext3 (rw,usrquota,grpquota)
否则就只能重启或在 remount 是手工添加这两个参数。

然后打开 quota 功能:
# quotaon /

查看单个用户的 quota:
[root@localhost ~]# quota sysadm
Disk quotas for user sysadm (uid 501): none
[root@localhost ~]# quota magic
Disk quotas for user magic (uid 500):
Filesystem blocks quota limit grace files quota limit grace
/dev/mapper/VolGroup00-LogVol00
301600* 8192 8192 17489 0 0

查看全部的:
[root@localhost ~]# repquota /
*** Report for user quotas on device /dev/mapper/VolGroup00-LogVol00
Block grace time: 7days; Inode grace time: 7days
Block limits File limits
User used soft hard grace used soft hard grace
----------------------------------------------------------------------
root -- 8092660 0 0 181769 0 0
daemon -- 20 0 0 3 0 0
lp -- 8 0 0 1 0 0
rpm -- 36688 0 0 111 0 0
netdump -- 16 0 0 2 0 0
rpcuser -- 8 0 0 1 0 0
smmsp -- 12 0 0 2 0 0
apache -- 52 0 0 10 0 0
squid -- 16 0 0 2 0 0
webalizer -- 32 0 0 4 0 0
xfs -- 0 0 0 1 0 0
ntp -- 16 0 0 2 0 0
magic +- 301600 8192 8192 6days 17489 0 0
sysadm -- 40 0 0 11 0 0
admin -- 40 0 0 11 0 0
test -- 40 0 0 11 0 0

可以使用 edquota 命令来编辑指定用户的磁盘配额,如 edquota magic [/]。Blocks 反映的是空间的大小,一般 1 block = 1K,而 indoe 反映的是对文件数量的限制,可以认为 1 inode = 1 file。

在上面的例子中,magic 用户已经超过了 hard limit,那么当他使用磁盘时,会报错如下:
$ echo "fjiajefi" >test
dm-0: write failed, user block limit reached.


很多时候,我们会需要批量处理大量用户的磁盘配额设置,不可能一个个去做,那么应该使用 edquota 的 -p 选项,例如:
# edquota -p magic admin test
即可将 admin 和 test 用户的配额设置为 magic 用户的模式。

更多的例子:
# edquota -p bob `awk -F: '$3 > 499 {print $1}' /etc/passwd`
# edquota -p ftpd `awk -F: '$1~/^user_.*$/ {print $1}' /etc/passwd`
在这里,user_* 是由程序自动生成的(根据网站页面注册提供的一种服务,并会自动添加系统帐户,来自于一个商业软件),数量是比较大的。

另外可以使用基于命令行的 setquota 命令。具体参见 man 手册吧:)

星期三, 一月 24, 2007

Apache mod_rewrite & .htaccess for mass VirtualHost

我遇到这样的应用,在 httpd.conf 中 Include virtual.conf,而在 virtual.conf 中全部都是以 "<"VirtualHost">" 方式建立的虚拟主机,每一个虚拟主机的配置大体上一致,是由程序根据注册的情况自动添加的。

现在的要求是,针对每一个站点,可能需要定义一些 Rewrite 规则。因为 "<"VirtualHost">" 是由程序自动生成的,所以不可能在每一个 VirtualHost 中手工更改,而这又是一个商业软件,更改代码也不是那么方便。一种安全一点的考虑是在全局来实现,即编写一个全局的 Rewrite 规则,在所有的虚拟主机中都可以使用。

但这种方法实际上是行不通的。回忆一下 VirtualHost 的基础知识,如果定义了一个 VirtualHost,那么定义的很多全局变量都将失效的,而且原来的主机也需要定义成 VirtualHost 才能继续使用。对 Rewrite 也是如此。实验如下:
httpd.conf
NameVirtualHost 192.168.0.98:80
"<"VirtualHost 192.168.0.98:80">"
ServerName www1.test.com
DocumentRoot /var/www/html/dir
RewriteEngine on (1)
RewriteRule "^/index\.html" "/test.html" (2)
"<"/VirtualHost">"

"<"VirtualHost 192.168.0.98:80">"
ServerName www2.test.com
DocumentRoot /var/www/html/dir2
# RewriteEngine on
# RewriteRule "^/index.html" "/test.html"
"<"/VirtualHost">"

"<"VirtualHost 192.168.0.98:80">"
ServerName www.test.com
DocumentRoot /var/www/html/test
"<"/VirtualHost">"

$ cat /var/www/html/dir/index.html
Index of TEST 1
$ cat /var/www/html/dir/test.html
TEST 1
$ cat /var/www/html/dir2/test2.html
TEST 2
$ cat /var/www/html/test/index.html
INDEX at TEST
$ cat /var/www/html/test/test.html

$ cat /etc/hosts
...
192.168.0.98 www1.test.com
192.168.0.98 www2.test.com
192.168.0.98 www.test.com
192.168.0.98 www.example.com
将(1),(2)分别放置于全局和 VirtualHost 内,就可以看到差异。即使在 VirtualHost 之前增加如下的规则也无效:
RewriteEngine on
RewriteCond %{HTTP_HOST} "!^www.test.com$" [NC,OR]
RewriteCond %{SERVER_NAME} "!^www.test.com$" [NC]
RewriteRule "(.*)" "http://www.test.com$1
这是希望把所有不是到 www.test.com 的请求重定向到 www.test.com(记得修改 /etc/hosts 文件如上),则理论上到 www.example.com 的请求应该会转向 www.test.com,如果这样可以做到,那么我只需要增加一个虚拟主机,然后在这里定义所有的 RewriteRule,再 Rewrite 回去也许是可以的。

但实际上这样也仍然是无效的,最后连接到的仍然是和默认的一样:www1.test.com──这是符合 VirtualHost 的基本原理的。所以 VirtualHost “优先级”更高,会覆盖掉全局的 Rewrite 设置。

那么另一个解决办法就是利用 .htaccess 来实现了。所有虚拟主机的 DocumentRoot 均是 /www/users/$virtualhostname,则方法是:
$ cat httpd.conf
"<"Directory "/www/users"">"
AllowOverride FileInfo
Order Allow, Deny
Allow From All
"<"/Directory">"

$ cat /www/users/www.test04.com/.htaccess
RewriteEngine on
RewriteRule "^index\.html" "/index.php"
注意这里的正则表达式是"^index\.html"而不是象全局和 VirtualHost 段中使用的 "^/index\.html",这是因为 .htaccess 是在指定的 Directory 中,所以其使用的是一个类似于相对路径的概念,当然这里是 URL 的一个相对路径。所以"^/index\.html"将得不到任何匹配。而 RewriteRule 部分的正则匹配串则是 URL 部分去除主机名后的那部分路径,并一定以"/"开头──也就是 DocumentRoot

这样就基本上可以实现了,但开放 .htaccess 以后可能会有负载问题,目前来看,可以考虑使用 PAM 来实现对于资源的控制看是否可行?

星期二, 一月 23, 2007

ncftp 几点

ncftp 客户端主体是基于交互式的 ftp browser,同时它也提供的专门的非交互模式的命令 ncftpget, ncftpput 等。

有几个命令比较重要的,了解一下:
get -R 传递一个目录
show 显示变量
set 设置变量,例如 set passive off 设置使用 PASV 模式(对 passive 可以用 passive 命令开关)

另外,设置环境变量 EDITOR:
export EDITOR=/usr/local/bin/vim
则可以在交互命令行上直接调用 vim 编辑远端的文件!

ftp 传输模式和防火墙设置

ftp 总是要用到两个端口,一个指定的默认端口(21)用来接受 ftp 指令和一个数据传输的端口。而 ftp 传输又可分为两种模式,即 PORT 和 PASV(passive 被动模式)。PORT 模式是由服务器端来指定数据传输所使用的端口,一般是20端口,而 PASV 则是由客户端来指定,一般是一个随机的高端端口。

所以为了 ftp 的正常使用,需要正确配置 iptables 防火墙,如果采用 PORT 模式,那么只需要加载了 ip_conntrack 模块,并且有:
iptables -A INPUT/FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
这样的配置即可,因为20端口可以看做一个 RELATED 的状态。

而如果使用 PASV 模式,则需要加载 ip_conntrack_ftp 和 ip_nat_ftp 这两个模块,否则客户端无法使用 passive 模式。

从实际使用的情况来看,如果不添加 ip_conntrack_ftp 和 ip_nat_ftp 模块,即不使用 PASV 模式时,速度比较慢,而且有时候会挂起、断掉。这可能也和 Windows 的客户端的有关,比如 cuteftp,运行时有如下的记录:
COMMAND:> [2007-1-31 10:55:18] PORT 192,168,0,64,11,208
[2007-1-31 10:55:18] 200 PORT command successful. Consider using PASV.

Red Hat kickstart

使用 Red Hat 作为生产系统,则应该利用 kickstart 提供的自动化安装和定制功能来提高装机的效率,并且可以减小企业环境中软件环境的不一致所造成的重复。一个 ks 配置文件如下:
# Kickstart file automatically generated by anaconda.

install
url --url ftp://192.168.0.98/pub/
lang en_US.UTF-8
langsupport --default=en_US.UTF-8 en_US.UTF-8 zh_CN.UTF-8 zh_TW.UTF-8
keyboard us
xconfig --card "ATI Rage XL" --videoram 8128 --hsync 30-61 --vsync 50-120 --resolution 800x600 --depth 16 --startxonboot --defaultdesktop kde
# network --device eth0 --bootproto dhcp
network --device eth0 static --ip 192.168.0.99 --netmask 255.255.255.0 --gateway 192.168.0.1 --nameserver 192.168.0.230 --hostname shopex
network --device eth1 --bootproto dhcp
rootpw --iscrypted $1$ntYDkgk/$AY.RmHTCjLUFJWzHlPYfF1
firewall --enabled --port=22:tcp --port=80:tcp
selinux --disabled
authconfig --enableshadow --enablemd5
timezone Asia/Shanghai
bootloader --location=mbr
# The following is the partition information you requested
# Note that any partitions you deleted are not expressed
# here so unless you clear all partitions first, this is
# not guaranteed to work
clearpart --all --drives=sda
part /boot --fstype ext3 --size=100 --ondisk=sda
part pv.5 --size=0 --grow --ondisk=sda
volgroup VolGroup00 --pesize=32768 pv.5
logvol swap --fstype swap --name=LogVol01 --vgname=VolGroup00 --size=1000 --grow --maxsize=2000
logvol / --fstype ext3 --name=LogVol00 --vgname=VolGroup00 --size=1024 --grow

%packages
@ editors
@ emacs
@ text-internet
@ dialup
@ compat-arch-support
@ chinese-support
@ development-tools
grub
kernel-smp
kernel-smp-devel
kernel-devel
e2fsprogs
lvm2
subversion
ntp
samba-common
samba-client
vsftpd

%post --interpreter /bin/bash
PKGSTORE=192.168.0.98
cd /opt
wget ftp://$PKGSTORE/pub/linux-2.6.14.2.tar.bz2 \
&& tar xfj linux-2.6.14.2.tar.bz2 \
&& cd linux-2.6.14.2 \
&& wget ftp://$PKGSTORE/pub/config-2.6.14.2.SMP -O ./.config \
&& make oldconfig \
&& make \
&& make modules \
&& make modules_install \
&& cp -vbf arch/i386/boot/bzImage /boot/vmlinuz-2.6.14.2.SMP \
&& cp -vf System.map /boot/System.map-2.6.14.2.SMP \
&& cp -vbf .config /boot/config-2.6.14.2.SMP \
&& mkinitrd -v -f /boot/initrd-2.6.14.2.SMP.img 2.6.14.2 \
&& /sbin/depmod -ae -F System.map 2.6.14.2 \
&& echo -e "title Red Hat Linux AS4 (2.6.14.2.SMP)
\troot (hd0,0)
\tkernel /vmlinuz-2.6.14.2.SMP ro root=/dev/VolGroup00/LogVol00
\tinitrd /initrd-2.6.14.2.SMP.img" >>/boot/grub/grub.conf \
&& sed -i "s/default=0/default=2/" /boot/grub/grub.conf
cd ..
sed -i 's/id:5:initdefault:/id:3:initdefault:/' /etc/inittab
wget ftp://$PKGSTORE/pub/trun-off.list
for SERVICE in `cat turn-off.list`
do
/sbin/chkconfig $SERVICE off
done
这样,通过在 %package 部分定制需要的包,在 %post 部分运行需要的命令(这里是给内核升级并关闭一些服务,因为原来的 2.6.9-SMP 内核有问题,不能正常重启),可以勉强达到对软件环境的定制和复用需求。

在这个例子里,kickstart 的配置文件和所有的安装包以及其他辅助文件都放置在 ftp 服务器上:
ftp://192.168.0.98/pub/ks/base-baode_36G.ks
ftp://192.168.0.98/pub/ks/turn-off.list
ftp://192.168.0.98/pub/config-2.6.14.2.SMP
ftp://192.168.0.98/pub/linux-2.6.14.2.tar.bz2
ftp://192.168.0.98/pub/RedHat
注意安装树 ftp://192.168.0.98/pub/RedHat/RPMS 应该包含了所有需要的 rpm 包,可以将4张光盘的所有的 RPMS 下的 rpm 文件都拷贝到这里面,而在 ks 文件中只需指定 url --url ftp://192.168.0.98/pub/。

然后在需要安装的主机上运行安装光盘,在 boot: 下键入:
boot: linux ks=ftp://192.168.0.98/pub/ks/base-baode_36G.ks text
安装程序会询问你使用哪个网络接口,保证网线正确连通了之后,选择正确的接口,以后的东西都会自动完成了。

针对不同的系统制作不同的 ks 文件,可以的得到不同的定制环境。然后正确的作法应该是,利用 DHCP 和 tftp 来实现 PXE 的自动安装,通过 PXE 启动 grub,在菜单中选择需要的安装项目,可以得到更好的自动化实现。

服务器安装完毕之后,有一些内容需要检查一下,最主要的是:
1. 服务器是否能够正确的启动、关闭和重启
2. 服务器的 22 号端口是否能够正常访问,并能够 su - 到 root 帐号

星期日, 一月 21, 2007

一个关于网络基本原理的问题

另外一个问题是,接到同一个物理设备如交换机的两台主机使用不同的 IP 网段,能否 ping 通?

想一想 IP 的基本原理,想想主机是怎么计算路由的。

当系统需要连接到另一台主机时,会首先将目标路由与自己的网络地址进行比较(关于网络地址的计算,参考这里),如果发现不是自己网段的,就会到自己的路由表里寻找路由,一般会是默认网关,

docbook xml (1)

XML 的应用已经非常广泛,现在必须放到我们的学习计划中来。但目前好像没有什么很好的材料,而 XML 本身也是比较复杂的,所以如果单纯的去啃那些理论性的东西,一定会非常的枯燥和令人厌烦,特别是那种文档性质的东西就更是如此(系统化介绍的书籍会好些)。那么正确的方法应该是从一种实际的应用开始,这相对来说应该会简单一些。DocBook 就是这样的一种应用。

不过 DocBook 本身也够理论化的,一大堆概念摆在面前也不是那么好消化的。在看了《DocBook: The Definitive Guide》后,对 XML 和 DocBook 基本上有了点认识(当然对 XML 以前也不是没有一点概念:_),但仍然是有点头大的。所以我想,干脆不管多的,先实际操作起来,边做边学吧──从实践中去领会应该是最好的办法。

首先是安装 DocBook,因为是使用 XML 的 DocBook 而不是 SGML,所以安装 docbook-xml-4.4。所谓安装 docbook-xml,其实基本上就是把一些 DocBook XML 的 DTD 文件拷贝到系统中。DTD(Document Type Definition)实际上就是一些文本文件,用来定义 XML 的有效性的,即 XML 可以包含那些元素,这些元素应该是怎样的顺序和嵌套关系、它们可以有那些子元素等。比如在 DocBook 这个具体的例子中,一个 Book 可以包含 Abstract, Chapter, Section, Paragraph, Bibliography... 这些元素,而象 Foo 这样的东西出现在你书籍的结构中就毫无意义;同时,你不可能把 Chapter 放在 Section 里;也不能把 Bibliography 放在书籍开头而把 Abstract 放在结束的地方,所有这些都由 DTD 定义,当处理程序进行处理时会自动进行检查。通常 DocBook 的 DTD 是 docbook.dtd,XML 为 docbookx.dtd。

我直接使用了 BLFS 上的安装方法:
pkgname = "docbook-xml";
version = "4.4";
user = "docbook-xml";
groups = "";
group = "docbook-xml";
archive = "docbook-xml-4.4.zip";
command = "mkdir docbook-xml";
command = "cd docbook-xml";
command = "unzip ../docbook-xml-4.4.zip";
command = "install -v -d -m755 /usr/share/xml/docbook/xml-dtd-4.4";
command = "install -v -d -m755 /etc/xml";
command = "cp -v -af docbook.cat *.dtd ent/ *.mod /usr/share/xml/docbook/xml-dtd-4.4";
command = "if [ ! -e /etc/xml/docbook ]; then xmlcatalog --noout --create /etc/xml/docbook; fi";
command = "xmlcatalog --noout --add "public" "-//OASIS//DTD DocBook XML V4.4//EN" "http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//DTD DocBook XML CALS Table Model V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/calstblx.dtd" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//DTD XML Exchange Table Model 19990315//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/soextblx.dtd" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ELEMENTS DocBook XML Information Pool V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/dbpoolx.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ELEMENTS DocBook XML Document Hierarchy V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/dbhierx.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ELEMENTS DocBook XML HTML Tables V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/htmltblx.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ENTITIES DocBook XML Notations V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/dbnotnx.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ENTITIES DocBook XML Character Entities V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/dbcentx.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "public" "-//OASIS//ENTITIES DocBook XML Additional General Entities V4.4//EN" "file:///usr/share/xml/docbook/xml-dtd-4.4/dbgenent.mod" /etc/xml/docbook";
command = "xmlcatalog --noout --add "rewriteSystem" "http://www.oasis-open.org/docbook/xml/4.4" "file:///usr/share/xml/docbook/xml-dtd-4.4" /etc/xml/docbook";
command = "xmlcatalog --noout --add "rewriteURI" "http://www.oasis-open.org/docbook/xml/4.4" "file:///usr/share/xml/docbook/xml-dtd-4.4" /etc/xml/docbook";
command = "if [ ! -e /etc/xml/catalog ]; then xmlcatalog --noout --create /etc/xml/catalog; fi";
command = "xmlcatalog --noout --add "delegatePublic" "-//OASIS//ENTITIES DocBook XML" "file:///etc/xml/docbook" /etc/xml/catalog";
command = "xmlcatalog --noout --add "delegatePublic" "-//OASIS//DTD DocBook XML" "file:///etc/xml/docbook" /etc/xml/catalog";
command = "xmlcatalog --noout --add "delegateSystem" "http://www.oasis-open.org/docbook/" "file:///etc/xml/docbook" /etc/xml/catalog";
command = "xmlcatalog --noout --add "delegateURI" "http://www.oasis-open.org/docbook/" "file:///etc/xml/docbook" /etc/xml/catalog";
command = "cd ..";
command = "rm -rf docbook-xml";
time = "20070121 22:12:35 Sun";
这中间有许多 xmlcatalog(属于 libxml2 包),只是为了创建 /etc/xml/catalog 和 /etc/xml/docbook 这两个文件。这两个文件本身也是 XML 文件,只是比较特殊,是专门的 catalog 文件:!DOCTYPE catalog PUBLIC "-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN"。所谓 catalog,实际上是一些映射文件,将 Public Identifier 映射为 System Identifier。

而 Identifier 是 DocBook XML 文档中对于外部文件的引用的说明,即引用了那些外部文档,通常都是 DTD,如 DocBook 本身的 DTD docbookx.dtd(注意区分一下 DocBook 文档和 DocBook 本身!)。Identifier 通常分为 PUBLIC 和 SYSTEM 两种,例如在一个 XML 文档中:
"<"!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.4//EN"
"http://www.oasis-open.org/docbook/xml/4.4/docbookx.dtd"">"
这里是一个文档类型申明,即 DTD,PUBLIC 后面是一个 PUBLIC Identifier,然后是一个 SYSTEM Identifier,这里实际上是一个映射,将 PUBLIC Identifier 映射为 SYSTEM Identifier,所以 System Identifier 并不是指只有系统内部的 DTD!如上,只要是一个 URI 就行,当然必须是实际有效的。另外,XML 只能使用 System Identifier,所以必然会有这样一个映射,或者直接使用 SYSTEM:
"<"!DOCTYPE chapter SYSTEM "/usr/share/sgml/docbook/xml-dtd-4.2-1.0-25/docbookx.dtd"">"
所以编写一个 XML 文档,通常包括如下步骤:
1. 按照 DTD 定义编辑 XML 文档
2. 使用解析器进行解析,需要 Validate 检查
3. 依照样式表 XSL 将 XML 转换成其他格式,如 HTML/TEX 等
4. 针对不同格式使用不同工具检查效果

mplayer -zoom

我在 LFS 下安装的 mplayer,播放视频时电击最大化或拉大窗口,屏幕大小却不变。可以通过使用 -zoom 参数来解决这个问题。

在 Fedora 下没有这个问题,不知道是否是编译的参数的问题?

星期六, 一月 20, 2007

*** 关于 upm 的用户的问题 ***

主要是操作包的用户还是应该和操作数据的用户分开!比如安装 mysql,如果使用了如下的方式:
pkgname = "mysql";
version = "4.1.22";
user = "mysql";
groups = "";
group = "mysql";
archive = "mysql-4.1.22.tar.gz";
command = "tar xfz mysql-4.1.22.tar.gz";
command = "cd mysql-4.1.22";
command = "./configure --without-debug --with-extra-charsets=gb2312 --enable-assembler --without-isam --without-innodb --with-pthread --enable-thread-safe-client --with-client-ldflags=-all-static --with-mysqld-ldflags=-all-static --localstatedir=/data/mysql";
command = "make";
command = "make install";
command = "scripts/mysql_install_db --user=mysql";
command = "cp support-files/mysql.server /etc/rc.d/init.d/mysqld";
command = "chmod 0700 /etc/rc.d/init.d/mysqld";
command = "cd ..";
command = "rm -rf mysql-4.1.22";
command = "cd /usr/local/libexec";
command = "cp mysqld mysqld.backup";
command = "strip mysqld";
time = "20070107 21:22:55 Sun";
那么 /data/mysql 的属主和这个包本身的属主就都是 mysql 这个用户了。这在安装的时候固然是方便了,但在删除的时候就会有问题,因为你基本上只会希望删除包的的程序,而不是它产生的其他数据。

所以类似于 /var 这样的目录不应该出现在初试化目录中;我在 userpack 中也设定了给用户名增加前缀的功能,比如增加"_"前缀(不过没有经过完善的测试,对于测试,我也需要花很多时间好好学习学习──想做的事情很多,能做的事情很少,唉),当然如果分类的问题解决了,那么每个用户名可以自然的增加一个分类名,如 db.mysql,就不会有这个问题了;而对于运行象 mysql_install_db 这样的命令,还是在完善了 sudo 命令的基础上,由 root 身份来执行并指定不同的用户比较好。

星期五, 一月 19, 2007

更改 MSN 联系人列表字体大小

默认安装的 MSN Live Messenger 的联系人列表里中文字体很小,都挤到一起去了,很难看。而且在 MSN 的选项中没有更改的设置,这需要修改系统字体:
桌面 -> "属性" -> "外观" -> "高级" -> "图标"
将字体增大一号。这样整个系统的字体都会增大,而 MSN 里的字体就可以了。

工欲善其事,必先利其器。虽然 Windows 用的很少(在 Linux 下用 Gaim 就好了),但既然工作环境不得不使用 Windows,那还是把它设置的舒心一点吧 :-)

星期四, 一月 18, 2007

bash debugging

使用 set -x 设置 debug 模式,set +x 关闭调试模式。也可以用 bash -x。

find -empty

找到所有的空文件和空目录。

所以找空目录可以:
$ find -type d -empty

文件和目录的最后修改时间

如果更改了一个文件,它自己的最后修改时间会变化,但它的父目录的最后修改时间不变,到它的符号链接的最后修改时间也不会变。

而如果在目录中新增一个文件,则该目录的最后修改时间就会变化!那么如果子目录的最后修改时间发生变化,父目录呢?结果是父目录的最后修改时间仍然不变!

这些特性对增量和差分备份可能会产生影响。故特此记录。

星期三, 一月 17, 2007

diff patch

使用 diff 生成 patch 文件的命令可以这样:
$ diff -Naur old-dir new-dir >dir.patch

如果只是一个文件,这样就可以了:
$ diff -u old-file new-file >file.patch

-N: 在目录的比较中,如果一个文件只存在与一个目录,则另一个目录中视为空文件
-a: 只作为文本
-u: "Use the unified output format", 生成 patch 的主要参数
-r: 递归

"mysqld_multi stop N" truble shooting

前面使用 mysqld_multi 来启停多实例的 mysqld 服务器。现在却发现当用 mysqld_multi --mysqld=mysqld_safe start 01 启动服务以后,却无法用 mysqld_multi stop 01 来停止。

从 --help 所提供的参数来看,有 --mysqladmin 和 --password 参数,所以应该是密码问题,通过增加参数:
$ mysqld_multi --password='********' stop 01
即可正常的停止编号为 01 的 mysqld。

星期一, 一月 15, 2007

DNS 对 MySQL 连接速度的影响

将原来的 LAMP 的 MySQL 和 Apache 分离,数据库迁移到另一台主机后,发现连接和查询速度很慢。仅仅使用数据库连接:
$ mysql -u root -h 192.168.0.2 -p
也是需要很长时间才能通过认证。

可以初步推断可能是 DNS 解析的问题,假设 Apache 所在主机为 192.168.0.1,则编辑 MySQL 主机的 /etc/hosts:
192.168.0.1 192.168.0.1

再次连接,速度就比较快了。页面刷新速度也可以接受了。究其原因,还是 MySQL 在认证时在查询 DNS,将 IP 地址作为一个域名来查询。

MySQL 本身应该有一个变量可以进行控制的。mysqld 的启动参数中有 --skip-name-resolve,"然而,在这种情况下,你只可以使用 MySQL 中的授权表中的 IP" -- 这可能意味着使用主机名授权的表项将不起作用。

http://dev.mysql.com/doc/refman/5.1/zh/optimization.html#dns
http://hackmysql.com/dns

mysqld_multi 运行多实例数据库

之所以使用多实例,一方面是因为资源有限,没有更多的主机资源,而同时为了保证以后迁移和扩展的需要,最好将所有的数据从文件系统层面上分开,并使用不同的端口(port)来进行连接。

使用以前提到的包管理器,其安装 profile 如下:
pkgname = "mysql";
version = "4.1.22";
user = "mysql";
groups = "";
group = "mysql";
archive = "mysql-4.1.22.tar.gz";
command = "tar xfz mysql-4.1.22.tar.gz";
command = "cd mysql-4.1.22";
command = "./configure --without-debug --with-extra-charsets=gb2312 --enable-assembler --without-isam --without-innodb --with-pthread --enable-thread-safe-client --with-client-ldflags=-all-static --with-mysqld-ldflags=-all-static --localstatedir=/data/mysql";
command = "make";
command = "make install";
command = "scripts/mysql_install_db --user=mysql";
command = "cd ..";
command = "rm -rf mysql-4.1.22";
command = "cd /usr/local/libexec";
command = "cp mysqld mysqld.backup";
command = "strip mysqld";
time = "20061231 16:34:51 Sun";
注意需要指定 --localstatedir=/data/mysql,因为这里 /data 是专门用来存放数据的数据盘,在我们的应用中需要分开已方便管理。

然后需要修改配置文件 /etc/my.cnf:
......
# [mysqld]
[mysqld01]
datadir=/data/mysql01
socket=/data/mysql01/mysql.sock
port=3306

skip-locking
key_buffer = 16M
max_allowed_packet = 1M
table_cache = 64
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M

log-bin

[mysqld02]
datadir=/data/mysql02
socket=/data/mysql02/mysql.sock
port=3307

skip-locking
key_buffer = 16M
max_allowed_packet = 1M
table_cache = 64
sort_buffer_size = 512K
net_buffer_length = 8K
read_buffer_size = 256K
read_rnd_buffer_size = 512K
myisam_sort_buffer_size = 8M

log-bin
......
要注释 [mysqld],否则不行;[mysqldX] X 必须为数字。有一些配置重复,但目前来看没有其它办法,不知道怎样让 [mysqld01] 和 [mysqld02] 共享一部分配置?

然后进入 /data,将 mysql 拷贝为 mysql01 和 mysql02
# cd /data
# cp -Rp mysql mysql01
# cp -Rp mysql mysql02

再启动两个实例的数据库:
# mysqld_multi --mysqld=mysqld_safe start 01,02

因为是多实例运行,所以原来的 sysv init 启动脚本 /etc/rc.d/init.d/mysqld(从 mysql.server 拷贝)不能使用,因为它只启动 mysqld_safe 而不会运行 mysqld_multi。可以考虑更改这个脚本,也可以将 mysqld_multi 命令加入 /etc/rc.d/rc.local,以后要关闭数据库时使用 kill 命令或:
# mysqld_multi stop 01
关闭编号为 01 的数据库,并使用
# mysqld_multi start 01
再启动它。

mysql_fix_privilege_tables

将 MySQL 表文件从一台主机拷贝到另一台主机(通常做数据库迁移时需要这样)。源主机的 MySQL 为 3.23.58,目标主机为 4.1.13(3.23.58 不支持 replication,所以无法使用镜像同步来进行迁移)。

数据库表文件拷贝过来之后,原来的 mysql.user 表比 4.1.13 默认的 mysql.user 包要少一下字段,因此少一些对权限的设定,比如 REPLICATION SLAVE 来进行镜像的权限。所以需要对数据库系统表进行升级。可以使用这个脚本:mysql_fix_privilege_tables。
$ mysql_fix_privilege_tables --host=127.0.0.1 --password='******'
$ mysqld_multi stop 01
$ mysqld_multi --mysqld=mysqld_safe start 01
5.5.1. mysql_fix_privilege_tables — Upgrade MySQL System Tables

星期日, 一月 14, 2007

Apache MPM prefork vs. work

MPM: Multi-Processing Module。

主要的区别是是否使用线程。prefork 使用线程而 work 模式不使用。

python MySQLdb simple usage

import MySQLdb
import _mysql_exceptions

......
goaldb = MySQLdb.connect(user=dbuser, passwd=dbpass, host=dbhost, port=dbport, db=dbname)
SQL = "select ..."
goaldb.query(SQL)
rows = goaldb.store_result().fetch_row(maxrows=0)
if rows:
for r in rows:
......
SQL = "update ..."
goaldb.query(SQL)
else:
SQL = "insert ..."
goaldb.query(SQL)

except _mysql_exceptions.ProgrammingError, (errno, errstr):
strerr = "SQL Error: %d, %s" % (errno, errstr)
print strerr >> sys.stderr
except _mysql_exceptions.MySQLError, (errno, errstr):
strerr = "MySQL DB Error: %d, %s" % (errno, errstr)
print strerr >> sys.stderr

google interview: 1st by phone

从原来的公司辞职的时候,其实还没有考虑过再找什么样的工作,只是觉得再那样做下去不会有什么长进了,因为自己产生的很多新的设想都没有办法去实现,工作变成了朝九晚六的坐班,每天就做着那些数据搬运工和系统救火员的 IT 民工事业,提出的想法和意见也没人听,和维护组的那位 Partner 也不太投契,也许有点犯相吧,倒是和其他组的几位比较合得来。

而且象这种游戏公司,做 IT 维护自然会非常了累,加上我这个人对于重复性的劳动本来就没有多少疲劳强度,感觉身体也是一日不如一日。

再者,游戏这种东西,总是难逃圈钱和毒害青少年之嫌,如果可以有别的选择,我想我还是应该做出改变了。

所以,虽然这时候还没有找到下家,但我还是毅然辞职了。因为心里的设想也已经渐渐趋于成熟了。一个是关于 Enterprise Architecture 的架设,另一个是从网上看到一个德国人写的基于用户的包管理方法的原理,觉得可以写成一个软件。于是我打算从这个地方入手,先做点东西出来,可以拿得出手,这样再找工作也有点资本。

于是回家休息了一段时间,然后开始设计基于用户的包管理器。这时候还没有想过要在找什么工作、什么公司。后来覃建议我投到 google,因为那种文化比较适合我的个性吧。

在完成了 0.1.1alpha 版本的时候就回上海了,时间是 11 月了。然后投了简历到 google,以及其他在 51job 上的一些公司。开始文本格式有问题,结果是石沉大海,其他公司也没有消息。无论如何,生计问题不能不解决。后来也投了几次其他公司,跑了两次面试,基本上没什么效果,在面试时也谈到了自己的设想和目标,但似乎不能引起对方的关注。

其实这也很正常。现在想来,是当时在定位上有问题。

后来在 11 月下旬左右又投了一次,这次大概在 12 月中旬收到 google HR 的电话,问了一些诸如大学成绩之类的问题,然后安排的第一次面试,是从美国打过来的越洋电话,因为时差的关系,很早就得爬起来。

这次只是问了一些很基础的问题。例如:

问: IP, HTTP, TCP, 以太网,按协议由低到高排列?
答: 以太网, IP, TCP, HTTP。

问: 子网掩码为 255.255.128.0,问可以容纳多少个 IP 地址?
答: 128 * 256 个。

问: (提示)有没有什么特殊的地址?
答: 是网络地址和广播地址,所以应该是 128 * 256 - 128 * 2 - 2 个。

问: 那么为什么这两个地址不能作为 IP 使用?
答: 广播地址是用来发送广播报文,这样子网中所有的主机都能够接收到;网络地址...
正解: 网络地址可以用来计算路由。

问: traceroute 的工作原理是怎样的?
答: 它首先发送一个报文,设置其 TTL=1,这样在第一个路由器就会返回报文并被 traceroute 记录;然后它设置 TTL=2,一次类推,直到到达目标的所有路由都被记录。

问: 那么它使用的是什么协议?
答: ICMP。

问: 下面是一些 UNIX 的基础问题。请将这些服务和端口号对应起来:
...(省略,比较简单)

问: 如果不知道某个服务的端口号应该怎么办?
答: 查看 /etc/services 文件。

问: DNS 的 TTL 是做什么的?
答: ...(答错,和备份 DNS 的序列号弄混淆了)
正解: 缓存的 Time To Live,即只缓存服务器过多长时间更新一次记录。所以域名解析的变更通常不是立即生效的。

问: id 为 1 的进程(init)是做什么的?(印象中大概是这样的)
答: 是系统中所有进程的父进程,用来 fork 其他进程。

问: 如果父进程中止,那么子进程会怎样?(不记得是否有这个问题,但答案的话是说过的)
答: 子进程由 init 接管。

问: 僵尸进程是怎么回事?
答: 僵尸进程是指父子进程已经中止,而父进程却没有的到其退出状态的进程。

问: 那么为什么要这样设计?
答: 这样至少保证如果一个进程有问题,那么会显示出来。(回答得不是很好,但大体意思答出来了)

问: uptime 的输出是什么意思?
答: 表示1分钟、5分钟和15分钟之内的平均负载。

问: 那么负载是什么意思?
答: 即单位时间内在等待队列中等待执行的进程的数量。

问: 那么进程有几种状态?
答: 运行、等待、睡眠和挂起等。

问: Linux 中做 RAID5 至少需要几块磁盘?
答: 3 块。

问: 如何保证和提高 UNIX 系统的安全性?
答: 首先保证操作系统本身的文件权限和用户的安全性,设置正确的防火墙规则,还有可以添加 tripwire 这样的工具来保证系统的完整性...(回答得不是太好)
补充: 增加良好的认证如 PAM 规则以及 Kerberos,还有 IDS(入侵检测系统)如 snort,并加强监控和日志审查。

可能还有一两个问题记不起来是什么了。

总体上,当时个人感觉还可以。

不过后来回想起来,当时的简历实在是写得太差劲,用语、结构都有问题。用朋友的话说,就你那种简历,竟然还面了几次,简直是奇迹。

一个好的简历是非常关键的,在心理学上称之为定位调整偏见,又称“锚定”,在这里就是给考官的第一印象。基本上每个考官在谈话之前都要看过你的简历的。

所以后来我找了本专门介绍写简历的书,按上面的原则重写之后,效果奇好。不过那时已经失掉 google 的机会了...

know IP/netmask, ask for net address

例如,已知 IP/netmask: 124.74.193.210/255.255.255.224,则网络地址应该是:124.74.193.192。

计算方式如下:
IP: 124.74.193.210 --> 11010010(最后8位)
netmask: 255.255.255.224 --> 0xffffffe0 --> 1110000(最后8位)
在最后8位中,根据掩码取前3位,即 210 中的 110,后面补 0 即得 11000000 --> 192。所以网络地址是 124.74.193.192

ssh forwarding

本地转发:
client$ ssh -L $local_port:server:$server_port forwarder sleep 60
client$ telnet localhost $local_port
例如托管的网络有一台主机拥有外网地址,假设是 123.45.67.89,而另外一台是内网的数据库 192.168.0.3,没有外网地址,并且也没有做防火墙的映射,那么可以通过如下方式来登录内网数据库以进行维护:
$ ssh -L 2022:192.168.0.3:22 123.45.67.89 sleep 60
$ ssh $user@localhost 2022
注意第一个也必须登录,如果第一个登录退出,则第二个也会中断链接。

远程转发:
forwarder$ ssh -R $client_port:server:$server_port client
client$ telnet localhost $client_port
限制和禁止转发:
forwarder:/etc/hosts.deny
ALL:ALL

forwarder:/etc/hosts.allow
sshdfwd-$port: client
/etc/ssh/sshd_config:
AllowTcpForwarding yes/no

这将禁止通过该服务器的所有端口转发,不禁止端口转发尝试的调用。比如,如果禁止 client 上的端口转发,仍然能从 client 上使用 ssh 的 -R 标记建立 forwarder 上的转发端口。

--disable-client-port-forwardings, --disable-client-server-forwarding 编译参数。

星期五, 一月 12, 2007

recompile kernel remotely

有时侯可能不得不远程编译和升级内核,但问题是,如果在启动管理器的配置中设置了新内核为默认选项,而新内核启动失败,那么通常你就只能跑到机房去实地调试了(我不知道机房的管理人员在帮忙重启机器的时候,除了按一按开关之外还能有什么别的帮助?),那么远程编译还有什么意义?

如果本地有和远端一样的主机,那么不妨先在本地进行编译和配置,确保可以启动之后再将内核拷贝到远端或直接在远端编译。在拷贝时注意确保3样东西:
1. kernel
2. Red Hat Linux(如果是)或自己配置的 initrd
3. 所有的内核模块:/lib/modules/$kernel-version

之后最好再运行一下 /sbin/depmod -ae -F System.map-$kernel-version $kernel-version 以确保内核模块都没有问题。

拷贝之后再更改 /boot/grub/grub.conf,添加新的条目。

对于远程的重启操作,关键是 grub 的 grub-set-default 程序和 savedefault & fallback 选项!但目前都不行,不知道为什么?

星期三, 一月 10, 2007

sshd DNS

默认情况下登陆 ssh 时反应很慢,要很长时间才出现 password: 提示。这是因为 sshd 默认会做域名的解析,即将一个 IP 地址作为域名先解析一遍,如果没有成功再作为 IP 使用,而通常是没有的。

要禁止这一点,更改 /etc/ssh/sshd_config:
UseDNS yes --> no

星期五, 一月 05, 2007

SSL 及电子商务在线支付

CLI md5sum for "string"

很多网站数据库的用户表里的密码都是使用的 MD5 值。如果需要比较一个用户的密码设置是否正确,知道明码当然是可以得到 MD5 值的,当然可以编写 PHP 等脚本来生成,但最简单的是使用命令行的 md5sum 命令:
# echo -n "password" | md5sum
注意这里必须使用 -n 选项,否则'\n'被自动加入串,最后的结果就不对了。

同时,MySQL 本身是有一个 MD5() 函数。

但为什么 grub-md5-crypt 每次生成的值都不同呢?

Apache basic configuration for PHP

# Apache Configuration for PHP
LoadModule php4_module modules/libphp4.so
AddType application/x-httpd-php .php
AddType application/x-httpd-php-source .phps
DirectoryIndex index.php
"<"Files *.php">"
SetOutputFilter PHP
SetInputFilter PHP
LimitRequestBody 9524288
"<"/Files">"

星期二, 一月 02, 2007

root sudo?

以 root 身份运行 sudo 命令没有不需要输入密码,这与普通用户不同。所以如果在 crablfs 中对 upm(userpack)使用 sudo来进行进程间通信的方式以 root 身份运行某些命令,在换成 preload(LID_PRELOAD)方式后,不需要进程间通信的方式了,但被记录的 sudo 命令不会产生影响。

这意味着被 upm 使用的 installation profiles,如果使用 preload,也是可以使用的!

google talk for gaim

如何为 Google Talk 配置 Gaim?

# 在添加账户窗口中输入下列信息:

* 协议:Jabber
* 用户名:您的 Google Talk 用户名(不包括 @ 符号或网域)。
* 服务器:gmail.com

* 如果您要使用未链接到任何 Google 电子邮件服务的 Google 帐户登录,请输入 gmail.com
* Google Mail 帐户应输入 googlemail.com
* 对于 Google 应用服务企业版用户,请访问应用服务支持中心获取说明。

* 密码:为增加安全性,建议您将此字段留空,但是如果您想自动登录,请输入您的密码
* 别名:请将此字段留空
* 记住密码:如果希望 Gaim 记住密码,请选中此框(选择此选项后,每次登录时系统将不会提示您输入密码)
* 自动登录:如果想在每次使用 Gaim 时连接到 Google Talk 服务,请选中此框

* Jabber 选项:选中如果可用的话请使用 TLS 并取消选中其他框。
* 端口:5222
* 连接服务器:talk.google.com
* 代理类型:使用全局代理设置

网线制作

主要是线头的顺序为,如果左手握住水晶头,将有弹片的一面朝下,带金属片的一面朝上,线头的插孔朝向右手一侧时,可以看到连接头中的8个引脚:
橙白、橙、绿白、蓝、蓝白、绿、棕白、棕

用压线钳剥线时只需要先转一圈,割出裂口,然后用手剥除,不要用钳直接剥除外皮,因为可能切坏里面的线路。排好顺序之后,剪除多余的线,保留大约一厘米多一点的样子,插入水晶头,用压线钳压紧,再用仪器测试,所有的8个 LED 都亮过即可。

双机直接,网卡互联的双绞线和普通的网卡连接集线器的网线不同,它需要进行错线(双绞线),进行错线后,一边是:橙白、橙、绿白、蓝、蓝白、绿、棕白、棕;一边是:绿白、绿、橙白、蓝、蓝白、橙、棕白、棕。这时候测试仪的 LED 灯两边的顺序会不同,只要保证两边的8个灯都亮过就可以了。

有时侯可能会出现接触不好的情况,导致时效,这时可以先尝试用压线钳再压紧一下,通常都会有效,而不必报废一个水晶头。

http://www.pcbookcn.com/article/1917.htm