RHELのリリース番号を調べる
RedHat系ディストリビューション(RHEL4, CentOSなど)では,リリースパッケージの識別用にRPMパッケージが導入されている.このリリースパッケージは,"redhat-release" をProvideしている.RHELであればこの識別子を調べることで,そのシステムのリリース番号を調べる事ができる.
% rpm -q --provides `rpm -q --whatprovides redhat-release`
または,もっと単純に以下のようにしても良い.
% rpm -qi redhat-release
この識別子は,例えば,自分でRPMパッケージを作る際,「このRPMは,RHELの特定のリリースにのみインストールを許可したい」という制限を課すために使うことができる.例えば,specファイル中で "Required: redhat-release >= 5.0" のように表記することでこのような動作が実現できる.
RPMマクロ
便利に使えるRPMのマクロたち
%define foo bar
%fooをbarに定義するマクロ.cpp(1)の#defineみたいなもの.
後から上書きできる.
%define foo 1 %define foo 2 %foo # => 2
%{?name:expr}
nameが定義されていたらexprを評価.cpp(1)の#ifdefみたいなもの.
%{!?name:expr}
nameが定義されていなかったらexprを評価.cpp(1)の#ifndefみたいなもの.
%(expr)
exprの中身を/bin/shで実行する.評価結果は標準出力に出力された文字列となる.cpp(1)にはこんな変態マクロは無い.
組み合わせても使える
%{!?uname:%define uname %(uname)}
%ifもあるよ
condは,少なくとも 0 or 1 の場合に,期待通りに動くようだ.
%if cond commands %endif
RPMマクロの働きの調べ方
RPMのspecファイル中に現れる%{...}という形の式は,rpm(8)がマクロと判断して評価する.RedHatベースのシステムには,いくつか定義済みのマクロがあり,これらは/usr/lib/rpm/macrosファイルを調べる事で確認することができる.
しかし,わからないマクロが出てくるたびにいちいちこのファイルを読むのはとても面倒である.そんなときのために,rpm(8)には--evalオプションがある.これを使うと,マクロがどのように展開されるのか確認することができる.
実行例
% rpm --eval '%configure'
evalオプションを複数書くこともできる.この場合,オプションの順番通りに評価が行われる.
% rpm --eval '%{!?python_sitelib: %define python_sitelib %(python -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}' \ --eval '%{python_sitelib}' /usr/lib/python2.3/site-packages
PAM設定ファイルは,どれが読み込まれるのか?
Linux-PAM(pam(8))が入っているような,いまどきの普通のシステムでは,/etc/pam.dディレクトリ(または/etc/pam.confファイル)が存在する.各プログラム(デーモンなど)が認証処理を行う必要があるときに,ここから設定ファイルを読み込んで,PAMを通じて必要な処理を行う.例えば,sshdの場合,/etc/pam.d/sshdを読み込んで認証処理を行う.
ところで,各プログラムはどのようにして自身が読み込むファイル名(サービス名)を決めているのだろう? 結論を先に書くと,各プログラムがpam_start(3)を呼ぶ時の第一引数(サービス名)によって決まる.
例えば,OpenSSH(sshd)のコードを追ってみると,次のようなコードが見つかる.
freebsd-updateを使いたければRELEASEにしとかないとだめ
freebsd-update(8)を使ってシステムをアップデートしたい人は,セキュリティブランチ(RELENG_7_0等)でcsupしてビルドしましょう.freebsd-update(8)はRELEASEのみをサポートしているので,うっかりSTABLEをビルドするとはまります.
いきさつ
RELEASE-6.2なシステムを7.0-STABLE(RELENG_7)に上げた.
% uname -a FreeBSD yourhost.yourdomain 7.0-STABLE FreeBSD 7.0-STABLE #1: Sun Jun 15 02:29:39 JST 2008 root@yourhost.yourdomain:/usr/obj/usr/src/sys/GENERIC i386
次回からは,自分でちまちまとビルドしたりといった作業は避けたかったので,freebsd-update(8)を試す.ところが,下記のようなよくわからないエラーとなりだめ.
% sudo freebsd-update fetch Looking up update.FreeBSD.org mirrors... 1 mirrors found. Fetching public key from update1.FreeBSD.org... failed. No mirrors remaining, giving up.
素直にエラーメッセージだけを読むと,update1.freebsd.orgが落ちているように読めるが,pingは通るし当該サーバが落ちたという情報も特に見当たらない.
そこで,エラーメッセージでググるとFreeBSD PR: misc/12137に行き着いた.がーん,これだ.ショック.
あと,よく見るとfreebsd-update(8)のmanにも「RELEASEしかサポートしてないもんね」と書いてあった.がーん.
portsnapを使ってportsツリーを更新
csupを使うかわりに,portsnapを使ってportsツリーを更新することにしました.
手順は以下の通りです.「BSD にくびったけ - portsnap」にすばらしい解説記事があるので,ここを参考に作業しました.ほとんどこの記事の通りです.
手順
使い始める時にやること
(1) とりあえずportsnap fetch
% sudo portsnap fetch
(2) 次にportsnap extract
% sudo portsnap extract
(3) 最後にportsnap update
% sudo portsnap update
(4) 更新されたportsパッケージを確認
% sudo portversion -vL=
(5) 必要に応じてアップデート
とりあえず全部更新な場合は次のような感じで.
% sudo portupgrade -aRP
2回目以降にやる事
(1) portsnap fetch && portsnap update
% sudo portsnap fetch && sudo portsnap update
portsパッケージの確認や更新は同じ.
/etc/crontabに自動更新を仕込む
いちいち自分でportupgradeするのもだるい.というか,このくらい自動でアップデートして頂きたい.こんな事を毎回手動でやっていたらIT土方そのものである.そんなわけで.スクリプトを書いてcrontabに仕込むことにした.
まず,次のようなスクリプトを書いて/usr/local/sbinあたりに放り込んでchmod 755する.
- /usr/local/sbin/update-ports-cron.sh
#!/bin/sh # $Id: update-ports-cron.sh 204 2008-06-15 11:30:39Z genta $ ( /usr/sbin/portsnap fetch && /usr/sbin/portsnap update ) 2>&1 >/dev/null /usr/local/sbin/pkgdb -F echo "---- portversion:" /usr/local/sbin/portversion -vL= echo echo "---- portupgrade:" /usr/local/sbin/portupgrade -aRP --batch echo echo -n "Finished at: " LANG=C date
次に,/etc/crontabに以下の行を追加.
0 5 * * * root /usr/local/sbin/update-ports-cron.sh
これでうまく行くんじゃないだろうか.いくといいな.
WWW::Mechanize(Rubyの)で一部のフォームが取れない問題
どうも,well-formedじゃないHTMLの場合に,フォームの一部を取れないみたい.例えば,以下のようなフォームがあるときにbarが取れない(WWW::Mechanize#page.forms.first.field('bar').nil? == trueになる).
<p> <form> <input name="foo"> </p> <input name="bar">
検索してみたけど情報が無い.みんな困っていないのだろうか?
まだ対策できていないけど,テストだけ張っておく.これがpassすれば問題解決.
#!/usr/local/bin/ruby # $Id$ require 'rubygems' require 'mechanize' require 'logger' require 'webrick' require 'test/unit' require 'ruby-debug' class DumbHTTPD class Servlet < WEBrick::HTTPServlet::AbstractServlet @@tmpl = lambda do |val| <<_EOT_ <html> <body> <h1>Fill out below form:</h1> <p> <form action="/" method="POST"> <input type="text" name="foo" value="#{val['foo']}"> </p> <input type="text" name="bar" value="#{val['bar']}"> <input type="submit" value="save"> </form> </body> </html> _EOT_ end def do_GET(req, res) res['Content-Type'] = 'text/html; charset=utf-8' res.body = @@tmpl.call({ 'foo' => 'Hello world', 'bar' => 'hoge', }) return res end def do_POST(req, res) res['Content-Type'] = 'text/html; charset=utf-8' res.body = @@tmpl.call(req.query) return res end end attr_accessor :webrick, :runtime, :bindaddr, :port def initialize(bindaddr, port) self.bindaddr, self.port = bindaddr, port end def init_webrick self.webrick = WEBrick::HTTPServer.new( :BindAddress => self.bindaddr, :Port => self.port) self.webrick.mount('/', Servlet) end def start self.init_webrick self.runtime = Thread.new(self.webrick) {|w| w.start} return self end def stop self.runtime.kill.join self.webrick.shutdown return self end end class TC_Mech < Test::Unit::TestCase attr_accessor :agent, :servlet def setup self.agent = WWW::Mechanize.new {|a| a.log = Logger.new($STDERR) } self.agent.max_history = 1 self.agent.user_agent_alias = 'Windows IE 6' self.servlet = DumbHTTPD.new('localhost', 10182).start end def teardown self.servlet.stop end def test_scrape_not_wellformed_html page = self.agent.get('http://localhost:10182') form = page.forms.first {'foo' => 'Hello world', 'bar' => 'hoge', }.each do |k, v| field = form.field(k) assert(!field.nil?, "form field '#{k}' is not exists") assert_equal(v, field.value, "form field '#{k}'.value != '#{v}'") end end end
対処法の考察(メモ)
WWW::Mechanize::Page#formsは,最初の一回呼び出された時にHpricot.parseしてsearch('form')して,出てきたHpricot::Elements分だけformを作って(WWW::Mechanize::Pageのインスタンス変数に)キャッシュする.んだけども,Hpricotは上記のような壊れたHTMLを読む時,後続のinputを無視してしまう.
だけど,Hpricot的には多分悪くない動作.多分,WWW::Mechanizeで対策するべきで,Mechはplaggable_parserという機構がありHTMLパーサを動的に差し替える事が可能.これを使って「ゆるくHTMLフォームを解釈するWWW::Mechanize::Pageの子孫クラス」を適当に作って以下のようにすればよい.
class WWW::Mechanize::LamePage < WWW::Mechanize::Page def initialize(uri=nil, response=nil, body=nil, code=nil) super(uri, response, body, code) end def forms # ここでformsを再定義 end end agent = WWW::Mechanize.new agent.pluggable_parser.html = WWW::Mechanize::LamePage # 標準のパーサを差し替え # 後は普通に使う
と,対策手法はわかっているんだけど,Hpricotの使い方が難しくてなかなか進みません…….
追記 (2009/2/24)
WWW::Mechanize 0.9.0で追試したところ,本エントリで触れた問題は解決していた.よかった.
どうやら,HTMLパーサがHpricotからNokogiriに変わったことで,このような問題がなくなった模様.
Twitterの自動follow返しをRubyで
Twitter でイチイチ follow するのが面倒くさい んだけど自前でメールサーバも立ててない - Djangoへの片思い日記 関連.
Rubyだとこんな感じかな? 以下,付属ライブラリのみ使用.
自前のIMAP4サーバに対してしかテストしてないけど,たぶんGmailでも動くんじゃないかと思う.
#!/usr/local/bin/ruby require 'net/imap' require 'open-uri' # IMAP4 configuration: SERVER = 'imap.gmail.com' USER = 'exampleuser@gmail.com' PASSWD = 'gmail_password_here' class Twitter USER = 'twitter_username_here' PASSWD = 'twitter_password_here' TwitterURI = 'http://twitter.com/friendships/create/%s.json' def self.add(id) open(TwitterURI % [id], :http_basic_authentication => [USER, PASSWD]).read end end imap = Net::IMAP.new(SERVER, 993, true) imap.login(USER, PASSWD) imap.select('INBOX') imap.search(['FROM', 'noreply@twitter.com', 'SUBJECT', 'is now following you on Twitter!', 'UNSEEN']). each do |num| data = imap.fetch(num, 'BODY[TEXT]') body = data[0].attr['BODY[TEXT]'] Twitter.add(id = body.match(%r|http://twitter\.com/(\w+)|).to_a[1]) imap.store(num, '+FLAGS.SILENT', [:Seen]) puts "add #{id}" end imap.close