2010 年 2 月 26 日 | カテゴリー: プログラム, 日記

vmlinuz.dump

お題「vmlinuz を解凍して vmlinux を取りだそう」。
環境は Mandriva Linux 2010.0。

Traditionally, when creating a bootable kernel image, the kernel is also compressed using the zlib algorithm, or since Linux 2.6.30[1], using LZMA or BZIP2, which requires a very small decompression stub to be included in the resulting image.

vmlinuz は gzip で圧縮されていることが知られています。Kernel 2.6.30 以降は LZMA か BZIP2 が使われていることもあるらしいですが、結論から言うと今回のは gzip。

ただし、ただの gzip 圧縮というわけではなく、16進数ダンプやらなにやらして確認して、ようやく解凍できました。

普通に gunzip を試みる

普通に gunzip を試してみます。

kei@frederica ~ $ gunzip /boot/vmlinuz-2.6.31.12-desktop-1mnb 
gzip: /boot/vmlinuz-2.6.31.12-desktop-1mnb: unknown suffix -- ignored

gunzip は拡張子を見るプログラムだったみたいですよ。
拡張子をつけて改めて。

kei@frederica ~ $ cp /boot/vmlinuz-2.6.31.12-desktop-1mnb ./vmlinuz.gz
kei@frederica ~ $ gunzip ./vmlinuz.gz 
 
gzip: ./vmlinuz.gz: not in gzip format

!? gzip じゃねーよとおこられます。
ちなみにこのあと、BZIP2 や LZMA を試しても同様に撃沈。

16進数ダンプして gzip ヘッダーを確認する

od (8進数ダンプツール) にオプションをつけて16進数出力して確認してみます。

od -A d -t x1 /boot/vmlinuz-2.6.31.12-desktop-1mnb  > vmlinuz.dump

-A d で表示されるオフセットの基数は10進数に。
-t x1 で16進数出力に。

gzip のヘッダーは「1f 8b 08 00」。なんか頭にいないんですけど。

0015952 c7 c1 c0 16 2c 00 48 c1 e9 03 fd f3 48 a5 fc 5e
0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e
0015984 4b 02 03 ec fd 0b 7c 14 d5 fd f8 ff cf e6 02 41

ファイルに落として確認するのは面倒ですので、grep に渡すだけでいいです。

kei@frederica ~ $ od -A d -t x1 /boot/vmlinuz-2.6.31.12-desktop-1mnb | grep '1f 8b 08 00'
0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e

gzip ヘッダーの開始バイト位置を確認する

0015968 48 8d 83 20 d8 2b 00 ff e0 1f 8b 08 00 d8 a0 5e

15968 から数えて 1f は 15968 + 9 = 15977。
つまりここまでスキップしてから gzip として解凍すれば、解凍できるはず。

dd でバイトスキップして zcat に渡す

dd でインプットファイルに /boot/vmlinuz-2.6.31.12-desktop-1mnb を食べさせて、ブロックサイズを1で 15977 個スキップします (0 から数えるから)。

kei@frederica ~ $ dd if=/boot/vmlinuz-2.6.31.12-desktop-1mnb bs=1 skip=15977 | zcat > vmlinux 
 
gzip: stdin: decompression OK, trailing garbage ignored

「decompression OK」!

strings で vmlinux を参照

無事解凍できましたが、当然バイナリ。
でも、Kernel version とかの文字列が含まれているはずです。

kei@frederica ~ $ strings vmlinux | grep "Linux version"
Linux version 2.6.31.12-desktop-1mnb (qateam@titan.mandriva.com) (gcc version 4.4.1 (GCC) ) #1 SMP Tue Jan 26 02:59:07 EST 2010

とれたー!

結論

vmlinuz は gzip ヘッダーが頭にいないので、dd でスキップして解凍しましょう。
ちなみに fedora 8 の xen kernel は拡張子を .gz にしたうえで gunzip したら普通に解凍できたり。xen kernel は単純な gzip なのかなー。

2009 年 9 月 5 日 | カテゴリー: プログラム, 日記

20090829 しばらくお待ちください。

