博客

iconv(3)

iconv 可能是我遇过的最让人困惑的C标准库函数了——因为它竟然一次性的返回 5 个值,如果算上全局变量 errno 的话,那就是 6 个(如果你知道还有哪个 libc 函数能超过它的,请告诉我)。而作为调用者,必须对返回的 6 个值逐一检查,然后,OMG,可能还需要再调用 iconv 继续转码...如此反复。

所以每当我需要在 C 里面 iconv 一个字符串的话,我会去找 php 4.x 的源代码(现在最新应该是 4.4.9),从 ext/iconv/iconv.c 里面 Copy&Paste 一段代码,再自己简单包装一下使用。

在上周我帮一个程序员提供这个函数包装的时候,我不禁开始思考怎么会有一个如此违背 UNIX 设计哲学的函数存在,后来想到两种可能。一个是字符编码转换这件事情,本来就是一个不那么 UNIX 的操作,再牛的天才,也只能如此设计接口;另一个可能是这个函数完全是 UNIX 商业化过程中为了向非 ANSI 字符语种国家推广,而由一个标准化委员会搞出来的怪物。

如果对 iconv 使用的复杂程度还没有一个感性认识的话,从 PHP 搞来的这段东西会帮助理解这一点:

  1. /*
  2. --------------------------------------------------------------------
  3.                   The PHP License, version 3.01
  4. Copyright (c) 1999 - 2008 The PHP Group. All rights reserved.
  5. --------------------------------------------------------------------
  6.  
  7.      "This product includes PHP software, freely available from
  8.      <<a href="http://www.php.net/software/>".
  9. */
  10. #include">http://www.php.net/software/>".
  11. */
  12. #include</a> <stdlib.h>
  13. #include <string.h>
  14. #include <iconv.h>
  15. #include <errno.h>
  16.  
  17. //from PHP 4.4.9 ext/iconv/iconv.c
  18. //为了便于在 blog 上表现,把返回简化成 3, 2, 1, 0, -1, -2, -3
  19. //返回值非零表示转码失败,其中大于零则说明返回的 out 不需要 free
  20. //这段代码只在 Linux 下测试
  21. int _iconv_string(const char *in_p, size_t in_len, char **out, size_t *out_len, const char *in_charset, const char *out_charset)
  22. {
  23.     iconv_t cd;
  24.     size_t in_left, out_size, out_left;
  25.     char *out_p, *out_buf, *tmp_buf;
  26.     size_t bsz, result = 0;
  27.     int retval = 0;
  28.  
  29.     cd = iconv_open(out_charset, in_charset);
  30.     if (cd == (iconv_t)(-1)) {
  31.         if (errno == EINVAL) {
  32.             return 1; // WRONG_CHARSET
  33.         } else {
  34.             return 2;
  35.         }
  36.     }
  37.  
  38.     in_left= in_len;
  39.     out_left = in_len + 32; /* Avoid realloc() most cases */
  40.     out_size = 0;
  41.     bsz = out_left;
  42.     out_buf = (char *)malloc(bsz+1);
  43.     out_p = out_buf;
  44.  
  45.     while (in_left > 0) {
  46.         result = iconv(cd, (char **) &in_p, &in_left, (char **) &out_p, &out_left);
  47.         out_size = bsz - out_left;
  48.         if (result == (size_t)(-1)) {
  49.             if (errno == E2BIG && in_left > 0) {
  50.                 /* converted string is longer than out buffer */
  51.                 bsz += in_len;
  52.                 tmp_buf = (char*)realloc(out_buf, bsz+1);
  53.                 if (tmp_buf != NULL) {
  54.                     out_p = out_buf = tmp_buf;
  55.                     out_p += out_size;
  56.                     out_left = bsz - out_size;
  57.                     continue;
  58.                 }
  59.             }
  60.         }
  61.         break;
  62.     }
  63.     if (result != (size_t)(-1)) {
  64.         /* flush the shift-out sequences */
  65.         for (;;) {
  66.             result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left);
  67.             out_size = bsz - out_left;
  68.             if (result != (size_t)(-1)) {
  69.                 break;
  70.             }
  71.             if (errno == E2BIG) {
  72.                 bsz += 16;
  73.                 tmp_buf = (char *)realloc(out_buf, bsz);
  74.                 if (tmp_buf == NULL) {
  75.                     break;
  76.                 }
  77.                 out_p = out_buf = tmp_buf;
  78.                 out_p += out_size;
  79.                 out_left = bsz - out_size;
  80.             } else {
  81.                 break;
  82.             }
  83.         }
  84.     }
  85.     iconv_close(cd);
  86.     if (result == (size_t)(-1)) {
  87.         switch (errno) {
  88.             case EINVAL:
  89.                 retval = -1;
  90.                 break;
  91.             case EILSEQ:
  92.                 retval = -2;
  93.                 break;
  94.             case E2BIG:
  95.                 retval = -3;
  96.                 break;
  97.             default:
  98.                 free(out_buf);
  99.                 return 3;
  100.         }
  101.     }
  102.     *out_p = '\0';
  103.     *out = out_buf;
  104.     *out_len = out_size;
  105.     return retval;
  106. }
  107.  
  108. //qyb wrap _iconv_string
  109. //由调用者负责 free 返回值
  110. char *iconv_string(const char *string, const char *in_charset, const char *out_charset)
  111. {
  112.     char *result;
  113.     int retval;
  114.     size_t in_len, out_len;
  115.     in_len = strlen(string);
  116.     retval = _iconv_string(string, in_len, &result, &out_len, in_charset, out_charset);
  117.     if (retval > 0) {
  118.         return NULL;
  119.     }
  120.     return result;
  121. }
