Random Tech Thoughts

The title above is not random

Safari 又不能使用本地 PAC 文件了

之前解决过 Lion 下 Safari 使用本地 PAC 文件的问题。升级到 Mountain Lion 后发现 Safari 不能访问放在 ~/Library/Internet Plug-Ins 目录下的 PAC 文件了。对比了下 Lion 和 ML 的 webkit2 sandbox configuration 文件,发现 ML 把下面这两行去了

1
2
(subpath "/Library/Internet Plug-Ins")
(home-subpath "/Library/Internet Plug-Ins")

这个应该就是问题原因所在了。(不知道为什么安装在这里的插件依然能够正常使用。)

为了避免以后系统升级再遇到问题,决定还是通过本地开 http 服务器来提供 PAC 文件。我平常使用 polipo 把 socks 代理转成 http 代理,所以直接用了它自带的 http 服务器功能。

没装过 http 服务器的可以用系统自带的 apache。Mountain Lion 把 web sharing 从系统配置中移除了(放到了 OS X Server 中),但 apache 其实还在。这篇文章介绍了如何在 ML 下使用 apache。

用 Go 写 Bprint 的一点体会

上次说到 hexdump 指定二进制文件格式以及打印的方式不好用,所以用 Go 写了一个替代,起名叫 bprint (想不到更好的名字了)。代码以及使用说明都在 github 上。这个工具跟 hexdump 的 format string 最大的差别在于把二进制文件格式和打印方式分开来指定,个人觉得这种方式使用起来更加方便。

Go 的确是个不错的语言,标准库很多函数的接口设计得很好用。说下写这个小工具时的一点感受吧。

写这个程序时要处理二进制文件。Go 是强类型的,没法像 C 一样读一块内存以后随便当成各种类型的整数,必须用 encoding.binary 这个库提供的函数来读取各种类型的整数。这一点使得 bprint 的性能肯定没法跟 C 版本的相比。

另一方面强类型使得 Go printf 一类函数的 format string 不需要指定整数大小,有符号无符号整数可以使用相同的字符指代,因为这些信息从参数的类型里可以直接得到。Go 的 printf 类函数用起来很舒服。

标准库的正则表达式用起来也还算方便(当然还是没法跟 Ruby 这样内建正则表达式支持的语言相比),其 API 命名很有意思也很值得学习。用来做字符串匹配的函数名匹配于下面这个正则表达式:

Find(All)?(String)?(Submatch)?(Index)?
  • String 表示函数接受 string 类型的参数,没有这个名字的接受 []byte 为参数
  • Submatch 表示返回值是否包含匹配的各个 group
  • Index 则表示返回匹配内容还是返回匹配项的起止索引
  • All 表示是否返回所有匹配内容

这种命名方式使得我们看函数名就能知道函数的用途,所以很容易找到自己需要的函数。(这让我想起 Joel Spolsky 的文章 Making Wrong Code Look Wrong 里提到的正确的匈牙利命名法。)貌似标准库的正则表达式是把 Russ CoxRE2 用 Go 重新实现了一遍,有空的时候想看下它的代码。

另外 flag 库用来做命令行选项的解析真是比 C 的 getopt 好用太多。

Go 没有 exception,类似 C 通过返回值来表示错误,不过由于有 multi return value,所以 Go 使用这种方式比 C 方便一些。这种错误处理的方式的确会使代码看起来不那么干净,但不会带来 exception 的问题。(Exception 本质上是一种 non-local goto,其存在的问题可以看 Raymond Chen 的文章 Cleaner, more elegant, and harder to recognize)。

go 这个工具使得我们可以不用写 Makefile,自动格式化代码,方便的编写和执行测试,这些特性都使得我们可以更加专注于写代码而不用跟编译构建系统做斗争。go 这个工具的存在让我体会到了下面这句话的含义

“Go is not meant to innovate programming theory. It’s meant to innovate programming practice.” — Samuel Tesla

Go 的确是一个很好的 C 的替代,目前性能还不是非常好,但相信未来随着编译器的进步会有提升。暂时还没有用到 Go concurrency 的特性,但看起来也是很诱人。

如果对 Go 有兴趣,想知道 Rob Pike, Ken Thompson 还有 Robert Griesemer 为什么发明 Go 以及 Go 的设计思想,推荐 Rob Pike 的文章 Less is exponentially more

Hexdump Format String 的说明

对于简单的结构化数据的二进制文件,可以通过 hexdump 指定 dump 的格式来方便的查看。但它的 man page 写的非常难懂,我把自己的理解做些记录。

