lsっぽいコマンドを作る

作成:

UNIX 環境のコマンドラインを触ったことがある人ならどんな人でも使ったことがあるコマンドの一つ、 lsっぽいコマンドラインプログラムを作りながら、そのために必要な要素について解説していく。 あくまで「っぽい」であり、本物と全く同じものを作るわけではないので注意。

説明に使用するプログラムコードについては GitHub で公開している。 ソースコードの全文をよく見たい、ダウンロードしたいなどの場合はこちらを参照してほしい。

今回は ls8.c を利用した説明になる。

ロングフォーマットの表示

ロングフォーマット表示は前回までで概ね完了したが、 今回はもう一つシンボリックリンクのリンク先表示を行う。

当たり前に表示されているので特に意識していないとピンと来ないかもしれないが、 シンボリックリンクに対しては、以下の様にリンク先を -> で表示するというものだ。

lrwxrwxrwx 1 ryosuke ryosuke    4 12月 27 21:37 link -> file

リンク情報の読み出し

シンボリックリンクがどこを指しているかを調べるには以下の関数を利用する。

#include <unistd.h>
ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

pathname で指定したパスのシンボリックリンクの情報を buf に格納する。 バッファサイズも指定することができ、もしリンクのパスがこれよりも長い場合は、この長さに切り詰められる。 そして、戻り値として格納した文字列の長さが返される。

注意が必要なのは、格納されるパス名はヌル終端されていない点だ。 戻り値を参照し、この値を元に終端を自分で行う必要がある。

また、戻り値はあくまで格納した長さであり、バッファより長い場合は切り詰めた値が返される。 そのため、長さが不足したことはわかるが、どれだけの長さがあれば十分かは不明である。 それを知るには、 lstat で取得した st_size の値を参照する。 この値は、シンボリックリンクではそのリンクの長さを表しているのだ。 文字列の長さ+終端で、1を加算した長さが十分な長さと言えるだろう。 ただし、lstat 、メモリ確保、 readlink 、を不可分にコールすることはできないので、 readlink の戻り値の確認は必要だ。

リンク先の表示処理

リンク先の表示のために、以下の処理を追加する。

if (long_format) {
  if (S_ISLNK(dent_stat.st_mode)) {
    char link[PATH_MAX + 1];
    int link_len = readlink(path, link, PATH_MAX);
    if (link_len > 0) {
      link[link_len] = 0;
      printf(" -> %s", link);
    }
  }
}

ここでは、固定長のバッファを使って読み出しを行っている。 リンク長については必ずしも PATH_MAX あれば十分というわけでもないが、 バッファオーバーフローが発生するわけでもないので簡易的にこのような対応としている。 readlink に渡すサイズはバッファサイズであり、終端を行うために 1 加算した長さのバッファを用意して、 PATH_MAX サイズを指定している。

エラーが発生した場合は -1 が返り、また、長さ 0 は有効な情報ではないので、 戻り値が 0 より大きい場合に、終端したうえで表示を行う。


以上の対応を行うことで以下のような表示ができるようになった。

$ ./ls8 -l ../lstest
srwxrwxr-x   1  ryosuke  ryosuke         0 12/15 20:11 socket
prw-rw-r--   1  ryosuke  ryosuke         0 12/15 20:01 fifo
-rw-rw-r--   1  ryosuke  ryosuke         0 12/15 19:57 file
drwxrwxr-x   2  ryosuke  ryosuke      4096 01/09 19:14 dir
-rwxrwxr-x   1  ryosuke  ryosuke      8672 12/15 20:32 a.out
lrwxrwxrwx   1  ryosuke  ryosuke         4 12/27 21:37 link -> file

これでロングフォーマットへの対応は終了である。