Python with statement

Python の2.6からwith statement が標準機能となった.2.5でもfuture 機能として取り込まれていたので目新しくはないが,なかなか便利で,もう2.4には戻れない.with statementはtry, except, finally で実現できる構造をもう少し簡単に書けるようにしてくれる.以前のエントリで書いたthreading ライブラリのConditionなどに使うととても簡単に書ける.

このようなwithで使うオブジェクトをContext Managerと呼ぶ.Context Managerを書くのは割に簡単なので,ちょっと書いてみた.こちらの記事によくまとまっている.

実効ユーザIDのセット

Unixのプロセスにはreal user id(実ユーザID)と effective user id(実効ユーザID)の二つのidが割り当てられている.普通のプロセスでは両者は一致しているのだが,セキュリティの関係でことなる値を使いたい場合がある.例えば,任意のユーザのプロセスを操作する必要があるので,root権限が必要なのだけど,普段からroot権限で動くのはいかにも剣呑なので,普段はnobodyで動いているデーモンも多い.実効ユーザIDの戻し忘れはセキュリティホールに直結するので,注意が必要だ.

この実効ユーザIDを操作するContext Manager を書いてみよう.
with文に入るところで __enter__ が呼ばれ,出るところで__exit__が呼ばれるので,それぞれos.seteuidを呼び出しているだけ.__exit__にはちょっと注意が必要で,with文のなかで生じた例外のための引数が余分に存在する.withブロック内部で発生した例外を外に伝搬させるためには,__exit__文の戻り値をFalseにしなければならない.

import os

class Euid():
    def __init__(self, id):
        self.id = id
    
    def __enter__(self):
        self.orgeuid = os.geteuid()
        os.seteuid(self.id)

    def __exit__(self, exc_type, exc_value, traceback):
        os.seteuid(self.orgeuid)
        if exc_type:
            return False
        return True

使い方はこんな感じ.

print os.geteuid()
with Euid(501):
    print os.geteuid()
print os.geteuid()

これをroot権限で実行してみるとこうなる.

$ python setuid.py
0
501
0

withブロックの中でだけ実効ユーザIDが変更されていることが分かる.ただし,マルチスレッドで使う場合には注意が必要.実効ユーザIDはプロセス単位で定義されるので,別のスレッドから見ると突然実効ユーザIDがパカパカ切り替わることになる.実際あんまりいいサンプルじゃなかったかもしれない.

所感

with文はとても便利.CentOS 5ではいまだにpythonが2.4なので,まだ標準機能としては使いにくいのだけど.せめて2.5になってくれないものだろうか.