南京大学学位论文 LaTeX 模板

由于女朋友要写毕业论文,一些前人写的模板我不熟悉,并且摘要格式好像都不符合南京大学研究生院要求,所以我就在中科院学位论文模板的基础上给她改了一个南大研究生学位论文 LaTeX 模板。主要工作是将封面和摘要格式都改成符合南大研究生院给的样张格式,实话说,花了不少工夫。

既然模板都已经写好,后来我干脆又完善了一下,添加了博士毕业论文需要的国家图书馆论文封面。这样大概可以作为一个完整的,包含硕士和博士毕业论文格式的南大研究生学位论文 LaTeX 模板包,我将这个 LaTeX 模板释出在下面这个地址:
http://share.solrex.org/njuthesis/,或者Google Code页:http://njuthesis.googlecode.com/
下面是一个 flash 的预览:
http://share.solrex.org/njuthesis/template-preview.swf

有需要的同学可以去下载,最起码可以作为一个修改的基础,希望这些工作能够对别人有所助益。我会尽量地维护这个模板,所以如果有哪位校友觉得有不完善的地方,可以和我联系,我会修正相应的缺点。

An IPv6 Enabled NTP Client for Windows in Python

Python NTP library (ntplib) offers a simple interface to query NTP servers from Python. But it does not support IPv6 NTP servers. I wrote a patch for ntplib to support IPv6 connections. You can download the patch file here and the patched library here.

The code bellow is a simple IPv6 enabled NTP client (ntpdate.py) in Python for Windows, using the patched ntplib. It doesn't (and won't) support Linux because the official NTP release offers IPv6 support on that platform.

#!/usr/bin/env python
# ntpdate.py - set the date and time via NTP
# An IPv6 enabled ntp client, for Windows ONLY.

import ntplib, time
from os import system
from sys import argv

def usage():
  print '''Usage: ntpdate.py  [-qh] server
Example:
  ntpdate.py 210.72.145.44      # IPv4
  ntpdate.py ntp6.remco.org     # IPv6
Options:

  -q     Query only - don't set the clock.
  -h     Print this message.

IPv6 NTP Server List:
  ntp6.remco.org               [2001:888:1031::2]
  ntp6.space.net               [2001:608:0:dff::2]
  time.buptnet.edu.cn          [2001:da8:202:10::60]
  time.join.uni-muenster.de    [2001:638:500:717:2e0:4bff:fe04:bc5f]
  ntp.sixxs.net                [2001:1291:2::b]
  ntp.eu.sixxs.net             [2001:808::66]
  ntp.us.sixxs.net             [2001:1291:2::b]
  ntp.rhrk.uni-kl.de           [2001:638:208:9::116]
  ntp.ipv6.uni-leipzig.de      [2001:638:902:1::10]
  ntp.hexago.com               [2001:5c0:0:2::25]
  ntp1.bit.nl                  [2001:7b8:3:2c::123]

Report bugs to http://solrex.org.'''
  sys.exit()

def main():
  ntp_svr = ''
  query = False

  for a in argv[1:]:
    if a == '-q':
      query = True
    elif a == '-h':
      usage()
    else:
      ntp_svr = a
  if ntp_svr == '':
    usage()

  c = ntplib.NTPClient()
  res = c.request(ntp_svr, version=3)
  t_epoch = res.offset + res.delay + time.time()
  t = time.localtime(t_epoch)
  centi_sec = t_epoch%1 * 100
  time_str = time.strftime('%H:%M:%S', t)
  if not query:
    system('time %s.%2.0f' % (time_str, centi_sec))
    date_str = time.strftime('%Y-%m-%d', t)
    system('date %s' % date_str)
  if query:
    print 'server %s, stratum %d, offset %f, delay %f' % (
           ntp_svr, res.stratum, res.offset, res.delay)
  print '%s %s ntpdate.py: time server %s offset %f sec' % (
         time.strftime('%d %b', t), time_str, ntp_svr, res.offset)

if __name__ == '__main__':
  main()

一个 Windows 对时小工具

由于在 CERNET 内,我经常需要用代理上网,没办法直连到 NTP 服务器,因此不能使用 Windows 时间服务对时。偶尔维修电脑或者不小心调整错时间,再加上电脑时钟本身就有一定的漂移,对时就变成了件麻烦的事情。

手动调时也没个参照,误差往往比较大。IPv6 网络上存在一些 NTP 服务器,Linux 下有 ntpdate 是支持 IPv6 NTP 服务器的,但是我搜索了半天,才在一篇文章上看到有人评论说 Windows 下只有一款 NTP 客户端支持 IPv6,还是收费软件——可他也没给出名字。

无奈之下想到 Python 的 httplib 是支持 IPv6 连接的,于是我就仿照 htpdate 写了一个利用 Google 的 IPv6 Web 服务器进行对时的 Python 小工具 htpdate.py。虽然误差比 NTP 大不少,但是还是在可接受范围内(不到 1 秒),而且比较方便,连日期也一块更新了。下面是代码,比较粗糙。

#!/usr/bin/env python
import httplib, time
from os import system

def main():
  conn = httplib.HTTPConnection('google.com')
  time.clock()
  conn.request('HEAD', '')
  t_rtt = time.clock()
  res_time = conn.getresponse().getheader('date')
  t = time.localtime(time.mktime(time.strptime(res_time,
                                 '%a, %d %b %Y %H:%M:%S %Z')) - time.timezone)
  time_str = time.strftime('%H:%M:%S', t)
  local_time = time.asctime()
  t_exe = time.clock()
  centi_sec = (t_exe - t_rtt/2)*100
  if centi_sec > 99:
    centi_sec = 99
  system('time %s.%2.0f' % (time_str, centi_sec))
  date_str = time.strftime('%Y-%m-%d', t)
  system('date %s' % date_str)
  print 'LOCAL  TIME: ' + local_time
  print 'SERVER TIME: ' + time.asctime(t)
  print 'LOCAL  TIME: ' + time.asctime()
  if (t_exe - t_rtt/2) >= 1:
    print 'Round trip time is too long. Time error might be larger than 1 sec.'

if __name__ == '__main__':
  main()

RSS Feed 迁移方法

由于政策的调整,目前很多博主都将博客域名从 .cn 迁出,相信很多朋友都会遇到 RSS Feed 迁移的问题。如果一直使用 Feedburner/Feedsky 这种第三方烧录网站管理订阅,只需要更换第三方抓取的源即可;但是如果之前订户多用 WordPress 原始的源 example.cn/feed/、example.cn/?feed=rss2,或者使用自定的域名 feed.example.cn 的话,当域名迁移时,原来的 example.cn 被弃用后,订户就无法得到文章更新了。

我之前一直使用 feed.solrex.cn 作为 Feedsky 的自定义域名,因为我觉得 solrex.cn 可能比 feedsky.com 更长久,后来发现这是非常愚蠢的想法。当我把域名迁移到 .org 时,就面临 feed 迁移的问题。

最简单的方法是将原来的 feed url 重定向到 Feedburner/Feedsky,但这要求网站主必须仍然控制原来域名,那就没有更换域名的必要了。

或者使用一篇博客来通知订户更换 feed url,但是实践证明这种方法收效甚微。很多人(包括我)不会去看自己使用的是什么源,认为自己使用的就是正确的 feed url。

起初我是使用的直接重定向,但后来一封域名注册商的邮件,威胁如果不办理某些手续,24日之后会停止我的 .cn 域名解析。我想,还是用一些略显卑劣的手段通知大家更换订阅源吧。这种卑劣的方法是:如果使用原来的源订阅本站,就会看到每天一篇的“网站迁移通知”,直到用户更改订阅源,或者无法忍受直接删除 feed。