Lightweight Language Television

LLTV おつかれさまでした! # もう先週の話です。

VAIO 持って行ったけど、iPhone から twitter に投げるだけでログが済んでしまいました。昨年からの大きなライフスタイルの違いを実感。

20090829 ライトニングトーク準備中ー

今回も10時間という長時間の LL。
言語話が少ない印象でした。
昨年は処理系の話とかコードの話とか熱かった気がするけど。

Apribase » Lightweight Language Future
Apribase » Lightweight Language Spirit

20090829 番組終了です。おつかれさまでした。

ログを残すと同時に、リアルタイムで会場内の twitter ユーザの声も聞けたのが楽しかったかな。
たしか2007年の LL 魂のとき、Lingr を使ってチャットがあった気がするけど、あのときのときめきの再来。

以下、twitter で流してた分のログ。

10:25 開会宣言

LLTV はじまるよー
posted at 10:29:05

10:30 朝から生テレビ

しろうさんの昔話はじまた。 #LLTV
posted at 10:44:44
pypy いいよ pypy。しろうさんも興味もったよ。
posted at 10:50:17
上位から下位まで楽しいのはゲーム業界。ですよねー。 #LLTV
posted at 11:01:35
これ、話、落ちるのかしら。めっさ飛んでる。司会大変。 #LLTV
posted at 11:07:38
Clousure きたー #LLTV
posted at 11:12:54
Squeak とかに繋がる振りと思ったのにクラウドに飛んだ。。 #LLTV
posted at 11:27:19
「今日あたりのブログでグズグズ感満載」 #LLTV
posted at 11:51:08

12:00 LL フィーリングカップル

LL フィーリングカップルはじまるよー #LLTV
posted at 12:04:23
デートでペアプロいいじゃない。 #LLTV
posted at 12:25:24
ここいちさん「同じ職場でなければ」生々しいな #LLTV
posted at 12:28:46
女性らしいコードってなに。 #LLTV
posted at 12:30:23
女性でもラムダラムダ言ってますが。 #LLTV
posted at 12:35:14
ところで、LL 全く関係ない。 #LLTV
posted at 12:43:39
牧さんのライフはゼロよ! #LLTV
posted at 12:53:56
しおりさんの意見に同意。近いほうがいいです。 #LLTV
posted at 13:01:07

13:00 昼休み

ひるやすみー http://movapic.com/pic/200908291331514a98af377955b
posted at 13:31:55

渡る世間は雲ばかり

