2008年04月26日

    这个想法很早就有了,只是一直没有动手去做。我是喜欢想清楚再动手的,如果没有想清楚,宁可不动手。我们团队的ftp原来的用户密码简直就是不设防的,而且使用的是mysql做的,改密码非常的不方便。
proftpd自身已经提供了一套接口,只要所开发的新模块按照相应的认证接口提供功能,那么使用BBS用户密码作为ftp的用户密码,是不困难的。
    所实现的接口无非是提供类似系统调用的getgroup、getpwnam等等,认证的部分是在auth当中来完成,具体的接口表像下面这个样子:
        static authtable auth_bbs_authtab[] = {
  { 0,  "setpwent",        bbspw_setpwent },
  { 0,  "endpwent",        bbspw_endpwent },
  { 0,  "setgrent",     bbspw_setgrent },
  { 0,  "endgrent",        bbspw_endgrent },
  { 0,        "getpwent",        bbspw_getpwent },
  { 0,  "getgrent",        bbspw_getgrent },
  { 0,  "getpwnam",        bbspw_getpwnam },
  { 0,        "getpwuid",        bbspw_getpwuid },
  { 0,  "getgrnam",     bbspw_getgrnam },
  { 0,  "getgrgid",     bbspw_getgrgid },
  { 0,  "auth",         bbspw_auth        },
  { 0,  "check",        bbspw_check },
  { 0,  "uid2name",        bbspw_uid2name },
  { 0,  "gid2name",        bbspw_gid2name },
  { 0,  "name2uid",        bbspw_name2uid },
  { 0,  "name2gid",        bbspw_name2gid },
  { 0,  "getgroups",        bbspw_getgroups },
  { 0,  NULL }
};
    其实,虽然系统提供了这些接口要求,但是也并不是所有的接口都需要用上,在一般的认证和ftp session过程当中,主要用到的有getgroups、uid2name、gid2name、auth、getpwnam等有限的几个,还有一些如setpwent、endpwent、setgrent、getpwent、endpwent等等虽然会被调用到,但是并不会对proftpd的内部处理造成影响,甚至proftpd也不判断返回值。

今天已经将这一套功能正式在团队里测试,下一部可以做的是:
1、如何提高认证的效率?在FB BBS当中,有专门的共享内存负责存放用户名在密码文件当中的位置,这样在检索的时候比在文件当中索引要快得多,考虑到ftp上传下载的时候通常会多线程处理,意味着同时会有一个用户的多个认证线程在工作,如何提高这里的效率,值得考虑;
2、如何将用户与多个组相关联。团队里一个同学会隶属于多个项目组,那么如何建立用户与项目组之间的关系,这可能要通过团队的短信平台数据库来完成,目前对于通过BBS用户密码认证的用户全部是认为是一个组,全部放在temp目录下。这对于分组管理来说还是不方便的。

    不过现在至少我们迈出了第一步了,呵呵。

    这两天最后的新版本编译是分布在新服务器和现有服务器上完成后才投入使用的,不过在编译的过程当中也碰到一些小小的问题,居然都是在库的链接上。在新服务器上,由于centos虽然安装了mysql,但是却没有mysql的头文件,导致proftpd在增加sql支持的时候编译不过,上网下载了mysql的头文件后解压到/usr/include下解决,但在链接的时候说找不到mysqlclient的库,查了/usr/lib/mysql目录下明明有,在./configure的时候也指定了路径,可就是报错,后来才发现那个目录下没有libmysqlclient.so的文件,只有xxxx.so.x,只好做一个符号链接了事。在现有的服务器上,mysql是另外安装的,找到了/usr/local/mysql目录以后,在configure的时候敲错了库的目录,也是一番折腾,呵呵。看样子熟手也有马失前蹄的时候啊~~

