多くの人が、Linuxはサーバー用だと思っているが、今や、Linuxはデスクトップでもどんどん使われている。
音楽好きの人のため、コンピュータは沢山の曲をハードドライブ又はCD-ROMに収容出来る。それには曲をオーディオCDからコンピュータ・ファイルに変換しなければならない。便利なツールは grip で、 bladeenc (rpm) からダウンロードするとオーディオデータをMP3に変換する。
だがもっと新しい方法がある。 Ogg Vorbis だ。無料だが品質は良い。情報を見るには ここthis article をクリック。
次の RPM : XMMS plugin, encoder and libraries をダウンロードして搭載しoggencをgripのエンコーダとして設定する。手持ちCDをコンピュータ・ファイルにエンコードし、 X Multimedia System で聞く。
火に映えるクリスマスツリー
今の曲が終わり近くなったら XMMS plugin for crossfading; を使うと、前の曲が次第に消えて次の曲が現れる。聞きながら絵を描かないか? GNU Image Manipulation Program,がLinux上の最良デジタル技術だ。筆者はデジタルアートはやらないが、Michael Hammelは自分の甥をエイリアンにしてしまった。
the Graphics Muse site には沢山のGIMP素材と Linux Artist がある。3Dグラフィックス・プログラムは this articleを参照。歌わせて描かせた次はfestivalで話させる。 無料スピーチシンセサイザだ。いつものように rpmfind がrpmへのリンクを作る。
無料スピーチ認識エンジンはIBM : ViaVoiceから入手出来る。コマンドなどの音声制御は出来るが、文書全部を書き取るのはまだ先の話だ。音楽の他に smpeg で映画もコンピュータで見られる。
1gb以上を使って320x240解像度では不満足なら、300JMhz以上のプロセッサが要るが、700mb以下で700x400解像度のDivxがある。問題は純正Linuxプレーヤがないことで、avifileはウインドウズDLLを使う。
Project MayoにあるDivxのオープンソースのお陰でLinuxプレーヤも利用出来る。
TVは持っていないが、TVはPCで見ている。遠隔操作もできる。
TVを予約録画するならMovie Making on your Linux Box.
Linuxデスクトップを楽しむ方法をお教えしよう。ウインドウズとはおさらばだ(On becoming a total Linux userを参照)。英語は不要だ。 GNOME translation project を見れば言葉のサポートが判る。Linuxには新人ならGNOMEの用法を示した筆者の前の文献を見て Linux as a Video Desktopも見られたい。
乱数を使用するプログラムを書くには誤差論、確率論、統計学その他の数学の理解が必要である。乱数はプログラムに予期しない動作をさせる。
コンピュータは「実世界」乱数は使わない。実世界乱数には予期しないバイアスが掛かっていることがあるが、コンピュータはルールと論理的行動に縛られた機械だからだ。
コンピュータは均一分散(つまり無作為だが過剰に無作為でない)の数値を発生する機構に頼る。これは見掛け上繰り返しのない数列を作る数学関数を用いて発生した「準乱数」である。長い間には、全く同じ数列が現れる。
Linux スタンダードC ライブラリ(stdlib.h)は、二つの組み込み乱数機能を持つ。第一は、0と RAND_MAXとの間の無作為integerを返すrand()である。
printf( " rand() is %d\n", rand() );
printf( " rand() is %d\n", rand() );
printf( " rand() is %d\n", rand() );
とタイプすると、rand() は
rand() is 1750354891
rand() is 2140807809
rand() is 1844326400
のように、新規の無作為に抽出された正のintegerを返す。
別の標準ライブラリ関数 random() は正の long integer を返す。Linux では integer と long integer は同じサイズである。random() には後述の別のプロパティがある。
乱数発生には、古い、廃れた方法もある。
* drand48/erand48 return a random double between 0..1.
* lrand48/nrand48 return a random long between 0 and 2^31.
* mrand48/jrand48 return a signed random long.
これは、UNIXの旧式との互換性のため設けられた。
rand()とrandom()は、勿論そのままでは役に立たず、直接呼び出すことは希れである。
0と実際に大きい数との間の数を、実際の問題に適用するため探すことは稀である。rand()を使うには、1と最大値との間のような有用な範囲に尺度を変える必要がある。モジュール(%)演算子がうまく働く。数値を割った時の剰余は、元の数より0と1の間だけ少ない。モジュールに1を加えると探している範囲が示される。
int rnd( int max ) {
return (rand() % max) + 1;
}
この一行関数は、1と定義数との間の数値を返す。rnd(10)は1と10の間、rnd(50)は1と50の間の数を返す。
Linuxマニュアルの rand() は、無作為になり易いので "upper bits"(つまりモジュールでなく除算)を取ることを薦めているが、上記のrnd()の方が殆どの応用に適している。
次のプログラムは1と10の間の数を100回発生し、発生頻度を計数する。完全に均一なら各数値が10回宛現れる筈である。
int graph[11];
int i;
for (i=1; i<=10; i++)
graph[i] = 0;
for (i=1; i<=100; i++)
graph[ rnd(10) ]++;
printf( "for rnd(), graph[1..10] is " );
for (i=1; i<=10; i++)
printf( "%d " , graph[i] );
printf( "\n" );
このルーチンを走らせて、以下の結果を得た。
for rnd(), graph[1..10] is 7 12 9 8 14 9 16 5 11 9
Linuxの rand() 関数は高品質乱数の発生に労力を使い過ぎるので費消CPU時間が大きい。中庸品質乱数を多数迅速に発生するには、次のような関数が良い。
unsigned int seed = 0;
int fast_rnd( int max ) {
unsigned int offset = 12923;
unsigned int multiplier = 4079;
seed = seed * multiplier + offset;
return (int)(seed % max) + 1;
}
この関数の発生する乱数は数学的に一様ではないが迅速に発生する。理想的には、offset とmultiplier を素数にすると、特定の数が他より有利になることがない。
テストファンクションの中のfast_rnd() を持つrndを下記で置き換えてもrand()の良い近似値が得られる
for fast_rnd(), graph[1..10] is 11 4 4 1 8 8 5 7 6 5
seed は、乱数発生器に与えられて第一乱数を生じる初期値である。初期値を一定にすると同一数値から始まる数列が定まる。例えば、ゲームを書くとき、seedを一定値にして fast_rnd() を用いると、位置情報をセーブしなくても、敵は何時でも同じ位置に現れる。
seed = room_number;
num_enemy = fast_rnd( 5 );
for ( enemy=1; enemy<=num_enemy; enemy++ ) {
enemy_type[enemy] = fast_rnd( 6 );
enemy_horizontal[enemy] = fast_rnd( 1024 );
enemy_vertical[enemy] = fast_rnd( 768 );
}
Linux rand() 関数用seedは srand()により設定される。例えば、
srand( 4 );
は rand() seed を4 に設定する。
別のLinux関数random()を用いて数列を制御する方法が二つある。第一は、srandom()で、srand()のようにrandom()のためseedを設定する。
第二は、精度を高くしたいとき、Linuxはrandom()の速度と精度を制御する二つの関数を備えている。initstate()を用いて、random()にseedと関数中間結果保存用バッファを与える事が出来る。バッファの大きさは8, 32, 64, 128 又は 256バイトである。バッファが大きいと精度は上がるが計算時間が長くなる。
char state[256]; /* 256 byte buffer */
unsigned int seed = 1; /* initial seed of 1 */
initstate( seed, state, 256 );
printf( "using a 256 byte state, we get %d\n", random() );
printf( "using a 256 byte state, we get %d\n", random() );
initstate( seed, state, 256 );
printf( "resetting the state, we get %d\n", random() );
を実行すると下記が得られる。
using a 256 byte state, we get 510644794
using a 256 byte state, we get 625058908
resetting the state, we get 510644794
seedを特定値に初期化するため、setstate()に続きsrandom() を用いて、random()ステートを切り換えることが出来る。setstate()はポインタを常に以前の状態に戻す。
oldstate = setstate( newstate );
プログラム開始時にseedを変えない限り、乱数は常に同じになる。乱数列を変えるには、プログラム外から又はユーザー制御でseedを別の値に設定しなければならない。
時刻は常に変わるので、time.hのtime()は良い例である。実行毎に乱数列が変わる。
srand( time( NULL ) );
古典的ゲーム問題の一つは、リストの項目順を無作為に変えることである。例えば0から51の番号のついた52枚のトランプをシャッフルするには、次のようにする。
int deck[ 52 ];
int newpos;
int savecard;
int i;
for ( i=0; i<52; i++ )
deck[i] = i;
printf( "Deck was " );
for ( i=0; i<52; i++ )
printf( "%d ", deck[i] );
printf( "\n" );
for ( i=0; i<52; i++ ) {
newpos = rnd(52)-1;
savecard = deck[i];
deck[i] = deck[newpos];
deck[newpos] = savecard;
}
printf( "Deck is " );
for ( i=0; i<52; i++ )
printf( "%d ", deck[i] );
printf( "\n" );
シャッフル前後のトランプ順は
前は、 0 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
後は、35 48 34 13 6 11 49 41 1 32 23 3 16 43 42 18 28 26 25 15 7 27 5 29 44 2 47 38 39 50 31 17 8 14 22 36 12 30 33 10 45 21 46 19 24 9 51 20 4 37 0 40
統計学に詳しい人は、実生活の出来事が一様なパターンでは起こらない事を知っている。例えば、自動車の最初の大修理は、購入後5年から9年だが、7年が最も多い。
このような出来事は正規分布と言われる頻度で発生する。このように複雑な形状に合致する乱数の発生は難しそうだが、そうでもない。rnd() 関数は均一分布「偶発」事象を発生するので、正規分布乱数の発生に統計学教科書は必要でない。rnd()を数回呼び出して、その平均を取り、正規分布をシミュレートするだけだ。
int normal_rnd( int max ) {
return (rnd( max ) + rnd( max ) + rnd( max ) + rnd( max ) +
rnd( max ) + rnd( max ) + rnd( max ) + rnd( max ) ) / 8;
}
このテストファンクションの中の normal_rnd() を用いて、1と最大値との間で分割された数値を得る。
for normal_rnd(), graph[1..10] is 0 0 4 26 37 23 10 0 0 0
正規乱数は、敵の動きの弾力性を減じてゲームを実際に近くする。
範囲の外側に向かって絞られた数値については、1に近い値を有利にする low_rnd() を作ることが出来る。
int low_rnd( int max ) {
int candidate;
candidate = rnd( max );
if ( rnd( 2 ) == 1 )
return candidate;
else if ( max > 1 )
return low_rnd( max / 2 );
else
return 1;
}
各回帰において、low_rnd()は範囲を二つに分けて、範囲の下半分を有利にする。
範囲のトップから低い乱数を引いて、最大値に近い値を有利にするhigh_rnd()を作ることが出来る。
int high_rnd( int max ) {
return max - low_rnd( max ) + 1;
}
テストプログラムを用いると絞り込みが容易に見られる。
low_rnd()について、 graph[1..10] is 36 15 11 8 9 3 4 3 3 8
high_rnd()について、 graph[1..10] is 4 5 8 5 4 10 6 10 14 34
odds()ファンクションを用いて、論理において任意の分岐をおこなうことが出来る。
int odds( int percent ) {
if ( percent <= 0 )
return 0;
else if ( percent > 100 )
return 1;
else if ( rnd( 100 ) <= percent )
return 1;
return 0;
}
この関数は、ifステートメントに組み込まれるのを容易にする規定百分比の時間の間だけtrueである。
if ( odds( 50 ) )
printf( "The cave did not collapse!\n" )
else
printf( "Ouch! You are squashed beneath a mountain of boulders.\n" );
スタンダードCライブラリのrand() と random() ファンクションは均一分布乱数を持つプログラムを与える。数列と精度は別のライブラリ関数で制御されるので、数値の分布は簡単な関数で変更することが出来る。乱数は、プログラムに意外性を与え、勿論コンピュータゲームを刺激的にする要素である。
以下は、Webベースの複数プレーヤ・ロールプレーイング・ゲームMerchant Empiresの創始者であるBryan Bruntonとの対談である。
Q: MEを書いた理由は?
A: 幾つかあります。第一は、宇宙が舞台の戦略ゲームを作りたかったことです。第二は、Space Merchantと言うゲームをプレーして、色々な点で満足出来なかったことです。最も気に入らなかったのは「エラーストームで中断」されることでした。だが同時にSMには啓発されました。
Q: ME 実現に使ったソフトウエアは?
A: ME開発に使った open source ソフトウエアは以下です。
Q: MEプレーヤの多くが、MEサイトは時々安定性が低かった、と言っています。ME開発中に遭遇した問題は?
A: 色々ありました。配布Linux OS自体のスケーラビリティと、Apache やPostgreSQLのようなアプリケーションは脅威的でした。普通の配布CDから平均的PCに搭載された配布(pre-configured)Linuxは中程度の通信量、データベース支援Webサイト (Apache + PHP + PHPLIB + PostgreSQL)、に安定なプラットホームを提供しないと思います。
遭遇した問題は次の通りです(殆どが頭痛の種)
A: 敵船が宇宙塵の中にプレーヤを閉じこめたとき、プレーヤがいたい場所は市民権のない無効なHTMLなのは確かです。だがブラウザベースのゲーム環境には、私の認める利点があります。MEを書く前に類似のプロジェクトを沢山調べました。多くは立ち往生しており、デベロッパはプレー出来るゲームを持ないでサーバーとクライアントに手紙を書くのに半年も掛けていました。私は、直ちにゲームコードを書こうと思いました。MEのプレーに共通分母が少ないのが好きです。MEのプレーに必要なのはjavaスクリプトをサポートするWebブラウザだけです。MEは、クライアントに搭載と構成を要求するゲームより遙かに多くの場所からアクセスしてプレーすることが出来ます。良いゲームを作る限り、秒当たりフレームでなく、知的なターンベースのゲームを楽しみます。
Q:MEに関しゲーム産業は沈黙しています。最近、代表的企業にMEについてとMEのようなゲームの市場導入について質問したところ無視されました。MEにどんな商業的関心が示され「死者から復活、BBS2HTML」ゲーム市場の将来を如何お考えですか?
A: 商業的関心はありません。バナー広告は嫌いです。MEを走らせるサイトでは使いません。MEのホスト用に追加帯域幅は買えないと思います(現在友人の768K DSLラインを使用)。MEの帯域幅追加を協賛したい有力組織にはチャンスがあります。DSLは、安い帯域幅を大衆に提供した点では偉いけれども、DSLの信頼性はひどいものです。QWESTのような市場独占会社だけがこんな良い加減なサービスの提供をするのです。
Q: MEはどれ位、普及しましたか?
A: 7,000人以上のユーザーが出来ました。MEには定期的にプレーする百人から二百人位のグループがあります。私の意見では、極端に単純な経済的、政治的モデルのため遊び方が限られています。ここを変えれば伸びるでしょう。海賊行為とプレーヤ殺しを超えたロールプレーイングの可能性は限られています。
MEのホストになることを私は楽しんでいます。何度も使われ沢山のデータを生じるソフトウエアを書くのには、何か爽やかさがあります。データを集めるのも好きです。古いゲームとプレーヤからのデータを削除するときMEデータベースは100メガに達しています。
Q: MEプレーヤが最も楽しんでいるのは何ですか?
A: 殺し合う方法の作戦です。戦闘を含むオンラインゲームでも同じです。MEでは、プレーヤが一方の側に立って銀河征服の目標を目指します。舞台は宇宙です。各種連合軍が作る独裁制から民主制までの組織方法を見るのは楽しいものです。MEプレーヤの多くはプログラマでもあって、開発を助けます。プレーヤはゲームの成長と改良を見るのを楽しみにしています。
Q: MEの改良にどんな計画をお持ちですか?
A: IMO、スケーラブル・ベクトル・グラフィック(SVG)がWebの将来です。SVGは、本質的に Flashをオープンにしたものです。SVGは、XMLやJavaスクリプトのようなオープン標準に基づいているので、もっと強力になる筈です。Linux上のブラウザベースSVGサポートは、 Mozillaで構築したMathML-SVG の中の少ない関数コードに限られているのが不幸です。WindowsとMacの側では、Adoveが良質SVGプラグインを提供しますが、私のデスクトップはLinuxなので、SVGのジレンマに悩んでいます。
MEに入れたいものが少しあります。リアルタイムゲーム情報を提供する java appletを実行したいと思います。コンピュータ制御の宇宙船と惑星も導入したいと思います。最後には、コンピュータ制御Imperium(MEの警察)がゲームと大部分を占めるでしょう。
また、MEのPostgreSQL依存性を消したいと思います。PostgreSQLに反感はありませんが、MEをMySOLで走らせることを求める人がいます。現在、MEのデータベースへはPHPLIBが提供するクラスを通じてアクセスしているので、コードの中の少数のPostgreSQL主義の削除には大して手が掛からないでしょう。
ME2.0では大きい変更を計画しています。多角形ベースマップを作ります(今は正方形)。これにはSVGが必要です。文字通り数百の商品があり契約書ベースの取引が出来る商業モデルにします。別の実体としての港から離れて、港を惑星の特性にします。MEの経験値モデルを技術に基づく進級モデルにします。これらはSourceForgeにあるME Wish Listで論議します。
Q:プレーヤは貴方のコードが吸い込むと言っています。誤解しないで欲しいが賛成です。この対談の前にコードを一覧しましたが、プログラムの情報全部がネットワークループへのコマンドであることに気付きました。
A: MEは私が出来るだけ早く書いたことを考えて下さい。私の方法は簡単です。Space Merchantを見て出来るだけ早くそれを再生します。またMEを書くのは、全く意図的な私自身の学習過程でした。MEの部品はC++、PHP及びPytonを使います。C++の経験はあるが使ったことはなく何も知りません。PHPもPytonもです。これらの言語を習いたいと思いました。CSSの首尾一貫しない用法や戦闘機能性などMEの部品は、未だコンセプト段階のコーディング見地からのものです。イベントプロセッサを書いたとき、私はネットワークループ選択が何かも知りませんでした。今ではそのコンセプトについて学んだことは全部忘れて、コードがまだ働くのを喜んでいるだけです。
Q: つまり貴方のコードは、縁辺で極めて荒っぽいと言うことですね。綺麗にするのに、再帰プログラム技法を使う気はありませんか?
A: 再帰は、正しく使えば、強力なツールですが、使ったことはありません。この記事のため、自分と対談して、再帰の概念を導入すべきだと思ったので、将来考えましょう。
プログラム言語でなすべきことは、次のことである。
<!--#include virtual="/lthead.html" -->
chmod 755 LinuxToday.py
#!/usr/bin/python # しなければならないのが明白なのはurlダウンロードのためのエラー点検追加である
# ダウンロードには少なくとも一つのエントリが無ければならないが、新ファイルを
# 作ることが出来る。これは後でおこなう
### web module, string module, regular expression, module, os modulを輸入
import urllib, string, re, os
### 作成する新webpageと情報入手場所を定義
Download_Location = "/tmp/lthead.html"
Url = "http://linuxtoday.com/backend/lthead.txt"
#-----------------------------------------------------------
### Urlのあるwebオブジェクトを作成
LinuxToday = urllib.urlopen( Url )
### 全情報補アレーに入れる(大きいときは、一回一行を行うよう変更)
Text_Array = LinuxToday.readlines()
New_File = open(Download_Location + "_new", 'w');
New_File.write("<ul>\n")
### デフォルトをInvalidに設定
Valid = 0
### 有効エントリ数を記録
Entry_No = 0;
Entry_Valid = 0
### デフォルト設定
Date = ""
Link = ""
Header = ""
Count = 0
### mattern matching expressionを作成
Match = re.compile ("^\&\&")
### 最終エントリ分析を確認のため && を追加
Text_Array.append('&&')
### 行毎に、下記を行う
for Line in Text_Array :
### && がある時は、scratchからスタート最終エントリを付加
if Match.search(Line) :
###カレントエントリが有効で第一行を飛ばしているとき
if (Entry_No > 1) and (Entry_Valid > 0) :
### PerlがPythonより良いのはプリントコマンドで、筆者はPython
### プリントを好まない(可変内挿がない).
New_File.write('<li> <a href="' + Link + '">' + Header + '</a>. ' + Date + "</li>\n")
## values を nothingにリセット
Header = ""; Link = ""; Date = ""; Entry_Valid = 0
Count = 0
### 白紙と行末を削除
Line = string.rstrip(Line)
### カウントが1なら header, 2なら link, 3 ならdate
if Count == 1: Header = Line
elif Count == 2: Link = Line
elif Count == 3:
Date = Line
### 全フィールドを終えたとき、有効エントリを得る
if (Header != "") or (Link != "") or (Date != "") :
Entry_No = Entry_No + 1
Entry_Valid = 1
###カウントに1を加える
Count = Count + 1
New_File.write("</ul>\n")
New_File.close()
### 有効エントリがあるときは、新ファイルを実際の場所に移す
if Entry_No > 0 :
#/bin/sh ### Crontab file ### ファイルを "Crontab" と命名して"crontab Crontab"で実行 ### 二時間毎にダウンロード */2 * * * * /www/Cron/LinuxToday.py >> /www/Cron/out 2>&1
#!/usr/bin/perl
# Copyright Mark Nielsen January 20001
# Copyrighted under the GPL license.
system ("lynx --source http://www.linuxgazette.com/ftpfiles.txt > /tmp/List.txt");
###ダウンロードしたwebpageを開いてアレーに入れる
open(FILE,'/tmp/List.txt'); my @Lines = <FILE>; close FILE;
### magic 文字を含まない行を篩い分ける
my @Lines = grep(($_ =~ /lg\-issue/) || ($_ =~ /\.tar\.gz/), @Lines );
my @Numbers = ();
foreach my $Line (@Lines)
{
## 左の詰め物を捨てる
my ($Junk,$Good) = split(/lg\-issue/,$Line,2);
## 右の詰め物を捨てる
($Good,$Junk) = split(/\.tar\.gz/,$Good,2);
## If it is a valid number, it is greater than 1, save it
if ($Good > 0) {push (@Numbers,$Good);}
}
### 番号をソートして最高に飛ぶ(pop off)
@Numbers = sort {$a<=>$b} @Numbers;
my $Highest = pop @Numbers;
## ダウンロードしようとするurlを作る
my $Url = "http://www.linuxgazette.com/issue$Highest/index.html";
## Download it
system ("lynx --source $Url > /tmp/LG_index.html");
### インデクスを開く
open(FILE,"/tmp/LG_index.html"); my @Lines = <FILE>; close FILE;
### 始めとTOC終わりとの間にある部分を抽出
my @TOC = ();
my $Count = 0;
my $Start = '<!-- *** BEGIN toc *** -->';
my $End = '<!-- *** END toc *** -->';
foreach my $Line (@Lines)
{
if ($Line =~ /\Q$End\E/) {$Count = 2;}
if ($Count == 1) {push(@TOC, $Line);}
if ($Line =~ /\Q$Start\E/) {$Count = 1;}
}
### ポイントへのリンク全部をLinux Gazette magazineに再リンク
my $Relink = "http://www.linuxgazette.com/issue$Highest/"; grep($_ =~ s/HREF\=\"/HREF\=\"$Relink/g, @TOC); ###出力をセーブ open(FILE,">/tmp/TOC.html"); print FILE @TOC; close FILE; ### 完了!
#!/usr/bin/perl
# Copyright Mark Nielsen January 20001
# Copyright under the GPL license.
system ("lynx --source http://www.debian.org/News/weekly/index.html > /tmp/List2.txt");
### ダウンロードした webpage を開いてアレーに入れる
open(FILE,'/tmp/List2.txt'); my @Lines = <FILE>; close FILE;
### 始めとTOC終わりとの間にある部分を抽出
my @TOC = ();
my $Count = 0;
my $Start = 'Recent issues of Debian Weekly News';
my $End = '</p>';
foreach my $Line (@Lines)
{
if (($Line =~ /\Q$End\E/i) && ($Count > 0)) {$Count = 2;}
if ($Count == 1) {push(@TOC, $Line);}
if ($Line =~ /^\Q$Start\E/i) {$Count = 1;}
}
### ポイントへのリンク全部をDWNに再リンク
my $Relink = "http://www.debian.org/News/weekly/";
grep($_ =~ s/HREF\=\"/HREF\=\"$Relink/ig, @TOC);
grep($_ =~ s/\"\>/\" target=_external\>/ig, @TOC);
### 出力をセーブ
open(FILE,">/tmp/D.html"); print FILE @TOC; close FILE;
### 完了!
Pyton を使うのは易しく、Perl の中のLWPよりwebpagesの扱いが容易だ。
選択肢は色々あるが人手が最良だ。昔はPerl Expectスクリプトを用いてfdiskプログラムを自動化したので、これを続けることにした。
ユーザーがfdiskコマンドをタイプするのをシミュレートするExpectコードを用いた。Expectコードが全パーティションを消去し大きいパーティション一つを作った。
sfdiskで新パーティションの大きさを知り、それが一杯になるまで連続的に、ループでガベージデータを書き込んだ。
Perlの乱数関数と"chr"関数を使って無作為バイナリデータを作り、Perl Blowfishモジュールを使って暗号化した。暗号を解いてもガベージなので惑わされる筈だ。数学的に無作為と見られないように暗号化した。
これは簡単。 "mkfs" コマンドを使うだけ。
ユーザーに使い易くするため強調すべき点が多々ある。エラー点検、望み通りになったか聞くためのプロンプトを増やす必要がある。
コードに沢山コメントを付けたので私がしたかったことを、多くのPerlプログラム初心者が理解してくれることを望む。 (このリストのテキスト版はここ)
#!/usr/bin/perl ##### なすべきこと # 1. 誰かがログインした場合のセキュリティリスクを避けるため
# 一時搭載用の全く新規なディレクトリを作ったことを確認のこと
# 2. 多数のシステムコールを扱うため perl ファンクションを使う # 3. ハードドライブとフロッピイドライブを自動検出させ、未装着の
# ハードドライブとフロッピイドライブ上でアクションを実行するだけ
##### use strict; use Expect; use Crypt::Blowfish; #----------------------------------------------- my $Junk; ### Primary IDEコントローラ上でドライブをスレーブに設定 my $Drive = "hdb"; ### コンピュータに一人加わったと仮定して、
### 無作為行動を沢山実行し、それを本当に無作為にするため、
### /etc/passwd ファイルから最終行を得る
my $time = time();
my $Ran = rand($time);
my $Ran = rand(10000000000000);
my $LastLine = `tail -n 1 /etc/passwd`; chomp $LastLine;
$LastLine = substr ($LastLine,0,30);
my $Blowfish_Key = $LastLine . $Ran . $time;
$Blowfish_Key = substr ($Blowfish_Key,0,20);
while (length ($Blowfish_Key) < 56)
{
$Blowfish_Key .= $Ran = rand($time);
}
$Blowfish_Key = substr ($Blowfish_Key,0,56);
### 無作為キイの作成を終わったら、Blowfish Encryption オブジェクトを作る
my $Blowfish_Cipher = new Crypt::Blowfish $Blowfish_Key;
#------------------------------------
system "clear";
print "This will wipe out the hard drive on Drive /dev/$Drive\n";
print "Press enter to continue\n";
my $R = <STDIN>;
### 消去するドライブ上に取り付けられたパーティションのリストを入手する
my @Mounted = `df`;
@Mounted = grep($_ =~ /\/dev\/hdb/, @Mounted);
### Foreach mounted partition, umount it
foreach my $Mount (@Mounted)
{
my ($Partition,$Junk) = split(/\s+/, $Mount,2);
print "Unmounting $Partition\n";
my $Result = system ("umount $Partition");
if ($Result > 0)
{
print "ERROR, unable to umount $Partition, aborting Script, Error = $Result\n";
exit;
}
}
### expect scriptを実行、これは人が expect コマンド入力するのをシミュレートする
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");
### パーティションリストをプリントして取り付けパーティションを入手
print $Fdisk "p\n"; my $match=$Fdisk->expect(30,"Device Boot Start"); my $Temp = $Fdisk->exp_after(); my @Temp = split(/\n/, $Temp); ## パーティションを知らせるラインを入手 my @Partitions = grep($_ =~ /^\/dev\//, @Temp); ## 各ラインにつきForeach でパーティションを削除
foreach my $Line (reverse @Partitions)
{
## /dev/hdbパートと、その番号を入手
my ($Part,$Junk) = split(/[\t ]/, $Line,2);
my $No = $Part;
$No =~ s/^\/dev\/$Drive//;
print "Deleting no $Drive $No\n";
## コマンドを削除
print $Fdisk "d\n";
$match=$Fdisk->expect(30,"Partition number");
## 削除ラインを決定
print $Fdisk "$No\n";
$match=$Fdisk->expect(30,"Command (m for help):");
}
$Fdisk->clear_accum();
### パーティションがあれば、変更を書込、無ければ終了
if (@Partitions < 1) {print $Fdisk "q\n"; $Fdisk->expect(2,":");}
else
{
print $Fdisk "w\n";
$Fdisk->expect(30,"Command (m for help):");
}
#-------------------------------
## ハードドライブのジオメトリを入手
my $Geometry = `/sbin/sfdisk -g /dev/$Drive`;
my ($Junk, $Cyl, $Junk2, $Head, $Junk3, $Sector,@Junk) = split(/\s+/,$Geometry);
if ($Cyl < 1)
{print "ERROR: Unable to figure out cylinders for drive. aborting\n"; exit;}
### 新xpect スクリプトを作ってfdisk を使う人をシミュレート
my $Fdisk = Expect->spawn("/sbin/fdisk /dev/$Drive");
#### fdisk に新パーティションを作らせる
print $Fdisk "n\n"; $Fdisk->expect(5,"primary"); ### 新パーティションは主パーティションであると宣言 print $Fdisk "p\n"; $Fdisk->expect(5,":"); ### 1番にするパーティション print $Fdisk "1\n"; $Fdisk->expect(5,":"); ### シリンダ1からスタート
print $Fdisk "1\n"; $Fdisk->expect(5,":"); ### endに行く print $Fdisk "$Cyl\n"; $Fdisk->expect(5,":"); ### 書込とセーブ
print $Fdisk "w\n";
$Fdisk->expect(30,"Command (m for help):");
#------------------------------------------
### パーティションをフォームして搭載
my $Partition = "/dev/$Drive" . "1";
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error making partition, aborting.\n"; exit;}
### ここにエラー点検を入れるのが良い
system "umount /tmp/WIPE_IT";
system "rm -rf /tmp/WIPE_IT";
system "mkdir -p /tmp/WIPE_IT";
system "chmod 700 /tmp/WIPE_IT";
## 新パーティションを取付出来るか否かを見る
my $Result = system ("mount $Partition /tmp/WIPE_IT");
if ($Result > 0) {print "Error mounting drive, aborting.\n"; exit;}
system "chmod 700 /tmp/WIPE_IT";
#--------------------------------
### ファイルを作り大きさが一杯になったらstop
my $Count = 0; my $Written_Size = 0; ### 新ファイルを開く open(FILE,">>/tmp/WIPE_IT/Message.txt"); ### 誰かがドライブの周りでうろうろしていたら、
### 一緒に遊び、強い相手をあてがって時間を潰させる
my $Ran = rand 259200000; # between now and ten years ago (approx) ($Ran, $Junk) = split(/\./, $Ran, 2); ## 新日付マイナス秒の乱数 my $Date = `date --date '-$Ran seconds'`; print FILE "DATE CREATED $Date\n"; my $Ran = rand 50; ($Ran, $Junk) = split(/\./, $Ran, 2); $Ran = $Ran + 10; print FILE "この文書は極秘です。無資格者は何人も読むことを
許されません。パスワードを持っている人は
$Ranメソッドを使ってデータ\nを解読して下さい";
### 乱数プラス25000 を作る my $Ran = rand 25000; ($Ran, $Junk) = split(/\./, $Ran, 2); $Ran = $Ran + 25000; ### 殆どの時間使用する数字のアレーを作る
my @Blank = (1..$Ran); ### アレーを文字列にする my $Blank = "@Blank"; ### アレーを空にしてメモリを解放する @Blank = (); my $B_Length = length $Blank; ### 手持ちパーティションの実際の空き容量を入手
my @Temp = `df`; @Temp = grep($_ =~ /^$Partition/, @Temp); my $Line = $Temp[0]; my ($Junk,$Blocks,@Junk) = split(/\s+/, $Line,4); ### 1k ブロックを仮定 my $Size = $Blocks*1000; ## 書き込んだ手持ちファイルがパーティションのサイズより
## 小さい間、別のデータを書き込む
while ($Written_Size < $Size)
{
$Count++;
### 10回のうち9回、空白をプリントして時間を節約、
### 10回のうち1回だけ、カベージバイナリをプリント
my $Ran = rand (10);
if ($Ran > 1)
{
print FILE $Blank;
$Written_Size = $Written_Size + $B_Length;
}
else
{
## この部分は長い無作為データ文字列(10000バイトまで)を作る
my $Garbage = "";
my $Length = rand(10000);
($Length, $Junk) = split(/\./, $Length, 2);
for (my $i = 0; $i < $Length; $i++)
{
my $Ran = rand 256;
($Ran, $Junk) = split(/\./, $Ran, 2);
$Garbage .= chr $Ran;
}
## この部分は一度に無作為データ8バイトを暗号化する
my $Temp = $Garbage;
my $Encrypted = "";
while (length $Temp > 0)
{
while (length $Temp < 8) {$Temp .= "\t";}
my $Temp2 = $Blowfish_Cipher->encrypt(substr($Temp,0,8));
$Encrypted .= $Temp2;
if (length $Temp > 8) {$Temp = substr($Temp,8);} else {$Temp = "";}
}
### 暗号化無作為データをファイルにプリント
print FILE $Encrypted;
$Length = length $Encrypted;
$Written_Size = $Written_Size + $Length;
my $Rest = $Size - $Written_Size;
print "$Size - $Written_Size = $Rest to go\n";
}
### 500 プリント毎に新ファイルへの保存をスタート
if ($Count =~ /500$/)
{
close FILE;
open(FILE,">>/tmp/WIPE_IT/$Count");
}
}
close FILE;
#----------------------------------------------------
my $Result = system ("umount $Partition");
if ($Result > 0) {print "Error unmounting partition $Partition, aborting.\n"; exit; }
### パーティションを再フォーマット。データを消去しないで
### ディレクトリから移動するだけ
my $Result = system ("mkfs -t ext2 $Partition");
if ($Result > 0) {print "Error making partition, aborting.\n"; exit;}
ハードドライブが完全に分かっていないので、残留データがあるかも知れない。MILASの開発を進めれば完全になるだろうが、今の目的にはこれで十分。
ラップトップでUSBを働かせるのに必要なことは
## linux kernelのためsrcディレクトリを変更 ## xconfigには、オプションとVESA VGA グラフィックコンソールを選んだ ## コンソールドライバの下でラップトップのため
make xconfig make clean make dep make bzImage make install make modules make modules_install
tar -zxvf pcmcia-cs-3.1.23.tar.gz ### 新kernel用にルートディレクトリを規定すること
### mine は /usr/src/linux-2.2.18/linux ### その他のデフォルトオプションは変えなかった make config make all ### これはモジュールを /lib/modules/2.2.18 の下に置く make install
旧コンフィギュレーション
### GNUJobs.comテストラップトップ用コンフィギュレーション
vga=791
boot=/dev/hda
map=/boot/map
install=/boot/boot.b
prompt
timeout=50
default=linux
image=/boot/vmlinuz-2.2.12-32
label=linux
initrd=/boot/initrd-2.2.12-32.img
read-only
append="hdc=ide-scsi"
# ramdisk_size=40000
root=/dev/hda5
新 lilo.conf コンフィギュレーション
### GNUJobs.comテストラップトップ用コンフィギュレーション ### 新 kernel 搭載。コンソールドライバを新kernelに搭載しないと、
### vga=791 が働かないことに留意.
vga=791
#vga=ask
boot=/dev/hda
map=/boot/map
install=/boot/boot.b
prompt
timeout=50
default=linux_new
image=/boot/vmlinuz-2.2.18
label=linux_new
read-only
append="hdc=ide-scsi"
### /dev/hda5 は GNUJobs.comラップトップ用ルート
root=/dev/hda5
image=/boot/vmlinuz-2.2.12-32
label=linux
initrd=/boot/initrd-2.2.12-32.img
read-only
append="hdc=ide-scsi"
### /dev/hda5 は GNUJobs.comラップトップ用ルート
root=/dev/hda5
### このコマンドはusb用ファイルシステムを /proc/bus/usbに取り付ける
mount -t usbdevfs none /proc/bus/usb ### 一般的usbモジュールをロード−、マザーボード又はUSBポートにより
### 次に三つのうち一つを選ぶ。筆者はuhci 又は usb-uhcを選んだ
### 分からない時はLinux-USB Guideの中の"Basic USB Configuration" を参照
###t http://www.linux-usb.org/USB-guide/c122.html#AEN124 にある insmod /lib/modules/2.2.18/usb/uhci.o # insmod /lib/modules/2.2.18/usb/usb-uhci.o # insmod /lib/modules/2.2.18/usb/usb-ohci.o ### Ricochetのようなモデモ用モジュールをロード
insmod /lib/modules/2.2.18/usb/acm.o
mkdir /dev/usb mknod /dev/usb/ttyACM0 c 166 0
ここでも、モデモを /dev/ttyS0使用から /dev/usb/ttyACM0 に変更した。ここで Ricochetが働いた。これら二つのコマンドは恒久的なので一度入力すれば良いことに注意。またこれは /dev/usbであって、/proc/bus/usb ではない( Linux-USB Guideで説明)。デバイスを差し込み又は引き抜くとkernelファイルが /proc/bus/usb の中に魔術のように出没するが、これはこのファイルあるべき姿ではない。USB Ricochet モデモは /dev エントリを必要とする。usbdevfs は /proc/bus/usb を管理するのであって not /dev/usb ではない。
GNUJobs.comでEmperor Linuxから別のラップトップを買った。これはうまく働いている。抜かったのはiso960フォーマットをkernelに搭載するのを忘れたことだ。CDROMが読めない。やり直す必要がある。
警察や消防が帰った後、残るのは古典的な「焦げたMBR」だ。Linuxを搭載する。ウインドウズがブートレコードを動かすのに気付く。Linuxパーティションを削除して、ウインドウズを先ず搭載しようとする。ギョッ、ウインドウズが設定出来ない。
原因は、元のMBRを書いたLILOのアンインストールを忘れたからだ。MBRの中のブートレコードがLinuxに制御を渡そうとするが、相手がいない。
救い様がない。白紙のマスターブート・レコードを書く筈の無記録 "fdisk/mbr"オプションは何の効果もない、会話モードの "fdisk" は、爆弾が爆発しなくても、「非DOS」パーティションの削除を拒否する。どうしよう・・・
ともかく、問題の原因はウインドウズの "lock" コマンドだ。規定値で、ディスクへの 'raw writes' を許さない、"lock c:"がドライブの書込許可を「ロック」する。
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
注記:以下の助言はマスターブート・レコードを完全に消し去る
これにはパーティション情報のすべてが含まれている。本当にそうしたい
との確信が内限り「実行しないこと」ーHDは、「工場出荷状態」即ち、
データが全くなく、パーティションとフォーマットを必要とする状態
になり。ブート出来なくなる。
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Linuxベースの対策
少しでも、つまり Tom's Root-Boot フロッピイ経由でLinuxが使えるなら、次のように"dd"を呼び出すだけだ。
dd if=/dev/zero of=/dev/hda bs=512 count=1
これで終わり、MBRは消えた 。これをルートしなければならないのは明らかだ。
DOSベースの対策
"debug" のあるDODフロッピイを使って、 "debug" を走らせる。 '-' プロンプトで、メモリの512バイト分をゼロで"block-fill"する
f 9000:0 200 0
'a' コマンドでアセンプリモードを起動し、次のコードを入力する。
mov dx,9000
mov es,dx
xor bx,bx
mov cx,0001
mov dx,0080
mov ax,0301
int 13
int 20
<Enter> を押してアセンブリモードから出る。 "g" を押して実行。"q" を押して"debug"を出る。HDはバージンになった。
クライアントシステムはサーバーにSYNTメッセージを送ることから始まる。SYNメッセージを受けたサーバーがSYN-ACKメッセージをクライアントに送ると、クライアントはACKメッセージを送って接続を確立する。互いにサービス固有データの交換が出来るようになる。
TCPは一連番号を使う。二つのホストの間に仮想回路が出来ると、TCPが各パケットに識別票として番号を付ける。両ホストはこの番号をエラー点検と報告に用いる。
Rik Farrowはその著作"Sequence Number Attacks"(一連番号アタック)の中で、一連番号システムを次のように説明している。
そこでアタッカには二つの課題がある
1) ソースアドレスの偽造
2) 攻撃目標に一連番号を維持しなければならない
第二の課題が最も複雑である。攻撃目標が初期一連番号を設定したとき、アタッカは正しい応答をしなければならないからである。一連番号を正しく推定出来れば、攻撃目標に同期して有効なセッションを確立出来る。
IP スプーフィングに弱いサービス:
IPスプーフィングに弱いコンフィギュレーションとサービスは、
TCP/IP スプーフィング・ツール:
1) Linux用Mendax
Mendax は、TCP一連番号予測とrshdスプーフィングに使いやすいツールである。
2) spoofit.h
spoofit.h は、自分のプログラムにIPスプーフィング機能を含ませるための、巧くコメントされたライブラリである [現 URL は未詳. -Ed.]
3) ipspoof
ipspoof は、TCP / IP スプーフィング・ユティリティである
4) hunt
hunt は、沢山のスプーフィング機能もまた提供するスニッファ(探知器)である。
5) dsniff
dsniff は、関係データ(パスワード、e−メール、ファイルなど)についてネットワークを受動的に監視するdsniff, filesnarf, mailsnarf, msgsnarf, urlsnarf及びwebspy、ネットワーク通信の盗聴を容易にするarpspoof, dnsspoof及びmacof の、ネットワーク監査及び進入試験のためのツールコレクションである。
IPスプーフィング攻撃の防止方法:
結語:
スプーフィング攻撃は危険で防御し難い。段々増えている。これら攻撃への防御はネットワーク安全保障のため暗号化認証の様な安全対策を実施しなければならない。
ns_xmlコマンドを加える。
cvs -d:pserver:anonymous@cvs.aolserver.sourceforge.net:/cvsroot/aolserver login cvs -z3 -d:pserver:anonymous@cvs.aolserver.sourceforge.net:/cvsroot/aolserver co nsxml
2.xは自分で搭載する必要がある。 nsxmlモジュールを搭載するには、nsxmlディレクトリに行く。MakefileのパスをAOLサーバーを指すようエディットして、makeを走らせる。nsxml.so を入手してAOLサーバーのbinディレクトリに置かなければならない。 nsd.tclコンフィギュファイルに続けて次ぎを加える
ns_section "ns/server/${servername}/modules"
ns_param nsxml ${bindir}/ns_xml.so
tail -f $AOLSERVERDIR/log/server.log
をシェルウインドウに使う方法もある。
set doc_id [ns_xml parse ?-persist? $string]
set doc_stats [ns_xml doc stats $doc_id]
ns_xml doc free $doc_id
set node_id [ns_xml doc root $doc_id]
set children_list [ns_xml node children $node_id]
set node_name [ns_xml node name $node_id]
set node_type [ns_xml node type $node_id]
set content [ns_xml node getcontent $node_id]
set attr [ns_xml node getattr $node_id $attr_name]
set doc_id [ns_xml doc create ?-persist? $doc-version]
set xml_string [ns_xml doc render $doc_id]
set node_id [ns_xml doc new_root $doc_id]
set node_id [ns_xml node new_sibling $node_id $name $content]
set node_id [ns_xml node new_child $node_id $name $content]
ns_xml node setcontent $node_id $content
ns_xml node setattr $node_id $attr_name $value
ns_xml parse $xml_doc を使って、文字列 $xml_doc の中のXMLドキュメントを解析しそのドキュメントIDを得る
ns_xml doc root $doc_id を使ってルート・ノードのIDを得る。
ns_xml node children $node_id を使ってドキュメントツリーを横断し、ns_xml node ...コマンドを使ってノード内容と属性を得る。
ns_xml parse に -persistフラッグを作ったときは、ns_xml doc free $doc_idを呼び出してこのドキュメントに結合するメモリを解放しなければならない。そうでないときはスクリプト実行後自動的に開放される。
コードでは次のようになる。
proc dump_node {node_id level} {
set name [ns_xml node name $node_id]
set type [ns_xml node type $node_id]
set content [ns_xml node getcontent $node_id]
ns_write "<li>"
ns_write "node id=$node_id name=$name type=$type"
if { [string compare $type "attribute"] != 0 } {
ns_write " content=$content\n"
}
}
proc dump_tree_rec {children} {
ns_write "<ul>\n"
foreach child_id $children {
dump_node $child_id
set new_children [ns_xml node children $child_id]
if { [llength $new_children] > 0 } {
dump_tree_rec $new_children
}
}
}
proc dump_tree {node_id} {
dump_tree_rec [list $node_id] 0
}
proc dump_doc {doc_id} {
ns_write "doc id=$doc_id<br>\n"
set root_id [ns_xml doc root $doc_id]
dump_tree $root_id
}
set xml_doc "<test version="1.0">this is a
<blind>test</blind> of xml</test>"
set doc_id [ns_xml parse $xml_doc]
dump_doc $doc_id
ns_xml parse コマンドは、XMLドキュメントが無効(例えば、フォームされていない)ときエラーを出すので、コード作成に当たってはこれを捕らえて、例えば次のように意味のあるエラーメッセージを表示しなければならない。
if { [catch {set doc_id [ns_xml parse $xml_doc]} err] } {
ns_write "次のXMLドキュメント解析にエラーがありました:"
ns_write [ns_quotehtml $xml_doc]
ns_write "エラーは:"
ns_write [ns_quotehtml $err]
ns_write "\n"
return
}
今日では、人のためにヘッドラインを提供使用とするサイトは、これを一定にURLの下で公表してXMLフォーマット解析を容易にできる。我々の場合データはhttp://www.linuxtoday.com/backend/linuxtoday.xml.で提供される。このファイルのフォーマットは、ここSee the format of this file 。
お解りの通りLinuxToday サイトでXMLドキュメントはヘッドラインをあらわす。これはストーリイの組で、各ストーリイは、タイトル、URL、著者などを有する。XMLドキュメントを解析した後、容易に情報を取り出すことが出来る。Structure and interpretation of computer programs (偉大な書物だ)で唱道された書込方法を使用する。XML表現をオブジェクトに転換したと仮定する。データを示すHTMLの構築には次の手順が必要である。
headlines_get_stories_count $headlines
headlines_get_story $headline $story_no
story_get_url $story
story_get_title $story
proc story_to_html_table_row { story } {
set url [story_get_url $story]
set title [story_get_title $story]
return "- <a href=\"$url\"><font color=#000000>$title</font></a><br>\n"
}
# 与えられたヘッドラインがこのデータを有する表のHTMLコードを作る
proc headlines_to_html_table { headlines } {
set to_return "<table border=0 cellspacing=1 cellpadding=3>"
append to_return "<tr><td><small>"
set stories_count [headlines_get_stories_count $headlines]
for {set i 0} {$i < $stories_count} {incr i} {
set story [headlines_get_story $headlines $i]
append to_return [story_to_html_table_row $story]
}
append to_return "</td></tr></table>\n"
return $to_return
}
proc headlines_get_stories_count { headlines } {
return [llength $headlines]
}
proc headlines_get_story { headlines story_no } {
return [lindex $headlines $story_no]
}
proc story_get_url { story } {
return [lindex $story 0]
}
proc story_get_title { story } {
return [lindex $story 1]
}
headlines_to_html_table の次の部分を書き直すことが出来る:
set stories_count [headlines_get_stories_count $headlines]
for {set i 0} {$i < $stories_count} {incr i} {
set story [headlines_get_story $headlines $i]
append to_return [story_to_html_table_row $story]
}
foreach story $headlines {
append to_return [story_to_html_table_row $story]
}
# $node_id が識別したノード名は $nameと同じか
proc is_node_name_p { node_id name } {
set node_name [ns_xml node name $node_id]
if { [string_equal_p $name $node_name] } {
return 1
} else {
return 0
}
}
# $node_id が識別したノード型は $typeと同じか
proc is_node_type_p { node_id type } {
set node_type [ns_xml node type $node_id]
if { [string_equal_p $type $node_type] } {
return 1
} else {
return 0
}
}
# これはタイプ「属性」のノードか?
proc is_attribute_node_p { node_id } {
return [is_node_type_p $node_id "attribute"]
}
# ノード名が $name と異なるときはエラーを起こす
proc error_if_node_name_not {node_id name} {
if { ![is_node_name_p $node_id $name] } {
set node_name [ns_xml node name $node_id]
error "node name should be $name and not $node_name"
}
}
# ノード型が $type と違う時はエラーを起こす
proc error_if_node_type_not {node_id type} {
if { ![is_node_type_p $node_id $type] } {
set node_type [ns_xml node type $node_id]
error "node type should be $type and not $node_type"
}
}
# 入手したURLとタイトルがこれら属性を有するストーリイ
# オブジェクトを構成
proc define_story { url title } {
return [list $url $title]
}
# "story" 名のノードをストーリイをあらわすオブジェクトに転換
proc story_node_to_story {node_id} {
set url ""
set title ""
# チルドレンを縦覧してurlとタイトルノードを抽出
set children [ns_xml node children $node_id]
foreach node_id $children {
# 名が "url" or "title" のノードにのみ関心がある
if { [is_attribute_node_p $node_id]} {
if { [is_node_name_p $node_id "url"] || [is_node_name_p $node_id "title"]} {
set node_children [ns_xml node children $node_id]
# those should only have one children node with
# the name "text" and type "cdata_section"
if { [llength $node_children] != 1 } {
set name [ns_xml node name $node_id]
error "$name node should only have 1 child"
}
set one_node_id [lindex $node_children 0]
error_if_node_type_not $one_node_id "cdata_section"
error_if_node_name_not $one_node_id "text"
set txt [ns_xml node getcontent $one_node_id]
if { [is_node_name_p $node_id "url"] } {
set url $txt
}
if { [is_node_name_p $node_id "title"]} {
set title $txt
}
}
}
}
return [define_story $url $title]
}
# XMLドキュメントをヘッドライン・オブジェクトに転換
proc xml_to_headlines { doc_id } {
set headlines [list]
set root_id [ns_xml doc root $doc_id]
# ルート・ノードの名は "linuxtoday" でタイプは "attribute" でなければならない
error_if_node_name_not $root_id "linuxtoday"
error_if_node_type_not $root_id "attribute"
set children [ns_xml node children $root_id]
foreach node_id $children {
# 名が "story" のアトリビュート・タイプ・ノードにのみ関心がある
if { [is_node_name_p $node_id "story"] && [is_attribute_node_p $node_id]} {
set story [story_node_to_story $node_id]
lappend headlines $story
}
}
return $headlines
}
データの中間表現は無駄なようだが、理由がある。XMLドキュメントから直接HTMLテーブルを作る proc xml_to_html_tableを書いたがこれは、複雑で大きく修正が困難だ。上のように分割すると、複雑でなくなり、融通性を生じ、別の headlines_to_html_table プロセジュアの書き方が容易に想像できる。
実際の働き方はSee how it works in practice 、ソースを得るにはget the source
このコードで抜けているのは、取得である。これは、XMLファイルが呼び出される度に他の人のサーバーから取得する。これを新バージョンだけ取得するようなロジックを加えるのは易しい。