假定某个二进制文件的每个元素对应于下列 C struct:

1
2
3
4
5
6
struct foo {
    int8_t  i8;
    int16_t i16;
    int32_t i32;
    int32_t k32;
} __attribute__((packed));

用下面的 Ruby 脚本可以生成一个测试的二进制文件:

1
2
3
4
5
6
7
a = [8, 16, 32, 32]
b = a.map { |e| -e }

File.open("bindata", "w") do |f|
  f.write(a.pack("csll"))
  f.write(b.pack("csll"))
end

在 dump 该二进制文件时希望把每个元素的内容单独作为一行打印出来。format string 可以告知 hexdump 如何处理二进制的字节。我先给出 hexdump 的调用方式,然后具体解释其中 format string 的含义:

hexdump -e '/1 "%i " /2 "%i " 2/4 "%i " "\n"' bindata

-e 后面跟的就是 format string。每个 format string 可以包含多个 format unit,每个 format unit 可以包含三个部分,其形式如下(方括号表示可选,format 一定要用双引号引起来):

[ [iteration_count]/byte_count ] "format"

例如 2/4 "%i ",其中 2/4 表示循环 2 次,每次处理 4 个字节,"%i " 表示以十进制数打印,format 支持 printf conversion string,不过不支持大小修饰符。几个注意点:

  • 转换字符处理的字节数由 byte count 决定(默认为 4),如果指定了 byte count,则 format 中只能包含一个 conversion string
  • conversion string 支持的 byte count 是有限制的,例如 %i 只支持 1, 2, 4 byte count,不能使用 /3 "%i"(Debian 6 上的 hexdump 不支持 64 bit 的整数,byte count 不能为 8,但是 Minix 的 hexdump 是支持的
  • 上面例子中 format string 最后的 "\n" 是一个只包含 format 的 format unit

如果想在打印十进制的同时以十六进制输出,可以通过多次指定 format string 的方式实现,如下:

hexdump -e '/1 "%2i " /2 "%3i " 2/4 "%3i " " | " ' \
    -e '/1 "%02x " /2 "%04x " 2/4 "%08x " "\n"' bindata
# output
#  8  16  32  32 | 08 0010 00000020 00000020
# -8 -16 -32 -32 | f8 fff0 ffffffe0 ffffffe0

指定多个 format string 时,hexdump 每次处理一个 block 的数据,block 的大小为 format string 中需要字节数最大的那个。(format string 处理数据大小不同时的行为 man page 描述难以理解,现在没有需求就不去弄清楚了。)

hexdump 有一些特殊的 convertion string,例如 %_a[dox] 可以获取当前的 offset:

hexdump -e '"offset %2_ad: " /1 "%i " /2 "%i " 2/4 "%i " "\n"' bindata

理解 format string 以后再看 man page 查找其他功能就会方便很多了。

关于 format string 设计的一点想法

hexdump 的 format string 其实也是一个 mini language,不过觉得它设计得怪怪的,把指定二进制文件格式和指定如何打印混在了一起。

指定二进制文件的格式用 Ruby Array.pack 中的 template string 我觉得更加方便直观,然后可以另外的 print string 来指定如何打印。

例如上面的例子,直接用创建二进制文件时的 template string "csll" 作为 format string, print string 为 "%c %hd %d %d\n"。这样的 mini language 使用起来就方便直观很多。有空的时候自己实现一个看看。

我的第一个 OS X 程序 PinYinNick

去年买了 Dialvetica 却发现这个应用不支持用拼音搜索。十一回家的时候写了个汉字转拼音的库,跟作者联系希望能加入拼音的支持。未果,失望之余也无他法。后来了解到 MacRuby 可以直接调用 Cocoa Framework,写了个 Ruby 脚本给所有联系人添加了拼音缩写,在 Dialvetica 里打开昵称搜索后用的也挺欢的。一个没想到的好处是其他程序比如 Address Book 里要找联系人也可以用昵称,iPhone 上凡是搜索联系人的地方也都能用拼音缩写。

我那个 Ruby 脚本只能在装了 MacRuby 的机器上用。这次五一回去的时候学了下 Cocoa 编程,写了个 native 的程序来做这事。

代码在 Github 上,项目为 PinYinNick。编译好的文件也已上传,可点此下载。欢迎使用。

我几乎从来不写 GUI 程序,写这个程序的过程中得到了不少以往没有的体验。自从不碰 Java 以来一直都是用 Vim 写代码,这次觉得有个好用的 IDE 还是能提高不少效率的。 Interface Builder 真心好用,跟 Cocoa 的 key value coding/observing 结合使可以少写很多代码(当然这个程序的界面还是相当简单的)。静态程序分析对代码的检查的确可以避免一些错误,我刚开始用 ARC,有些内存管理的问题 Xcode 直接提示我解决掉了。最大的缺点是 Xcode 4.3 挺容易崩溃的。

虽然我没什么 GUI 编程的经验,不过 Cocoa 用起来还算顺手,感觉很方便、功能很强大。(比本科时用 Java Swing 方便太多了。)或许以后会再尝试写些程序来满足自己的需求。

QEMU 源代码笔记

自己的代码过段时间不看就会忘,看别人的代码忘的更快。折腾 QEMU 很久了,经常会要回头看看过的代码,然后发现忘记各种细节。几次之后终于开始做笔记。

今天又多记了一笔,干脆稍微整理一下发布出来,有兴趣的点此访问

由于是想到才记,所以没有统揽全局的那种 overview,非常零碎。有几个 QEMU 的模块比较复杂,用 LucidChart 画了几张图,看起来可能会方便一些。

跟 tips 一样,会不定期更新。

让 Safari 的 Google Search 使用 HTTPS

Google Plus 我很少用,但是 +1 我觉得还是一个不错的功能。以前看到不错的页面会存书签,后来发现这些书签平时不会用到,想搜东西的时候一来不会去搜书签,二来书签不包含内容也搜不到。而 +1 过的页面以后再次 Google 的时候会更容易找到,可以很方便的用来 mark 自己觉得有价值的网页。(Twitter 的 search 太差。Evernote 的 webclip 不错,不过比起单击一下就能 mark 还是麻烦了一点。所以只有非常有价值的内容我才会用 webclip。)

不过在使用 Safari 时,即使登录了 Google,搜索页面也没有 +1 按钮。原因是 Safari 的 Google Search 默认用了 http,而 +1 按钮只有使用 https 时才会显示。我通过修改 SafariOminibar 的 search provider 文件使 Google Search 默认使用 https。

SafariOminibar 是一个使 Safari 地址栏和 Chrome 地址栏行为类似的一个 plugin,个人使用下来很稳定,推荐使用。注意它不是 extension,而是通过 SIMBL 利用 InputManager system 注入代码这种 hack的方式实现的一个插件。下载安装之后修改 /Library/Application Support/SIMBL/Plugins/SafariOmnibar.bundle/Contents/Resources/SearchProviders.plist 这个文件,把 Google 的 url 全部改成 https 开头即可达到目的。

由于 Safari 5.2 将实现 Ominibar 的功能,因此 SafariOminibar 已经停止开发。等 Safari 5.2 发布之后可能得寻找其他方法。

Safari 无法使用本地 PAC 文件的原因和解决办法

Mountain Lion 修改了 Safari 能访问的目录,所以下文的修复方法失效了。在 Mountain Lion 上还是通过 http 服务器来提供 pac 文件吧,具体参考我的新文章

最近网络环境非常糟糕,无法访问 Google 的情况越来越多。实验室的公用代理经常无法使用,不得不在本地写几个 PAC 文件备用。使用过程中发现用本地的 PAC 文件设定代理 Chrome, Firefox, Twitter For Mac 都能正常使用,唯独 Safari 不行。但是一旦本地开一个 web server 来获取这个文件就一切正常。

Google 一番得到结果,原因是 OS X Lion 的 sandbox 机制限制了 Safari 能够访问的文件,而我指定的 PAC 文件 Safari 没有权限读取。

很多程序的 sandbox configuration 文件位于 /usr/share/sandbox,这些文件限定了程序能够访问什么文件。例如这个目录下的 sshd.sb 文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
;; Copyright (c) 2008 Apple Inc.  All Rights reserved.
;;
;; sshd - profile for privilege separated children
;;
;; WARNING: The sandbox rules in this file currently constitute
;; Apple System Private Interface and are subject to change at any time and
;; without notice.
;;

(version 1)

(deny default)

(allow file-chroot)
(allow file-read-metadata (literal "/var"))

(allow sysctl-read)
(allow mach-per-user-lookup)
(allow mach-lookup
  (global-name "com.apple.system.notification_center")
  (global-name "com.apple.system.logger"))

有没有很惊讶?本来我只会发个推说下 PAC 文件会因为 sandbox 而无法使用,但是看到它的 configuration 使用 Scheme 来定义的时候决定写博客了。

Safari 的 sandbox configuration 位于 /System/Library/PrivateFrameworks/WebKit2.framework/WebProcess.app/Contents/Resources/com.apple.WebProcess.sb

知道 PAC 文件不能使用的原因后解决起来就方便了。修改配置文件当然可以,但是遇到 Safari 升级可能要重新修改。另一个简单点的办法是把 PAC 文件放到 Safari 可以访问的目录,例如 ~/Library/Internet\ Plug-Ins 目录

PAC 文件及其调试

Update on 2012-04-29. 重构 PAC 文件,用数组保存所有需要用代理访问的域名,遍历数组来创建对象以加快查找。

Proxy auto-config (PAC) 文件可指定使用代理服务器的规则。比如在教育网内不能访问国外网站,我们可以通过 PAC 文件指定仅当访问某些国外网站时使用代理服务器,其他时候直接访问。

如果熟悉 PAC 文件,只对调试方法有兴趣,请跳到 调试 PAC 文件

PAC 文件用 JavaScript 编写,必须包含 FindProxyForURL(url, host) 函数。在访问某个网址时,浏览器会调用 FindProxyForURL 根据其返回值来决定该如何访问。该函数的说明如下:

  • 参数:
  • 返回值为字符串,告诉浏览器如何访问。下面是一些可用的返回值:
    • "DIRECT" 直接访问,不使用代理
    • "PROXY host:port 使用 HTTP 代理
    • "SOCKS host:port" 使用 SOCKS 代理,因为不支持 DNS 解析,不推荐
    • "SOCKS5 host:port" 使用 SOCKS5 代理
      • 注意,Safari 虽然支持 SOCKS5 代理,但是不支持在 PAC 文件中返回的 SOCKS5,只认 SOCKS
      • 一个 workaround 是返回多个代理服务器配置,把SOCKS5 放在最前面,接一个 SOCKS,这样支持 SOCKS5 返回值的浏览器可以正常使用,而 Safari 会忽略第一个 SOCKS5。例如 SOCKS5 127.0.0.1:1080; SOCKS 127.0.0.1:1080; DIRECT
      • 另一个办法是把 SOCKS 代理用polipo 转成 http 代理
    • 可以返回多个代理服务器,用分号分隔,浏览器会按顺序尝试。例如 "PROXY host:port; SOCKS5 host2:port2; DIRECT"

下面是一个 PAC 文件的例子(这个文件的目的应该很清楚),完整版本见 github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
var direct = 'DIRECT';
var http_proxy = 'PROXY host:port; DIRECT';

var blocked_list = [
  "akamai.net",
  "akamaihd.net"
];

var blocked = {};
for (var i = 0; i < blocked_list.length; i += 1) {
  blocked[blocked_list[i]] = true;
}

function host2domain(host) {
  var dotpos = host.lastIndexOf(".");
  if (dotpos === -1)
    return host;
  // Find the second last dot
  dotpos = host.lastIndexOf(".", dotpos - 1);
  if (dotpos === -1)
    return host;
  return host.substring(dotpos + 1);
};

function FindProxyForURL(url, host) {
  return blocked[host2domain(host)] ? http_proxy : direct;
};

发布了收集的一些 Tips

把自己收集的关于 OS X, Linux 还有一些软件使用的 tips 用 Octopress 发布了出来。都是在用 Mac 以后才开始记录的,Spotlight 让我不至于因为不能迅速打开文件而懒得记录。

Tips 列表,直接浏览的话肯定挺无聊的,有些笔记内容非常少。这些笔记会不断的更新,也方便自己在没带笔记本的时候查看。

Octopress 默认的 CSS 对 list 的处理真难看,不过实在不擅长改网页相关的东西,等出更漂亮的主题以后再换吧。

SaaS Class Environment Set Up on OS X

Update

ruby-debug-base19 version 0.11.25 does not work with ruby 1.9.3-p0 on OS X. I found a workaround on WyeWorks Blog. Here’s my modified gist to install the working version with RVM managed ruby-1.9.3-p0


I’m taking the SaaS class. The class project can be done using the pre-created VirtualBox image or on Amazon EC2 virtual machine, but I want to do it directly on my Mac because its more convenient for me. Luckily, there is instructions on how to configure a bare VM.

Looking into the configure script used to configure the ubuntu VM shows that there is nothing special in the software used. It just needs some ruby gems, PostgreSQL, and Sphinx. (The script will also install and configure Vim, Emacs, but that’s not relevant to me.) Configuring the environment on OS X should be easy, if you are not using Xcode 4.3.