其技术实现方法是:使用 php 模仿 WP 的 rss 源生成一个 xml 文件,该文件只包含一篇文章,将原来的源指向它(或者 url 重定向到它)。该 xml 中的更新日期、文章 url 每天更新一次,这样阅读器就会认为博客有更新,把这篇文章抓取回去。我本以为阅读器是根据更新日期判断文章是否重复,后来发现是根据文章 url 来判断。为减少工作量,我们可以将文章的 url 指向某篇目标文章,然后在 url 后面加上 “?date=***”,这样阅读器就不会认为是同一篇文章,而且读者仍然能够点入目标文章。

方法很简单,如果您比较懒的话,可以参考我使用的文件(也可以从这里直接下载 php 源代码):

<?php echo '<?xml version="1.0" encoding="UTF-8"?>'."\n"; ?>
<?php echo '<?xml-stylesheet type="text/xsl" media="screen" href="http://feeds.feedburner.com/~d/styles/rss2chinesetwfull.xsl"?><?xml-stylesheet type="text/css" media="screen" href="http://feeds.feedburner.com/~d/styles/itemcontent.css"?>'; ?><rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" version="2.0">

<channel>
    <title>Solrex Shuffling</title>
   
    <link>http://blog.solrex.org</link>
    <description>Engineering a better life, programming a great future.</description>
    <pubDate><?php echo date('D, d M Y ', strtotime("+7 hour")); echo '00:00:00 GMT'; ?></pubDate>
    <generator>http://wordpress.org/?v=2.7.1</generator>

    <language>en</language>
    <sy:updatePeriod>hourly</sy:updatePeriod>
    <sy:updateFrequency>1</sy:updateFrequency>
        <item>
        <title>站点迁移通知-<?php echo date('d M Y', strtotime("+7 hour")); ?></title>
        <link>http://blog.solrex.org/?p=638679&amp;q=<?php echo date('Ymd', strtotime("+7 hour")); ?></link>
        <comments>http://blog.solrex.org/?p=638679&amp;q=<?php echo date('Ymd', strtotime("+7 hour")); ?>#comments</comments>
        <pubDate><?php echo date('D, d M Y ', strtotime("+7 hour")); echo '00:00:00 GMT'; ?></pubDate>
        <dc:creator>Solrex Yang</dc:creator>
       
        <guid isPermaLink="false">http://blog.solrex.org/?p=638679&amp;q=<?php echo date('Ymd', strtotime("+7 hour")); ?></guid>
        <description><![CDATA[您好,您之所以看到这篇文章是因为您仍在使用被遗弃的 feed 地址 http://feed.solrex.cn 订阅我的博客Solrex Shuffling。我已经将网站从 http://blog.solrex.cn 迁移到了 http://blog.solrex.org。由于 .cn 域名潜在被删除的危险,为了不丢失和您交流的渠道,我不得不出此下策以每天一篇博客的方式提醒您更新 feed 地址,希望您能谅解!...
]]></description>
            <content:encoded><![CDATA[<p>您好,您之所以看到这篇文章是因为您仍在使用被遗弃的 feed 地址 http://feed.solrex.cn 订阅我的博客<a href="">Solrex Shuffling</a>。我已经将网站从 <a href="">http://blog.solrex.cn</a> 迁移到了 <a href="">http://blog.solrex.org</a>。由于 .cn 域名潜在被删除的危险,为了不丢失和您交流的渠道,我不得不出此下策以每天一篇博客的方式提醒您更新 feed 地址,希望您能谅解!</p>
<p>如果您觉得<a href="">本站</a>对您还有点儿用处,可以使用以下方式继续订阅:</p>
<ul>
<li><p>如果您使用离线阅读器,请将本站的 feed 地址 <a href="http://feeds.feedburner.com/solrex">http://feeds.feedburner.com/solrex</a> 或者 <a href="http://feed.feedsky.com/solrex">http://feed.feedsky.com/solrex</a> 添加到您的订阅器中,并删除现有这个 feed。</p></li>
<li><p>如果您使用在线阅读器,比如 Google Reader、抓虾 之类,您可以点击<a href="">这里</a>到本站首页,在右侧选择您的在线阅读器,重新订阅,并将现在这个 feed 删除。</p></li>
</ul>
<p>如果您觉得<a href="">本站</a>对您不再有用,可以使用以下方式退订:</p>
<ul>
<li><p>如果您使用离线阅读器,请咨询阅读器帮助如何删除 feed,一般情况下在 feed 上直接点 del 键即可。</p></li>
<li><p>Google Reader 用户可以在左侧 Subscriptions 中找到本 feed(一般名为 Solrex Shuffling),将鼠标移动至其上,您会发现右侧有一个向下的小箭头,点击箭头,您就会发现有 Unsubscribe 的选项;或者您也可以到右上角的 Setting 中,点入 Subscriptions 标签页,对所有 feed 进行管理时删除 Solrex Shuffling 这个 feed。您可以在<a href="http://www.google.com/support/reader/bin/answer.py?hl=zh_CN&answer=73062">这个页面</a>找到更多帮助。</p></li>
<li><p>抓虾用户可以在<a href="http://zhuaxia.com/help.php#3_3">这个页面</a>找到退订的帮助。</p></li>
<li><p>其它在线阅读器用户请咨询该网站帮助。</p></li>
</ul>
<p>无论如何,感谢您一直以来对本站的支持,我希望能在<a href="">新的站点</a>继续收到您的批评或支持!祝您好运!</p>
<p>Solrex Yang</p>
<p><?php echo date('D, d M Y ', strtotime("+7 hour")); ?></p>
]]></content:encoded>
            <wfw:commentRss>http://blog.solrex.org/?p=638679&amp;q=<?php echo date('Ymd', strtotime("+7 hour")); ?>/feed/ ?></wfw:commentRss>
        </item>
</channel>
</rss>

您可以到 feed.solrex.cn 查看效果。

Fix Black Screen After Boot Problem of Ubuntu 9.10 on D630

Platform: Dell Latitude D630, Nvidia NVS 135M, Intel CPU, Ubuntu 9.10

Problem:

1. Boot from livecd, select install. After a glowing ubuntu Symbol, screen shows nothing, even no CLI.

2. After installation with "text mode", boot from HD. After a glowing ubuntu Symbol, screen shows nothing, even no CLI.

Solution:

I am not very sure whether this problem is caused by the NV 185 driver(nvidia-glx-185). Installing or removing this package gave no help. I googled a lot, found many people encountered the same problem. However, no answer could work on my laptop. Then I tried to install NV driver manually...it works! So, here is the fix:

1. When you get the GRUB boot menu screen, press 'e' to edit the fist entry. Add a word 'single' after 'linux /boot/vmlinuz..... quiet splash', then press 'Ctrl+x' to boot. (I cannot enable networking in recovery mode, so I tried with the single mode.)

2. Select the 'netroot' entry. You will get a root command line with network support.

3. Download the latest NV driver for linux (2010-11-3):

# wget http://us.download.nvidia.com/XFree86/Linux-x86/190.42/NVIDIA-Linux-x86-190.42-pkg1.run