2008年04月21日

    周六跟着mm单位体验了一把真人CS,激光装备的,可不是彩弹CS的哦。23人分成两队,12vs11,上午两场战斗,下午一场战斗,后面就遇上了迟到的大雷雨,早早收工了。说说战绩,我方上午一负一平,下午在遇雨之前基本是平局,不我我方略为占优。个人战绩,上午第一场命中14次,被命中7次;上午第二场命中3次,没有被命中记录;下午一场命中24次,被命中3次。
    体验的最大感触是——打仗还真不是那么简单的事情。第一场战斗当中,我方负责守,对方攻。可是带到野外场地以后,面对不熟悉的地形,完全不知道如何布置。这可不比在电脑上打游戏,有地图给你,12个人一分散,还真不知道大伙都在哪里。而对于攻方的行动,也完全不知道他们会从哪里攻过来,于是第一场战斗就在混乱当中展开。
    上午的第二场战斗是易地再战,我方担负攻击,对方防守。可惜我们队的队长安排任务犹豫不决,这场战斗变成了遭遇站,对方居然反突击了。不过我和另外两个弟兄作为奇兵准备绕道对方身后,在绕山而行的过程当中偶遇对方反突击的三人,幸运的是我们的位置比他们高,占据着有利地形,本想在对方没有发觉的情况下悄然通过的,怎奈有一个弟兄忍不住开火暴露了目标,好在双方都没怎么纠缠。此役由于双方缠斗极为混乱,我方三人长途奔袭之后也没遇到几个敌人,所以结果到最后是个平局,我居然只打中3人,当然也没有被命中,倒是同去的两个弟兄在互相掩护的过程中误伤一次,哈哈。
    下午在短暂的午饭、休息和交流经验之后,开始了人质争夺战。下午的一役是规模最大和最混乱的一仗,我方负责守护人质,对方前来抢夺。一开始我方就将主力分散到山坡上,山坡下的草丛里则布置了几个疑兵,我还是带着两个弟兄在比人质所在位置更高的山坡上布防,并前出靠近攻方位置掩护。由于所处位置较高,而且担心对方会从山坡背面绕后攻击,我还越过山坡警戒了一阵,确认后背无虞之后慢慢沿山坡向下戒备,而此时山下的战斗已经开始,偶有枪声传出,可惜无法看清。犹豫无法判定对手位置,我迟迟没有运动,直到发现对方自下而上进攻我身后的主阵地后,我才移动到对方身后,很爽的干掉几个还没被对方发现,哈哈。直至被发现开始1vs1,凭借移动和树木的掩护,又在1vs1的情况下命中对方多次,并逃脱。之后在山腰自下而上掩护上方的我方撤退人员,并再次在1vs1的过程中占了点便宜。当然在这里也被命中几次了,呵呵。后来,由于天气变坏提前收工,mm帮看背后的记录器结果是24次打中,3次被击中,看样子是那天的最好成绩了。
    回来以后,感慨打仗太累了。不停的爬上爬下,何况我们背负的装备还只是塑料枪具,基本没有作战负荷,但却在最后没有力气跑动。直到今天,大腿还酸疼不已。另一方面,在对战的环境当中,视场受限很大,远没有在游戏当中那样可以随时查看的地图(虽然我没有玩过CS,但还是看过别人玩的),也很难知道敌我双方的位置,就连自己人的位置都只能依靠喊话来确认,我们队的队长也就是因为频繁的喊话自我暴露被搞定的,呵呵。
    可见打仗还真不是那么简单的事,游戏当中的感觉与真人CS差远了。向辛苦训练的解放军们致敬吧~~

2008年02月19日

by quickmouse <quickmouse@263.net> 2008年2月19日

    e2fsprogs的工具包当中携带了一个叫blkid的库,可以用来探测一个文件系统的类型。当我们使用mount device mntpoint时候,mount命令自动识别设备的文件系统类型就是通过这个库来完成的。
    blkid库通过扫描指定设备上的文件系统特征值来判断是否属于对应的文件系统。在blkid库的源码文件probe.c当中,我们可见这样的结构值:
