libcurlのC-APIでFTPしてMLSDコマンドで詳細なリストを取る方法

分かると簡単なんだけど悶絶したので備忘録&日本語情報なさげだったので。



やりたかった事

libcurlのC用のAPIを使ってFTP接続し、各ディレクトリのリストを取りたい。


使用したlibcurlバージョン:libcurl 7.19.7
僕が使用している某サーバはFTPサーバとして ProFTPD を使用(バージョンは不明)
開発環境はmac os x 10.6.6



試した事

libcurl example - ftpgetresp.c
ここにサンプルがあったのでとりあえずこの通りにしてみた。
(サンプルはレスポンスをファイルに書きこむ内容)

レスポンスを見ると、

drwx---r-x 2 hogeUser fugaGroup 4096 Mar 8 14:15 testDir

普通に取れた。よしよしと思ってたけど、よく見ると
年が無い。

更に、少し古いファイルを取得してみたら、

-rw-r--r-- 1 hogeUser fugaGroup 4152 Aug 1 2010 test.html

年はある。でも時間がなくなった。

どうやら今年のファイルは年が省略されて、去年以前は年が入る代わりに時間が省略されるらしい(なんだこれ)。


どうやったら日時に関する全情報が取れるのだろう。ProFTPDのマニュアルを見てみる。
ProFTPD Supported FTP Commands

MDTM, SITEコマンドのUTIME なんかで取れそう。でもファイル単位。
取りたいのはディレクトリのリスト。



調べた

とりあえず、FEATコマンドで、サーバが対応しているコマンドを取得してみる。


libcurl で用意されているAPIは、curl_easy_setoptという関数になる。
libcurl - curl_easy_setopt()
マニュアルの FTP OPTIONS 項目を見ると、CURLOPT_QUOTEというオプションで実行したいコマンドを指定すればいいらしい。

CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://ftp.example.com/");
// この行でオプションを設定
curl_easy_setopt(curl, CURLOPT_QUOTE, "FEAT");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}

これでFEATコマンドが実行されて、結果をファイルなりコンソールなりに書きだして見てみる、と。

> FEAT< 211-Features:< MDTM< MFMT< AUTH TLS< MFF modify;UNIX.group;UNIX.mode;< MLST modify*;perm*;size*;type*;unique*;UNIX.group*;UNIX.mode*;UNIX.owner*;< PBSZ< PROT< REST STREAM< SIZE

という結果が返された。MDTMはサポートしてるぽいけど、これはファイル単位なので除外。



Filezilla のログを見てみた

Filezillaでログインすると、日時は当然年月日時刻まで全て表示されているので、何かしら方法があるはず。
目的のサーバにFilezillaでログインしてみて、ログを確認。

コマンド: PBSZ 0
応答: 200 PBSZ 0 successful
コマンド: PROT P
応答: 200 Protection set to Private
状態: 接続されました
状態: ディレクトリーの一覧を読み出しています...
コマンド: PWD
応答: 257 "/" is the current directory
コマンド: TYPE I
応答: 200 Type set to I
コマンド: PASV
応答: 227 Entering Passive Mode (***,***,**,**,***,**).
コマンド: MLSD
応答: 150 Opening ASCII mode data connection for MLSD
応答: 226 Transfer complete
状態: ディレクトリー一覧の表示成功

(以上一部抜粋)

どうやら MLSD というコマンドを発行している様子。

ググってみたところ、このコマンドは通常の LIST コマンドに変わる、詳細なリストを取得する比較的新しいコマンドのようである。

これで最終更新日のタイムスタンプを取得できる。



libcurl APIで実行してみた

早速やってみる。

CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://ftp.example.com/");
// この行でオプションを設定
curl_easy_setopt(curl, CURLOPT_QUOTE, "MLSD");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}


しかし、エラーが返って来た。

> MLSD< 425 Unable to build data connection: Invalid argument

どうやらコネクション確立する前にコマンド発行しているのでエラーになっている様子。


ここでひたすら悩んだ。
明示的に PASV コマンドもオプションで入れてみたりもした。けど駄目だった。


そして結局解決した方法が…



libcurl APIでのMLSDコマンド実行方法

CURL *curl;
CURLcode res;
curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "ftp://ftp.example.com/");
// このオプションでMLSDコマンドは指定する必要がある
curl_easy_setopt(curl_, CURLOPT_CUSTOMREQUEST, "MLSD");
res = curl_easy_perform(curl);
curl_easy_cleanup(curl);
}

この CURLOPT_CUSTOMREQUESTというオプション、マニュアルの FTP OPTIONS のところには記述されてなくて、PROTOCOL OPTIONS 欄に書いてあったんですねー。

FEATコマンドでこのコマンドが返ってこなかったのは、MLSDコマンドが新しいコマンドゆえ、FEATに含まれていないものかと思いました。


…これが分からなくて泣いた。


以上、libcurlのC-APIを使ってMLSDコマンドでタイムスタンプを取る方法でした。
@shivaken さんのサポートがあってここまで辿りつけました。RESPECT!