[You can visit http://www.nvidia.com/object/unix.html for the latest NV driver.]

4. Install development tools to build NV driver on you os.

# apt-get update
# apt-get install build-essential

5. Install the NV driver:

# chmod u+x NVIDIA-Linux-x86-190.42-pkg1.run
# ./NVIDIA-Linux-x86-190.42-pkg1.run

6. Reboot.

If you are using a livecd, please use the "text mode" to install Ubuntu 9.10. After installation, try the solution above.

Ubuntu 9.10 启动后黑屏的解决方法

平台: Dell Latitude D630, Nvidia NVS 135M, Intel CPU, Ubuntu 9.10

问题描述:

1. 从 livecd 启动后,选择安装,在白色 Ubuntu 图标闪烁结束之后,无任何屏显,连命令行都没有。

2. 用文本模式安装完成后,从硬盘启动,在白色 Ubuntu 图标闪烁结束之后,无任何屏显,连命令行都没有。

解决方法:

我不太清楚是不是 NV 的 185 驱动有问题(nvidia-glx-185),安装或者删除它对状况没有任何帮助。我搜索了一下,发现很多人遇到和我类似的问题,不过没有任何解决方法可以在我的电脑上工作。无奈下我尝试手动安装了一下 NV 的最新驱动——居然解决了!下面是我的解决方法:

1. 当你进入 grub 启动菜单选择屏幕时,在第一条上按 e 进入编辑状态,在 'linux /boot/vmlinuz..... quiet splash' 这一行最后添加一个单词 single,然后按 Ctrl+x 启动。其实 recovery mode 能做类似的事,但是 9.10 的 recovery mode 好像不能启动网络,所以只好自己进入 single 模式了。

2. 启动后选择 'netroot' 选项,进入带网络的 root 命令行。

3. 下载最新的 NV 驱动(2010-11-3):

# wget http://us.download.nvidia.com/XFree86/Linux-x86/190.42/NVIDIA-Linux-x86-190.42-pkg1.run

[你可以先访问 http://www.nvidia.com/object/unix.html 查看 NV 最新驱动的地址。]

4. 安装编译 NV 驱动需要的编译工具:

# apt-get update
# apt-get install build-essential

5. 安装 NV 驱动:

# chmod u+x NVIDIA-Linux-x86-190.42-pkg1.run
# ./NVIDIA-Linux-x86-190.42-pkg1.run

6. 重启

如果您使用 livecd 安装 Ubuntu 9.10 的话,您应该选择 "text mode" 进行安装。成功安装完成后,仍然遇到黑屏问题,请尝试上述方法。

Cygwin GCC qsort 函数错误(续)

上一篇文章中提到我在为 qsort 写 compare 函数时犯了一个愚蠢的错误:我脑袋陷入了一个错误的逻辑,以为 compare 函数嘛,就是要 compare 一下,那么我用 '>' 或者 '< ' 这种比较算符就可以满足要求(潜意识里认为 > 会返回 1 或者 -1,显然是错的,上篇文章的评论者 Stephen 开始也犯了同样的直觉错误,不过他马上就醒悟过来了)。我当时脑袋里也犹豫了一下要不要处理相等的情况,后来想快排算法中没有判断相等的情况,那么我没必要加上等号。

这个错误直接导致了快排算法失效。

但是为什么在 Linux 下的 gcc 可以输出正确的排序结果呢?我想了很久,最终还是把 glibc 的代码看了一下,才发现,原来当数组规模比较小时时(数组大小小于物理内存的四分之一),glibc 的 qsort 其实不使用 quick sort(_quicksort),而是使用 merge sort(msort_with_tmp)。而且在 msort_with_tmp 中,对 compare 的处理是比较其返回值是否 <=0,这样排序的结果就是正确的了。[1]

事实上最简单的快排算法是只使用 '<' 号或者 '<='的,比如 Wikipedia 上给出的快排算法,那么我们的 compare 只返回 -1 和 0 行吗?这取决于实现,比如对快排算法的优化中有一个就是对数组中有大量相等元素情况下的优化,其中一种实现 Three-way partition, 就需要使用到三种情况:大于、小于或等于。原始的快排 partition 是将数组按照与 pivot 的比较分为两段,Three-way partition 则是将数组分为三段,中间增加一段与 pivot 值相等的子数组。C 玩具代码的实现如下:

void qsort_3way(int a[], int lo, int hi)
{
  if (hi <= lo) return;
  int lt = lo, gt = hi, i = lt;
  int v = a[lo], t;
  while (i <= gt) {
    if (a[i] < v) {
      t = a[i]; a[i] = a[lt]; a[lt] = t;
      ++i; ++lt;
    } else if (a[i] > v) {
      t = a[i]; a[i] = a[gt]; a[gt] = t;
      --gt;  
    } else i++;
  }
  qsort_3way(a, lo, lt - 1);
  qsort_3way(a, gt + 1, hi);
}

但是 '<' 和 '>' 真的都需要吗?理论上来讲,'>' 是不需要的,我们显然可以将 a[i] > v 改成 v < a[i]。这也是 C++ 里面做的,C++ 中的 sort 函数只需要类重载 '< ' 运算符。但是 C 中并没有这种约定,我们不能预设 qsort 如何拿 compare() 的返回值与 0 比较。因此让 compare() 按照 C 的约定,返回大于、小于和等于 0 的三种情况是绝对正确的而且必要的。

我了解了正确的结果怎么得来的,但是我仍然不知道错误的结果是怎么得来的。看起来 Cygwin 使用的 libc 中没有采取类似 Linux 下 gcc 的策略(比如无法取到物理内存大小?)。quick sort 算法有很多优化的技巧和实现:有的使用 '< ' 符号比较,有的在分支数组足够小时采用插入排序,有的同时使用 '<', '> 两个符号,有的随机取 pivot,有的取三点中值作为 pivot。[2] 没有看到代码和调试,很难判断 Cygwin 的 libc 使用了什么算法(当然,尝试分析不同的输入输出是可以得到规律的,比密码分析还是要简单一些)。

[1] glibc/stdlib/msort.c.
[2] Jon Bentley and M. Douglas McIlroy, "Engineering a sort function", Software - Practice and Experience, Vol. 23 (11), 1249-1265, 1993.

Cygwin GCC qsort 函数错误

我平时在 Windows 下写代码时,经常使用 Cygwin 的 gcc。但是今天我居然发现 Cygwin 下 gcc 的 qsort 函数是错误的!这种基本的函数出错,太让人惊讶了。为了验证是不是代码有错,我使用 tcc 和 Linux 下的 gcc 都编译了同样一段程序,它们两个都输出了期望的结果,只有 Cygwin 的 gcc 是错的。下面是示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int compare(const void *p, const void *q)
{
  return *(const char *)p > *(const char *)q;
}

int main()
{
  char a[] = "1312515";
  printf("%sn", a);
  qsort(a, strlen(a), sizeof(char), compare);
  printf("%sn", a);
  return 0;
}

按说它应该输出:

1312515
1112355

但是我用 Cygwin gcc 编译后,它居然运行出这样的结果:

1312515
2111355

太诡异了。我尝试调试它,结果 gdb 无法步入 qsort 代码中。谁能告诉我是为什么?

附 Cygwin gcc 信息:

$ gcc -v
Using built-in specs.
Target: i686-pc-cygwin
Configured with: /gnu/gcc/package/gcc4-4.3.2-2/src/gcc-4.3.2/configure --srcdir=/gnu/gcc/package/gcc4-4.3.2-2/src/gcc-4.3.2 --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/sbin --datadir=/usr/share --localstatedir=/var --sysconfdir=/etc --infodir=/usr/share/info --mandir=/usr/share/man --datadir=/usr/share --infodir=/usr/share/info --mandir=/usr/share/man -v --with-gmp=/usr --with-mpfr=/usr --enable-bootstrap --enable-version-specific-runtime-libs --with-slibdir=/usr/bin --libexecdir=/usr/lib --enable-static --enable-shared --enable-shared-libgcc --enable-__cxa_atexit --with-gnu-ld --with-gnu-as --with-dwarf2 --disable-sjlj-exceptions --enable-languages=ada,c,c++,fortran,java,objc,obj-c++ --disable-symvers --enable-libjava --program-suffix=-4 --enable-libgomp --enable-libssp --enable-libada --enable-threads=posix AS=/opt/gcc-tools/bin/as.exe AS_FOR_TARGET=/opt/gcc-tools/bin/as.exe LD=/opt/gcc-tools/bin/ld.exe LD_FOR_TARGET=/opt/gcc-tools/bin/ld.exe
Thread model: posix
gcc version 4.3.2 20080827 (beta) 2 (GCC)

我犯了一个愚蠢的错误,感谢来自 Stephen 的评论

你的compare函数有问题,你的compare函数不会返回负数。修改compare为:
int compare(const void *p, const void *q)
{
return *(const char *)p - *(const char *)q;
}
再编译运行就正确了。

Windows Tips: 修改热键和文件访问权限

我平时习惯使用 Win+E 打开 Windows 的资源管理器,但对资源管理器的左侧栏一直不感冒。用热键打开我的电脑本身就是为了键盘操作方便,但是多了个左侧栏使方向键选择文件夹相当不方便。昨天我总算找到了覆盖 Win+E 热键的方法。

AutoHotKey 是一个编辑和管理 Windows 热键的开源软件,SciTEAutoHotkey 是编辑 AutoHotKey 脚本的开源软件。(也许某些人会惊讶,AutoHotKey 居然不自带脚本编辑器,还要别人帮它写,我想这也许是受到 Unix 哲学的影响:Do one thing and do it well.)

AutoHotKey 脚本的基本语法是很简单的,前面是热键,后面是执行的命令,启动脚本后热键就会起作用了。键盘上每个特殊键对应的符号在热键列表中有列出,像我前面替换热键 Win+E 为打开“我的电脑”且无左侧栏的语句是:

#e::Run ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}

::{20D04FE0-3AEA-1069-A2D8-08002B30309D} 是“我的电脑”的 CLSID(多谢 IronFeet 的提示),如果需要打开非特种文件夹,就不需要这么麻烦了,直接类似于 #e::Run C:WINDOWS 即可。同理,设置用 Win+C 打开控制面板可以写成:

#c::Run ::{20D04FE0-3AEA-1069-A2D8-08002B30309D}::{21EC2020-3AEA-1069-A2DD-08002B30309D}

利用 AutoHotKey 可以做很强大的事情。比如我在 Linux 下使用 Evince 查看 pdf,可以使用 vim 的习惯,jkhl 上下左右移动 pdf 文档,那么我们可以在 Windows 下用 AutoHotKey 来实现对 Adobe Reader 同样的操控,语句如下:

#IfWinActive ahk_class AcrobatSDIWindow
j::Send {Down}
k::Send {Up}
h::Send {Left}
l::Send {Right}
#IfWinActive

这几句话的意思是,如果 AcrobatSDIWindow 这个 ahk_class 窗口在激活状态下的话,那么 jkhl 就被换成键盘上的上下左右键。AutoHotKey 的高级使用还是很麻烦的,它网站上的 Tutorial 是一个不错的入门教程。其实一般我都是用 Launchy 启动程序,对 AutoHotKey 的需求不是那么多,简单的几个组合键就够了。

我昨天才发现 Windows XP 家庭版和专业版的一个大区别:家庭版没有“组策略”编辑器。我电脑的“所有程序”->"启动"目录不知道被什么安全软件设置成只读了,我以为 ATTRIB 命令是修改文件权限的,却发现无法修改“启动”文件夹的权限。后来找到 CACLS 命令,可以用来指定特定用户对文件的访问权限,才把"启动"文件夹访问权限修改成了完全控制:

cacls 启动 /G Solrex:F

设置可写以后就可以把 AutoHotKey 脚本的快捷方式拖到“启动”文件夹,随系统一起启动了。

SVN 技巧:GUI 版本比较和可执行属性

我曾经在《使用 kdiff3 进行 svn 版本比较》中介绍了为什么以及如何使用 kdiff3 或者 meld 等 GUI 比较工具进行 SVN 版本比较。但这样做有个小问题,就是如果设置了 GUI 工具作为比较工具,那么就没办法输出 diff 文件,而且每次都要关掉窗口才会出现下一个文件,就无法比较多个文件了。所以我觉得下面这种做法会更好一些:

$ more svndiff
#!/bin/bash
sed -i -e 's/^# diff-cmd.*$/diff-cmd=meld/' ~/.subversion/config
svn diff
sed -i -e 's/^diff-cmd.*$/# diff-cmd = meld/' ~/.subversion/config

其实就是用一个脚本 svndiff 来做 GUI 比较的工作。svndiff 执行时首先将 svn 配置文件中的比较工具改为 meld,然后进行比较,比较完后再将修改注释掉,这样就不会影响正常 svn diff 的功能。这样一来,svndiff 是 GUI diff,svn diff 就是命令行 diff。

设置文件可执行属性对 Windows 用户来说可能没什么用,可是对 Linux 用户来说用处就大了。没人希望每次一 update,就要重新对需要执行的脚本 chmod 一下。svn 修改文件可执行属性的命令太长了,我老记不住,所以放在这里做个笔记吧:

svn propset svn:executable ON filename

那些搅屎棍儿们

最近在社区或者列表里闲逛,总发现有一些有意思的人,他们根本不懂自己在讨论什么东西,却像行家一样置评,还纠缠不休,像搅屎棍似的,搞得让人抓狂。特摘录几个,以飨大家:

1. 我的朋友王聪的博客

... 谁知道这人怎么回复?第一条回复是问我是不是在说判断endian的技巧?扯淡!你自己的留言什么意思你自己不知道?!第二条回复中他似乎意识到自己前一个回复很白痴,于是补了一句Linux内核中没有。放屁!它有才怪呢!它凭什么要有?!貌似他们家的C语言技巧都是出自Linux内核!一个人能傻到这种地步真的挺不容易的!

别慌。他还能继续向我们证明他更傻呢!这个人在链接中给出了这么个地址,稍微有常识的人都看的出来,那是一个patch,那个patch的作者是Changli Gao,我review了这补丁,认为可以接受,然后Linus回复了,回复的意思也很简单,他不喜欢那个patch,他解释说如果用户程序能触发这个问题就说明你那程序是一坨shit。稍微有常识的人都明白这话什么意思:Linus只不过是借虚构的“你那程序”来说明这个问题不应该在内核中修复。而精彩的事情这时发生了!lovecreatesbeauty@gmail.c0m同学成功地把这话联想到了Linus所说的“你那程序”就是我写的程序!太伟大了!真不知道这人上小学时语文怎么学的?!估计他的语文老师看了都会气得跳楼自杀了!唉,语文没学好也就罢了,你找找整个邮件的存档,看看里面到底有没有shit程序啊。问题是他连找到没找就脑残式地下结论了。没找就没找吧,你仔细看看patch不行么?很不幸,他连patch是谁发的到看不出来!所以这个人不光脑残,眼也有问题,那么大大的一行Signed-off-by他看不到!!...more

2. Ubuntu 的 BUG tracker: Kubuntu 9.04 alpha6 panel corruption

#46 dotancohen wrote on 2009-06-14:

Are you trying to piss him off by any way that you can? You are not a developer, and you go around confirming and invalidating components, and playing ping pong confirmed/invalid with a dev. Then you make a remark like that?!? I personally am angry at you right now. I need this bug fixed, and you are going to piss off the developer so that he leaves us _both_ here to rot.

Go away. File a different bug, you have every right to as the dev implies that your issue is not the same as the OP (yes, that's me). Piss the devs off there. But let them do their work here and help those of us who appreciate it. ...more

简单地来说,就是开发者把该 BUG 标记为 Invalid,说该 BUG 应该是属于 Driver 的 BUG,有个用户不满意开发者对 BUG 的处理,就跟开发者对着干,开发者标记为 Invalid,他重新标记成 Confirmed,开发者开启一个新 BUG 报告,他去给人家改成 Invalid。一般来说修改 BUG 的状态应该是开发者来做,用户可以提交 BUG,assign 给某个开发者,但是不应该修改 BUG 状态,这是一种非常不礼貌的行为。一个用户一般情况下不可能比开发者对目标系统了解更多。

3. WordPress 2.8.x(x<4) 的一个密码重设漏洞

From: laurent gaffie < laurent.gaffie_at_gmail.com >
Date: Tue, 11 Aug 2009 01:11:07 -0400

Mr Fabio,

You dont even understand the bug, so please shut the hell up.

2009/8/11 Fabio N Sarmento [ Gmail ] < fabior2_at_gmail.com >

> if this is an bug, please close Twitter.com, MSN.com and other services,
> because they have the same stupid "Reset password" service.
>
> So please make my day, and create a stupid script to flood with mutiple
> request to reset password. ...more

翻译过来就是,某个人发现了一个 WordPress 2.8.x(x<4) 的密码重设漏洞,报告了出来。有个人评论说:“如果密码重设是 BUG 的话,那么所有网站都有 BUG 了,你没事干就写点儿傻逼程序去到处重设别人的密码吧。”然后报告漏洞的人就无奈了:“你根本没有理解这个 BUG,那么就请闭上你的臭嘴吧。”过了两天,Wordpress 就紧急发布了新版本 2.8.4,fix 了这个 BUG。

于是,我现在非常理解为什么 Linus 大神说话经常那么难听了,要是我成天跟这种人打交道,我也会抓狂。

有些人根本不了解和别人交流、在社区中交流应该遵循什么样的礼仪,应该使用什么样的方法。我遇到过的不礼貌行为包括(但不限于):不去搜索 BUG 列表和邮件列表,一遍又一遍地提出重复的问题;有 BUG 列表和邮件列表时,还直接与开发者联系,或者只 reply 开发者,不知道 reply all 到邮件列表;提交 patch 时,不知道如何使用 diff 和 patch 工具,而是直接提交整个文件;描述 BUG 时,不提供 BUG 出现的环境和步骤,就来一句“xx出问题了”。尽管有时候我都懒的搭理这些人,但是我不想被别人认为是一个没礼貌的人,所以我都尽量回复,但的确心中很不爽。

我希望那些想要在社区中和别人讨论、交流并想赢得别人尊重的朋友能够多了解点儿社区交往的礼仪,起码应该去了解点儿入门的知识,免得遭受挫折打击积极性,反倒以为别人非常不友好不礼貌。推荐的基本资料包括:

1. 如果您想在邮件列表或者社区中提问,那么请首先阅读 “How To Ask Questions The Smart Way ”,中文翻译《提问的智慧

2. 如果您想提交软件 bug, patch, feature request 并想得到开发者的尊重和重视,那么请首先阅读“The Art of Unix Programming” 第 19 章的“Best Practices for Working with Open-Source Developers”。

Tohr - HTTP 层上的洋葱路由器

Tohr (The Onion HTTP Router) 是我上个星期写的一个小的研究项目,它的名字起源于 Tor (The Onion Router)。简单地来说,它就是试图在 HTTP 层上实现类似于 Tor 实现的功能—— HTTP 层上的 HTTP(S) 隧道。

这个 idea 不是我想出来的,而是受到 GAppProxy 的启发(GAppProxy 是一个 great work)。但是 GAppProxy 只是利用 Google 的平台,而且受到 GAE 的很多限制。那么我的贡献呢,就是把这个 idea 通用化,设计一个协议使其支持多平台,解决了 GAppProxy 的一些 bug,完善了 HTTPS 的支持。(哦,忘记了,我还给它起了一个很 fancy 的名字 ^_^)目前 Tohr 的路由器可以是 Google App Engine 上的 Python 网站,也可以是普通的 Apache+PHP 站点。

Tohr 是怎么工作的?

Tohr 的工作原理

Tohr 的工作原理见上图。首先您得拥有一个墙外的主机(免费或者收费的)作为 Tohr 路由器,Tohr目前支持 Google App Engine 和 Apache+PHP 服务器,您将对应的 tohr-router 文件上传到服务器上;然后您在本地运行一个 tohr-daemon 守护程序,设置 tohr-daemon 连接 tohr-router 的 url,tohr-daemon 默认会开启 9090 来提供一个 HTTP(S) 代理服务,您只需要将浏览器的 HTTP 代理设置为 localhost:9090,您的访问请求就会通过 tohr-daemon 转发到 Tohr 路由器上,这样就能通过它来访问被防火墙禁止访问的网站了。

Tohr 是给什么人用的?

目前来讲 Tohr 仍然不很完善,而且还需要加入对其它类型的主机,比如 asp.net、jsp 的支持,还有对多跳和匿名的支持,还没有一个针对普通用户易用性的优化。要求普通用户都有一个墙外主机也是件比较为难的事情(虽然申请一个国外免费 PHP 空间并不困难,比如这里),因此 Tohr 目前还仅适合爱折腾的人使用,尤其是懂 Python 或 PHP 的爱折腾的人,所以在这里是找不到一个一步步的图文教程教普通用户怎么配置的。当然,如果哪位用户愿意做一个,请发送到邮件列表或者提交补丁,我很乐意将它放在项目文档里。

JPerf Single Jar with UDP BW Unit Fixed

JPerf is the GUI frond-end of IPerf, a TCP and UDP bandwidth performance measurement tool which allows the tuning of various parameters and UDP characteristics.

The official JPerf release (2.0.2 version) has some flaws. First, it mistakenly uses bytes/sec as the unit of UDP bandwidth, which should be bits/sec according to IPerf man-page:

-b, --bandwidth #[KM]
       for  UDP,  bandwidth  to  send  at  in  bits/sec (default 1 Mbit/sec,
       implies -u)

Second, starting it from command line is error prone. The command to start it (jperf.sh) is:

java -classpath jperf.jar:lib/forms-1.1.0.jar:lib/jcommon-1.0.10.jar:lib/jfreechart-1.0.6.jar:lib/swingx-0.9.6.jar net.nlanr.jperf.JPerf

We can see that all jar paths in classpath are relative paths. So if we create a symbol link to the jperf.sh script, e.g. /usr/bin/jperf -> /opt/jperf-2.0.2/jperf.sh. Then calling /usr/bin/jperf will result in some errors like:

Exception in thread "main" java.lang.NoClassDefFoundError: net/nlanr/jperf/JPerf
Caused by: java.lang.ClassNotFoundException: net.nlanr.jperf.JPerf
at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
at java.lang.ClassLoader.loadClass(ClassLoader.java:252)
at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:320)
Could not find the main class: net.nlanr.jperf.JPerf. Program will exit.

This error can be fixed by resolving the real path of the symbol link, as I reported.

However, a better way to solve this problem is to pack all libs JPerf needed(i.e. forms*.jar, jcommon*.jar, jfreechart*.jar, swingx*.jar) to a single jar, and add a proper "Manifest". Then we will be able to start JPerf with a much simpler command:

java -jar jperf.jar

And finally, I gave a try to solve the above 2 flaws and put my work (deb/jar/src packets) on my site. You can find them here .

qRFCview Proxy Patch

This patch enables qRFCview to load proxy settings from environment variables such as "http_proxy" and "socks_proxy". Yes, popping up a dialog to set these things is fancier, but that means more coding work. I am pretty satisfied with this solution.

You can download the modified source code tarball from http://share.solrex.org/ibuild/qrfcview-0.62-solrex2.tar.gz . Binary packets for special Linux distributions can be found at http://share.solrex.org/ibuild/ too.

diff -aurN qrfcview-0.62/src/mainwindow.cpp qrfcview-0.62-solrex/src/mainwindow.cpp
--- qrfcview-0.62/src/mainwindow.cpp    2006-01-13 17:56:45.000000000 +0800
+++ qrfcview-0.62-solrex/src/mainwindow.cpp    2009-07-14 16:03:56.000000000 +0800
@@ -122,8 +122,10 @@
{
   // Load a RFC number
   bool bOK;
+  /* NOTE 20090714/Solrex  <http://solrex.org>:
+     Enlarge RFC number limit to 10000. */
   int iRFCNum = QInputDialog::getInteger(this, tr("Please enter a RFC number"),
-                                             tr("RFC#:"), 0, 1, 5000, 1, &bOK);
+                                             tr("RFC#:"), 0, 1, 10000, 1, &bOK);
   if (bOK)
     RFCLoad( iRFCNum );
}
diff -aurN qrfcview-0.62/src/rfcloader.cpp qrfcview-0.62-solrex/src/rfcloader.cpp
--- qrfcview-0.62/src/rfcloader.cpp    2006-01-13 17:56:45.000000000 +0800
+++ qrfcview-0.62-solrex/src/rfcloader.cpp    2009-07-14 15:58:50.000000000 +0800
@@ -25,11 +25,41 @@
#include <QMessageBox>
#include <QtDebug>
#include <QDir>
+#include <QNetworkProxy>

QRFCLoader::QRFCLoader(QObject *parent)
  : QObject(parent)
{
   m_qHttp=new QHttp(this);
+  /* NOTE 20090714/Solrex <http://solrex.org>:
+     Detect proxy settings via system environment variable
+     ``http_proxy'' and ``socks_proxy''. */
+  char *p;
+  QNetworkProxy qNetworkProxy(QNetworkProxy::NoProxy);
+  if ((p = getenv("socks_proxy")) != NULL) {
+    qNetworkProxy.setType(QNetworkProxy::Socks5Proxy);
+  } else if ((p = getenv("http_proxy")) != NULL) {
+    qNetworkProxy.setType(QNetworkProxy::HttpProxy);
+  }
+  if (p != NULL) {
+    QString proxyStr = p;
+    proxyStr = proxyStr.trimmed();
+    proxyStr.remove("http://");
+    QStringList list = proxyStr.split("@");
+    QStringList list1 = list[0].split(":");
+    if (list.count() > 2) {
+      qNetworkProxy.setType(QNetworkProxy::NoProxy);
+      qDebug() << "Unresolvable proxy setting:" << list;
+    } else if ( list.count() == 2) {
+      qNetworkProxy.setUser(list1[0]);
+      qNetworkProxy.setPassword(list1[1]);
+      list1 = list[1].split(":");
+    }
+    qNetworkProxy.setHostName(list1[0]);
+    qNetworkProxy.setPort(list1[1].toInt());
+  }
+  m_qHttp->setProxy(qNetworkProxy);
+  qDebug() << "Loaded proxy:" << p;
   connect(m_qHttp, SIGNAL( requestStarted(int) ), this, SLOT( startDownload(int) ) );
   connect(m_qHttp, SIGNAL( requestFinished(int, bool) ), this, SLOT( fileDownload(int, bool) ) );
   connect(m_qHttp, SIGNAL( responseHeaderReceived(QHttpResponseHeader) ), this, SLOT( receivedHeader(QHttpResponseHeader) ) );

RFC number limit issue was discussed at deb-packages-of-qrfcview-and-jabref.html .

解决 GAppProxy Set-Cookie 和 HTTPS Cert Bugs

我自己写了一个类似 GAppProxy 的工具,支持 Python 和 PHP,有兴趣可以看这里

研究 GAppProxy 有两个原因:一、最近 Twitter 不能用,而我常用的 GAppProxy 却不支持我登录 Twitter;二、我最近在琢磨 SSL 证书的问题,正好用 GAppProxy 登录 Twitter 也有证书错误。

第一个 BUG:Set-Cookie Bug

GAPPProxy 目前对 Cookie 的处理有一些问题,主要出在对 header 中的多个 Set-Cookie 域处理错误,就会导致用户登录一些网站错误,无法获得正确的会话 Cookie。

举例,当服务器返回的 header 中有多个 Set-Cookie 域时,比如一般的 wordpress 返回的 header 中,Set-Cookie 域至少有三个:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly
Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly
Set-Cookie:
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

GAPPProxy 会把它作为一个串传给本地浏览器:

Set-Cookie:
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-content/plugins; httponly,
wordpress_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C8b89cfc80161853957182ddfc481cd72;
path=/wp-admin; httponly,
wordpress_logged_in_776c41a2fee8d137928f3750eb1f0736=admin%7C1247298611%7C545dcea44d5e69aec5c1203c64bee061;
path=/; httponly

这样本地浏览器对 Cookie 的设置就会错误。解决办法很简单,将这个长串用split(', ')切开,同样设置三个 Set-Cookie 域即可。

Update 20090710/Solrex:
有人评论说 ', ' 也是可能在 Cookie 中出现的合法字符串;那么我就另外想了一个办法,先用正则表达式替换将 ', ***=' 替换成 'n***=',再用 'n' 对字符串进行切割。由于在 Cookie 中正常出现的 ', ' 后面会首先跟着 ';' 或 ',',然后才可能出现 =,因此用 ‘, ([^,;]+=)’ 匹配就可以了。而且这次把修改放到服务器端了,原来的客户端就不需要修改了

Patch:

Index: fetchserver/fetch.py
===================================================================
--- fetchserver/fetch.py    (revision 92)
+++ fetchserver/fetch.py    (working copy)
@@ -29,6 +29,7 @@
from google.appengine.ext import webapp
from google.appengine.api import urlfetch
from google.appengine.api import urlfetch_errors
+import re
# from accesslog import logAccess

@@ -153,14 +154,12 @@
             if header.strip().lower() in self.HtohHdrs:
                 # don't forward
                 continue
-            ## there may have some problems on multi-cookie process in urlfetch.
-            #if header.lower() == 'set-cookie':
-            #    logging.info('O %s: %s' % (header, resp.headers[header]))
-            #    scs = resp.headers[header].split(',')
-            #    for sc in scs:
-            #        logging.info('N %s: %s' % (header, sc.strip()))
-            #        self.response.out.write('%s: %srn' % (header, sc.strip()))
-            #    continue
+            # NOTE 20090710/Solrex: Fix multi-cookie process problem
+            if header.lower() == 'set-cookie':
+                scs = re.sub(r', ([^,;]+=)', r'n1', resp.headers[header]).split('n')
+                for sc in scs:
+                    self.response.out.write('%s: %srn' % (header, sc.strip()))
+                continue
             # other
             self.response.out.write('%s: %srn' % (header, resp.headers[header]))
             # check Content-Type

第二个 BUG:HTTPS Cert Bug

简单地来说,GAppProxy HTTPS 连接的实现是一个欺骗本地浏览器的过程,类似于中间人攻击。它首先用 GAE 获得页面的明文,抓到本地,然后假冒 HTTPS 站点与本地浏览器通信。因此它就需要提供一个 SSL 证书,来完成 HTTPS 连接的建立。

但这就有一个问题,SSL 证书哪儿来的?目前 GAppProxy 对所有的站点都使用一个证书,而且这个证书是未经任何授权 CA 认证的证书,因此就会产生很多错误。首先,该证书中的授权机构不是可信 CA,Firefox 和 IE 中捆绑的证书中没有该 CA 的证书;其次,该证书的 CN (Common Name)与站点域名不同,不可用于站点的通信。因此可能每次都需要用户自己点击添加证书例外。

为了避免这种缺陷,我想出来的做法是——自己做 CA,正所谓做事情要专业,要骗就骗彻底点,骗得浏览器神不知鬼不觉。首先,建立 CA 的密钥,自己给自己签发一个证书作为 CA 的根证书,将该 CA 的证书安装到 Firefox 浏览器中,在运行时使用该 CA 为每个 HTTPS 连接的站点签发对应于该站点的证书。由于 Firefox 中已经安装了该 CA 的证书,那么所有该 CA 签发的证书都能够通过 Firefox 的检测了。

这个方法比较麻烦,而且需要电脑上有 openssl,对于 Linux 完全没有问题,对于 Windows 可能就有点儿困难了。所以我这里就给出个思路,具体实现就不谈了。
您可以在 http://share.solrex.org/ibuild/ 找到我修改后的代码,感兴趣的话可以下载下来看看。

在 Ubuntu 9.04 上安装 Kscope

Kscope 是我很喜欢的 Linux 平台上的代码查看工具,因为我不会用 Emacs,vim + ctags 又用得不熟,看看小程序还可以,看大项目就傻眼了。以前也尝试过 Source-Navigator(这个项目N年没更新,06年时候我装都装不上,08年底居然又复活了,有空了再去试试)、Eclipse、Kdevelop、CodeBlocks,总之都没有 Kscope 用着最舒服。Kscope 让我欣赏的特点主要有:

1. 它号称是代码编辑环境(source-editing environment),而不是IDE。我不用在建立 Kscope 项目时烦心地去选择项目类型、编译器、编译选项等等。编译我有 Makefile,我就是找个工具看看代码,用得着那么麻烦吗。 建立 Kscope 项目时只需要干两件事:选择项目名、项目保存地址和添加源文件。

2. 它不会在源文件目录下建立一堆乱七八糟的文件,影响市容。我记得 Eclipse、CodeBlocks 等都会把项目信息保存在源文件目录下,而 Kscope 的项目保存位置可以自己选,比如我一般都保存在 workspace/kscope 目录下面,这样对要查看的源文件目录没有任何影响。因此 Kscope 的项目和源文件基本没关系,我可以添加任何位置的源文件到某个项目中去。

3. 它不会去读非指定类型的文件。这是针对 Eclipse 来说的,每次在 Eclipse 项目中搜索时,一堆 .svn 目录中文件的结果让我感觉非常闹心,两年没用不知道现在的 Eclipse 是不是更智能点儿了,但是 Eclipse 改不了的毛病就是慢和吃内存。

4. 它支持代码查看的基本功能。其实我最常用的也就那么几个功能:语法高亮、同时打开多文件、整个项目中搜索字符串、查找函数定义位置和引用、项目文件列表+搜索。在这些条上据说 Windows 下的 SourceInsight 做得更好,但我没用过没有发言权。

简而言之,Kscope 与其它工具比就是快、简单、省心。但是时代在变革呀,转眼到了 KDE4 的时代,而 Kscope 仍然停留在 KDE3.5 上。现在的 Ubuntu 9.04 的依赖关系里,居然已经撤掉了 Kscope,在 9.04 上 sudo apt-get install kscope,会得到这样的消息:E: Couldn't find package kscope,真是让人丧气。

其实 Kscope 之所以不能安装,主要原因是它依赖于 Kate 的两个库:libkateinterfaces.so.0 和 libkateinterfaces.so.0,只需要从 KDE3.5 的 Kate 中提取出来这两个库安装到系统中后,Kscope 就可以正常运行了。Ubuntu 9.04 的依赖关系中虽然找不到 Kscope,但是 Ubuntu 的软件仓库中还有 Kscope 的包,我们可以手动下载安装。下面这个脚本的功能就是自动安装 kscope 到 Ubuntu 9.04,稍微修改一下也可以用于在其它 KDE4 桌面系统中安装 Kscope,或者解决 Kscope 无法运行的问题。您也可以从这里下载到该脚本:

#!/bin/bash
# This script helps you install Kscope on Ubuntu 9.04.
# You can also use it to fix "Kscope doesn't run in KDE4" bug.

echo "Determining machine hardware name... "
MACHINE=`uname -m`
case "$MACHINE" in
  i386 | i586 | i686)
    ARCH="i386"
    ;;
  x86_64)
    ARCH="amd64"
    ;;
  *)
    ARCH="i386"
    ;;