static struct blkid_magic type_array[] = {
/*  type     kboff   sboff len  magic                        probe */
  { "oracleasm", 0,        32,  8, "ORCLDISK",                probe_oracleasm },
  { "ntfs",         0,         3,  8, "NTFS    ",                probe_ntfs },
  { "jbd",         1,   0×38,  2, "\123\357",                probe_jbd },
  { "ext3",         1,   0×38,  2, "\123\357",                probe_ext3 },
  { "ext2",         1,   0×38,  2, "\123\357",                probe_ext2 },
  { "reiserfs",         8,   0×34,  8, "ReIsErFs",                probe_reiserfs },
  { "reiserfs", 64,   0×34,  9, "ReIsEr2Fs",                probe_reiserfs },
  { "reiserfs", 64,   0×34,  9, "ReIsEr3Fs",                probe_reiserfs }
 …
};
    很明显,这是各个文件系统的特征值,当然仅仅有特征值还不能完全断定是这个文件系统,还需要进一步判断。正是看到了这个结构值解决了我的一个U盘长期只能用mount -t vfat …的指定文件系统方式来挂载的问题,在此不细谈。至于进一步判断,我们大可不关心实现细节,如果要使用,只需要在查询前创建blkid的cache,然后再用blkid_get_tag_value查询即可,现在支持的tag包括"TYPE"(文件系统类型)、"UUID"(格式化后文件系统的唯一ID)和"LABEL"(卷标),最后用blkid_put_cache把cache释放即可。
    使用blkid cache的好处是如果进行多个重复查询,则速度会快一些。blkid_get_cache当中的第二个参数是cache存放的文件名,若传入NULL,则使用系统缺省的/etc/blkid.tab,若不希望创建,可使用/dev/null。
    当然,查询也可以不通过cache进行,这时候可以简单的使用一个blkid_get_tag_value(NULL, "TYPE", devicename)来完成,实际上在blkid_get_tag_value函数内部发现没有cache参数传入时,会自己根据/etc/blkid.tab创建cache,查询完毕后再释放,即屏蔽了blkid_get_cache和blkid_put_cache的操作。
    一个小小的测试程序如下,编译的时候记得加-lblkid的库链接参数:
#include <stdio.h>
#include <blkid/blkid.h>

int main(int argc, char **argv)
{
        blkid_cache cache;
        const char *pfstype;

        if(argc != 2)
        {
                printf("Usage: %s devicename\n", argv[0]);
                return 0;
        }

        blkid_get_cache(&cache, "/dev/null");
        pfstype = blkid_get_tag_value(cache, "TYPE", argv[1]);
        blkid_put_cache(cache);
        printf("The fstype by probing is %s\n", pfstype==NULL?"<don’t know>":pfstype);

        return 0;
}

【补充于2008-4-21】在nuonuo同学询问之后有如下附加意见:
    值得注意的是,在调用了blkid_get_tag_value之后,需要对所指向的空间进行free操作,该库函数在返回前把数据放置在了malloc的一段内存空间当中。

2008年01月01日

    今天是2008年的第一天,元旦既没有出去逛,也没有窝在电视前面看上一天。当然,懒觉还是要睡的,11点爬起来也不算太晚,呵呵。
    原本期待我们的QQ今天能回来的,等到中午也没有消息,只能作罢。打开电脑,继续专攻DELL R200的支持问题,在昨天初步看到眉目的情况下,研究PCI中断、PIRQ和PIRQ routing table的问题。对着Intel ICH9的datasheet看了又看,用纸笔记下机器开机POST之后信息画面汇总的信息,想了又想,决定在PIRQ routing control里面做一点手脚。在不断的修改内核、休眠windows、启动CHP image的过程当中,终于在下午4点前成功的搞定了在OS层面将Intel ICH9R的SATA工作模式从Native SATA切换到Legacy SATA的问题。
    当然,还是有一些问题没有完全搞清楚的,比如用什么方式获得PCI中断四个引线到PIRQ #A-#H的对应,如果设计routing table的分配关系等等。另外,就是现在的试验仅仅是在有BIOS选项对应参考的情况下搞定的,真实的DELL R200可没有对应的选项给参考,如何去对付?
    不过毕竟是2008的开门红,已经看到曙光了,光明还会远吗?呵呵。

