情報処理 II − 第8回

講義に入る前に shori2 にカレントディレクトリを移しておいてください。


1.ファイル操作(SSI の使い方)


みなさんのホームディレクトリ以下には、mule などのテキストエディタで作成した文書や受け取ったメール、 camera でキャプチャしたり netscape などを使ってダウンロードした画像など、 様々なデータがファイルとして保存されていると思います。 ここでは自分でファイルの操作を行うプログラムを作ってみましょう。

kaku.cc
#include <fstream.h>

int main(void) {
  ofstream ofile("data");      // ファイル data を開く

  if (ofile != 0) {            // ofile が非0
    ofile << "はろ〜" << endl;  // data に出力
  }
  else {
    cerr << "ファイルが開けませんでした" << endl;
  }

  return 0;
}

では、上のプログラムをコンパイルして実行してみてください。 実行ファイル名は kaku にしましょう。

% CC kaku.cc -o kaku[Enter]
% kaku[Enter]

このプログラムを実行しても何も起こらないように見えますが、 カレントディレクトリ上には data というファイルが出来ているはずです。 ls コマンドで確かめてみましょう。

% ls[Enter]
... data ...

では、このファイルの中に何が入っているのか cat コマンドで見てみましょう。 オブジェクト ofile に出力した文字列が入っていると思います。

% cat data[Enter]
はろ〜

それでは、今度はこのデータを読むプログラムを作成してみましょう。

yomu.cc
#include <fstream.h>

int main(void) {
  ifstream ifile("data");              // data を開く

  if (ifile != 0) {                    // ifile が非0
    char line[80];

    ifile.getline(line, sizeof line);  // data から入力して line に格納
    cout << line << endl;              // 画面に出力
  }

  return 0;
}

ところで、このプログラムは cout を使っているのに #include <iostream.h> が無くても正常にコンパイルできます。 実は ifstream, ofstream はそれぞれ istream(cin のデータ型), ostream(cout のデータ型)から派生した (これらを元に作成した) データ型(クラス)なのです。 このため、fstream.h の中には #include <iostream.h> が既に入っているのです。

このプログラムをコンパイルして実行してみてください。 実行ファイル名は、今度は yomu にしましょう。

% CC yomu.cc -o yomu[Enter]
% yomu[Enter]
はろ〜

課題25


実行するたびに、 そのプログラムが実行された回数を出力するプログラム mycounter を作成してください。

% mycounter[Enter]
1
% mycounter[Enter]
2
% mycounter[Enter]
3
%

プログラムが確保したメモリは、 たとえ静的変数であってもプログラムが終了したときにはすべて解放されます。 このため、プログラム内で変数にデータを格納しても、 そのプログラムの実行が終了してしまえば、 もう一度そのプログラムを動かしても、 以前その変数に格納した内容を取り出すことはできません。 そこで、プログラムを次に実行したときに以前のデータが取り出せるよう、 プログラムが終了する前にデータをファイルに保存しておきます。 以下の手順を参考にしてください。

  1. データを保存するファイルのファイル名を mycount とします。
  2. mycount から読み出したデータを格納する unsigned int 型の変数 count を用意します。
  3. mycount を読み出しモードで開きます。もし mycount が無ければ、count に 0 を代入します。
  4. mycount があれば、そこに書かれているデータ(回数〜整数値)を count に格納します。
  5. mycount を閉じます。
  6. mycount を、今度は書き込みモードで開きます。
  7. count を1増し、それをファイルに出力します。
  8. mycount を閉じます。
  9. count を出力ストリームにも出力します。

開かれたファイルは、 そのファイルを指している変数が無効になった時点で自動的に閉じられますが、 この課題では一度読み出しモードで開いた後、 書き込みモードで開く前に一旦ファイルを閉じる必要があります。 明示的にファイルを閉じるには close() を使います。

ifstream ifile("mycount");   // ファイル "mycount" を開く
...
ifile.close();               // ファイル "mycount" を閉じる
...
ofstream ofile("mycount");   // ファイル "mycount" を開く

ファイルに書かれている数字を数値として count に格納するには、 cin から読み込むのと同様抽出演算子 >> を使うことに気をつけてください。 作成したソースプログラムをメールに添付して、 tokoi まで送ってください。Subject:(件名)は kadai25 としてください。


課題26


Web サーバの SSI (Server Side Include) という機能を使えば、 mycounter を使って簡単なアクセスカウンタが実現できます (本当は複数の人から同時にアクセスされたときに 勘定し損なわないようファイルをロックすべきなんですが、 その辺は将来の課題ということで)。 mycounter を public_html の下にコピーしてください。

% cp mycounter ~/public_html[Enter]

次に、public_html に下のような HTML ファイルを作成してください。 ファイル名は mycounter.shtml とでもしてください。 以前に作成した Web ページに組み込んでも構いません。 拡張子が .shtml になっていることに気をつけてください。