esac

# If Kscope is not installed, install it.
which kscope &> /dev/null
if [ $? -ne 0 ]; then
  echo "Installing kscope..."
  sudo apt-get install kscope || \
  wget http://archive.ubuntu.com/ubuntu/pool/universe/k/kscope/kscope_1.6.0-1_${ARCH}.deb && \
  sudo dpkg -i kscope_*.deb || \
  sudo apt-get -fy install || \
  echo "Oops, some error happens..."
fi

kscope -v &> /dev/null
if [ $? -eq 0 ]; then
  echo "Kscope works fine."
  exit
fi

echo "Downloading KDE3 libraries needed by kscope..."
wget http://ftp.debian.org/debian/pool/main/k/kdebase/kate_3.5.9.dfsg.1-6_${ARCH}.deb
dpkg -x kate_3*.deb kate

echo "Installing KDE3 libraries..."
sudo cp kate/usr/lib/libkateinterfaces.so.0.0.0 /usr/local/lib/
sudo cp kate/usr/lib/libkateutils.so.0.0.0 /usr/local/lib
sudo ln -s /usr/local/lib/libkateinterfaces.so.0.0.0 /usr/local/lib/libkateinterfaces.so.0
sudo ln -s /usr/local/lib/libkateutils.so.0.0.0 /usr/local/lib/libkateutils.so.0
sudo ldconfig