渡る世間は雲ばかり。 #LLTV
posted at 14:32:25
MS の方が Mac 使うのはツッコミ待ちと理解。 #LLTV
posted at 14:40:39
しかも Windows7 デスクトップ見せられた。釣り針いっぱい。 #LLTV
posted at 14:43:09
まとめ。MS の人は Macbook に Bootcamp で Windows7 で発表。 #LLTV
posted at 14:46:13
誘惑に負けないひがさんのテストケース。頑張って司会さん。 #LLTV
posted at 15:07:05
「ひがさんのが眠い」とか、出だしから飛ばす高井さん。 #LLTV
posted at 15:11:32
第一言語 Java だけど、この会場で手をあげられなかった(ぇー #LLTV
posted at 15:13:38
ぶ、Mongrel 開発者とんずらしてたの今知ったよ! #LLTV
posted at 15:14:36
昨年と比べ、Ruby 周りも分かる今、JRuby の理解度に隙はなかった。 #LLTV
posted at 15:17:27
JRuby-Rack #LLTV
posted at 15:20:33
「詳細はググれ」 #LLTV
posted at 15:21:11
スフィンクスってドキュメントツール?あとでぐぐる。 #LLTV
posted at 15:40:04
高井さんが萌える。「ゴメンね?」 #LLTV
posted at 15:58:36

プロトタイピング〜「もの作り」の流儀〜

ビデオプロトタイプ。ハードウェアスケッチ。 #LLTV
posted at 16:25:41
十時間は伊達じゃなす。ねむいー。 #LLTV
posted at 17:09:30

17:20 夕休み

18:00 大改善!!劇的ビフォーアフター

劇的ビフォーアフター始まりました。 #LLTV
posted at 18:05:36
Vimperator で SL コマンド実装。 #LLTV
posted at 18:28:29
lucky☆st◯r #LLTV
posted at 18:48:02
普通は ls なんて使わない。 buffer! つかうので。 #LLTV
posted at 18:49:14
Real/Macro Metaprogramming on C #LLTV
posted at 18:50:46
BSD の ls ソースは優秀。 #LLTV
posted at 18:53:04
cmp.c 冗長なコード。こうかいかんすうがつかえない。 #LLTV
posted at 18:54:22
util.c クロージャが使えない。 foreach がない。 #LLTV
posted at 18:55:46
CiSE C In S Expression #LLTV
posted at 18:58:03
マクロでコード生成 繰り返しパターンの除去。DSL も実装。 #LLTV
posted at 19:02:34
マクロクラブ マクロを書くな。 それがカプセル化の唯一の手段ならマクロを書け。関数よりいいなら書け。 #LLTV
posted at 19:06:49
クロージャ(JVM LISP のほう) より。しろうさん翻訳中。 #LLTV
posted at 19:08:52
バージョン管理もデータベースも涙目。テキスト最強説展開中。 #LLTV
posted at 19:15:17

LL レッドカーペット

ライトニングトーク準備中ー http://movapic.com/pic/200908291938294a990525d4db0
posted at 19:38:33
しばらくお待ちください。 http://movapic.com/pic/200908291942414a99062115c26
posted at 19:42:45
ゆっくりしていってね。 #LLTV
posted at 19:57:34
形態素解析。全裸で。 #LLTV
posted at 19:59:00
IRC の発言がすべて全裸に。 #LLTV
posted at 20:00:30
Twitter zenra_bot #LLTV
posted at 20:01:16
これを全裸イナーといいます。 #LLTV
posted at 20:03:18
Konoha せんせーw #LLTV
posted at 20:10:50
なるとより。木の葉の里。最近潰れましたけど。 #LLTV
posted at 20:11:40
LL でカーネルを書こう。 #LLTV
posted at 20:12:51
いや無理。 #LLTV
posted at 20:13:01
Konoha が落ちると、カーネルも落ちます。 #LLTV
posted at 20:14:38
TVML 一行イベントでアニメ。 #LLTV
posted at 20:25:28
ビッチー シュール…! #LLTV
posted at 20:26:13
小さい女の子が話す pykook。 #LLTV
posted at 20:29:02
残り一分が虚しかった。 #LLTV
posted at 20:33:07
H8 LED マトリクス。 #LLTV
posted at 20:35:39
PySerial が実にクール。 #LLTV
posted at 20:36:10
リモコンでテレビ をロボットハンドで。 #LLTV
posted at 20:37:46
ほりぐれさんの漫才はじまた。 #LLTV
posted at 20:38:43
番組終了です。おつかれさまでした。 http://movapic.com/pic/200908292050124a9915f4d73a6
posted at 20:50:26

帰り

帰りの電車なう。つかれたー。 #LLTV
posted at 21:21:22

2009 年 8 月 19 日 | カテゴリー: プログラム

C++ にも python の commands.getoutput を作ろう
C++ で外部プロセスを呼び出そうとしたらすごく大変だったから、python の commands.getoutput(”ls -l”) みたいに一行で書けて標準出力が返ってくれば幸せになれるのに・・・!という経緯です。

CreateProcess

今回は WIndows 用に調べてたんで MSDN。
CreateProcess 関数

泣きたくなるような引数の数に加え、型が外国語すぎて読めません;
それでも一応頑張って読んでみると、STARTUPINFO ってのを外から与えると、構造体の中を破壊してきて標準出力とかがこの中に書き込まれるみたいですよ。
STARTUPINFO

このへんで投げ出したけど、真面目に実装するとけっこう長くなるかんじ。ハンドルって概念が分からないんですよ。なにそれ。

popen/pclose

まだこっちのが分かるよねってのが C 標準関数の popen/pclose。
Manage of POPEN
Windows でも _popen/_pclose で実装されてるみたいで、とりあえずこっちで書いてみることに。

python commands

目指すインターフェースは以下なかんじ。とりあえず getoutput だけ。
16.16 commands — コマンド実行ユーティリティ

commands.hpp

インターフェースをまねるとこんなかんじですよね。
インスタンス生成とかせずに、シンプルに関数呼び出しで扱える形に。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
 * File:   commands.hpp
 * Author: kei
 *
 * Created on 2009/08/18
 */
 
#include <string>
 
#ifndef _COMMANDS_HPP
#define _COMMANDS_HPP
 
namespace commands {
    const std::string getoutput(const std::string& command);
}
 
#endif /* _COMMANDS_HPP */

commands.cpp

実装。

C 言語特有のエラーコードを返す関数を、全部例外投げるようにラップしてみたけど、finally がないからクローズ処理がまとめられなかったり、throws がないから再 throw になっちゃったり。
あと Java の IOException みたいな例外定義がデフォルトでなくて、そもそも catch しなくてもコンパイル通るとか。
それぞれの例外クラスを定義して、catch で分けるのが正しいんですが、とりあえず std::runtime_error 投げたんで、よい子はマネしないでね。

popen がファイルディスクリプタを返してくるんですが、この子からファイルストリームを生成するうまい方法がなくて(昔のコンパイラはコンストラクタに喰わせて fstream 作れたみたいだけど)、素直に C 言語な実装に。

あと必ず pclose で閉じること!って書いてあったから、boost::shared_ptr なんか使って解放しちゃ危なそうってことで boost も出番なし。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
/*
 * File:   commands.cpp
 * Author: kei
 *
 * Created on 2009/08/18
 */
 
#include <cstdio>
#include <sstream>
#include <stdexcept>
#include "commands.hpp"
 
/**
 * 指定したコマンドのプロセスへのファイルディスクリプタを返します。
 * 使用後は close_process(const FILE* process) でクローズしてください。
 * プロセスのオープンに失敗した場合は例外を投げます。
 * @param command
 * @return プロセスへのファイルディスクリプタ
 * @throw std::runtime_error
 */
FILE* open_process(const std::string& command) {
    FILE* process = popen(command.c_str(), "r");
    if (process == NULL) {
        throw std::runtime_error("open " + command + " failed.");
    }
    return process;
}
 
/**
 * 実行プロセスの出力を返します。
 * @param process
 * @return プロセス実行出力
 */
const std::string read_process(FILE* process) {
    std::stringstream output;
    char buffer[128]; // allocate memory
    while (fgets(buffer, sizeof (buffer), process)) {
        output << buffer;
    }
    return output.str();
}
 
/**
 * 指定したプロセスをクローズします。
 * プロセスのクローズに失敗した場合は例外を投げます。
 * @throw std::runtime_error
 */
void close_process(FILE* process) {
    const int result = pclose(process);
    if (result == -1) {
        throw std::runtime_error("close_process faild.");
    }
}
 
/**
 * 指定したコマンドをシェル経由で実行し、出力を返します。
 * プロセスのオープン/実行/クローズのいずれかに失敗した場合は例外を投げます。
 * @param command コマンド
 * @return コマンド出力
 * @throw std::runtime_error
 */
const std::string commands::getoutput(const std::string& command) {
    FILE* process = NULL;
    std::string output;
    try {
        process = open_process(command);
        output = read_process(process);
    } catch (std::runtime_error error) {
        // finally 処理の代わりに、例外時にもクローズ処理を書いてあります。
        // open/read の例外時のため、close の例外確認は行いません。
        if (process != NULL) {
            close_process(process);
        }
        throw error;
    }
    // 正常時の close 処理です。例外確認も行います。
    if (process != NULL) {
        try {
            close_process(process);
        } catch (std::runtime_error error) {
            throw error;
        }
    }
    return output;
}

main.cpp

date コマンドを打ってみた例。
まあこんなとこでしょ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * File:   main.cpp
 * Author: kei
 *
 * Created on 2009/08/18
 */
 
#include <cstdlib>
#include <iostream>
#include <stdexcept>
#include "commands.hpp"
 
/**
 * メインエントリです。
 * 以下は date コマンドを実行するサンプルです。
 * @param argc
 * @param argv
 */
int main(int argc, char** argv) {
    try {
        std::cout << commands::getoutput("date") << std::endl;
    } catch(std::runtime_error error) {
        std::cerr << error.what() << std::endl;
    }
    return (EXIT_SUCCESS);
}

/usr/lib/python2.6/commands.py

python の実装を真面目に読んでみる。
os.popen() を使ってるみたい。

"""Execute shell commands via os.popen() and return status, output.

確かに。

def getstatusoutput(cmd):
    """Return (status, output) of executing cmd in a shell."""
    import os
    pipe = os.popen('{ ' + cmd + '; } 2>&1', 'r')
    text = pipe.read()
    sts = pipe.close()

/usr/lib/python2.6/os.py

subprocess の Popen からパイプを生成してるもよう。

import subprocess
PIPE = subprocess.PIPE
p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
                                stdin=PIPE, stdout=PIPE, close_fds=True)
return p.stdin, p.stdout

/usr/lib/python2.6/subprocess.py

あー、Windows 版はやっぱり CreateProcess を使って実装してるのね。

On UNIX, with shell=False (default): In this case, the Popen class
uses os.execvp() to execute the child program. args should normally
be a sequence. A string will be treated as a sequence with the string
as the only item (the program to execute).

On UNIX, with shell=True: If args is a string, it specifies the
command string to execute through the shell. If args is a sequence,
the first item specifies the command string, and any additional items
will be treated as additional shell arguments.

On Windows: the Popen class uses CreateProcess() to execute the child
program, which operates on strings. If args is a sequence, it will be
converted to a string using the list2cmdline method. Please note that
not all MS Windows applications interpret the command line the same
way: The list2cmdline is designed for applications using the same
rules as the MS C runtime.

startupinfo の文字が。やっぱり頑張らないとかー。

# Start the process
try:
    hp, ht, pid, tid = CreateProcess(executable, args,
                                         # no special security
                                         None, None,
                                         int(not close_fds),
                                         creationflags,
                                         env,
                                         cwd,
                                         startupinfo)

最後に

boost::process みたいなのないですか。いやぐぐるとひっかかるんですけど、これ boost パッケージに含まれてます?
Pstreams ってのもあったけど使ってる人いるのかしら?

特になければ、今回作ったものをもう少しちゃんと実装して使い回していく方向かなあ。ぐぐっても出てこないあたり需要ないのか知らないけど、Windows で ssh ライブラリがないからこれで叩こうと思ったわけで。
PuTTY のソースも見たし、python paramiko も試したけど、大人の事情がもにょもにょだったので。

C++ 素人につきリファクタ職人の降臨が望まれます。

2009 年 8 月 4 日 | カテゴリー: プログラム

20090804 paramiko で ssh

paramiko: ssh2 protocol for python
Pythonで ssh 接続するのはすごく簡単でした。

Linux なら command で ssh を叩けばいいんですけど、Windows だとそうもいかなくて。
cygwin 入れるのもなんだし、PuTTY はオープンソースだから組み込むってのもできるけど・・・。

あれ?Python のコレ、Windows でも動くんじゃない?というときめく期待のもとに paramiko 初体験という経緯なのです。

LGPL だから気をつけないといけないけど、これだけ使いやすくて更新もされているわけで。

どっかで見たことあると思ったら、bazaar とかでも依存ライブラリに書いてあったし、けっこうあちこちで使われてそうというのもいいかんじ。
C++ から boost.python で呼ぶなり、単純に外部プロセスとして呼び出すなりで、いろいろできそう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/python
# -*- mode:python; coding:utf-8 -*-
# paramiko_test.py
 
import paramiko
 
__author__ = "kei"
__date__ = "$2009/08/04 22:04:05$"
 
def command():
    return "df -h"
 
def hostname():
    return "apribase.net"
 
def username():
    return "apribase"
 
def password():
    return "foobar"
 
if __name__ == "__main__":
    client = None
    try:
        client = paramiko.SSHClient()
        client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        client.connect(hostname(), username=username(), password=password())
        stdin, stdout, stderr = client.exec_command(command())
        print "hostname: " + hostname()
        print "command: " + command()
        print stdout.read()
    finally:
        if client != None:
            client.close()
2009 年 7 月 22 日 | カテゴリー: プログラム

20090722 rsync3 による世代管理バックアップスクリプト

勉強会のときにも使った python 製世代管理バックアップスクリプトです。

rsync3 のプロトコルによる高速化と、–rsync-path と sudo の組み合わせによる一般ユーザ権限ログインによる実行、そして –link-dest を使ったハードリンクによる差分バックアップを実現しています。
スクリプトでまかなっているところは、各オプションの指定方法が分かりにくかったり、空のディレクトリと同期してデータが全滅したりするのを防ぐところ。あと毎日のバックアップと毎週のスナップショットをとってローテーションをまわすところ。

スナップショットをとる曜日や、システムユーザ(一般ユーザ権限)等、直接スクリプトを編集して運用するつもりなので、下記に公開してるものをそのまま使われることは想定していません。
twitter 上でも少し話が出ましたが、元々 iSCSI 環境で問題が出たのと、GFS 等を利用できない環境のために作成したものです。

使い方

バックアップサーバ側で実行し、バックアップ対象のデータを pull します。

python pull_backup.py --hostname="apribase" --directory="/home/"

上記のように実行すると、hostname 用のディレクトリが /home/ 配下に生成され、日付付きでディレクトリが保存されます。
最新版にシンボリックリンクがはられ、次のバックアップ時には前回からの差分(正確には重複ファイルをハードリンクするのであって、差分という言葉は若干違う)を保存します。

home -> /home/apribase/home-200907-22
/home/apribase/home-200907-22

拡張オプションとして、exclude に対応してあります。
カンマ区切りで指定すると、rsync オプションの exclude を複数指定する記述に展開します。

python pull_backup.py --hostname="apribase" --directory="/home/" --exclude="db*,socket,server.pem,tls_sessions.db"

pull_backup.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
#! /usr/bin/python
# -*- mode:python; coding:utf-8 -*-
# pull_backup.py
 
import calendar
import commands
import datetime
import os.path
import socket
from optparse import OptionParser
 
__author__ = "kei"
__date__ = "$2009/07/22 02:43:23$"
 
# ============================================================================
# setup option parser
# ============================================================================
 
parser = OptionParser(usage="python %prog --hostname=HOSTNAME --directory=DIRECTORY --exclude=FILE\n ex: python %prog --hostname=\"apribase\" --directory=\"/home/\" --exclude=\"db*,socket,server.pem,tls_sessions.db\"")
parser.add_option("--hostname", dest="hostname",
                  help="--hostname=\"apribase\"")
parser.add_option("--directory", dest="directory",
                  help="--directory=\"/home/\"")
parser.add_option("--exclude", dest="exclude",
                  help="--exclude=\"db*,socket,server.pem,tls_sessions.db\"")
options, args = parser.parse_args()
 
# ============================================================================
# target strings
# ============================================================================
 
def hostname():
    return options.hostname
 
def directory():
    return options.directory
 
def exclude():
    str = ""
    if not options.exclude == None:
        for e in options.exclude.split(","):
            str += "--exclude=" + "\"" + e + "\' "
    return str
 
def domain():
    return ".apribase.net"
 
def rsync_user():
    return "rsync"
 
def rsync_bin():
    return "/opt/rsync/bin/rsync"
 
# ============================================================================
# rsync
# ============================================================================
 
# rsync -azXA -e ssh --delete --rsync-path="sudo /opt/rsync/bin/rsync" --link-dest="/home/apribase/home/" rsync@apribase.apribase.net:/home/ /home/apribase/home-2009-07-22/
def rsync():
    return rsync_bin() + " -azXA -e ssh --delete " + rsync_path() + " " + link_dest() + " " + exclude() + " " + src() + " " + dst()
 
# rsync@apribase.apribase.net:/home/
def src():
    return rsync_user() + "@" + hostname() + domain() + ":" + directory() + "/"
 
# /home/apribase/home-2009-07-22/
def dst():
    return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat() + "/"
 
# --rsync-path="sudo /opt/rsync/bin/rsync"
def rsync_path():
    return "--rsync-path=\"sudo /opt/rsync/bin/rsync\""
 
# --link-dest="/home/apribase/home/"
def link_dest():
    return "--link-dest=\"/home/" + hostname() + directory() + "/" + "\""
 
# prepare for rsync
def mkdir():
    return "mkdir -p " + dst()
 
# ============================================================================
# lotate backups
# ============================================================================
 
# lotate backup directory
# 7/20       7/21       7/22
#     06-30      06-30      07-07
#     07-07      07-07      07-14
#     07-14      07-14      07-21
#     -----      -----      -----
#     07-20      07-21      07-22
def lotate():
 
    def snapshot_day():
        return calendar.MONDAY
 
    # /home/apribase/home-2009-07-22
    def today():
        return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat()
    # /home/apribase/home-2009-07-21
    def yesterday():
        return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(1)).isoformat()
    # /home/apribase/home-2009-06-30
    def oldest():
        return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(22)).isoformat()
    # /home/apribase/home
    def latest():
        return "/home/" + hostname() + directory()
 
    # rm -rf /home/apribase/home-2009-07-21; rm -f /home/apribase; ln -s home-2009-07-22 /home/apribase/home
    def daily():        
        return "rm -rf " + yesterday() + "; rm -f " + latest() + "; ln -s " + os.path.basename(directory()) + "-" + datetime.date.today().isoformat() + " " + latest()
    # rm -rf /home/apribase/home-2009-06-30; rm -f /home/apribase; ln -s home-2009-07-22 /home/apribase/home
    def weekly():
        return "rm -rf " + oldest() + "; rm -f " + latest() + "; ln -s " + os.path.basename(directory()) + "-" + datetime.date.today().isoformat() + " " + latest()
 
    if((datetime.date.today() - datetime.timedelta(1)).weekday() == snapshot_day()):
        return weekly()
    else:
        return daily()
 
# ============================================================================
# check options
# ============================================================================
 
# script needs hostname and directory.
def check_none():
    if hostname() == None:
        parser.error("set hostname.")
    if directory() == None:
        parser.error("set directory.")
 
# valid hostname?
def check_hostname():
    try:
        socket.gethostbyname(hostname() + domain())
    except socket.gaierror:
        parser.error(hostname() + domain() + " is invalid hostname or ipaddress.")
 
# exist?
def check_directory():
    # python -c "import os; print os.path.exists(\"/\")"
    def command_python():
        return "python -c \"import os; print os.path.exists(\\\"" + directory() + "\\\")\""
    # ssh rsync@apribase.net 'python -c \"import os; print os.path.exists(\"/\")\"'
    def command_ssh():
        return "ssh " + rsync_user() + "@" + hostname() + domain() + " \'" + command_python() + "\'"
 
    # /home/apribase/ is TRUE, apribase is FALSE
    if not (directory().startswith("/")):
        parser.error(directory() + " is not full path.")
 
    # remote directory is not exist...
    print command_ssh()
    if (commands.getoutput(command_ssh()) == "False"):
        parser.error(hostname() + ":" + directory() + " is not exist.")
 
# /home/ to /home
def remove_end_slash(directory_):
    if (directory_.endswith("/")):
        return directory_.rstrip("/")
    return directory_
 
if __name__ == "__main__":
    check_none()
    check_hostname()
    check_directory()
    options.directory = remove_end_slash(options.directory) # override
    if not (os.path.exists(dst())):
        print mkdir()
        commands.getoutput(mkdir())
    print rsync()
    print commands.getoutput(rsync())
    print lotate()
    print commands.getoutput(lotate())

commands でシェルコマンドを叩く

commands.getoutput でシェルのコマンドを叩けます。
結果が return されるので print 文で出力してあげましょう。
これでシェルスクリプトが書けない僕でも python のおかげで結婚できました状態。

import commands
 
print commands.getoutput("echo HelloWorld!")

optparse でオプション指定

コマンド引数が args に。-h –help などが options にタプルで入ります。
add_options で追加していけます。
-d –directory みたいに2つセットしないといけないというわけでもなく、片方だけでも大丈夫でした。

生の argv を見るよりエラーチェックがやりやすいのが嬉しいかな。

from optparse import OptionParser
 
parser = OptionParser(usage="python %prog --hostname=HOSTNAME --directory=DIRECTORY --exclude=FILE\n ex: python %prog --hostname=\"apribase\" --directory=\"/home/\" --exclude=\"db*,socket,server.pem,tls_sessions.db\"")
parser.add_option("--hostname", dest="hostname",
                  help="--hostname=\"apribase\"")
parser.add_option("--directory", dest="directory",
                  help="--directory=\"/home/\"")
parser.add_option("--exclude", dest="exclude",
                  help="--exclude=\"db*,socket,server.pem,tls_sessions.db\"")
options, args = parser.parse_args()
 
print options
print args

os.path でディレクトリを操作する

os でディレクトリの操作などが行えます。ディレクトリの存在確認など。

import commands
import os.path
 
if not (os.path.exists("/home/apribase/workspace")):
    print commands.getoutput("mkdir /home/apribase/workspace")

ssh とワンライナーを使った離れ業

ssh の引数にコマンドを打つと、リモートでコマンドを実行してくれます。
これを使って python -c のワンライナーを飛ばすと、リモート側で実行できるので、それを commands で実行してリモートのディレクトリの存在を確認したり。
シングルクォーテーションとダブルクォーテーションの入れ子にものすごく悩みましたが。

ssh rsync@apribase.net 'python -c \"import os; print os.path.exists(\"/\")\"'

文字列操作

ここでは directory_ が文字列ということで。
末尾の文字をチェックしたり、末尾の指定文字を消した文字列を生成したり。

def remove_end_slash(directory_):
    if (directory_.endswith("/")):
        return directory_.rstrip("/")
    return directory_</code>

文字列と数字を変換する関数

str(1), int(”1″)。こんなかんじだったかな。
文字列結合のときに数字は結合できないので str() 関数で文字列を返す必要が出てきます。
オブジェクトに toString とかじゃないのですよ。

socket の使用も短く書ける

socket も便利なメソッドが揃っていてすぐに使えます。
例外等も公式ドキュメントにしっかり書いてあるので簡単でした。

import socket
 
try:
    socket.gethostbyname(hostname() + domain())
except socket.gaierror:
    parser.error(hostname() + domain() + " is invalid hostname or ipaddress.")

関数の中に関数?

そういうこともできるんです。
スコープ内でしか使わないからって意味でしか使わなかったけど、表現としてどういう美しさを表現するためのものかはまだ知らないのです。
あと、定数(変数)を返したかっただけなので、素直に代入演算子使ったほうがいいと思います。
関数スタックに積まれて遅くなるだけかと。

def lotate():
    def snapshot_day():
        return calendar.MANDAY

if not

曰く、「かっこいい」らしい。

if not (directory().startswith("/")):>

行コメントは?

# で。

# ============================================================================
# setup option parser
# ============================================================================

if __name__ == “__main__”: ?

python pull_backup.py のように実行されたとき、ここが実行されるのですよ。

datetime で日付操作

today() で今日が取得できて、timedelta で差分を足し引きすることで調整できたり。
isoformat() を使うと 2009-07-22 みたいに整形してくれます。

import datetime
 
# /home/apribase/home-2009-07-22
def today():
    return "/home/" + hostname() + directory() + "-" + datetime.date.today().isoformat()
# /home/apribase/home-2009-07-21
def yesterday():
    return "/home/" + hostname() + directory() + "-" + (datetime.date.today() - datetime.timedelta(1)).isoformat()

シェルスクリプトが書けない僕でも

python のおかげで身長が伸びて結婚できて幸せになれるよ!

Page 1 of 20123451020...Last »
TOP