mycounter.shtml
<head>
<title>counter test</title>
</head>

<body>

あなたは <!--#exec cmd="./mycounter"--> 番目の訪問者です。

</body>

こうすると、このページにアクセスするたびに mycounter が実行されて、 <!--...--> の部分がその出力に置き換わります。 このように、Web ページに他のファイルやプログラムの出力を埋め込む機能を、 SSI (Server Side Include) と呼びます。

ところが、このままでは何度アクセス (reload) してもカウンタは増えないと思います。 chmod コマンドを使って mycounter に setuid ビットを立ててください (setuid ビットについては注意事項がありますので、 あとの文章も必ず読んでください)。 reload してカウントが進むようになったら、 Netscape の「ファイル」メニューにある「リンクを送信」で、 そのページを私 (tokoi) に知らせてください。Subject:(件名)は kadai26 としてください。

% cd ~/public_html[Enter]
% ls -l mycounter[Enter]
-rwxr-xr-x    1 s035000  student    17816   1月 13日 10時50分  mycounter
% chmod u+s mycounter[Enter]
% ls -l mycounter[Enter]
-rwsr-xr-x    1 s035000  student    17816   1月 13日 10時50分  mycounter

ls コマンドにオプション -l を付けると、 ファイル名の他にファイルの様々な情報を表示します。 各欄は左から、アクセス権、リンク数、所有者 (user)、グループ (group)、 ファイルサイズ(バイト)、最終修正日時、ファイル名を示しています。

みなさんのホームページには、和歌山大学以外からのアクセスもあります。 そういう人達はこのコンピュータには登録されていませんから、 本当はこのコンピュータを利用したり、 コンピュータのデータを見たりすることはできないきません。 そこで、Web ページへのアクセスについては、 nobody という利用者として扱うことにしています。 すなわち、Web ページの HTML ファイルには、 nobody という利用者が読み出せるようにアクセス権を設定する必要があります。

たいていのファイルには、others に対しても読み出し権と(実行ファイルの場合は)実行権が設定されていますから、 読み出すだけなら特別なことをしなくても何も問題はありませんでした。 しかし、mycounter は mycount というファイルにデータを書き込みますから、 これが許されていないためにカウントが増えないのです。

実行プログラムに setuid ビットを立てると、 そのプログラムは実行した人ではなく所有者によって実行されたことになります。

すなわち mycounter に setuid ビットを立てなければ、 nobody によって実行された mycounter は nobody として mycount データを書き込もうとして失敗しますが、 setuid ビットを立てておけば mycounter は mycounter の所有者(= mycount の所有者)によって実行されたのと同じになりますから、 mycount への書き込みは成功します。

ただ、こうするとあなた以外のいかなるユーザも、 このプログラムを通じて、 あなたの所有するすべてのファイルにアクセスできる可能性が出てきます (下手をするとあなたの名をかたって他で悪さを働くこともできます)。 したがって、setuid ビットを立てるプログラムは、 他のユーザがこのプログラムを使って他のことができないよう (myconter なら プログラム中でファイル名を /usr/people/ユーザ名/public_html/mycount というようにフルパス名で指定し、この以外のファイルが操作できないよう ) 注意深くプログラムを作成する必要があります。

もし、そういう自信がないなら、 プログラムに setuid ビットを立てる代りに、 データファイルの方のアクセス権を誰もが書き込めるように設定してください。 また Perl スクリプトや sh スクリプトに setuid ビットを立てても効果はないので、 その場合も同様です。

% ls -l mycount[Enter]
-rw-r--r--    1 s035000  student        3   1月 13日 10時50分  mycount
% chmod go+w mycount[Enter]
% ls -l mycount[Enter]
-rw-rw-rw-    1 s035000  student        3   1月 13日 10時50分  mycount

ただしこうすると、 そのファイルを他の利用者が好きなように修正できるようになります。


課題27


mycount.cc を修正して、 カウント数の各桁の数字に下のような画像 (自分で作った画像でも構いません) を使ったグラフィカルなアクセスカウンタを作成してください。

/icons/0.gif /icons/1.gif /icons/2.gif /icons/3.gif /icons/4.gif
/icons/5.gif /icons/6.gif /icons/7.gif /icons/8.gif /icons/9.gif

うまくできたら、Netscape の「ファイル」メニューにある「リンクを送信」で、 そのページを私 (tokoi) に知らせてください。Subject:(件名)は kadai27 としてください。

この方法は CGI (Common Gateway Interface) を使う方法に比べれば「ベタ」な方法ですが、 画像データ自体を加工せずに済むので簡単です。 しかし、排他制御(複数の人が同時にカウンタにアクセスしないようにすること) を行っていないので、 アクセスが集中するとカウンタの値が壊れてしまう恐れがあります。 ということで、このプログラムはあんまり「実用」にはなりません。