echo "Finished."

WordPress 2.8 和 2.8.1 beta1 毛病真多

前两天我才在 Twitter 炫耀了一下我忍住没当小白的努力,没想到昨天晚上被哥们一忽悠,他说他没有发现 WP 2.8 的 bug,就升级了一下 WordPress 到 2.8。很惨痛呀!

先是升级到 2.8,发现控制板引入链接内存分配失败,fatal error,然后控制台的链接点了都没用。这也是在官方 2.8.1 Beta1 对 2.8 修补的时候说明了的:

Dashboard memory usage is reduced. Some people were running out of memory when loading the dashboard, resulting in an incomplete page.

于是我说升级到 2.8.1 Beta1 呗,控制台是差不多加载完了,插件不好用了。Google Sitemap 插件内存分配失败,fatal error;编辑插件文件内存分配失败,fatal error。

于是我不能忍了,回退到 2.7.1,世界真美好呀!

Linux 下 Firefox 变身 Google Chrome

几乎可以达到以假乱真的效果,屏幕截图请看:

Linux 下 Firefox 变身 Chrome

要求:
1. KDE 4 ---> Gnome 的窗口无法隐藏标题栏,影响变身效果(这也是目前我觉得 KDE 更好玩的原因之一)
2. Firefox 3.5 以上 ---> 3.5 以下无法使用 Chromifox Extreme 主题,只能使用 Basic Chromifox Extreme 主题
3. Chromifox Companion 扩展 ---> 配合 Chromifox Extreme 主题修改工具栏图标
4. Hide Menubar 扩展 ---> 隐藏菜单栏,可以使用 alt 呼出