Topic: 技术

去铁道博物馆和电影博物馆的行车路线

查看大图

今天去了一趟铁道博物馆和电影博物馆,自驾主要是去铁道博物馆的路不好找。我去的时候是走北四环,然后霄云桥下上芳园西路,酒仙桥路,酒仙桥北路,然后受纸质地图的错误影响,拐到了彩虹路上——其实只要一直沿酒仙桥北路走过了五环就是了——还好从彩虹路一路往东北方向只有一条道,然后上了南皋路,某路口再误打误撞的向南拐,开到一个丁字口的时候停车四处张望,居然就是正确的路口。

这两个博物馆靠的挺近的,适合一天的游玩。免费停车,中午可在电影博物馆里的食堂解决午饭。

铁道博物馆适合小孩玩,和蒸汽机车头合影留念;电影博物馆适合大人,而且理所当然的配置了一个电影院。都值得一去

注意:必须从上图的左下角那个点标示的路口进铁道博物馆,然后向东北方向的蓝线是行车路线;另外一条蓝线则是今天从霄云桥过去绕的大弯。据我从卫星地图上观察,走五环是不太可能绕到南皋路上的,还是从四环过去,走酒仙桥北路或机场高速辅路才靠谱

Topic: 生活

最近看吴晓波和张维迎的文章

吴晓波是我所知不多的中国商业/企管作家,最近他的新文章首富与二富的劫难,将日照钢铁和4年前的铁本对比,让人嗟叹。

最近几个月网上一直热议杨佳,陇南。。。直到现在还有人在打口水仗。其实我觉得民营资本和国家权力的博弈更能影响我们社会的走向,应该更多关注。

还有就是张维迎,那个被网民骂烂了的人,在 FTChinese 上的文章我看中国改革30年。也可以看到很多政治事件和经济改革关联的地方,比如"...1988年春...邓小平同志...下决心搞“价格闯关”...同年8月份中旬,中央政治局开会讨论通过了《关于价格工资改革的初步方案》,并要求军队和警察做好准备...",真是环环相扣。他在文章里对未来三十年的预测也很有意思:"政治体制改革也可以分成两个15年。第一个15年,主要任务是建立相对独立的司法制度,减少政府部门的权力......第二个15年主要是要建立民主选举制度....."

貌似比他"左"很多的郎咸平可从来不说这么激进的话,呵呵

Topic: 社会

setproctitle

从 javaeye 的 robbin 那儿看到 rails 的这个技巧,觉得还是挺实用的,于是就下令在 web.py 上实现类似的功能。

经过 douyuan 同学的一番折腾,然后我也做了几个小实验,最后结论如下:

  • FreeBSD 下有 setproctitle(3) 函数来完成这个功能
  • Linux 对应的是系统调用 prctl(2), option 是 PR_SET_NAME, 宏定义是 15。注意 15 调用是从 2.6.9 引入的,虽然在 2.6.9 的内核头文件 linux/prctl.h 里没有 PR_SET_NAME 的宏定义,但仍然可以直接用 15 来成功调用... 换句话说,RHEL4/CentOS4的系统是可以利用这个特性的

对于 Linux 来说,prctl 并没有修改 argv/cmdline 的值,而是 /proc 文件系统里的内容(comm)。用 top 看是可以看到修改结果的,如果用 ps 命令,必须用参数 c 或者增加 output 格式 comm 才能看到修改结果,比如命令 ps axc (详情请参考 manual)

这样看在 FreeBSD or Linux 2.6.9+ 上用 ctypes 就可以搞定我想要的功能了,但对于更古老的 Linux 内核甚至别的操作系统怎么处理呢?看起来就只能去修改 argv[0] 的内容了,不过修改 argv[0] 也不是一件那么容易的事情,Linux 下运行例程:

  1. #include <unistd.h>
  2.  
  3. extern char **environ;
  4. int main(int argc, char *argv[])
  5. {
  6.     int i;
  7.     for (i = 0; i < argc; i++) {
  8.         printf("%p, %s\n", argv[i], argv[i]);
  9.     }
  10.     for (i = 0; environ[i]; i++) {
  11.         printf("%p, %s\n", environ[i], environ[i]);
  12.     }
  13. }

