yegle's Blog//blog.yegle.net/2014-02-11T00:00:00+01:00Mutable object as default value and memoization2014-02-11T00:00:00+01:00yegletag:blog.yegle.net,2014-02-11:2014/02/11/mutable-object-as-default-value-and-memoization/<p>Using mutable object as default value in Python is ususally considered a
bad idea because it may seems to be strange for new comers. There's alot
posts on the web that explained the reason why you shouldn't do that,
e.g. <a href="http://effbot.org/zone/default-values.htm">this one</a>.</p>
<p>But using a mutable object, for example a <code>dict</code>, can be convinient if
you want to implement <a href="http://en.wikipedia.org/wiki/Memoization">memoization</a> in your function.</p>
<p>Here's an example of memoization to implement <code>factorial</code>:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">factorial_non_memoization</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">return</span> <span class="n">factorial_non_memoization</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">n</span>
<span class="n">_cache</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">factorial_memoization</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">_cache</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="mi">1</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">_cache</span><span class="p">[</span><span class="n">n</span><span class="p">]</span> <span class="o">=</span> <span class="n">factorial_memoization</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">n</span>
<span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">n</span><span class="p">]</span>
</pre></div>
<p>If you need repeatly call to <code>factorial</code> function, the memoized version
can save a lot of time. But this function needs an extra global
variable, something that smells bad. The fix is simple, write a
decorator, and this decorator can even be generalized to <code>memoize</code> any
function call.</p>
<p>How about a simple solution, using a mutable object as default value?
Here it is:</p>
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">factorial</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">_cache</span><span class="o">=</span><span class="p">{</span><span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">}):</span>
<span class="k">if</span> <span class="n">n</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">_cache</span><span class="p">:</span>
<span class="n">_cache</span><span class="p">[</span><span class="n">n</span><span class="p">]</span> <span class="o">=</span> <span class="n">factorial</span><span class="p">(</span><span class="n">n</span><span class="o">-</span><span class="mi">1</span><span class="p">)</span><span class="o">*</span><span class="n">n</span>
<span class="k">return</span> <span class="n">_cache</span><span class="p">[</span><span class="n">n</span><span class="p">]</span>
</pre></div>Speedup scons over your small project2014-01-28T00:00:00+01:00yegletag:blog.yegle.net,2014-01-28:2014/01/28/speedup-scons-over-your-small-project/<h2 id="tldr">TL;DR</h2>
<p>In your <code>SConstruct</code> file, add the <code>tools</code> keyword argument to
<code>Environment</code> and <code>DefaultEnvironment</code> initialization to explicitly
specify the tools needed in your project. It saves a lot of time.</p>
<h2 id="the-story">The Story</h2>
<p>My resume was written in <code>LaTeX</code> and convert to <code>PDF</code> using a
handwritten <code>Makefile</code>, of which I always want to replace using a more
advanced automation tool.</p>
<p>So I'm looking at <code>scons</code>. It's written in <code>Python</code> which is a language
I like, and looks much simpler than <code>CMake</code>. But one thing I don't like
is the speed.</p>
<p>Here's the original <code>SConstruct</code>:</p>
<div class="codehilite"><pre><span class="n">convert</span> <span class="o">=</span> <span class="n">Builder</span><span class="p">(</span><span class="n">action</span><span class="o">=</span><span class="p">[</span>
<span class="s">"convert -alpha off -density 300 $SOURCE -append $TARGET"</span><span class="p">])</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">BUILDERS</span><span class="o">=</span><span class="p">{</span><span class="s">"Convert"</span><span class="p">:</span> <span class="n">convert</span><span class="p">})</span>
<span class="n">pdf</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">PDF</span><span class="p">(</span><span class="s">"resume.tex"</span><span class="p">)</span>
<span class="n">png</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">Convert</span><span class="p">(</span><span class="s">'resume.png'</span><span class="p">,</span> <span class="n">pdf</span><span class="p">)</span>
<span class="n">Default</span><span class="p">(</span><span class="n">pdf</span><span class="p">)</span>
</pre></div>
<p>So what this <code>SConstruct</code> file did is that it compiles the <code>resume.tex</code>
file into PDF, and then optionally convert the PDF into a PNG file.</p>
<p>And the time it takes? Here it is:</p>
<div class="codehilite"><pre><span class="nv">$ </span>scons -f SConstruct.before --debug<span class="o">=</span><span class="nb">time</span> . <span class="p">|</span>grep ^Total
Total build <span class="nb">time</span>: 8.844431 seconds
Total SConscript file execution <span class="nb">time</span>: 2.516214 seconds
Total SCons execution <span class="nb">time</span>: 0.094147 seconds
Total <span class="nb">command </span>execution <span class="nb">time</span>: 6.234070 seconds
</pre></div>
<p>In the total build time ~8 seconds, ~3 seconds was used in executing the
<code>SConstruct</code> file. Let's run a cleanup which make it more obvious:</p>
<div class="codehilite"><pre><span class="nv">$ </span>scons -f SConstruct.before --debug<span class="o">=</span><span class="nb">time</span> -c . <span class="p">|</span>grep ^Total
Total build <span class="nb">time</span>: 2.669596 seconds
Total SConscript file execution <span class="nb">time</span>: 2.579540 seconds
Total SCons execution <span class="nb">time</span>: 0.090056 seconds
Total <span class="nb">command </span>execution <span class="nb">time</span>: 0.000000 seconds
</pre></div>
<p>In a total build time ~2.6 seconds, ~2.5 seconds is wasted.</p>
<p>I know that there's a lot posts talking about <a href="http://www.electric-cloud.com/blog/2010/08/11/the-last-word-on-scons-performance/">how slow <code>scons</code> is</a>
but I don't know it could be <em>that</em> slow. If this is a large project
managed by <code>scons</code>, 2.5 seconds seems insignificant but for a simple
task like compiling <code>LaTeX</code> file it's unacceptable.</p>
<p>After a simple profiling it become obvious that <code>scons</code> takes too much
time in initializing the <code>Environment</code> instance:</p>
<div class="codehilite"><pre><span class="nv">$ </span>python2 -m cProfile -s cumtime <span class="sb">`</span>which scons<span class="sb">`</span> -f SConstruct.before -c .
...
Ordered by: cumulative <span class="nb">time</span>
<span class="nb"> </span>ncalls tottime percall cumtime percall filename:lineno<span class="o">(</span><span class="k">function</span><span class="o">)</span>
...
<span class="m">2</span> 0.000 0.000 2.838 1.419 Environment.py:917<span class="o">(</span>__init__<span class="o">)</span>
</pre></div>
<p>Luckily there's a <a href="http://www.scons.org/wiki/GoFastButton">GoFastButton</a> page on <code>scons</code> wiki that caught my
eyes: too many unnessesary <code>tools</code> in the <code>Environment</code> is a cause of
slow down.</p>
<p>It's straight forward after figuring out the cause. Here's an updated
<code>SConstruct</code>:</p>
<div class="codehilite"><pre><span class="n">DefaultEnvironment</span><span class="p">(</span><span class="n">tools</span><span class="o">=</span><span class="p">[])</span>
<span class="n">convert</span> <span class="o">=</span> <span class="n">Builder</span><span class="p">(</span><span class="n">action</span><span class="o">=</span><span class="p">[</span>
<span class="s">"convert -alpha off -density 300 $SOURCE -append $TARGET"</span><span class="p">])</span>
<span class="n">env</span> <span class="o">=</span> <span class="n">Environment</span><span class="p">(</span><span class="n">BUILDERS</span><span class="o">=</span><span class="p">{</span><span class="s">"Convert"</span><span class="p">:</span> <span class="n">convert</span><span class="p">},</span> <span class="n">tools</span><span class="o">=</span><span class="p">[</span><span class="s">'pdftex'</span><span class="p">])</span>
<span class="n">pdf</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">PDF</span><span class="p">(</span><span class="s">"resume.tex"</span><span class="p">)</span>
<span class="n">png</span> <span class="o">=</span> <span class="n">env</span><span class="o">.</span><span class="n">Convert</span><span class="p">(</span><span class="s">'resume.png'</span><span class="p">,</span> <span class="n">pdf</span><span class="p">)</span>
<span class="n">Default</span><span class="p">(</span><span class="n">pdf</span><span class="p">)</span>
</pre></div>
<p>Notice the <code>tools</code> keyword argument in initializing <code>Environment</code>, and
the <code>tools</code> keyword argument to <code>DefaultEnvironment</code>. The result is a
significantly reduced build time (for a small project):</p>
<div class="codehilite"><pre><span class="nv">$ </span>scons -f SConstruct.after --debug <span class="nb">time</span> . <span class="p">|</span>grep ^Total
Total build <span class="nb">time</span>: 6.579649 seconds
Total SConscript file execution <span class="nb">time</span>: 0.037326 seconds
Total SCons execution <span class="nb">time</span>: 0.112706 seconds
Total <span class="nb">command </span>execution <span class="nb">time</span>: 6.429617 seconds
<span class="nv">$ </span>scons -f SConstruct.after --debug <span class="nb">time</span> . -c <span class="p">|</span>grep ^Total
Total build <span class="nb">time</span>: 0.122485 seconds
Total SConscript file execution <span class="nb">time</span>: 0.030382 seconds
Total SCons execution <span class="nb">time</span>: 0.092103 seconds
Total <span class="nb">command </span>execution <span class="nb">time</span>: 0.000000 seconds
</pre></div>
<h2 id="conclusion">Conclusion?</h2>
<p>If you know what you are going to build (you already know it would be a
<code>C++</code> project, or in my case building <code>PDF</code> from <code>LaTeX</code>), you'd better
tell <code>scons</code> explicit what tools you want to be included in the
<code>Environment</code>. If your project is small, the saved time would be
significant.</p>
<p>P.S. I found it hard to decide what tools you want to be included in
<code>Environment</code>. The full list of available tools can be found in <code>man 1
scons</code>, but there's no description on what those tools do. For example,
in my case of compiling <code>*.tex</code> file into <code>*.pdf</code> file, there are three
relevant tools available: <code>latex</code>, <code>pdftex</code>, <code>pdflatex</code>. I have to test
among them to know <code>pdflatex</code> is the right one.</p>VPN服务关闭2012-12-23T00:00:00+01:00yegletag:blog.yegle.net,2012-12-23:2012/12/23/vpn-close/<p>以下为XXX VPN服务商的最后一个newsletter。</p>
<p>Goodbye my friends.</p>
<p>This is the last newsletter you'll receive from XXX VPN service provider.</p>
<p>Some of you may have heard about this, and some have received the refund I issued. Due to GFW's blockage, XXX has stopped providing VPN service.</p>
<p>The refund for active users will be issued ASAP. For those who want to receive refund earlier, please send email to XXX. But please understand that I can only send refund to users with more than 1 month remaining.</p>
<p>I wrote some explanation in Chinese below because I'm not sure if I can explain my feeling accurately in English.</p>
<hr />
<p>再见朋友们</p>
<p>这将是你从XXX VPN服务商收到的最后一封通知邮件。</p>
<p>有部分人可能已经听说,或者有部分人已经收到了退款。由于GFW的封锁,XXX已停止继续提供服务。</p>
<p>对尚在激活的用户的退款工作正在进行。如果希望尽快完成退款,请发送邮件到XXX。但是请理解,退款仅针对剩余有效期在一个月以上的用户。</p>
<p>这个VPN的服务是在2009年10月开始提供的。VPN服务连同其用户管理系统,原本作为我的本科毕业设计。在最开始做这个系统时,我完全没有想过这个系统能维持到现在。</p>
<p>3年时间,整个系统服务了超过2000个用户,服务器的数量从最早的1台服务器扩充到4台服务器,认证方式从最早的手工签发数字证书到后来的使用FreeRadius做认证后端,付款方式从手动付款到集成支付宝自动付款。</p>
<p>这3年真的认识了不少朋友,真心感谢你们的支持和鼓励。很多朋友希望我能提高价格继续提供服务。我确实考虑过,但由于我现在已经离开了上一个公司,目前正在求学,对于风险的承担能力已经下降很多,再也无法提供稳定服务。对于这些朋友的信任,我非常感谢但是只能说抱歉了。</p>
<p>俗套地说一句,我有一个梦想,有一天醒来听说GFW停止工作。</p>
<p>就这样吧。</p>在虚拟主机上运行Flask-Twip2012-11-22T00:00:00+01:00yegletag:blog.yegle.net,2012-11-22:2012/11/22/run-flask-twip-on-virtual-host/<p>关于<code>Flask-Twip</code>的介绍请参考<a href="/2012/11/19/flask-twip/">前一篇文章</a>。</p>
<p><code>Flask-Twip</code>支持在普通的虚拟主机上运行,通过虚拟主机的<code>CGI</code>界面运行。</p>
<h2 id="cgi">什么是CGI</h2>
<p><code>CGI</code>界面(Common Gateway Interface)是在每次HTTP请求时,由HTTP server调用一个脚本,在调用脚本时将客户的HTTP request headers信息放在环境变量中,然后将脚本到<code>stdout</code>的输出当作HTTP response返回给客户端。</p>
<p>每个HTTP请求都会进行一次fork操作,CGI脚本的性能非常糟糕。不过对API proxy来说足够了。</p>
<h2 id="flask-twip">如何在普通虚拟主机上搭建Flask-Twip</h2>
<p>要在虚拟主机上搭建Flask-Twip,需要虚拟主机提供了SSH访问以方便搭建环境。</p>
<h3 id="python">准备工作:安装Python</h3>
<p><code>Flask-Twip</code>是<code>Flask</code>框架的一个扩展,<code>Flask</code>本身已经支持Python3,但是它依赖的<code>werkzeug</code>目前最高仅支持Python2.7。为了方便未来支持Python3,<code>Flask-Twip</code>本身仅支持Python2.7。</p>
<p>首先判断Python版本:<code>ssh</code>到服务器上,执行<code>python --version</code>。若版本不等于Python2.7,需要手动安装Python2.7。具体步骤不在这里说明。安装Python2.7不需要root权限,编译安装到你自己的家目录里即可。</p>
<p>Python2.7安装完毕后,执行以下命令安装<code>distribute</code>、<code>pip</code>和<code>virtualenv</code>(如果你安装到自己家目录里,需要用绝对地址调用python,例如<code>/home/yegle/python3/bin/python</code>)</p>
<div class="codehilite"><pre>curl http://python-distribute.org/distribute_setup.py <span class="p">|</span> python
curl https://raw.github.com/pypa/pip/master/contrib/get-pip.py <span class="p">|</span> python
pip install virtualenv
</pre></div>
<h3 id="_1">创建虚拟环境</h3>
<p>虚拟环境(<code>virtualenv</code>)是Python的一个常用工具,用于自动化创建一个隔离的Python运行环境。</p>
<p>在任意位置执行<code>virtualenv venv</code>,其中<code>venv</code>是你需要的虚拟环境的名字(名字本身不重要)。<code>virtualenv</code>会在创建虚拟环境时输出信息。</p>
<p>创建完毕后执行<code>source venv/bin/activate</code>。你的命令行提示符会加上一个<code>(venv)</code>字样提醒你目前是在Python的<code>virtualenv</code>里。</p>
<h3 id="flask-twip_1">安装Flask-Twip</h3>
<p><code>pip install Flask-Twip</code></p>
<h3 id="cgi_1">准备CGI脚本</h3>
<p>请参考<a href="https://github.com/yegle/flask_twip/blob/master/examples/cgi/twip.cgi">https://github.com/yegle/flask_twip/blob/master/examples/cgi/twip.cgi</a>脚本。</p>
<p>注意:脚本头部的<code>#!/usr/bin/env python</code>需要修改为你虚拟环境内的Python(例如你创建的虚拟环境在<code>/home/yegle/venv/</code>,那么脚本头部应该修改为<code>#!/home/yegle/venv/bin/python</code>)</p>
<p>样例<code>twip.cgi</code>脚本中使用的是<code>FileBackend</code>,这是与以前twip的PHP版一样的文件形式存储。脚本中<code>be = FileBackend(folder='/home/yegle/cgi-bin/tokens')</code>一句指定了backend的存储目录是<code>/home/yegle/cgi-bin/tokens</code>。你可以根据你的需要进行修改。</p>
<h3 id="_2">配置文件</h3>
<p>配置文件请参考<a href="https://github.com/yegle/flask_twip/blob/master/examples/settings-example.py">https://github.com/yegle/flask_twip/blob/master/examples/settings-example.py</a>。需要填写你的OAuth Key/Secret。</p>
<p>配置文件中的<code>SECRET_KEY</code>变量请使用随机字符串。这个是Flask的session加密用key</p>
<p>将修改好后的配置文件与CGI脚本放在你的主机商指定的cgi-bin目录,并给<code>twip.cgi</code>脚本加上可执行权限(<code>chmod +x twip.cgi</code>)。</p>
<h3 id="_3">测试</h3>
<p>打开浏览器访问该CGI脚本,进行测试</p>
<h2 id="_4">测试站点</h2>
<p>我已搭建了一个测试站点供大家测试。由于域名被DNS污染,此测试站点仅做可行性测试 <a href="https://yegle.net/cgi-bin/twip.cgi/">https://yegle.net/cgi-bin/twip.cgi/</a></p>
<h2 id="_5">问题与解答</h2>
<p>如有问题可以在留言提出,我会尽量作答。提问前请先学习<a href="http://www.beiww.com/doc/oss/smart-questions.html">提问的智慧</a>,否则我会直接删除。</p>Flask-Twip2012-11-19T00:00:00+01:00yegletag:blog.yegle.net,2012-11-19:2012/11/19/flask-twip/<p>写<code>Flask</code>,拿<code>twip</code>练手。项目叫<code>Flask-Twip</code></p>
<p>项目主页: <a href="https://github.com/yegle/flask_twip/">https://github.com/yegle/flask_twip/</a></p>
<p>原来<code>twip</code>是Twitter API Proxy in PHP,现在仍然叫<code>twip</code>是因为现在是Twitter API Proxy in Python。当然未来也不排除蛋疼写个<code>Ruby</code>、<code>Node.js</code>版,所以<code>twip</code>还是当作Twitter API Proxy来解释比较好。</p>
<p><code>Flask-Twip</code>作为<code>Flask</code>的extension存在,与<code>twip</code>类似使用<code>MPL 1.1</code>开源。</p>
<p>目前支持原来的O模式,T模式还在测试尚未release,应该很快也能搞定了。</p>
<p>环境方面,已经实现CGI和Heroku支持,GAE的支持也会考虑(虽然实在没太大兴趣去支持这个陈旧的PaaS平台),以及Redhat的OpenShift。具体的脚本可参考项目中<code>examples</code>目录。</p>
<p>鉴于<code>Flask-Twip</code>使用CGI运行已经完全能支持现有的虚拟主机环境,初步决定是PHP版的twip不再进行特性增加,所有新功能仅在<code>Flask-Twip</code>上增加。</p>
<p>应该用Python的开发更多,希望大家常提Pull-Request吧。谢谢。</p>
<p>已上传到<code>PyPI</code>,可通过<code>pip install Flask-Twip</code>进行安装。</p>Flask与`unicode_literals`2012-10-21T00:00:00+02:00yegletag:blog.yegle.net,2012-10-21:2012/10/21/flask-and-unicode-literals/<p>最近在写<code>Flask</code>,遇到一个诡异的问题</p>
<div class="codehilite"><pre><span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/app.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1701</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__call__</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">wsgi_app</span><span class="p">(</span><span class="n">environ</span><span class="p">,</span> <span class="n">start_response</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/app.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1685</span><span class="p">,</span> <span class="ow">in</span> <span class="n">wsgi_app</span>
<span class="k">with</span> <span class="bp">self</span><span class="o">.</span><span class="n">request_context</span><span class="p">(</span><span class="n">environ</span><span class="p">):</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/ctx.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">274</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__enter__</span>
<span class="bp">self</span><span class="o">.</span><span class="n">push</span><span class="p">()</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/ctx.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">238</span><span class="p">,</span> <span class="ow">in</span> <span class="n">push</span>
<span class="bp">self</span><span class="o">.</span><span class="n">session</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">app</span><span class="o">.</span><span class="n">open_session</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">request</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/app.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">792</span><span class="p">,</span> <span class="ow">in</span> <span class="n">open_session</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">session_interface</span><span class="o">.</span><span class="n">open_session</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/flask/sessions.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">191</span><span class="p">,</span> <span class="ow">in</span> <span class="n">open_session</span>
<span class="n">secret_key</span><span class="o">=</span><span class="n">key</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/werkzeug/contrib/securecookie.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">312</span><span class="p">,</span> <span class="ow">in</span> <span class="n">load_cookie</span>
<span class="k">return</span> <span class="n">cls</span><span class="o">.</span><span class="n">unserialize</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">secret_key</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/git/apkay/apkay-venv/lib/python2.7/site-packages/werkzeug/contrib/securecookie.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">259</span><span class="p">,</span> <span class="ow">in</span> <span class="n">unserialize</span>
<span class="n">mac</span> <span class="o">=</span> <span class="n">hmac</span><span class="p">(</span><span class="n">secret_key</span><span class="p">,</span> <span class="bp">None</span><span class="p">,</span> <span class="n">cls</span><span class="o">.</span><span class="n">hash_method</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/.gentoo/usr/lib/python2.7/hmac.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">133</span><span class="p">,</span> <span class="ow">in</span> <span class="n">new</span>
<span class="k">return</span> <span class="n">HMAC</span><span class="p">(</span><span class="n">key</span><span class="p">,</span> <span class="n">msg</span><span class="p">,</span> <span class="n">digestmod</span><span class="p">)</span>
<span class="n">File</span> <span class="s">"/Users/yegle/.gentoo/usr/lib/python2.7/hmac.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">72</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__init__</span>
<span class="bp">self</span><span class="o">.</span><span class="n">outer</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">key</span><span class="o">.</span><span class="n">translate</span><span class="p">(</span><span class="n">trans_5C</span><span class="p">))</span>
</pre></div>
<p>排查之后发现是定义<code>secret_key</code>的文件头有加入<code>unicode_literals</code>,导致报错。</p>
<p>将<code>unicode</code>的<code>secret_key</code>改为<code>b'blablabla'</code>解决问题</p>Email与TLS加密传输2012-05-01T00:00:00+02:00yegletag:blog.yegle.net,2012-05-01:2012/05/01/email-and-tls/<p>提起Email与TLS加密传输,很多手动设置过Email客户端的人会说:我知道这个,我在用的qq邮箱/hotmail/yahoo.cn邮箱支持TLS加密传输。</p>
<p>曾经我也以为邮件的加密传输就是这么简单。但是昨天发现的一个事实让我知道,GFW不会让大家这么省心。</p>
<h2 id="connection-reset">诡异的connection reset</h2>
<p>事情的起因是:我想参与<a href="http://bbs.wps.cn/thread-22321491-1-1.html">金山WPS for Linux版的测试</a>,用我的Gmail邮箱向帖子中提到的邮箱地址<code>wps_linux@kingsoft.com</code>发送了报名邮件发送了报名邮件。</p>
<p>接下来的几天我的邮箱收到了多次Gmail发出的Delivery Status Notification:</p>
<div class="codehilite"><pre>This is an automatically generated Delivery Status Notification
THIS IS A WARNING MESSAGE ONLY.
YOU DO NOT NEED TO RESEND YOUR MESSAGE.
Delivery to the following recipient has been delayed:
XXXXXXX@kingsoft.com
Message will be retried for 2 more day(s)
Technical details of temporary failure:
Unspecified Error (SENT_RCPT): Connection reset by peer
</pre></div>
<p>这里的Connection reset by peer,对于所有了解过GFW的人都不陌生。在这之前陆续也有我的邮件收到类似的Delivery Status Notification。当时以为只是部分Google的IP地址被墙导致的超时。在看到这里的Connection reset之后终于忍不住我开始排查原因了。感谢<a href="https://twitter.com/yangzhe1990">@yangzhe1990</a>的帮助,在回忆了一下SMTP协议的命令之后我得到了排查的结论:</p>
<h2 id="_1">邮件地址被「墙」</h2>
<p>这个结论真是无比神奇。到底如何被墙呢?举实际的例子说明:</p>
<div class="codehilite"><pre># 从墙外向墙内SMTP服务器发起telnet连接
$ telnet mail.kingsoft.com 25
Trying 219.141.176.248...
Connected to telecom.mail.kingsoft.com.
Escape character is '^]'.
220 mail.kingsoft.com ESMTP
EHLO yegle.net
250-mail.kingsoft.com
250-8BITMIME
250 SIZE 20971520
MAIL FROM:我的邮箱地址@gmail.com
Connection closed by foreign host.
# 从墙内向墙外SMTP服务器发起telnet连接
$ telnet aspmx.l.google.com 25
Trying 209.85.225.27...
Connected to aspmx.l.google.com.
Escape character is '^]'.
220 mx.google.com ESMTP u6si11379881igw.58
EHLO yegle.net
250-mx.google.com at your service, [183.151.34.162]
250-SIZE 35882577
250-8BITMIME
250-STARTTLS
250 ENHANCEDSTATUSCODES
MAIL FROM:<mail@example.com>
250 2.1.0 OK u6si11379881igw.58
RCPT TO:<我的邮箱地址@gmail.com>
551 User not local; please try <forward-path>
Connection closed by foreign host.
</pre></div>
<p>也就是说,我无法从墙外向墙内SMTP服务器发邮件,墙内也无法向我的邮件地址发邮件(需要连接google的MTA服务器)</p>
<p>发现这点之后我连声感慨:这待遇真是高档,没话说。我终于明白为什么支付宝的通知邮件很久一段时间都没有成功收到了。</p>
<h2 id="_2">发送邮件时都发生了什么</h2>
<p>对于邮件收发的过程,之前还真没仔细了解过。遇到这种事情才去看了一下资料。整理了一下一封电子邮件的收发大致流程,这里以<code>tom@example.com</code>向<code>jerry@example.net</code>发送邮件为例。</p>
<p>几个词汇:</p>
<ul>
<li><code>SMTP服务器</code>:用于<em>用户->服务器</em>发送邮件时,接收来自用户的邮件。假设example.com的SMTP服务器地址为smtp.example.com</li>
<li><code>MTA服务器</code>:用于<em>服务器->服务器</em>发送邮件时,接收外部服务器投递到本坞的邮件。MTA服务器本身其实也是一个SMTP服务器。假设example.net的MTA服务器地址为mx.example.net</li>
</ul>
<p>发送邮件的大致流程:</p>
<ol>
<li>Tom在自己的邮件客户端里写好邮件</li>
<li>Tom的邮件客户端连接到smtp.example.com的25端口,将邮件内容发送到example.com的服务器上保存</li>
<li>example.com根据这封邮件的目的地址,判断本邮件需要传送给外部服务器example.net</li>
<li>example.com进行DNS查询,查询example.net的MX记录对应的MTA服务器是mx.example.net</li>
<li>example.com作为一个SMTP客户端,连接到mx.example.net的25端口,发送邮件给example.net</li>
<li>example.net判断邮件收件地址是本地地址,接收邮件并投递到相应用户</li>
</ol>
<p>UPDATE: 感谢<a href="https://twitter.com/xmxsuperstar">@xmxsuperstar</a>的提醒。一个域邮件到另外一个域的传递,中间有可能经过不止一个MTA做中转。</p>
<h2 id="_3">发信过程中如何进行传输加密?</h2>
<p>「加密」很时髦,各种免费邮箱都提供了TLS加密传输功能,有的还支持旧的SSL加密功能,让人很有「安全感」。但事实上怎样呢?</p>
<p>前面有提到「SMTP服务器」和「MTA服服务器」。而绝大多数免费邮箱提供的TLS加密传输、SSL加密只是在SMTP服务器上进行了部署设置。在MTA服务器上不支持TLS、SSL加密。</p>
<p>也就是说,在上面说的流程里,绝大多数免费邮箱在步骤2里(邮件客户端到邮箱SMTP服务器阶段)实现了TLS加密传输,而根本没有在步骤5里(邮件服务器到外坞邮件MTA服务器阶段)进行加密。这个服务端到服务端之间的通讯过程,用户是没有任何办法进行介入的。</p>
<p>更关键的是:即使你作为邮件服务提供商,让自己的MTA服务器支持了TLS加密传输,你仍然需要依赖外部邮件服务器在向你的MTA服务器发送邮件时,选择使用TLS加密传输。这样苛刻的条件让绝大多数邮件服务器之间的通讯都是明文、不加密的(至少,邮件收发地址是明文,不可能完成加密的)</p>
<p>我说的「绝大多数免费邮箱」,存在例外。以下是目前发现的,在收(自己的MTA服务器)、发(往外部MTA服务器发送邮件)两个方向都使用TLS加密传输的邮件服务提供商:</p>
<ul>
<li>Gmail</li>
<li>iCloud</li>
<li>FastMail.fm</li>
</ul>
<p>而国内常见的邮件服务都是不支持TLS加密传输的。已测试确认不支持TLS的有:</p>
<ul>
<li>qq.com</li>
<li>hotmail.com</li>
<li>live.com</li>
<li>163.com</li>
</ul>
<h2 id="gpgtls">我使用了GPG加密,有没有TLS加密传输无所谓吧?</h2>
<p>这个观点是错误的。你的邮件内容加密了,GFW可以用简单粗暴的方式直接过滤掉你的邮件。</p>
<ul>
<li>TLS加密传输是传输时加密,在两个邮件服务器通讯期间数据是加密状态,但是在这个阶段前后,邮件仍然是明文可读的。理论上说邮件服务提供商的工程师可以看见你的明文邮件内容</li>
<li>GPG加密是端到端加密,理论上说除了邮件收发双方,其他任何人都无法获取邮件明文。但是GPG不对收发地址进行加密</li>
</ul>
<h2 id="tls">如何测试我使用的邮件服务器是否支持TLS加密传输?</h2>
<p>证实自己的邮件服务器在收信方向上是否支持TLS加密传输比较简单。以下假设我需要知道qq.com邮箱是否支持收信时TLS加密传输</p>
<p>确认你的邮件服务提供商的MTA服务器地址。Windows用户请使用<code>nslookup -type=MX qq.com</code>, 感谢<a href="https://twitter.com/ayanamist">@ayanamist</a>的提醒</p>
<div class="codehilite"><pre><span class="nv">$ </span>dig MX qq.com
<span class="p">;</span> <<> DiG 9.7.3-P3 <<> MX qq.com
<span class="p">;;</span> global options: +cmd
<span class="p">;;</span> Got answer:
<span class="p">;;</span> ->>HEADER<span class="s"><<- opco</span>de: QUERY, status: NOERROR, id: 38689
<span class="p">;;</span> flags: qr rd ra<span class="p">;</span> QUERY: 1, ANSWER: 3, AUTHORITY: 4, ADDITIONAL: 0
<span class="p">;;</span> QUESTION SECTION:
<span class="p">;</span>qq.com. IN MX
<span class="p">;;</span> ANSWER SECTION:
qq.com. <span class="m">2178</span> IN MX <span class="m">30</span> mx1.qq.com.
qq.com. <span class="m">2178</span> IN MX <span class="m">10</span> mx3.qq.com.
qq.com. <span class="m">2178</span> IN MX <span class="m">20</span> mx2.qq.com.
<span class="p">;;</span> AUTHORITY SECTION:
qq.com. <span class="m">62766</span> IN NS ns1.qq.com.
qq.com. <span class="m">62766</span> IN NS ns2.qq.com.
qq.com. <span class="m">62766</span> IN NS ns3.qq.com.
qq.com. <span class="m">62766</span> IN NS ns4.qq.com.
<span class="p">;;</span> Query <span class="nb">time</span>: <span class="m">360</span> msec
<span class="p">;;</span> SERVER: 8.8.8.8#53<span class="o">(</span>8.8.8.8<span class="o">)</span>
<span class="p">;;</span> WHEN: Tue May <span class="m">1</span> 00:07:56 2012
<span class="p">;;</span> MSG SIZE rcvd: 156>>
</pre></div>
<p>使用telnet命令连接到MTA服务器的25端口</p>
<div class="codehilite"><pre><span class="nv">$ </span>telnet mx1.qq.com 25
Trying 64.71.138.83...
Connected to mx1.qq.com.
Escape character is <span class="s1">'^]'</span>.
<span class="m">220</span> newmx33.qq.com MX QQ Mail Server
EHLO example.com
<span class="m">250</span> newmx33.qq.com
STARTTLS
<span class="m">502</span> Error: <span class="nb">command </span>not implemented <span class="c"># 此处会有相关的错误信息,表示此MTA服务器不支持TLS加密传输</span>
</pre></div>
<p>证实自己的邮件服务器在发信方向上是否支持TLS加密传输,暂时没有找到简单的方法。在网上找了一个测试TLS的网站可以进行测试:<a href="http://checktls.com/perl/TestSender.pl">http://checktls.com/perl/TestSender.pl</a>。</p>
<h2 id="_4">我该如何选择?</h2>
<ol>
<li>国内的邮箱用户:请只与国内邮箱用户收发邮件。与国外邮箱用户收发邮件100%会经过GFW的审核(或者说检查)。(说实话我真高兴我的邮箱再也收不到国内邮箱用户的邮件了!</li>
<li>国外不支持TLS加密传输的邮箱用户:尽量转用支持TLS加密传输的邮箱如Gmail。如果无法做到,请尽量使用GPG加密传输你发出的邮件,并请提醒收信方,他回复的邮件将以明文在公网上传输</li>
<li>国外支持TLS加密传输的邮箱用户:好样的!但是请提醒所有没有使用支持TLS加密传输邮箱的收件人,你向他发送的邮件以及他回复的邮件将以明文在公网上传输,并请建议他使用支持TLS加密传输的邮箱服务</li>
</ol>
<h2 id="_5">总结</h2>
<ol>
<li>金山WPS for Linux的工程师,如果看见这篇日志,请把我加入到测试人员列表吧…</li>
<li>草泥马的GFW!</li>
</ol>Hello World2012-04-30T00:00:00+02:00yegletag:blog.yegle.net,2012-04-30:2012/04/30/hello-world/<p>由于Wordpress的臃肿低效,以及对markdown语法的喜爱,这里将成为我的新Blog。</p>
<p>博客的内容偏geek,不会涉及太多生活相关的内容。我不喜欢道听途说、转载+自己理解这种低级的博客风格,喜欢刨根问底甚至翻阅源码的问题解决思路。</p>
<p>废话不多说。写完这篇,开始准备本博客的第一篇日志。</p>Bash计算方差2011-07-17T00:00:00+02:00yegletag:blog.yegle.net,2011-07-17:2011/07/17/variance-bash/<p>一个计算方差的bash函数,使用bc进行计算,默认精度是20(bc的-l选项)</p>
<div class="codehilite"><pre>join<span class="o">(){</span>
<span class="c"># Concatenate arguments with seperator</span>
<span class="c"># like implode() in PHP or string.join() in python</span>
<span class="nb">local </span><span class="nv">ret</span><span class="o">=</span><span class="s2">""</span>
<span class="nb">local </span><span class="nv">seperator</span><span class="o">=</span><span class="nv">$1</span>
<span class="nb">shift</span>
<span class="nb"> </span><span class="k">for</span> i in <span class="nv">$@</span>
<span class="k">do</span>
<span class="k">if</span> <span class="o">[[</span> -z <span class="nv">$ret</span> <span class="o">]]</span>
<span class="k">then</span>
<span class="nv">ret</span><span class="o">=</span><span class="nv">$i</span>
<span class="k">else</span>
<span class="nv">ret</span><span class="o">=</span><span class="s2">"</span><span class="si">${</span><span class="nv">ret</span><span class="si">}${</span><span class="nv">seperator</span><span class="si">}${</span><span class="nv">i</span><span class="si">}</span><span class="s2">"</span>
<span class="k">fi</span>
<span class="k">done</span>
<span class="nb">echo</span> <span class="nv">$ret</span>
<span class="o">}</span>
variance<span class="o">(){</span>
<span class="nb">local </span><span class="nv">total</span><span class="o">=</span><span class="sb">`</span>join <span class="s1">'+'</span> <span class="nv">$@</span><span class="p">|</span>bc -l<span class="sb">`</span>
<span class="nb">local </span><span class="nv">average</span><span class="o">=</span><span class="sb">`</span><span class="nb">echo</span> <span class="s2">"</span><span class="si">${</span><span class="nv">total</span><span class="si">}</span><span class="s2">/</span><span class="nv">$#"</span><span class="s2">|bc -l`</span>
<span class="s2"> local d=0</span>
<span class="s2"> for i in </span><span class="nv">$@</span><span class="s2"></span>
<span class="s2"> do</span>
<span class="s2"> d=`echo "</span><span class="nv">$d</span>+<span class="o">(</span><span class="nv">$i</span>-<span class="nv">$average</span><span class="o">)</span>^2<span class="s2">"|bc -l`</span>
<span class="s2"> done</span>
<span class="s2"> echo "</span><span class="nv">$d</span>/<span class="nv">$#"</span><span class="p">|</span>bc -l
<span class="o">}</span>
</pre></div>
<p>2011-07-17 UPDATE:</p>
<p>haohaolee在留言中给出了另一种简单的join()函数的实现,所以整个variance()函数可以简化如下:</p>
<div class="codehilite"><pre>variance<span class="o">(){</span>
<span class="nv">IFS</span><span class="o">=</span>+ <span class="nb">local </span><span class="nv">total</span><span class="o">=</span><span class="k">$(</span> <span class="nb">echo</span> <span class="nv">$*</span><span class="p">|</span>bc -l <span class="k">)</span>
<span class="nb">local </span><span class="nv">average</span><span class="o">=</span><span class="k">$(</span> <span class="nb">echo</span> <span class="s2">"</span><span class="si">${</span><span class="nv">total</span><span class="si">}</span><span class="s2">/</span><span class="nv">$#"</span><span class="s2">|bc -l )</span>
<span class="s2"> local d=0</span>
<span class="s2"> for i in </span><span class="nv">$@</span><span class="s2"></span>
<span class="s2"> do</span>
<span class="s2"> d=</span><span class="k">$(</span> <span class="nb">echo</span> <span class="s2">"</span><span class="nv">$d</span><span class="s2">+(</span><span class="nv">$i</span><span class="s2">-</span><span class="nv">$average</span><span class="s2">)^2"</span><span class="p">|</span>bc -l <span class="k">)</span><span class="s2"></span>
<span class="s2"> done</span>
<span class="s2"> echo "</span><span class="nv">$d</span>/<span class="nv">$#"</span><span class="p">|</span>bc -l
<span class="o">}</span>
</pre></div>
<p>P.S.顺便把``方式的subshell换成了$()方式</p>
<p>使用方法:</p>
<div class="codehilite"><pre><span class="nv">$ </span>variance <span class="m">1</span> <span class="m">2</span> 3
.66666666666666666666
<span class="nv">$ </span>variance <span class="m">1</span> <span class="m">1</span> 1
0.00000000000000000000
</pre></div>抛砖:使用脚本添加、删除挂载的思路2011-06-12T00:00:00+02:00yegletag:blog.yegle.net,2011-06-12:2011/06/12/adding-removing-mount-script/<p>写这个日志的主要目的是想抛砖引玉得到更好的解决方案,为了准确描述我的需求只好废话比较多地讲一下具体的需求了:-)</p>
<p>最近有一个需求,需要限制某个用于察看线上日志的log用户的权限,要让权限要尽可能小但是又能看到日志文件。这样的需求很明显需要用chroot来完成。但是细想起来有一个日志目录访问问题。</p>
<p>假设目录所在文件夹是/var/logs,为了保证log用户能访问到这个文件夹,至少需要chroot环境中包含这个目录。如果直接这么做,log用户的chroot环境就与日志所在文件夹混在一起,不方便维护。我想到的解决方案是使用mount将目录bind到另一个文件夹下,然后在这个文件夹上级目录创建chroot环境。</p>
<p>例如:log用户的chroot环境处于/home/log中,那么使用mount --bind /var/logs /home/log/home/log。这样就可以让log用户进入chroot环境后看到日志,而又可以分开维护chroot文件夹和日志文件夹。</p>
<p>继续顺着这个思路往下走,就遇到了这篇日志的主要问题:如何使用脚本添加、删除挂载。</p>
<p>最终目的是尽可能自动化地进行chroot环境的创建及删除,所以同样地也要做到挂载的自动创建及删除。在挂载方面需要自动化完成以下目的:</p>
<ol>
<li>执行创建chroot脚本后,挂载相应目录并将挂载信息写入fstab</li>
<li>执行清理chroot脚本后,卸载对应目录并删掉fstab中的挂载信息</li>
</ol>
<p>第一条相对简单,在创建chroot脚本里直接写相应的mount语句并加入<code>echo XXX >>/etc/fstab</code>就可以。</p>
<p>第二条是我遇到的难点。如何识别“对应目录”以及如何识别fstab里对应的挂载信息?这个过程非常重要,必须要保证清理干净。实际情况是我还需要将/dev绑定到chroot环境中,如果不清理干净,其他人可能会直接rm删除chroot环境,导致/dev路径下的文件被删除。</p>
<p>我目前的解决方案:</p>
<p>创建挂载点:</p>
<div class="codehilite"><pre><span class="nb">echo</span> <span class="s2">"/var/logs /home/log/home/log/logs bind defaults,bind 0 0 #CHROOT_MOUNT_BIND_LOG"</span> >>/etc/fstab
mount -a
</pre></div>
<p>清理挂载点</p>
<ul>
<li>识别老的挂载点:<code>MOUNT_POINT=$(grep CHROOT_MOUNT_BIND_LOG /etc/fstab|awk '{print $2}')</code></li>
<li>去掉卸载:<code>umount ${MOUNT_POINT}</code></li>
<li>删掉fstab记录:<code>sed -i '/CHROOT_MOUNT_BIND_LOG/d' /etc/fstab</code></li>
</ul>
<p>这个方案个人感觉有点糟糕,不是很精确。希望大家能提供更好的思路:-)</p>Bash里使用getopts解析非选项参数2011-04-21T00:00:00+02:00yegletag:blog.yegle.net,2011-04-21:2011/04/21/parsing-non-option-argument-bash-getopts/<p><code>getopts</code>是一个Bash built-in,可以用它来实现与<code>getopt(3)</code>一致的参数解析功能。</p>
<p>注意它和<code>getopt(1)</code>的区别。<code>getopt</code>是一个单独的命令,而<code>getopts</code>是bash内置命令。</p>
<p>关于option与argument的区别,我这里实在给不出准确的定义,只能根据自己的理解试着解释一下。</p>
<p>argument是通常所说的命令行参数,在C语言里就是argv数组,根据参数出现的顺序从argv[1]开始依次递增(argv[0]里是被执行的程序本身的程序名)。在Bash里,$0表示脚本的名称,$1开始往后是各个argument。</p>
<p>option有点混乱。我的理解是:一个Option是以<code>-</code>(hyphen-minus character)或<code>--</code>开头的字符串,它后面有可选的argument,如果有则只有一个。也就是说,一个option由一个或两个argument组成</p>
<p>根据上面的描述命令行参数(Arguments)可以分为3类(抱歉我这里会用比较山寨的方法来描述这三类,如果有对应的标准名称,请留言指出)</p>
<ul>
<li>Option with an argument:例如wget的-O选项,必须要指定一个路径或文件名作为参数</li>
<li>Option without an argument:看到网上有的地方也称为switcher,开关选项。例如wget的-v和-q选项,可以使程序输出信息为verbose(详尽)或quiet(安静)</li>
<li>Non-option argument:其实根据上面的描述,这个第三类有点尴尬:它不符合我定义的Option。例子就是wget命令指定文件的URL,它的前面并没有对应的-开头的字符串。</li>
</ul>
<p>getopts的基本用法类似下面的例子:</p>
<div class="codehilite"><pre><span class="c">#!/bin/bash</span>
<span class="k">while</span> <span class="nb">getopts</span> :s:h opt
<span class="k">do</span>
<span class="k">case</span> <span class="nv">$opt</span> in
s<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-s=</span><span class="nv">$OPTARG</span><span class="s2">"</span>
<span class="p">;;</span>
:<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-</span><span class="nv">$OPTARG</span><span class="s2"> needs an argument"</span>
<span class="p">;;</span>
h<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-h is set"</span>
<span class="p">;;</span>
*<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-</span><span class="nv">$opt</span><span class="s2"> not recognized"</span>
<span class="p">;;</span>
<span class="k">esac</span>
<span class="k">done</span>
</pre></div>
<p>这个例子中,定义了一个<code>optstring</code>(<code>:s:h</code>)。其中第一个<code>:</code>字符,打开了getopts的silent模式,可以自行对未识别的选项进行处理而不是让getopts自己报错。随后的<code>s:</code>定义了一个带参数的选项,如果<code>-s</code>使用时没有带上对应的argument则可以在随后的case语句中做相应的报错处理。最后的<code>h</code>定义了一个不带参数的选项。getopts的使用基本就是这样了,更详尽的getopts的optstring的定义和使用请参考<code>bash(1)</code></p>
<p>上面的例子里我没有给出如何解析非选项参数(Non-option argument)。如果我执行<code>./test.sh -s test_string -h filename</code>,那么仅靠<code>getopts</code>是无法获取到filename这个字符串的。</p>
<p>仔细阅读<code>bash(1)</code>之后就会发现,getopts在访问到第一个non-option argument的时候即会停止解析,并设置<code>$OPTIND</code>为第一个non-option argument在<code>$@</code>中的指针。例如上面这个<code>./test.sh -s test_string -h filename</code>例子,getopts解析完之后会将<code>$OPTIND</code>设置为4。</p>
<p>结合<code>$OPTIND</code>变量和<code>shift</code>这个bash built-in,可以写出一个完整的解析所有参数的脚本。以下是一个例子,已经有恰当的注释信息,应该不需要额外解释了。:</p>
<div class="codehilite"><pre><span class="c">#!/bin/bash</span>
<span class="nb">declare</span> -a NOA <span class="c"># Array used to store non-option argument</span>
<span class="k">while</span> <span class="o">[</span> <span class="nv">$# </span>-ne <span class="m">0</span> <span class="o">]</span>
<span class="k">do</span>
<span class="c"># if OPTIND > $#+1, getopts will not change OPTIND's value,so set it to 0</span>
<span class="nv">OPTIND</span><span class="o">=</span>0
<span class="k">while</span> <span class="nb">getopts</span> :s: opt
<span class="k">do</span>
<span class="k">case</span> <span class="nv">$opt</span> in
s<span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-s=</span><span class="nv">$OPTARG</span><span class="s2">"</span>
<span class="p">;;</span>
<span class="se">\?</span><span class="o">)</span>
<span class="nb">echo</span> <span class="s2">"-</span><span class="nv">$OPTARG</span><span class="s2"> not recognized"</span>
<span class="p">;;</span>
<span class="k">esac</span>
<span class="k">done</span>
<span class="c"># if getopts find a non-option argument</span>
<span class="c"># it will stop parsing and return OPTIND</span>
<span class="c"># as index to the first non-option argument</span>
<span class="c">#</span>
<span class="c"># if getopts doesn't find any non-option argument</span>
<span class="c"># it will set OPTIND=$#+1</span>
<span class="k">if</span> <span class="o">[</span> <span class="nv">$OPTIND</span> -ne <span class="k">$((</span><span class="nv">$#+</span><span class="m">1</span><span class="k">))</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">shift</span> <span class="k">$((</span><span class="nv">$OPTIND</span><span class="o">-</span><span class="m">1</span><span class="k">))</span>
<span class="c"># This is just my favorate way to append element to an array in shell.</span>
<span class="c"># Feel free to change it</span>
<span class="nv">NOA</span><span class="o">=(</span><span class="sb">`</span><span class="nb">echo</span> <span class="si">${</span><span class="nv">NOA</span><span class="p">[*]</span><span class="si">}</span><span class="sb">`</span> <span class="nv">$1</span><span class="o">)</span>
<span class="nb">shift</span>
<span class="nb"> </span><span class="k">else</span>
<span class="nb">break</span><span class="p">;</span> <span class="c"># getopts doesn't find any non-option argument</span>
<span class="k">fi</span>
<span class="k">done</span>
<span class="nb">echo</span> <span class="si">${</span><span class="nv">NOA</span><span class="p">[*]</span><span class="si">}</span>
</pre></div>Partially blocked by GFW2011-02-20T00:00:00+01:00yegletag:blog.yegle.net,2011-02-20:2011/02/20/partially-blocked-by-gfw/<p>yegle.net及其子域名被DNS污染,发生时间北京时间2011年2月20日14时30分左右</p>
<div class="codehilite"><pre>184.105.128.93 yegle.net
</pre></div>
<p>Congrats for your being listed as "a good service provider", certified.</p>
<p>R.I.P. yegle.net & *.yegle.net</p>Mac字幕自动下载器2011-02-10T00:00:00+01:00yegletag:blog.yegle.net,2011-02-10:2011/02/10/mac-subtitle-auto-downloader/<p>射手影音最近做了一件很有争议的事情:<a href="http://blog.splayer.org/index.php/2011/02/splayerx-landing-on-mac-app-store/">将SPlayerX摆上了Mac AppStore</a>。SPlayerX是一个Fork自MPleryX的影音播放器。这次射手为了完全规避GPL协议的限制,将字幕下载部分功能用二进制可执行程序方式释出,以fork运行的方式调用这个名为sscl的二进制文件下载字幕。</p>
<p>姑且不论其将二进制文件与GPL协议软件捆绑发行是否违反GPL协议以及GPL协议软件与Mac AppStore的协议是否有冲突<a href="http://blog.splayer.org/index.php/2011/02/four-freedoms-of-gpl-free-speech-not-free-beer/comment-page-1/#comment-3017">via</a>。射手这次的做法恰好为所有Mac用户送上了sscl这个大礼,可以使用这个工具自动匹配本地硬盘的视频文件。</p>
<p><del>原来的设想是用AppleScript写一个脚本,并在Finder里自动监控特定目录,这样每当某目录下有新视频文件添加时就会自动下载字幕。但是我对AppleScript实在不熟悉,加上相关参考资料太少,遂作罢用bash写了一个脚本。如果有AppleScript高手,请改写之:-)</del></p>
<p>果然抛砖引玉,请参考<a href="http://epis.me/816">利用Hazel让Mac自动下载电影字幕</a>更加自动化进行字幕下载操作:-)</p>
<p>sscl的字幕下载目的目录是固定的,所以脚本里就直接Hardcode进去了。</p>
<p><strong>注意:
射手影音并没有为sscl单独设置一个license,我只能根据SPlayerX本身免费下载认为它是freeware。提取该文件单独运行引起的版权上的争议不在本文讨论范围内。
本文中提到的find、mktemp为GNU findutils和GNU coreutils里的版本,请通过MacPort或其他Port系统安装之后再执行</strong></p>
<p><strong>功能描述</strong>
执行脚本时,在预设的目录内查找mkv和avi文件,过滤掉某些0day release里的sample文件。
对于每个文件,如果不存在对应的.chn.srt字幕文件,就调用sscl进行下载并复制到电影文件所在文件夹。</p>
<p><strong>使用方法</strong></p>
<ol>
<li>下载sscl文件。该文件可以从SPlayer Mercurial源里获取,地址是:<a href="http://hg.splayer.org/splayerx/raw/0b9e84441210/binaries/x86_64/sscl">http://hg.splayer.org/splayerx/raw/0b9e84441210/binaries/x86_64/sscl</a></li>
<li>修改该文件权限,终端下<code>chmod +x sscl</code></li>
<li>将以下代码保存为<code>sub.sh</code>。请根据你系统内的<code>bash</code>路径修改<code>shabang</code>,我的系统<code>bash</code>是在<code>/Users/yegle/Gentoo/bin/bash</code>的,一般的mac系统应该是在<code>/bin/bash</code>。同时还要修改<code>SSCL</code>变量为你系统内<code>sscl</code>二进制文件路径,以及<code>MOVIE_DIR</code>你的电影文件所在路径。</li>
<li>在终端下执行<code>sh sub.sh</code>,即可自动匹配你的<code>MOVIE_DIR</code>目录下所有电影文件并自动将字幕复制到你的电影文件所在文件夹。如果有字幕没有下载到,将会在命令行里提示某个视频没有找到对应字幕。</li>
</ol>
<p>脚本</p>
<div class="codehilite"><pre><span class="c">#!/Users/yegle/Gentoo/bin/bash</span>
<span class="nv">MOVIE_DIR</span><span class="o">=</span>/Users/yegle/Downloads/
<span class="nv">MOVIE_LIST</span><span class="o">=</span><span class="k">$(</span>mktemp<span class="k">)</span>
<span class="nv">SSCL</span><span class="o">=</span>/Users/yegle/sscl
rm -rf <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/"</span>*
find <span class="nv">$MOVIE_DIR</span> -regextype posix-extended -iregex <span class="s1">'.*\.(mkv|avi)$'</span><span class="p">|</span>grep -iv sample ><span class="nv">$MOVIE_LIST</span>
<span class="k">while</span> <span class="nb">read </span>movie_file_path
<span class="k">do</span>
<span class="nv">movie_filename</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span><span class="k">)</span>
<span class="nv">movie_path</span><span class="o">=</span><span class="k">$(</span>dirname <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span><span class="k">)</span>
<span class="k">if</span> <span class="o">[</span> ! -e <span class="s2">"</span><span class="si">${</span><span class="nv">movie_file_path</span><span class="p">%.*</span><span class="si">}</span><span class="s2">.chn.srt"</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nv">$SSCL</span> --video-file <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span> --pull >/dev/null 2><span class="p">&</span>1
ls <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/</span><span class="si">${</span><span class="nv">movie_filename</span><span class="p">%.*</span><span class="si">}</span><span class="s2">.chn.srt"</span> >/dev/null 2><span class="p">&</span><span class="m">1</span> <span class="se">\</span>
<span class="o">&&</span> mv <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/</span><span class="si">${</span><span class="nv">movie_filename</span><span class="p">%.*</span><span class="si">}</span><span class="s2">"</span>.*.srt <span class="s2">"</span><span class="nv">$movie_path</span><span class="s2">"</span> <span class="se">\</span>
<span class="o">||</span> <span class="nb">echo</span> <span class="nv">$movie_file_path</span> subtitle not found
<span class="k">fi</span>
<span class="k">done</span> < <span class="nv">$MOVIE_LIST</span>
</pre></div>
<p>2011-06-23更新:重写了一个脚本,功能相同,但是使用的是Mac自带的<code>find</code>和<code>mktemp</code>,这样无论是否有GNU的<code>find</code>和<code>mktemp</code>安装,都能正常执行了</p>
<div class="codehilite"><pre><span class="c">#!/bin/bash</span>
<span class="nv">MOVIE_DIR</span><span class="o">=</span><span class="s2">"/Volumes/Macintosh HD/Movie"</span>
<span class="nv">MOVIE_LIST</span><span class="o">=</span><span class="k">$(</span>/usr/bin/mktemp -t sub<span class="k">)</span>
<span class="nv">SSCL</span><span class="o">=</span>/Users/yegle/sscl
rm -rf <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/"</span>*
/usr/bin/find -E <span class="s2">"</span><span class="si">${</span><span class="nv">MOVIE_DIR</span><span class="si">}</span><span class="s2">"</span> -iregex <span class="s1">'.*\.(avi|mkv)$'</span> <span class="p">|</span>grep -iv sample ><span class="nv">$MOVIE_LIST</span>
<span class="k">while</span> <span class="nb">read </span>movie_file_path
<span class="k">do</span>
<span class="nv">movie_filename</span><span class="o">=</span><span class="k">$(</span>basename <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span><span class="k">)</span>
<span class="nv">movie_path</span><span class="o">=</span><span class="k">$(</span>dirname <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span><span class="k">)</span>
<span class="k">if</span> <span class="o">[</span> ! -e <span class="s2">"</span><span class="si">${</span><span class="nv">movie_file_path</span><span class="p">%.*</span><span class="si">}</span><span class="s2">.chn.srt"</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nv">$SSCL</span> --video-file <span class="s2">"</span><span class="nv">$movie_file_path</span><span class="s2">"</span> --pull >/dev/null 2><span class="p">&</span>1
ls <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/</span><span class="si">${</span><span class="nv">movie_filename</span><span class="p">%.*</span><span class="si">}</span><span class="s2">.chn.srt"</span> >/dev/null 2><span class="p">&</span><span class="m">1</span> <span class="se">\</span>
<span class="o">&&</span> mv <span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Library/Application Support/SPlayerX/SVPSub/</span><span class="si">${</span><span class="nv">movie_filename</span><span class="p">%.*</span><span class="si">}</span><span class="s2">"</span>.*.srt <span class="s2">"</span><span class="nv">$movie_path</span><span class="s2">"</span> <span class="se">\</span>
<span class="o">||</span> <span class="nb">echo</span> <span class="nv">$movie_file_path</span> subtitle not found
<span class="k">fi</span>
<span class="k">done</span> < <span class="nv">$MOVIE_LIST</span>
</pre></div>
<p>2014-04-10更新:重写了一个Python版本,功能相同但是使用Python内置工具,应该比shell脚本方便</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># vim: set fileencoding=utf-8 ts=4 sw=4 tw=79 :</span>
<span class="kn">from</span> <span class="nn">__future__</span> <span class="kn">import</span> <span class="p">(</span><span class="n">unicode_literals</span><span class="p">,</span> <span class="n">absolute_import</span><span class="p">,</span>
<span class="n">division</span><span class="p">,</span> <span class="n">print_function</span><span class="p">)</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="kn">import</span> <span class="nn">shutil</span>
<span class="kn">from</span> <span class="nn">glob</span> <span class="kn">import</span> <span class="n">glob</span>
<span class="kn">from</span> <span class="nn">os.path</span> <span class="kn">import</span> <span class="n">exists</span><span class="p">,</span> <span class="n">join</span><span class="p">,</span> <span class="n">basename</span>
<span class="n">movie_dir</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s">'~/Movie/'</span><span class="p">)</span>
<span class="n">sscl</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">expanduser</span><span class="p">(</span><span class="s">'~/sscl'</span><span class="p">)</span>
<span class="n">base_args</span> <span class="o">=</span> <span class="p">[</span><span class="n">sscl</span><span class="p">,</span> <span class="s">'--pull'</span><span class="p">,</span> <span class="s">'--video-file'</span><span class="p">]</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">'__main__'</span><span class="p">:</span>
<span class="k">for</span> <span class="n">root</span><span class="p">,</span> <span class="n">dirs</span><span class="p">,</span> <span class="n">files</span> <span class="ow">in</span> <span class="n">os</span><span class="o">.</span><span class="n">walk</span><span class="p">(</span><span class="n">movie_dir</span><span class="p">):</span>
<span class="k">for</span> <span class="n">f</span> <span class="ow">in</span> <span class="n">files</span><span class="p">:</span>
<span class="n">f_wo_ext</span><span class="p">,</span> <span class="n">ext</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">splitext</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">if</span> <span class="n">ext</span> <span class="ow">not</span> <span class="ow">in</span> <span class="p">{</span><span class="s">'.mkv'</span><span class="p">}</span> <span class="ow">or</span> <span class="s">'sample'</span> <span class="ow">in</span> <span class="n">f_wo_ext</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
<span class="k">continue</span>
<span class="n">filename_wo_ext</span> <span class="o">=</span> <span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">f_wo_ext</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(</span><span class="n">glob</span><span class="p">(</span><span class="s">"</span><span class="si">%s</span><span class="s">*.srt"</span> <span class="o">%</span> <span class="n">filename_wo_ext</span><span class="p">)</span>
<span class="ow">or</span> <span class="n">glob</span><span class="p">(</span><span class="s">"</span><span class="si">%s</span><span class="s">*.ass"</span> <span class="o">%</span> <span class="n">filename_wo_ext</span><span class="p">)):</span>
<span class="k">continue</span>
<span class="n">filename</span> <span class="o">=</span> <span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">f</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">check_output</span><span class="p">(</span>
<span class="n">base_args</span> <span class="o">+</span> <span class="p">[</span><span class="n">filename</span><span class="p">],</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
<span class="n">subs</span> <span class="o">=</span> <span class="nb">set</span><span class="p">(</span><span class="n">result</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s">'utf-8'</span><span class="p">)</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s">'</span><span class="se">\n</span><span class="s">'</span><span class="p">))</span>
<span class="k">for</span> <span class="n">sub</span> <span class="ow">in</span> <span class="n">subs</span><span class="p">:</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">exists</span><span class="p">(</span><span class="n">join</span><span class="p">(</span><span class="n">root</span><span class="p">,</span> <span class="n">basename</span><span class="p">(</span><span class="n">sub</span><span class="p">))):</span>
<span class="n">shutil</span><span class="o">.</span><span class="n">move</span><span class="p">(</span><span class="n">sub</span><span class="p">,</span> <span class="n">root</span><span class="p">)</span>
<span class="k">except</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">CalledProcessError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"NOT FOUND: </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="n">f</span><span class="p">)</span>
<span class="k">except</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">TimeoutExpired</span><span class="p">:</span>
<span class="k">print</span><span class="p">(</span><span class="s">"TIMEOUT: </span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="n">f</span><span class="p">)</span>
</pre></div>在Mac OSX上安装gentoo-prefix2010-10-23T00:00:00+02:00yegletag:blog.yegle.net,2010-10-23:2010/10/23/install-gentoo-prefix-on-mac-osx/<p>不想看废话的同学们可以直接<a href="#installation">跳转到安装步骤</a></p>
<h2 id="ports">什么是Ports系统?</h2>
<p>众所周知Mac OSX是一个UNIX操作系统。UNIX操作系统的认证是Open Group给的,只有经过他们认证之后才能自称自己是UNIX操作系统并使用UNIX的商标(毕竟人家这是注册商标,不能随便用)。</p>
<p>得益于UNIX标准的建立,越来越多在Linux上开发的开源软件已经可以直接在UNIX下编译安装而不需要考虑跨平台问题,这意味着在Mac OSX上也可以享受绝大多数Linux下开源软件的最新版本。</p>
<p>注意到,我这里提到的是“编译安装”。即使你是一个对编译安装感觉很平常的Linuxer,在Mac下编译安装也不是一件简单的事情,因为你需要自行解决一大堆依赖。有多复杂,你可以尝试安装一次LFS。虽然Linux下常见的包管理系统Mac下没有,但是Mac上有相对于包管理系统来说更加强大的Ports系统(IMO),让这些软件包的安装过程自动化了。</p>
<p>Ports系统的历史还真不好找,从wiki获得的信息是,这是早期BSD系统就开始有的一套比较“原始”的包管理系统:自动从官方网站下载源码包、解压、打补丁、编译安装,并维护各个软件包之间的依赖关系。使用Ports系统,可以通过自动化方式从源码安装软件,而无需考虑其背后的繁琐操作。</p>
<p>除了BSD系统上的Ports系统之外,Linux下常见的Ports系统有:Gentoo Portage和ArchLinux ABS。在Mac OSX上也有多个Ports系统可以选择,比较有名的有<code>MacPorts</code>和<code>HomeBrew</code>。<code>Gentoo-prefix</code>反而是一个不太常用的Mac下的Ports系统。</p>
<h2 id="gentoo-prefix">为什么推荐Gentoo-prefix呢?</h2>
<p><code>Gentoo-prefix</code>使用的portage系统fork自Gentoo Portage,里面的ebuild文件是针对prefix环境专门修改过的。</p>
<p>上面提到了,Gentoo-prefix是一个不太常用的Ports系统。那么为什么要推荐这个Ports系统呢?</p>
<p>稍微列了一下Gentoo-prefix的优势:</p>
<ul>
<li>有Gentoo这个比较知名的Linux发行版做靠山,软件包的兼容性由一个庞大的社区保证</li>
<li>软件包类别全面(看了一下好像还是MacPorts里的包多…就不夸耀Gentoo-prefix的包多了…)</li>
<li>出现问题的时候,找个Gentoo-er基本可以帮你搞定问题,不需要另一个Mac用户</li>
</ul>
<p>由于其实我没用过MacPorts,所以更加具体的优势我真说不上来…嗯那就这样了,下一个问题就是Gentoo-prefix的安装使用了。</p>
<h2 id="installation">Gentoo-prefix 安装使用</h2>
<p>Gentoo官方提供了安装Gentoo-prefix到Mac的详细方法,英文过关的同学请自行参考英文原文:<a href="http://www.gentoo.org/proj/en/gentoo-alt/prefix/bootstrap-macos.xml">http://www.gentoo.org/proj/en/gentoo-alt/prefix/bootstrap-macos.xml</a></p>
<p>简单地介绍一下大致的步骤:
安装过程需要在终端中进行。打开<em>"应用程序"->"实用工具"->"终端"</em>(如果你有其他常用的终端,自行选择就可以)。</p>
<p>首先需要定义一个EPREFIX环境变量。后续所有的ebuild文件及通过Gentoo-prefix安装好的程序都会被安装到EPREFIX定义的目录下。假设这个目录是~/Gentoo,在终端里执行:</p>
<div class="codehilite"><pre><span class="nb">export </span><span class="nv">EPREFIX</span><span class="o">=</span><span class="s2">"</span><span class="nv">$HOME</span><span class="s2">/Gentoo"</span>
</pre></div>
<p>然后将该目录下的bin文件夹位置添加到PATH里,方便后面的bootstrap动作。</p>
<div class="codehilite"><pre><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$EPREFIX</span><span class="s2">/usr/bin:</span><span class="nv">$EPREFIX</span><span class="s2">/bin:</span><span class="nv">$EPREFIX</span><span class="s2">/tmp/usr/bin:</span><span class="nv">$EPREFIX</span><span class="s2">/tmp/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>
</pre></div>
<p>准备工作已经完成,下面就是枯燥的敲命令了。以下代码可以直接复制粘贴到终端里批量执行:</p>
<div class="codehilite"><pre>curl http://overlays.gentoo.org/proj/alt/browser/trunk/prefix-overlay/scripts/bootstrap-prefix.sh?format<span class="o">=</span>txt -o bootstrap-prefix.sh
chmod <span class="m">755</span> bootstrap-prefix.sh
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span> tree
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp make
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp wget
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp sed
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp python
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp coreutils6
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp findutils
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp tar15
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp patch9
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp grep
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp gawk
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span>/tmp bash
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span> portage
<span class="c"># 以上已经安装好基本portage,下面是安装基本命令</span>
emerge --oneshot sed
emerge --oneshot --nodeps bash
emerge --oneshot pax-utils
emerge --oneshot --nodeps wget
emerge --oneshot --nodeps baselayout-prefix
emerge --oneshot --nodeps xz-utils
emerge --oneshot --nodeps m4
emerge --oneshot --nodeps flex
emerge --oneshot --nodeps bison
emerge --oneshot --nodeps binutils-config
emerge --oneshot --nodeps binutils-apple <span class="c"># 注意,这里根据官方文档,需要判断系统内gcc版本。不过我估计现在Snow Leopard都是gcc 4.2.1以上了,直接写这个地址了。如果出现问题,请参考官方文档的说明</span>
emerge --oneshot --nodeps gcc-config
emerge --oneshot --nodeps gcc-apple
emerge --oneshot coreutils
emerge --oneshot findutils
emerge --oneshot tar
emerge --oneshot grep
emerge --oneshot patch
emerge --oneshot gawk
emerge --oneshot make
emerge --oneshot --nodeps file
emerge --oneshot --nodeps eselect
env <span class="nv">FEATURES</span><span class="o">=</span><span class="s2">"-collision-protect"</span> emerge --oneshot portage
rm -Rf <span class="nv">$EPREFIX</span>/tmp/*
<span class="nb">hash</span> -r
emerge --sync
<span class="nv">USE</span><span class="o">=</span>-git emerge -u system
</pre></div>
<p>接下来要编辑<code>make.conf</code>文件。<code>make.conf</code>是Portage系统的控制文件,在这里可以对软件包编译时使用的参数进行控制。另外还有一个比较重要的,在<code>make.conf</code>里可以加上恰当的CPU类型参数,让gcc编译时根据CPU类型进行优化,提高性能。</p>
<p>目前常见的CPU类型有:</p>
<ul>
<li>CoreDuo: 对应<code>-march=prescott</code></li>
<li>CoreDuo2:对应<code>-march=nocona</code></li>
<li>i3,i5,i7: 我的make.conf里写的是<code>-march=prescott</code>,不知道是不是我写错了…现在一会儿也查不到…</li>
</ul>
<p>执行以下命令编辑<code>make.conf</code>文件,其中<code>${my_cpu_flags}</code>用<code>-march=XXX</code>替换</p>
<div class="codehilite"><pre><span class="nb">echo</span> <span class="s1">'USE="unicode nls"'</span> >> <span class="nv">$EPREFIX</span>/etc/make.conf
<span class="nb">echo</span> <span class="s1">'CFLAGS="-O2 -pipe ${my_cpu_flags}"'</span> >> <span class="nv">$EPREFIX</span>/etc/make.conf
<span class="nb">echo</span> <span class="s1">'CXXFLAGS="${CFLAGS}"'</span> >> <span class="nv">$EPREFIX</span>/etc/make.conf
</pre></div>
<p>最后执行</p>
<div class="codehilite"><pre>emerge -e system
</pre></div>
<p>使用Gentoo-prefix中的gcc对system这个软件包集合进行重编译。编译完成后,整个安装过程就完成了</p>
<p>安装完成后,执行</p>
<div class="codehilite"><pre><span class="nb">cd</span> <span class="nv">$EPREFIX</span>/usr/portage/scripts
./bootstrap-prefix.sh <span class="nv">$EPREFIX</span> startscript
</pre></div>
<p>得到一个用于进入prefix环境的脚本,位于<code>~/Gentoo/startprefix</code>中。</p>
<p>通过Gentoo-prefix安装软件非常简单,下面以pygtk为例:</p>
<div class="codehilite"><pre>sh ~/Gentoo/startprefix
emerge pygtk
</pre></div>
<h2 id="_1">优化设置</h2>
<p>如果你希望在打开终端的时候直接使用prefix过的环境,可以通过以下步骤进行(假设你的prefix环境中的bash在<code>/Users/yegle/Gentoo/bin/bash</code>):</p>
<ol>
<li>将prefix环境中的bash加入到<code>/etc/shells</code>中。使用<code>sudo vi /etc/shells</code>进行编辑,在最后一行加上prefix环境中bash的完整路径(例如:<code>/Users/yegle/Gentoo/bin/bash</code>)</li>
<li>更换用户的默认shell。执行<code>chsh -s /Users/yegle/Gentoo/bin/bash</code>,输入你的密码保存设置</li>
<li>编辑<code>~/.bash_profile</code>文件,加入两行:<code>export EPREFIX="$HOME/Gentoo"</code>和<code>export PATH="$EPREFIX/usr/bin:$EPREFIX/bin:$EPREFIX/tmp/usr/bin:$EPREFIX/tmp/bin:/sbin:$PATH"</code>。</li>
</ol>
<p>重新打开终端程序,你会发现shell的提示符颜色已经变成绿色了,并且可以直接在这个环境里执行emerge程序进行程序安装了</p>
<h2 id="gentoo-prefix_1">Gentoo-prefix常见命令</h2>
<p>以下是一些小技巧,方便对Gentoo-prefix的使用:</p>
<h3 id="portage">对本地Portage树进行更新</h3>
<div class="codehilite"><pre>emerge --sync
</pre></div>
<h3 id="portage_1">对通过portage安装的所有软件进行升级</h3>
<div class="codehilite"><pre>emerge --deep --newuse --update world
</pre></div>
<h3 id="keywordportage">使用KEYWORD从portage中搜索软件包</h3>
<div class="codehilite"><pre><span class="c"># 需要先安装eix</span>
eix KEYWORD
</pre></div>
<h3 id="portage_2">更新本地Portage树</h3>
<div class="codehilite"><pre><span class="c"># 效果等同于emerge --sync,但是可以在执行完毕后告诉你哪些包可供升级</span>
eix-sync
</pre></div>
<h3 id="pkgname">列出PKGNAME软件的文件</h3>
<div class="codehilite"><pre># 需要先安装gentoolkit
equery file PKGNAME
</pre></div>多大个事?-记人人网泄密事件2010-09-23T00:00:00+02:00yegletag:blog.yegle.net,2010-09-23:2010/09/23/renren-leaking-user-info/<p>有一种逻辑很可怕。在做一些事情的时候,总有人跳出来问:这多大个事?至于吗?</p>
<p>就像当年<a href="http://blog.yegle.net/2009/12/18/shooterplayer-and-gpl/">举报射手播放器没有遵守GPL</a>,就像<a href="http://docs.google.com/View?id=dgjxsxws_196fnrzbccc">人肉某知名“女”推友doublechou其实是个男的</a>。这样的事情,有人总会发问:多大个事?至于吗?你这是什么目的?你怎么又拿出来说事?</p>
<p>为什么又拿出来说事?因为事情根本没有正面解决,不了了之。同样,这次人人网泄露用户资料事件,目前也处于不了了之的状态,我不希望这件事情不了了之,在这里回放一遍完整版本。</p>
<p>我这人习惯不好,Gmail里的spam邮件也会一个一个去看标题。9月3日打开邮箱spam的时候发现一封标题为《仅98元!畅想价值1088元一线明星御用摄影艺术总监萧瑶的全程拍摄服务,让你成为第二个性感小S!仅此一次,不容错过!》的垃圾邮件邮件。邮件的抬头是”请使用真实姓名“。</p>
<p>这个名字很特殊,第一时间让我联想起我某个被人人网封禁的帐号。这个帐号在封禁14天解封后,名字被管理员由原来的yegle修改为”请使用真实姓名“了。至于为什么被封禁,原因很有趣,需要另起一段。</p>
<p>我自己写了个<a href="http://blog.yegle.net/2010/08/28/twitter2renren-php/">脚本</a>,用于将twitter发的推自动同步到人人网。众所周知twitter上发的推很多是敏感话题,时不时就会被管理员删除掉,同时会发一封站内信到你的人人帐号,内容为”你的状态XXX由于不符合XXXX已被管理员删除“。某一天我心血来潮,又写了个脚本,自动将人人网收件箱里收到的发自管理员的”你的状态XXX由于不符合XXXX已被管理员删除“的站内信自动又发送到人人。于是在某一个下午,某个兢兢业业的人人网客服把我的某条状态删除、重发、删除,来回折腾几次之后一怒之下把我帐号封禁了,就是这样的。</p>
<p>好的扯远了。当时看到这个邮件和抬头之后,第一时间联想到的是人人网,并且没有联想到其他网站。因为全互联网,能把我的邮箱地址和”请使用真实姓名“联系起来的只有人人网上。于是我发了一条<a href="http://twitter.com/yegle/statuses/22863933213">tweet</a>:<em>http://img.ly/20RE 人人网出售用户资料的证据:我的某个人人网账号被封禁之后被管理员修改名字为“请使用真实姓名”,今天我收到了这样一封垃圾邮件。</em></p>
<p>当时只是很随意地发出来了,因为在我眼里,人人网出售用户资料简直是必然事件,这次只不过是抓了个现行,没什么可深究的。但是后来各位推友热情的转发,让东莞时报注意到了,发了一条<a href="http://dgtimes.timedg.com/html/2010-09/07/content_516177.htm">报道</a>。再加上<a href="http://twitter.com/xjp">@xjp</a>同学写的<a href="http://www.xjp.cc/2010-log/09/renren-selling-user-information.html">日志</a>推波助澜,终于让这件事情最大化了。</p>
<p>TechWeb、速途等等唯恐天下不乱、五十步笑百步的网站终于正大光明地出来五十步笑百步了,分别报道了这个事情。从那之后互联网上其他的关于此次事件的报道基本是这两篇本来就不是非常客观的报道的再次演义,包括QQ科技、网易等等网站上面的报道</p>
<ul>
<li>TechWeb:<a href="http://www.techweb.com.cn/news/2010-09-07/676874.shtml">人人网否认出售用户资料称正通过法律手段处理</a></li>
<li>速途:<a href="http://www.sootoo.com/content/54151.shtml">人人网涉嫌出售用户资料 被疑与团购网站利益互换</a></li>
</ul>
<p>其中TechWeb的报道是在2010.09.07 12:47发布的。此报道中,千橡互动集团公关部总监王毅声称,”人人网正在积极和报道中涉及的网友“一阁”联系,且已经联系上,并在帮他处理。“但是事实上,我当时特地将所有IM工具、所有邮箱、所有人人网马甲的站内信都翻了一遍,没有任何联系我的记录。于是我又在twitter上发了<a href="http://twitter.com/yegle/statuses/23213899581">一条tweet</a>感慨道:<em>原来这就是传说中的被联系啊</em></p>
<p>最后证明,原来是那篇报道的作者穿越到2个小时之后采访的王毅。因为我是在当天下午3点钟接到了人人网的电话(<a href="http://twitter.com/yegle/statuses/23215933775">tweet</a>记录)</p>
<p>客服的问答我也在twitter上播报了。相关内容如下:</p>
<ul>
<li><a href="http://twitter.com/yegle/statuses/23216185915">tweet</a>: 人人网客服对话:1、问我有没有用外挂,答:我不用windows</li>
<li><a href="http://twitter.com/yegle/statuses/23216206239">tweet</a>: 人人网客服对话:2、问我有没有用SNS同步软件,答:我自己写脚本同步的</li>
<li><a href="http://twitter.com/yegle/statuses/23216227915">tweet</a>: 人人网客服对话:3、问我有没有点各种应用的链接,答:没有</li>
<li><a href="http://twitter.com/yegle/statuses/23216272291">tweet</a>: 人人王客服问答:4、我提问:那你既然在调查隐私泄露问题,请问能否解释一下我的手机号码你是怎么获得的吗?答:这个是领导给的,我是客服不知道领导的做事方式(大致意思,非原话)</li>
<li><a href="http://twitter.com/yegle/statuses/23216308125">tweet</a>: 人人网客服问答:5、大意是希望我不要再发表某些言论,答:我确保我没有说错</li>
</ul>
<p>关于手机号码,很多人有疑问,是不是我自己不小心泄露了。第一我没有在人人网上填写手机号码,第二我的域名whois信息里的手机号码早已弃用。人人网如何获取到我的手机号码,我不想去猜测了,丫估计也不会给我答复。很遗憾,在那之后人人网再也没有联系过我,估计是准备把这个事件冷处理掉了。</p>
<p>感谢@xjp同学的跟进,陆续收到其他推友的反馈,也收到了类似或者一样的垃圾邮件,都与人人帐号相关。列举如下:</p>
<ul>
<li><a href="http://twitter.com/leeiio/status/23225159943">tweet</a>: 9月3号的时候我也收到了一个团购网站的邮件,里面直接写明了我的真名,而且是繁体的,综合@yegle的事件,我只有在人人网中使用的是繁体书写的中文名</li>
<li><a href="http://twitter.com/Mystryl/status/23225746737">tweet</a>: 翻垃圾邮件箱才发现9月3日我也收到了一个团购网站的邮件,用的是我在人人网去年64时违规被注销帐号的假名。截图如下。 http://twitpic.com/2m37ai cc @yegle</li>
</ul>
<p>本次事件回放完毕。</p>廉价OTP解决方案:yubikey2010-08-29T00:00:00+02:00yegletag:blog.yegle.net,2010-08-29:2010/08/29/yubikey-the-cheap-otp-solution/<p>从<a href="https://twitter.com/ohsc">@ohsc</a>那里了解到yubikey这个东西。作为信息安全的学生,我的第一反应是:两眼发亮。为什么两眼发亮呢?慢慢解释。</p>
<p>首先要从什么是OTP说起。</p>
<p>OTP是One-Time Password的简称,中文对应的准确翻译应该叫"一次一密"。根据香农(这个人…信息论、现代密码学,以及其他乱七八糟东西的发明者,牛逼至极的人物啊!)创立的现代密码学理论,一次一密是不可能被破解的,除此之外所有的其他加密方案都至少能被暴力破解,虽然破解的难度是指数级的。(当然,香农说的一次一密不可破解是有前提条件的,具体参考维基百科上的"<a href="http://zh.wikipedia.org/zh-cn/%E4%B8%80%E6%AC%A1%E6%80%A7%E5%AF%86%E7%A2%BC%E6%9C%AC">一次性密码本</a>"词条)。具体来说,一次一密就是在每次加密会话过程中,对传送的明文使用不同的密钥进行加密,每次使用的密钥是随机、不可预测、不重复使用的,从而保证了加密是绝对不可破解的。</p>
<p>当然,我说的是简单化的一次一密的实现,实际操作过程中,密钥的长度必须大于等于明文长度,而且密钥必须是可验证的(所以也可以看成是可预测的)。真正的一次一密,等于需要一个安全信道来传送不少于明文长度的密钥,几乎是没有意义的。</p>
<p>其实生活中已经有很多一次一密的例子,例如中国银行的E-TOKEN、<a href="http://www.warcraftchina.com/services/wow-token/">魔兽世界安全令牌</a>、中国建设银行的动态口令卡(纸制),这些都是一次一密的。</p>
<p>国内的一次一密电子令牌基本上使用的是<a href="http://en.wikipedia.org/wiki/SecurID">SecurID</a>,EMC公司下属的RSA公司出品的。这种一次一密解决方案使用一个预先定义的time-based的函数计算密码,称为Time-synchronized one-time password。每个token出厂前与服务器同步时间,在出厂后,token和服务器以相同的时钟计算,每分钟变化一次。</p>
<p><del>在验证token上读取的数字时,服务端可以设置允许的误差值,例如允许前后5分钟内产生的密钥输入都认为是有效的,这样可以最大限度避免token的时钟漂移。</del></p>
<p>经仔细查证后,上文描述有误。准确的实现是:RSA的服务端验证时允许前后30秒或前后60秒内的密钥为有效密钥。服务端会保留当前token,前一token,后一token三个token值,如果用户输入与当前token一致则不做变化。如果用户输入的是前一token或后一token,服务器将记录这个SecurID的ID对应的offset,下次验证时考量这个offset。如果用户超过了这三个token的值范围,用户将被提示输入SecurID显示的下一个token,如果与上一个输入的token顺序一致,系统将判断验证通过,并记录这个较大的offset值。</p>
<p>这样的方案安全性很高,很多公司内部的vpn系统、线上服务器登陆系统等等都使用了SecurID进行认证。缺点很明显:价格高,据说每个的成本在¥200(网易出的那个山寨版我就不评论了…售价居然才60左右…只能说不太靠谱了…),而且需要配套RSA的服务端方案;需要电池保证时钟运作,因此存在更换token的情况。</p>
<p>另一种完全不同的一次一密方案是使用数学方法生成密钥序列,称为Mathematical-algorithm-based one-time password。这种算法使用数学函数生成密钥序列,然后依次使用密钥序列,从而不需要考虑时间因素。由于密钥序列生成过程中没有时间因素,实际生产的硬件token可以不包含电池供电,免去服务端与token的对时,成本可以下降,同时也没有电池耗尽更换token的担忧(更换token=安全隐患,因为需要人的参与,人永远是任何安全体系的最大漏洞…)。维基百科上举例了一个使用hash chain方法产生此类OTP的方案:找到一个单向函数f(例如任意一个hash函数),给定一个初始种子s。根据f和s产生序列:f(s), f(f(s)), f(f(f(s)))…。然后将该序列倒置,依次作为密钥使用。第三方如果偶然获取到序列中某个密钥,他就必须要通过f的反函数来计算下一个密钥,而f的单向性保证了这样的计算是不可行的。</p>
<p>yubikey使用的是第二种方案。它是一个廉价的OTP解决方案,10枚yubikey的平均价格是$28,由yubico提供验证服务器并提供API供开发者调用。它不需要电池供电,也没有液晶屏显示密钥。在插入电脑之后,它会被识别为一个USB键盘,在任意输入框聚焦并按一下yubikey上的硬件按钮,yubikey将自动模拟键盘输入一串一次性密钥。由于yubikey在电脑上被识别为USB键盘,它可以做到最好的免驱动支持(现在没有操作系统不支持USB键盘了吧?),可以在Windows/MacOSX/Linux上使用。关于yubikey使用的OTP生成方案,强烈建议阅读一下官方提供的文档来了解:<a href="http://yubico.com/files/Security_Evaluation_2009-09-09.pdf">http://yubico.com/files/Security_Evaluation_2009-09-09.pdf</a></p>
<p>要注意的是,yubikey每次插拔都会在内置的非易失性存储器上的Session Counter增加1,该Session Counter总长度为16bit,在每天插拔20次的频率下,该Counter在9年左右到达满值。这个寿命还是很让人满意的。</p>
<p>yubikey是一个非常开放的硬件设备,它本身的硬件实现原理、服务端架设代码、服务端API代码,都是完全开放的。从而在它之上衍生了很多开源项目与之搭配使用。从<a href="http://wiki.yubico.com/wiki/index.php/Main_Page">yubico的wiki页面</a>可以看到一个简单的列表。稍微列举一下:</p>
<ul>
<li>YubiRADIUS:使用yubikey产生的OTP验证用户的RADIUS服务器。该服务器由yubico提供,任何拥有yubikey的用户都可以在上面开通管理员帐号进行使用。详细使用指南:<a href="http://wiki.yubico.com/wiki/index.php/Applications:YubiRADIUS_RADIUS_Service">http://wiki.yubico.com/wiki/index.php/Applications:YubiRADIUS_RADIUS_Service</a></li>
<li>RADIUS on Premise:使用LDAP维护yubikey ID与用户的联系,自行搭建使用yubikey做验证的RADIUS服务器。OTP+VPN+RADIUS,这几乎是最安全的上网方案了。</li>
<li>YubiKey WordPress Plugin:wordpress登录过程中可使用yubikey配合原密码进行加强安全的登录</li>
<li>LastPass Login:使用yubikey作为lastpass的master password,详见<a href="http://www.imchao.net/life/start-yubikey.html">@ohsc的日志</a>。该功能需lastpass的高级会员权限,每月$1。</li>
<li>Google Apps:使用yubikey登录Google Apps。要是Gmail也能用yubikey登录,那就nb了…</li>
</ul>使用PHP进行HTTP重定向2010-07-16T00:00:00+02:00yegletag:blog.yegle.net,2010-07-16:2010/07/16/http-redirect-using-php/<p>在Google里搜"<a href="http://www.google.com/search?q=php+%E9%87%8D%E5%AE%9A%E5%90%91">PHP 重定向</a>",这篇日志的排名在第四。但是这篇写得实在太烂了…重写一篇…</p>
<p>什么是HTTP重定向? 当你访问<a href="http://www.yegle.net">http://www.yegle.net</a>时,地址栏的地址会自动变成<a href="http://yegle.net">http://yegle.net</a>。当你访问<a href="http://ye.gl">http://ye.gl</a>的时候,地址栏的地址会变成<a href="http://yegle.net">http://yegle.net</a>。这就是HTTP重定向</p>
<p>一个HTTP请求,返回的HTTP Response Header里,第一行是HTTP的状态码。正常情况下,HTTP请求返回的状态码是<code>200 OK</code>。</p>
<p>正常的HTTP请求,返回200 OK:</p>
<div class="codehilite"><pre><span class="o"><</span> <span class="nt">HTTP</span><span class="o">/</span><span class="nt">1</span><span class="nc">.1</span> <span class="nt">200</span> <span class="nt">OK</span>
<span class="o"><</span> <span class="nt">Date</span><span class="o">:</span> <span class="nt">Fri</span><span class="o">,</span> <span class="nt">16</span> <span class="nt">Jul</span> <span class="nt">2010</span> <span class="nt">11</span><span class="nd">:21:10</span> <span class="nt">GMT</span>
<span class="o"><</span> <span class="nt">Server</span><span class="o">:</span> <span class="nt">Apache</span>
<span class="o"><</span> <span class="nt">X-Powered-By</span><span class="o">:</span> <span class="nt">PHP</span><span class="o">/</span><span class="nt">5</span><span class="nc">.2.4-2ubuntu5.10</span>
<span class="o"><</span> <span class="nt">Set-Cookie</span><span class="o">:</span> <span class="nt">PHPSESSID</span><span class="o">=</span><span class="nt">7f86ed2e5a4750275e98971773ac88ab</span><span class="o">;</span> <span class="nt">path</span><span class="o">=/</span>
<span class="o"><</span> <span class="nt">Expires</span><span class="o">:</span> <span class="nt">Thu</span><span class="o">,</span> <span class="nt">19</span> <span class="nt">Nov</span> <span class="nt">1981</span> <span class="nt">08</span><span class="nd">:52:00</span> <span class="nt">GMT</span>
<span class="o"><</span> <span class="nt">Cache-Control</span><span class="o">:</span> <span class="nt">no-store</span><span class="o">,</span> <span class="nt">no-cache</span><span class="o">,</span> <span class="nt">must-revalidate</span><span class="o">,</span> <span class="nt">post-check</span><span class="o">=</span><span class="nt">0</span><span class="o">,</span> <span class="nt">pre-check</span><span class="o">=</span><span class="nt">0</span>
<span class="o"><</span> <span class="nt">Pragma</span><span class="o">:</span> <span class="nt">no-cache</span>
<span class="o"><</span> <span class="nt">Set-Cookie</span><span class="o">:</span> <span class="nt">wassup</span><span class="o">=</span><span class="nt">ZjVjMWExMjZjNmIxNzU1NDBhZjU0MmM5MzhmYjllZDQ6OjEyNzkyODE5NzE6Ojo6MjAwMTozODg6ZjAwMDo6ZTZmOjoyMDAxOjM4ODpmMDAwOjplNmY</span><span class="o">%</span><span class="nt">253D</span><span class="o">;</span> <span class="nt">expires</span><span class="o">=</span><span class="nt">Fri</span><span class="o">,</span> <span class="nt">16-Jul-2010</span> <span class="nt">12</span><span class="nd">:11:11</span> <span class="nt">GMT</span><span class="o">;</span> <span class="nt">path</span><span class="o">=/</span>
<span class="o"><</span> <span class="nt">X-Pingback</span><span class="o">:</span> <span class="nt">http</span><span class="o">://</span><span class="nt">yegle</span><span class="nc">.net</span><span class="o">/</span><span class="nt">xmlrpc</span><span class="nc">.php</span>
<span class="o"><</span> <span class="nt">Vary</span><span class="o">:</span> <span class="nt">Accept-Encoding</span>
<span class="o"><</span> <span class="nt">Transfer-Encoding</span><span class="o">:</span> <span class="nt">chunked</span>
<span class="o"><</span> <span class="nt">Content-Type</span><span class="o">:</span> <span class="nt">text</span><span class="o">/</span><span class="nt">html</span><span class="o">;</span> <span class="nt">charset</span><span class="o">=</span><span class="nt">UTF-8</span>
<span class="o"><</span>
</pre></div>
<p>其他的比较知名的还有例如:</p>
<ul>
<li>404 Not Found</li>
<li>301 Moved Permanently</li>
<li>302 Found</li>
<li>500 Internal Server Error</li>
</ul>
<p>HTTP重定向就是通过301和302两种状态码来实现的。</p>
<p>302是临时重定向的意思。表示被访问页面因为各种需要被临时跳转到其他页面。具体的例子是访问 <a href="http://yegle.net/recursion.php">http://yegle.net/recursion.php</a></p>
<div class="codehilite"><pre>< HTTP/1.1 302 Found
< Date: Fri, 16 Jul 2010 11:26:48 GMT
< Server: Apache
< X-Powered-By: PHP/5.2.4-2ubuntu5.10
< Location: http://yegle.net/recursion.php
< Cache-Control: max-age=600
< Expires: Fri, 16 Jul 2010 11:36:48 GMT
< Vary: Accept-Encoding
< Content-Length: 0
< Content-Type: text/html
<
</pre></div>
<p>浏览器在收到302 Found的状态码之后会在返回的HTTP Response Header中查找Location字段,然后访问对应地址。在这个例子中,浏览器就会访问 <a href="http://yegle.net/recursion.php">http://yegle.net/recursion.php</a> (嗯这是一个递归,你懂的…)</p>
<p>301是永久重定向。这样的例子很好找。例如 <a href="http://google.com">http://google.com</a></p>
<div class="codehilite"><pre><span class="o"><</span> <span class="nt">HTTP</span><span class="o">/</span><span class="nt">1</span><span class="nc">.1</span> <span class="nt">301</span> <span class="nt">Moved</span> <span class="nt">Permanently</span>
<span class="o"><</span> <span class="nt">Location</span><span class="o">:</span> <span class="nt">http</span><span class="o">://</span><span class="nt">www</span><span class="nc">.google.com</span><span class="o">/</span>
<span class="o"><</span> <span class="nt">Content-Type</span><span class="o">:</span> <span class="nt">text</span><span class="o">/</span><span class="nt">html</span><span class="o">;</span> <span class="nt">charset</span><span class="o">=</span><span class="nt">UTF-8</span>
<span class="o"><</span> <span class="nt">Date</span><span class="o">:</span> <span class="nt">Fri</span><span class="o">,</span> <span class="nt">16</span> <span class="nt">Jul</span> <span class="nt">2010</span> <span class="nt">11</span><span class="nd">:29:07</span> <span class="nt">GMT</span>
<span class="o"><</span> <span class="nt">Expires</span><span class="o">:</span> <span class="nt">Sun</span><span class="o">,</span> <span class="nt">15</span> <span class="nt">Aug</span> <span class="nt">2010</span> <span class="nt">11</span><span class="nd">:29:07</span> <span class="nt">GMT</span>
<span class="o"><</span> <span class="nt">Cache-Control</span><span class="o">:</span> <span class="nt">public</span><span class="o">,</span> <span class="nt">max-age</span><span class="o">=</span><span class="nt">2592000</span>
<span class="o"><</span> <span class="nt">Server</span><span class="o">:</span> <span class="nt">gws</span>
<span class="o"><</span> <span class="nt">Content-Length</span><span class="o">:</span> <span class="nt">219</span>
<span class="o"><</span> <span class="nt">X-XSS-Protection</span><span class="o">:</span> <span class="nt">1</span><span class="o">;</span> <span class="nt">mode</span><span class="o">=</span><span class="nt">block</span>
<span class="o"><</span>
</pre></div>
<p>同样,浏览器在发现301的状态码之后会查找Location字段,然后访问那个地址。</p>
<p>Location字段的格式很随意,既可以是绝对地址,也可以是相对地址,还可以是相对根目录的地址。以下Location字段都是合法的:</p>
<ul>
<li>Location: http://yegle.net/</li>
<li>Location: /test/index.php</li>
<li>Location: index.php</li>
<li>Location: ../index.php</li>
</ul>
<p>301重定向和302重定向在SEO以及缓存上是有非常大区别的。</p>
<p>对于SEO也就是搜索引擎优化,一个页面302重定向到另一个页面,新页面的PageRank不会受原页面影响。而一个页面301重定向到另一个页面,原页面的PageRank会被传递到新页面。所以对于一个网站进行域名转换,最好的方法就是使用301重定向,在经过一段时间之后可以不损失PR地将全站转移到新域名下。</p>
<p>对于HTTP代理服务器例如squid来说,如果一个页面是302重定向到新页面并且没有指定Expire HTTP头,squid将不缓存这个信息,也就是说每次用户通过代理请求时都会重新获取一遍。而对于301重定向,squid可以将结果缓存以便快速响应下一个请求相同页面的用户。</p>
<p>PHP里的302重定向非常简单,只要在返回的HTTP Response Header里添加<code>Location</code>字段,PHP将自动返回302状态码。例如:</p>
<div class="codehilite"><pre><span class="cp"><?php</span>
<span class="nb">header</span><span class="p">(</span><span class="s2">"Location: http://yegle.net/recursion.php"</span><span class="p">);</span>
<span class="cp">?></span><span class="x"></span>
</pre></div>
<p>这段代码将自动重定向到<a href="http://yegle.net/recursion.php">http://yegle.net/recursion.php</a></p>
<p>而301重定向则稍微有点复杂,需要直接将301状态码用header函数返回给用户。例如:</p>
<div class="codehilite"><pre><span class="cp"><?php</span>
<span class="nb">header</span><span class="p">(</span> <span class="s2">"HTTP/1.1 301 Moved Permanently"</span> <span class="p">);</span>
<span class="nb">header</span><span class="p">(</span> <span class="s2">"Location: http://yegle.net/"</span> <span class="p">);</span>
<span class="cp">?></span>
</pre></div>
<p>注意的是,跳转不是在收到response header的时候马上进行,也就是说页面的剩余内容会被下载来之后浏览器才会跳转。新手常犯的一个错误是,在逻辑判断时对符合条件的情况进行header跳转之后,忘了在之后加上<code>exit()</code>,导致错误。例如,用<code>user_login()</code>判断用户是否进行了登录,如果未登录则跳转到登录页面。代码如下:</p>
<div class="codehilite"><pre><span class="x">if(!user_login()){</span>
<span class="x"> header("Location:login.php");</span>
<span class="x">}</span>
<span class="x">//display contents for login users.</span>
</pre></div>
<p>这里,容易以为header之后这段代码就结束了,没有在header之后使用<code>exit()</code>。后面的代码继续被执行,导致未登录用户看到了已登录用户才能看到的内容。</p>Windows下编译OpenVPN 2.1.12010-05-19T00:00:00+02:00yegletag:blog.yegle.net,2010-05-19:2010/05/19/compile-openvpn-2-1-1-in-windows/<h2 id="openvpn">为什么要编译OpenVPN</h2>
<ol>
<li>OpenVPN官方提供的Windows二进制文件默认是不包含从文件读取用户名密码功能的。</li>
<li>作为OpenVPN卖家,可以在自己编译的OpenVPN安装包里提供自己的logo等信息</li>
</ol>
<h2 id="_1">安装步骤</h2>
<p><em>注意:以下提到的安装路径皆为示例,如无特殊说明,皆可根据实际情况作更改。</em></p>
<h3 id="nsis">安装NSIS</h3>
<p>下载链接:<a href="http://sourceforge.net/projects/nsis/files/NSIS 2/2.46/nsis-2.46-setup.exe/download">http://sourceforge.net/projects/nsis/files/NSIS 2/2.46/nsis-2.46-setup.exe/download</a></p>
<p>正常安装即可。如无必要,请选择完整安装。</p>
<h3 id="mingw">安装MinGW</h3>
<p>下载链接:<a href="http://sourceforge.net/projects/mingw/files/Automated MinGW Installer">http://sourceforge.net/projects/mingw/files/Automated MinGW Installer</a></p>
<p>选择文件列表里的MinGW-5.1.6.exe下载。选择完整安装,安装到<code>D:\MinGW</code>下。</p>
<h3 id="msys">安装MSYS</h3>
<p>下载链接:<a href="http://downloads.sourceforge.net/mingw/MSYS-1.0.11.exe">http://downloads.sourceforge.net/mingw/MSYS-1.0.11.exe</a></p>
<p>选择安装到<code>D:\msys\1.0\</code>目录下。</p>
<h3 id="msysdtk">安装msysDTK</h3>
<p>下载链接:<a href="http://downloads.sourceforge.net/mingw/msysDTK-1.0.1.exe">http://downloads.sourceforge.net/mingw/msysDTK-1.0.1.exe</a></p>
<p>和MSYS选择安装在相同位置,<code>D:\msys\1.0\</code>目录下。</p>
<h3 id="_2">配置环境变量</h3>
<p>我的电脑,<code>右键->属性->高级->环境变量</code>,观察窗口上部_XXX的用户环境变量_,看是否有<code>PATH</code>这个环境变量。</p>
<p>如果有,双击修改,在最后加上<code>;d:\MinGW\bin;d:\msys\1.0\bin</code> (注意最前面的分号)。</p>
<p>如果没有,新建一个,变量名<code>PATH</code>,变量值<code>d:\MinGW\bin;d:\msys\1.0\bin</code> (注意最前面没有分号)</p>
<p>完成操作后,<code>开始->运行</code>,打开cmd窗口,输入<code>bash</code>。如果出现<code>bash-3.1$</code>字样,说明环境变量修改成功。输入exit退出bash。</p>
<h3 id="openvpn_1">下载OpenVPN预编译文件</h3>
<p>这里的预编译文件不是openvpn本身的预编译文件,而是例如openssl,tap驱动等等openvpn编译过程中需要的二进制文件</p>
<p>下载链接:<a href="http://openvpn.net/prebuilt/2.1_rc22-prebuilt.tbz">http://openvpn.net/prebuilt/2.1_rc22-prebuilt.tbz</a></p>
<p>版本不是<code>2.1.1</code>没有关系,因为<code>2.1_rc22</code>到<code>2.1.0</code>的代码变化不多,而<code>2.1.0</code>到<code>2.1.1</code>只是修正了一下内置的rpm打包用的spec文件而已…</p>
<p>解压,获得以下目录:</p>
<div class="codehilite"><pre>gen-prebuilt
lzo-2.02
openssl-0.9.8l
pkcs11-helper
</pre></div>
<p>将以上4个目录中都复制到<code>D:\</code>下</p>
<h3 id="openvpn-gui">下载OpenVPN GUI二进制文件</h3>
<p>下载链接:<a href="http://openvpn.se/files/binary/openvpn-gui-1.0.3.exe">http://openvpn.se/files/binary/openvpn-gui-1.0.3.exe</a></p>
<p>如果想深度定制自己的OpenVPN安装程序,可以自行编译安装OpenVPN GUI,修改源码中的rc文件得到自定义效果。具体就不在这里介绍了</p>
<p>下载后在D盘下建立<code>openvpn-gui</code>目录,将<code>openvpn-gui-1.0.3.exe</code>放到这个目录下</p>
<h3 id="openvpn-211">下载OpenVPN 2.1.1源码</h3>
<p>下载链接:<a href="http://openvpn.net/release/openvpn-2.1.1.tar.gz">http://openvpn.net/release/openvpn-2.1.1.tar.gz</a></p>
<p>下载解压,获得openvpn-2.1.1目录,放到D盘下</p>
<p>至此,D盘目录下有如下文件夹:</p>
<div class="codehilite"><pre>openvpn-2.1.1
openvpn-gui
gen-prebuilt
lzo-2.02
openssl-0.9.8l
pkcs11-helper
</pre></div>
<p>如果有缺少目录,请返回前面步骤检查:-)</p>
<h3 id="_3">修改编译配置文件</h3>
<p>注意:下面提到的所有修改文件操作,请一律用"写字板"或其他专业文本编辑器,如gvim,notepad++等打开。</p>
<ol>
<li>到<code>openvpn-2.1.1</code>文件夹,修改<code>version.m4</code>文件,将<code>define(PRODUCT_VERSION,[2.1.1])</code>中方括号的内容做自定义修改。例如修改为<code>define(PRODUCT_VERSION,[2.1.1-yegle])</code>。这个字符串会出现在OpenVPN安装程序中。</li>
<li>
<p>到<code>openvpn-2.1.1\install-win32</code>文件夹,修改<code>settings.in</code>文件,找到<code>;!define ENABLE_PASSWORD_SAVE</code>一行,去掉行首的分号。</p>
<p>注意:根据官方的manual,在<code>settings</code>里反注释掉<code>!define ENABLE_PASSWORD_SAVE</code>之后就可以保存密码了,但是实际编译的时候并没有生效。请直接修改<code>openvpn-2.1.1\misc.c</code>文件,在<code>#ifundef ENABLE_PASSWORD_SAVE</code>一行前面加上<code>#define ENABLE_PASSWORD_SAVE</code></p>
</li>
<li>
<p>最后一个修改比较复杂。为了减少编译安装的复杂度,就不自己编译tap-win32驱动了,用openvpn提供的prebuilt包里的tapinstall目录来代替。没有找到特别好的办法做这个hack,所以只能这么dirty了。到openvpn-2.1.1\install-win32文件夹,修改openvpn.nsi文件,找到以下代码:</p>
<div class="codehilite"><pre>; tap-64bit:
DetailPrint "We are running on a 64-bit system."
SetOutPath "<span class="nv">$INSTDIR</span>\bin"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\tapinstall\amd64\tapinstall.exe"
SetOutPath "<span class="nv">$INSTDIR</span>\driver"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\amd64\OemWin2k.inf"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\amd64\<span class="cp">${</span><span class="n">PRODUCT_TAP_ID</span><span class="cp">}</span>.cat"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\amd64\<span class="cp">${</span><span class="n">TAPDRV</span><span class="cp">}</span>"
goto tapend
tap-32bit:
DetailPrint "We are running on a 32-bit system."
SetOutPath "<span class="nv">$INSTDIR</span>\bin"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\tapinstall\i386\tapinstall.exe"
SetOutPath "<span class="nv">$INSTDIR</span>\driver"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\i386\OemWin2k.inf"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\i386\<span class="cp">${</span><span class="n">PRODUCT_TAP_ID</span><span class="cp">}</span>.cat"
File "<span class="cp">${</span><span class="n">GEN</span><span class="cp">}</span>\driver\i386\<span class="cp">${</span><span class="n">TAPDRV</span><span class="cp">}</span>"
tapend:
SectionEnd
</pre></div>
</li>
</ol>
<p>在这段代码第一行前插入:</p>
<div class="codehilite"><pre><span class="sx">!undef GEN</span>
<span class="sx">!define GEN "d:\gen-prebuilt\"</span>
</pre></div>
<p>在这段代码最后一行后面插入一行:</p>
<div class="codehilite"><pre>undef
!define GEN ".."
</pre></div>
<h3 id="openvpn_2">可选:给openvpn打补丁</h3>
<p>如果需要给openvpn打上特殊的补丁,例如openvpn ipv6补丁,可以在这个时候完成。</p>
<h3 id="_4">编译安装</h3>
<p><code>开始->运行</code>,运行cmd,依次输入以下命令:</p>
<div class="codehilite"><pre><span class="n">d</span><span class="o">:</span>
<span class="n">cd</span> <span class="n">openvpn</span><span class="o">-</span><span class="mf">2.1</span><span class="o">.</span><span class="mi">1</span>
<span class="n">bash</span> <span class="n">domake</span><span class="o">-</span><span class="n">win</span>
</pre></div>
<p>OK,openvpn开始编译了:-)</p>
<p>编译好之后的安装文件将出现在<code>D:\openvpn-2.1.1\gen\</code>文件夹下。Enjoy~</p>
<p>这里是我编译好的安装文件,根据官方源码+启用密码保存选项编译出来的,供参考:<a href="http://yegle.net/openvpn-2.1.1-yegle-install.exe">http://yegle.net/openvpn-2.1.1-yegle-install.exe</a></p>简单脚本检查portage安装的包是否有文件丢失2010-04-02T00:00:00+02:00yegletag:blog.yegle.net,2010-04-02:2010/04/02/script-to-find-missing-files-of-portage/<p>昨天开机fsck报错,正常fsck修复后丢了好多文件,导致现在系统各种不正常,ssh登录不上</p>
<p>写了个脚本检查丢失的文件:</p>
<div class="codehilite"><pre><span class="c">#!/bin/bash</span>
<span class="k">for</span> package in <span class="sb">`</span>eix -cI --only-names<span class="sb">`</span>
<span class="k">do</span>
<span class="k">for</span> file in <span class="sb">`</span>equery f <span class="nv">$package</span><span class="sb">`</span>
<span class="k">do</span>
<span class="nv">FILE</span><span class="o">=</span><span class="sb">`</span>basename <span class="nv">$file</span><span class="sb">`</span>
<span class="k">if</span> <span class="o">[</span> ! -e <span class="nv">$file</span> <span class="o">]</span> <span class="o">&&</span> <span class="o">[</span> <span class="s2">"x</span><span class="si">${</span><span class="nv">FILE</span><span class="p">:</span><span class="nv">0</span><span class="p">:</span><span class="nv">6</span><span class="si">}</span><span class="s2">"</span> !<span class="o">=</span> <span class="s2">"x.keep_"</span> <span class="o">]</span>
<span class="k">then</span>
<span class="nb">echo</span> <span class="nv">$package</span><span class="p">;</span>
<span class="nb">break</span><span class="p">;</span>
<span class="k">fi</span>
<span class="k">done</span>
<span class="k">done</span>
</pre></div>
<p>嗯,希望能帮助到其他人.</p>2010年3月26日2010-04-01T00:00:00+02:00yegletag:blog.yegle.net,2010-04-01:2010/04/01/2010-03-26/<p>Just to remember a special day for me.</p>