步骤:
1. 安装 Firefox 3.5。下载 tar.bz 包之后解压到 /opt 目录下,比如目录名为 /opt/firefox,修改 /usr/bin/firefox 软链接到 /opt/firefox/firefox。
2. 安装 Flash 插件。在 /opt/firefox/plugins 目录下建立软链接 libflashplayer.so 到 /usr/lib/flashplugin-installer/libflashplayer.so。
3. 打开 Firefox,查看版本是否 3.5,安装 Chromifox Extreme 主题
4. 安装 Chromifox Companion 扩展
5. 安装 Hide Menubar 扩展
6. 设置隐藏标题栏。右击标题栏,Advanced->Special Application Settings->Preferences->No border,下拉选择 Apply Initially,勾上后面的复选框。
7. 重启 Firefox。

Update:在 Ubuntu 下安装 Google Chrome (开发者版)和 Google Earth

$ echo "deb http://dl.google.com/linux/deb/ stable main" > google-chrome.list
$ sudo mv google-chrome.list /etc/apt/sources.list.d/
$ wget https://dl-ssl.google.com/linux/linux_signing_key.pub -O - | sudo apt-key add -
$ sudo apt-get update
$ sudo apt-get install google-chrome googleearth

遗憾的是,Chrome4Linux 目前还不支持中文。