可以看到命令行参数,以及 environ 环境变量,是存储在一整块连续的内存上的(据说 Solaris 也是这样),如果向 argv[0] 里复制内容过长,有可能造成 environ 被破坏,进而对程序运行产生影响。正确的做法是先把 environ 内容复制出来,再修改 environ[] 数组里的指针地址到新的位置,然后就可以对 argv[0] 为所欲为了。nginx 的 os/unix/ngx_setproctitle.c 也是这么工作的。

换个角度,argv/environ 的这种存储方式使得如果知道所有 argv 的长度之和,从 environ[0] 向前偏移这个数字就是 argv[0] 的地址。这样看写一个 python extension 完成这个功能是可行的——虽然无法在模块里直接获得 argv[0] 地址,但可以取到 environ[0] 的地址,argv 的总长度则可以从 sys.argv 里计算出来然后作为参数传入... 这种方法潜在的问题也不少,比如在一个 python 进程里多次调用该怎么办,可似乎也只能这么来做了。

UPDATE: 发现 prctl 的一个问题是,它最多能设 15 字节长度的 comm, 这就导致可用性降低,也许最终还是要改 argv

Topic: 技术

You're beautiful

吃晚饭的时候,听到背后电视里传来 "You're beautiful, you're beautiful, you're beautiful, it's true",我很诧异——怎么经济节目突然放起 James Blunt 了,回过头来才发现,原来这是大众新宝来的广告曲..

我几乎喷饭,这也太扯了吧,怎么用这首歌来做广告?歌词的结尾可是 But it's time to face the truth,I will never be with you

估计看我 blog 的人知道 James Blunt 的也比较少,分享一下这首歌的 MV

Topic: 音乐

立起一个口香糖瓶

From blog 配图

绝非PS

我右手网络冲浪鼠标点来点去的时候,左手就在玩这个瓶子。瓶里大约还剩21-22颗口香糖的时候,成功的将瓶子斜着立起来了

现在还剩18颗,仍然可以这样立起来。

Topic: 生活

前两天碰到 greader/gmail 暴慢的故障

前一分钟还好好的,突然 gmail 和 greader 就同时慢了下来

故障现象是加载页面(包括 js)速度还算可以接受,但 Ajax 开始运行后就没有数据返回了。在这个期间内其它同事的应用都很正常,可见不是网络问题。被迫用 gmail 的 BasicHTML 模式——不过也是很慢。持续了大约一天的时间,然后突然又同时恢复。

猜测在请求个人的数据的时候(mail & feed),google 的前端 web 服务器收到 request 后,首先要和某个服务交互一下,然后才能继续请求其它服务。好比以前 gmail 的 addressbook 服务故障后导致所有人登录不了邮箱一样,这次又是 reader 和 mail 同时依赖的一个服务出了故障.

下回再碰到这种情况应该就有经验了,耐心等待就是

Topic: 技术

奇怪的休止符

在小步舞曲里面除了波音之外,还出现了一种诡异的休止符(五线谱外的休止符),见下:

From blog 配图

从右手谱来看这里明明是3/4拍的曲子,可左手谱...数一数居然是5/4拍了

搜索到一个达人解释,但看得还是稀里糊涂。不过天才qyb终于最后想明白了这里该怎么弹,就是:左手可以视为在弹两组音。第一小节的一组(一指弹奏)是一个1/4休止符+1/2的 Re,另一组(三指弹奏)是1/2的 Si + 1/4的Si;第二小节同样是(一指弹奏)1/4休止+1/2 Mi,以及 (二指弹奏)1/2 Do + 1/4 Do

详细说第一小节的指法是,左手三指首先弹 Si, 1/4 拍后一指弹 Re, 再 1/4 拍后三指抬起,再落下,1/4拍后小节结束..

详细曲谱在http://www.free-scores.com/download-sheet-music.php?pdf=239

PS: 我的练琴现在完全是自学。女儿还是继续她的汤普森+拜厄教程,我则是按照随电钢琴带的 60 首曲子的难度从低到高练习——难度分A/B/C三档。A档大概是8首曲子,而经典到恶俗的《致爱丽丝》被列入B档,估计要到明年中才开始练习了。电钢琴+Youbute,我觉得是互联网时代的新潮组合,比看什么教学DVD有趣多了

Topic: 音乐

today

1. 第一次一个人开车.
2. 小步舞曲勉勉强强能弹完整了
3. 把内部wiki路径整理了一遍

4. 发现搜狐内部原来有一个 cygwin 的镜像,同时被维护的还有一个 CPAN 镜像.

Topic: 生活
订阅 RSS - 博客 | BT的花