2007年12月12日

    今天在网上搜索的时候偶然间又看到了前两日在央视网页上看到的爷爷的工作照,呵呵。所不同的是这张照片比央视的要大一些,看样子是央视截取了照片当中的一部分贴上了cctv.com的字样,唉,太不专业了。

  在爷爷身边的是谁呢?看样子只有春节回去问老爸他们了,呵呵。

2007年12月10日

    昨晚团队例会后,妈妈给我来了个电话,告诉我中央一套正在播放的节目里正在说爷爷发现银杉的事情,还是桂林的五叔打电话通知他们的。我很可惜的告诉她,我正在回宿舍的路上,也没法看电视,不过会尽量在第二天上网看看能否找到视频的。
    今天在google上用cctv和银杉两个关键字,一会儿就找到了昨晚的记录片,是中国首部大型生态记录片《森林之歌》当中的一集《孑遗 森林隐士》(http://www.cctv.com/docu/special/C19398/08/index.shtml)。好在还有视频可以下载,可以让我今晚一补昨夜的缺憾,更令人惊讶的是在网页上还有爷爷的工作照。刹那间,爷爷的音容笑貌又浮现在我的脑海里,虽然他已经离去了14年~


    记录片当中有相当一部分是在花坪自然保护区拍摄的,也是爷爷发现银杉的地方,那里还有以他的名字命名的济新杜鹃。不过最令我们自豪的还是他发现的植物中的“活化石”银杉。记得在92年,邮电部发行了一套“杉树”邮票,其中的第二枚就是“银杉”。那一次,爷爷买来七套纪念封,在每一套上郑重的签下自己的名字,说是给每一家留个纪念。不久以后,爷爷仙逝,那些纪念封也就成了绝版,再也不会有银杉发现者在银杉邮票旁签下的手迹。


    对爷爷的印象,一直留存于两个地方,一是在桂林植物研究所那一独栋的公寓里,单层的平房,有好大的房间,几乎位于植物所住宅的最高处,进入公寓的楼梯布满青苔,充满着植物的气味,以至于每次大人们都要冲着我们几个小孩嚷嚷注意别滑倒;另一处,就是在南宁新民路区政府的宿舍了,也是我们几个男孩儿淘气的地方,被我们冠以“奶奶家”的位置。
    可惜的是,植物所的那间公寓在几年前因为不知名的原因拆除掉了,或许是因为太潮湿没有人想去住,那年老爸还和五叔一道在留有墙基的废墟上指点这是厨房、那是客厅等等,面对墙边一棵枇杷树,他们唏嘘着年轻时随手栽下的树苗今天已经多么粗壮。而我只能依靠自己4、5岁时留下的依稀记忆来捕捉他们谈论的对象,唯一有深刻印象的只有老爸当年给我在那里翻箱倒柜找出来的磁铁,我用线吊着它跪在凳子上捕捉故意撒落在地上的针。而南宁新民路区政府的宿舍,也在去年春节时得知已经被变卖给他人,那条我小学、高中常走的新民路如今也只是很偶尔偶尔才会路过,却也绝不会忘记对曾经的8栋行上我的注目礼。
    对爷爷的许多记忆,在今天的那一刻全都迸发出来,仿佛又看到了熟悉的影子……

2007年12月08日

by quickmouse <quickmouse@263.net>

2007年12月8日

    在项目开发过程当中,发现CentOS 5当中,若对rpcsec_gss_krb5模块加载、卸载后再加载将导致kernel panic。通过对panic后Oops信息的分析可知panic位置处于sunrpc.ko模块auth_domain_put函数。这一函数在多个模块当中被调用,应该不至于出现严重问题,遂分析调用者auth_rpcgss模块中的svcauth_gss_register_pseudoflavor函数:
int
svcauth_gss_register_pseudoflavor(u32 pseudoflavor, char * name)
{
        struct gss_domain        *new;
        struct auth_domain        *test;
        int                        stat = -ENOMEM;

        new = kmalloc(sizeof(*new), GFP_KERNEL);
        if (!new)
                goto out;
        kref_init(&new->h.ref);
        new->h.name = kmalloc(strlen(name) + 1, GFP_KERNEL);
        if (!new->h.name)
                goto out_free_dom;
        strcpy(new->h.name, name);
        new->h.flavour = &svcauthops_gss;
        new->pseudoflavor = pseudoflavor;

        test = auth_domain_lookup(name, &new->h);
        if (test != &new->h) { /* XXX Duplicate registration? */
                auth_domain_put(&new->h);     // <<<<———- kernel panic here
                /* dangling ref-count… */
                goto out;
        }
        return 0;

out_free_dom:
        kfree(new);
out:
        return stat;
}

注意到几个特征:
1、rpcsec_gss_krb5模块是在再加载的过程当中才发生kernel panic的,于是这个panic可能与卸载模块工作不到位有关
2、panic的位置,处于auth_domain_lookup之后,从代码上下文和注释当中都可以看到调用auth_domain_put是为了避免重复注册,而对后注册的信息进行删除

    查看代码有这样的发现,首先第一次加载模块时,auth_domain_lookup通过对name进行哈希运算后发现new->h当中的地址并未在哈希表当中出现,于是将其插入表中,并将传入的new->h值返回。若第二次调用auth_domain_lookup使用相同的名字,不同的new->h值时,将返回哈希表中的值,而不是传入的new->h值,所以后续代码可以通过此检测是否有重复name的注册。不幸的是,rpcsec_gss_krb5模块在加载的时候注册了若干的name,但是却没有在卸载的时候从哈希表当中删除对应的表项,使得第二次加载模块时会发现有重复的表项出现。当然,这只是kernel panic的诱因,而不是直接原因。
    直接导致kernel panic的原因在auth_domain_put代码当中:
void auth_domain_put(struct auth_domain *dom)
{
        if (atomic_dec_and_lock(&dom->ref.refcount, &auth_domain_lock)) {
                hlist_del(&dom->hash);        // <<<———– looking here
                dom->flavour->domain_release(dom);
        }
}
    其在对传入的new->h信息进行了hlist_del操作,而hlist_del的函数如下,并调用了__hlist_del(n):
static inline void hlist_del(struct hlist_node *n)
{
        __hlist_del(n);        // <<——– pay attention
        n->next = LIST_POISON1;
        n->pprev = LIST_POISON2;
}

static inline void __hlist_del(struct hlist_node *n)
{
        struct hlist_node *next = n->next;
        struct hlist_node **pprev = n->pprev;        // <<——— pprev get a wrong value
        *pprev = next;                // <<——– use this wrong pointer will cause unknow result
        if (next)
                next->pprev = pprev;
}
    在解释上面代码之前,请回头看看svcauth_gss_register_pseudoflavor函数的代码,其对new->h的操作前后是先通过kmalloc为new分配了空间,而对new->h当中的结构体内容内部赋值了name,flavour,而对ref和hash没有进行操作,其希望是通过函数auth_domain_lookup来完成的:
struct auth_domain *
auth_domain_lookup(char *name, struct auth_domain *new)
{
        struct auth_domain *hp;
        struct hlist_head *head;
        struct hlist_node *np;

        head = &auth_domain_table[hash_str(name, DN_HASHBITS)];

        spin_lock(&auth_domain_lock);

        hlist_for_each_entry(hp, np, head, hash) {
                if (strcmp(hp->name, name)==0) {
                        kref_get(&hp->ref);
                        spin_unlock(&auth_domain_lock);
                        return hp;                // <<———- if name is duplicated, no operation to the new parameter
                }
        }
        if (new) {                // <<———- if name is fresh for the list, run here
                hlist_add_head(&new->hash, head);
                kref_get(&new->ref);
        }
        spin_unlock(&auth_domain_lock);
        return new;
}
    当然,很不幸的是,auth_domain_lookup函数只有在当name没有注册的时候才会对ref和hash成员进行赋值,而当有重复的name时,后传入的auth_domain结构完全没有被改变。于是在svcauth_gss_register_pseudoflavor函数当中,调用auth_domain_put时所操作的auth_domain结构体中的ref和hash成员都是kmalloc后未初始化的值,导致__hlist_del函数引用出错。
    我本想试着修正这个问题,不过现在看来牵扯比较大,只能暂时作罢。

2007年11月28日

by Quickmouse <quickmouse@263.net> 2007年11月27日

Reference: Linux kernel Documentation/filesystems/ramfs-rootfs-initramfs.txt

    以前一直在使用image-initrd的格式,也就是Linux 2.4内核当中所使用的格式,即便在升级到2.6内核以后,也依然如此,虽然2.6内核开始支持新的cpio-initrd格式。最近开始转向cpio-initrd的格式了,这种格式有不少好处,例如不需要事先生成一个block文件并对其进行格式化,也不必担心这个block文件在使用过程中会不够用而不得不重新制作,也不会因为这个block文件被反复改变以后导致压缩性能下降,好处真的是蛮多。当然也有坏处,呵呵,以往image-initrd只需要把一些准备工作做好,挂载真实根文件系统的工作可以由内核来完成(当然也可以骗过内核不用内核挂载),而在cpio-initrd当中,执行cpio-initrd下的init就是内核做的最后一件事情了,挂载真实根文件系统以及执行/sbin/init都要由cpio-initrd下的init来完成,包括根切换,任务更重了。
    好在这都不算什么难事,不过有一个事情却很让人疑惑。在使用image-initrd格式时,initrd.img当中的内容是被展开到/dev/ram0当中的,在挂载真实文件系统时,可以通过pivot_root进行根切换,并将原来/dev/ram0当中的内容挂载到新根下的某个目录上,此时在新的根目录内还可以访问到initrd.img当中的内容,也可以用umount把所占据的资源释放。而使用cpio-initrd格式时,这一点就完全不同了,cpio-initrd的img在展开时并不依赖于设备文件,即与/dev/ram0无关,其内容是直接存放于rootfs当中,在切换到新的根目录下时,无法使用pivot_root。在mount新根文件系统时,原cpio-initrd当中的内容将无法再访问到,于是原cpio-initrd所占用的空间将无法收回。
    如何解决这个问题?其实在Linux内核Documentation/filesystems/ramfs-rootfs-initramfs.txt当中已经有相关的描述,而我们通常使用的由系统自带的(2.6内核)initrd.img当中,是通过nash所解析的switchroot命令来完成的,这个命令不仅仅是完成了根切换的动作,更重要的是在根切换动作前执行了recursiveRemove的动作,如下面一段代码所示:
    chdir(new);
    nashHotplugNewRoot(_nash_context);
    nashHotplugNotifyExit(_nash_context);

    recursiveRemove("/");

    if (mount(new, "/", NULL, MS_MOVE, NULL) < 0) {
        eprintf("switchroot: mount failed: %m\n");
        close(fd);
        return 1;
    }

    recursiveRemove("/")就是从(原)根目录开始删除所有的文件,但是这个函数在实现时仅对当前文件系统进行递归操作,因此不会“殃及”新挂载的新根(此时挂在原根的某个目录下)。
    由此可见,对cpio-initrd所占用资源的释放就是通过删除来完成的。但是值得注意的是,这个动作我们几乎无法通过脚本使用通常的命令来完成,因为普通的rm、find等命令都依赖于glibc库,在删除的过程当中一旦glibc库被删除,则rm、find都无法再继续了,而通常之后还需要使用chroot等命令来执行新根目录下的/sbin/init,所以若试图通过普通脚本来完成这样的动作,是需要特别的技巧的。

2007年11月20日

    第十届挑战杯顺利结束了,Dian团队ARM9组的作品《多通道超声波无损检测系统》获得了特等奖,按刘老师的话说就是祖上积德了,哈哈。
    这次挑战杯现场正好我也去了,是在天津的南开大学举办的。那天我从北京转赴天津,坐的是京津城际,传说中的CRH2动车组,结果N不爽,买票买得太早,1号车厢4号坐位,上车以后发现正对挡板,没有一点风景可看,空间也比较狭窄,希望以后的兄弟姐妹们不要买CRH2的头5个坐位。京津之间距离137km,动车开了1小时9分,比广深之间(147km)要慢一些,我记得广深城际在部分路段开到200km/h,而京津城际只开到163km/h,完全浪费了动车的速度优势。而且京津城际平均间距1小时一趟,远远低于广深15min一趟的密度,失望之极。
    到达天津时,天津站正进行大规模的改造,所有列车都停靠天津临时站,月牙河火车站。火车站的出口很简陋,没有任何的电子公告屏幕,晚点列车的情况靠一块小黑板写着,而候车室则建在天桥上。出了出站口,就有一堆的拉客的出租司机、麻木司机围着你jjyy,好像要把你荷包里的银子吸干似的,不然就是问“要不要住店”(怎么都觉得像黑店),让我对这个号称直辖市的天津印象打了不少折扣。
    就如刘老师所说,天津像个大农村,初来的第一眼,周围建筑稀疏,没有高楼大厦,街面凌乱,或者是到了天津的农村的原因吧,呵呵。一路晃到南开,印象有了些改变,立交桥比较多——至少比武汉多多了,路面普遍宽畅——不堵车吧,其实主要是没到上下班高峰(几天后我离开的时候就在去火车站的路上花了一个半小时)。

    再转过来说挑战杯和南开,晃去南开的路上,是由南开大学的志愿者在大巴上引路的。不知道什么原因,这位志愿者同学居然让大巴在南开校园里走了一段冤枉路——她居然不知道怎么去南开的体育馆,还得下车问同学,汗……安排给参赛队员住宿的谊园宾馆似乎是学生宿舍改造的,我们的小猪同学很不幸只能在那种地方栖息,四人间的上下铺,没有独立卫生间。好在住宿的地方24小时供应开水,不过开水炉放在标有“女卫生间”的房子里,我还特意去“男卫生间”的房子看了看,确认确实只有那里才能打到热水,免得人家把我们小猪同学当色狼看了~~
    到挑战杯的现场,才发现预留的展位比我们想象的小很多,只有1×0.5米。原来我们希望有2×2米的位置,毕竟要放下大水桶,测绘三角架和仪器,按南开预留的大小,东西一展开,就把过道占用了近1/2了,如果对面的作品也这样,就不用走人了。相比起2年前在复旦的挑战杯现场,地方是小了很多~~另一个麻烦的地方是,不知道是不是没有规划好用电负荷,我们所在的机械、控制类展区一直没有送电,好在我们的仪器是为野外工作用的,可以在电池的带动下调试,而旁边的几个作品就只有呆呆的看着机箱的份了。送电的情况直到挑战杯开幕的那天下午4点以后,才得到了解决,效率实在是……唉。
    在调试仪器的过程当中,也不是一帆风顺的。一根用于连接编码盘的数据线居然在焊接点断了,害得小白同学一方面电话要求让刘老师顺便带一根过来,另一方面紧急求证颜色和线序,幸亏同行的其他作品组有携带了烙铁的,当晚我和小白同学在谊园艰难的把6根线焊接到了接头上,并顺利调通,当然也让我的手指亲吻了一下火热的烙铁头子,留下些白色印记。
    后续的过程就比较顺利了,除了小白和小猪同学在面对评委、观众时候说哑了嗓子,一切都平安。而在我们的大后方,Dian团队内部,在看到我传回来的图片之后立刻给两位队员起了“小白猪”的nick,说明他们的人气还是很高很高的~~
    最后,在收到获得特等奖的消息之后,我们都感到这是团队强调产品化工作所带来的一项成果,这个作品在后期仍然有很多的问题都需要解决,大部分都不是技术难点,而是我们在设计、实现过程当中没有能充分考虑到实际的情况,或者是质量不够可靠,希望这次获奖能进一步推动ARM9组甚至是全团队对产品化工作的重视程度。

2007年09月25日

 三、配置SVN服务器

     如果你对我们如何配置SVN的服务器,以及它的权限管理如何设置感兴趣,欢迎你阅读下面的部分,这里仅仅对第一节当中阐述的svn+ssh,且使用密钥对认证的方式进行说明。希望我们以后有更多的同学可以承担服务器的维护工作。这里我们假设你已经比较熟悉Linux上的一些基本操作,否则的话请去补习一下 ^_^

 1、创建独立的svn版本库属主,也就是前面提到过的svnowner。我们希望svnowner自己是一个独立的用户,同时也独立成组,这样可以避免不必要的外人干扰。以下的操作除非特别说明,均使用svnowner用户进行操作。

 2、以svnowner用户的身份创建版本库。首先使用umask 027的方式设置掩码值,使得之后创建的文件和目录对本组用户只有读权限,组外用户无权限。如果考虑更严格的权限控制,可以使用umask 077方式,仅允许svnowner用户自己访问,拒绝本组、组外用户的任何访问。接着用svnadmin create path_for_repository在path_for_repository路径上创建版本库。从便利和权限一致性的角度,建议把svn、svnserve、svnadmin、svnlook等svn工具改名为real_svnxxx,再创建svn等脚本程序,例如svn脚本可以这样编写:

#!/bin/bash
umask 027
/usr/bin/real_svn “$@”

 3、创建SSH公钥存放文件。在svnowner用户目录下创建.ssh目录,并在其下创建authorized_keys文件,目录和文件的都应当仅仅是本用户可读写的。将各用户发送给你的公钥添加到authorized_keys文件当中,注意:一行一个公钥,并在公钥前增加option字段,例如:

command="/home/svnowner/svnserve -t -r path_for_repository –tunnel-user=
svnuser1", no-port-forwarding,no-agent-forwarding,no-X11-forwarding,no-pty
ssh-rsa …….(RSA Key here)….user1

     上例当中,用户user1使用该密钥对认证之后将会执行/home/svnowner下的svnserve,显然这应当是一个包含设置umask的脚本,并且以path_for_repository 作为版本库的根,svnserve运行于tunnel模式下,使用的svn用户名是svnuser1。再次提醒大家的是以上内容需要在authorized_keys里面一行内写完。

 4、现在可以在版本库里面设置具体的svnuser的权限了。进入之前所创建的path_for_repository目录,在其中有conf目录,是负责配置版本库的设置的。找到svnserve.conf文件,在其中去掉注释并修改的行有:

anon-access = none
auth-access = write
authz-db = authz
realm = XXX Group Repository

     表明匿名用户无访问权限,认证用户可以具备写权限,版本库标识为“XXX Group Repository”(此项可选),权限认证数据库是authz文件。于是再修改authz文件,示例:

[groups]
manager = svnuser1
members = svnuser2, svnuser3
[/]
@manager = rw
* = r
[/src]
@members = r
@manager = rw
* =
[/src/arm7]
svnuser2 = rw
@manager = rw
svnuser3 = r
* =
[/src/arm9]
svnuser3 = rw
@manager = rw
svnuser2 = r
* =
[/shared]
@members = rw

     以上示例的目的是将所有的目录置于manager可读写的方式,members用户可以对src目录具有可读权限,svnuser2和svnuser3分别对src/下的ARM7、ARM9目录具有读写权限,三个用户均可以对/shared目录进行读写。匿名用户(其他用户)只能对/shared目录进行读操作。注意:目录路径均以path_for_repository作为根目录,这一点需要牢记。 

    SVN 的一个明显的优点是可以针对单个目录具备独立的控制权限,这是比cvs更灵活的方面。并且,这也很适宜在团队这样的大范围内推广、且具有可控保密措施的实施方案。更详细的一个权限示例建议大家参考由“郑新星<zhengxinxing@gmail.com>”所写的《Subversion之路——实现精细的目录访问权限控制》。

     至此,Subversion服务器的搭建工作完毕,管理员的后续维护工作包括接受组内用户密钥对的更改、删除,为Subversion作定期备份等等。本文也暂告一段落