Solrex Linux Cheatsheet

Cheatsheet:原意是考试的时候带的小抄,所以说是 cheat(作弊) sheet。在计算机科学领域里,主要是指记录一些难记命令或者操作的快查表,有时候和 quick reference card 意思相同,中文或者可以翻译成:命令速查表?

由于很少看到中文的比较完善的 Cheatsheet,我想,做一个这样的 Cheatsheet 应该挺不错。所以我就我自己使用 Linux 的经验,打算整理出来一个中文的 Linux Cheatsheet。目前放出零点零一版,您可以到这里下载,目前的具体状况如下图所示:

Solrex Linux Cheatsheet

这个 Cheatsheet 包含两个文件,一个 pdf 文档,一个 odt 文档。如果您想帮助我完善它,您可以将您修改过的 odt 文档发送给我。如果您的建议被采纳,您的名字(或代号)和链接将被添加到该页面的“感谢”一栏中,谢谢。

最后重复,Solrex Linux Cheatsheet 的主页在:https://github.com/solrex/solrex/tree/master/linux_cheatsheet/

PS: 其实我昨天就想发这篇博客了,但是两天来我所在的北京网通 ADSL 网络访问我的博客一直有问题,不排除被封的危险。如果您想继续关注本博客,推荐您使用 Google Reader 或者抓虾等在线 RSS 订阅器订阅本博客的 RSS,这样被封后至少您能看到本博客的文章更新。

使用 kdiff3 进行 svn 版本比较

svn diff 命令的效果总的来说还是不错的。因为它是基于行的比较,在比较格式规范的程序代码文件时候也足够了,尤其是每行不超过 termial 宽度的时候。但是在比较那些单行长度比较长的文件时,比如 HTML, TEX 等,仅仅修改一个字母也会引起两行不同,用 svn diff 查看被修改的地方时就比较痛苦了。

Linux 下有一些比较好的比较程序,比如 kdiff3, gdiff, vimdiff 等,kdiff3 可以用不同颜色显示两个文档中不同的行、字符,算是比较理想的比较程序。我们可以拿它来替换掉 svn diff 默认的比较程序。

很显然这种需求别人也会有,所以早就有高手解决了这样的问题,您可以从这里下载一个 bash 脚本,将其重命名为 svndiffwrapper,加上可执行权限放到一个可执行路径(比如 /usr/bin/)下。然后修改您的 ~/.subversion/config 文件,找到 diff-cmd,修改为如下所示:

### Set diff-cmd to the absolute path of your 'diff' program.
###   This will override the compile-time default, which is to use
###   Subversion's internal diffimplementation.
diff-cmd = svndiffwrapper
### Set diff3-cmd to the absolute path of your 'diff3' program.
###   This will override the compile-time default, which is to use
###   Subversion's internal diff3 implementation.
diff3-cmd = svndiffwrapper

这样,当您再执行 svn diff 的时候,svn 会默认调用 kdiff3 进行文件比较。由于 kdiff3 是 GUI 程序,所以文件比较将会是一个一个进行的,关掉第一个文件比较的 kdiff3 窗口,才会显示第二个文件的比较窗口。

网友 sleetdrop 评论指出,如果使用基于 Python 的 Meld 作为 GUI 比较工具,那么不需要任何 wrapper,只要修改上述配置为:

diff-cmd = meld

即可。不可否认,这比 kdiff3 简便了不少,而且 meld 还支持在比较时编辑,这也是一个很大的优点。

vasprintf 会将空间分配到栈上吗?

由于提交过几次 Linux Fetion 的 bug 和 patch,Linux Fetion 的开发者邀请我加入了 Linux Fetion GUI 的维护者团队中。

昨天晚上和今天下午,我和邓东东(DDD)一直在调试一个 Linux Fetion 在 64 位电脑上的段错误 BUG。这是一个非常奇怪的 BUG,其表现为在 64 位电脑上(Ubuntu 9.04)运行 Linux Fetion 在登录成功后会经常出现 Segmentation Fault。DDD 确定该 BUG 存在于 Libfetion 库中,并且和读取联系人信息的函数有关。Libfetion 论坛上一直有人抱怨类似问题,但是在 DDD 的 64 位虚拟机上却无法重现此 BUG(他的 libc 是 2.7 版本的)。

由于 DDD 仍然不愿意公开 libfetion 库的源代码,我只好等每次他修改库文件之后发给我再调试。经过了好几个小时的努力,今天下午我发现,该 BUG 的主要成因非常有可能是:子函数中本应被动态分配到堆(heap)上的空间被分配(或误写)到了栈(stack)上,子函数返回调用者之后指向子函数栈内容的指针非法。

由于该动态分配的空间是使用 vasprintf 自动分配的,从 DDD 给我的部分代码来看指针传递出问题的可能性不大。那么我想,是不是 vasprintf 函数并不能保证动态分配的空间在 heap 上呢?希望对此有了解的朋友指点一下,谢谢!

$ uname -a
Linux Slytherin 2.6.28-12-generic #43-Ubuntu SMP Fri May 1 19:31:32 UTC 2009 x86_64 GNU/Linux
$ gcc -v
Using built-in specs.
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.3.3-5ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-4.3/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --enable-nls --with-gxx-include-dir=/usr/include/c++/4.3 --program-suffix=-4.3 --enable-clocale=gnu --enable-libstdcxx-debug --enable-objc-gc --enable-mpfr --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.3.3 (Ubuntu 4.3.3-5ubuntu4)
$ ll /lib/libc.so.6
lrwxrwxrwx 1 root root 11 2009-04-13 10:26 /lib/libc.so.6 -> libc-2.9.so

PS:我在写这篇博文过程中搜索了一下 Wikipedia,发现这样一段话:

int asprintf(char **ret, const char *format, ...)

asprintf automatically allocates enough memory to hold the final string. It sets *ret to a pointer to the resulting string, or to an undefined value if an error occurred (GLibc is notable in being the only implementation that doesn't always set *ret to NULL on error).

那么是不是分配失败导致了错误的发生呢?但是如果分配失败,为什么子函数返回前的指针的确指向一段在栈上的字符串呢?