いつもこちらのML、定例会などでAndroidの勉強をさせてもらっております。
さて、本メールの題名にある件で悩んでおりますので、質問メールを出させて頂きました。
ご指摘やお気づきの点などございましたら、教えて頂ければと思います。
単刀直入に申し上げますと、
AndroidでSDカード上のディレクトリ、ファイルのショートネームは取得出来ますでしょうか?
もし出来るとしたらその方法を教えて頂けないでしょうか?
以下は私が調査した内容です。
もちろんショートネーム取得には至っておりません。
-★-★-★-★-★-★-★-★-★-★-★-★-
まずはじめにエミュレータ(Android 2.2)を起動して、adb shellで入って、
mountコマンドを叩いて確認したところ、
# mount
... 省略 ...
/dev/block/vold/179:0 /mnt/sdcard vfat rw,dirsync,nosuid,nodev,noexec,uid=1000,g
id=1015,fmask=0702,dmask=0702,allow_utime=0020,codepage=cp437,iocharset=iso8859-
1,shortname=mixed,utf8,errors=remount-ro 0 0
... 省略 ...
と出てきました。
「shortname=mixed」という記載があるということはとれるのでは?と思い調査を開始しました。
Android-SDKにあるクラスライブラリを見る限り、そのようなAPIは見当たりませんでした。
ですので、ネイティブコード側で取得することは出来ないかと
NDK(r5b, android-8)のヘッダファイルを探していたところ
・include/linux/msdos_fs.h
#define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2])
#define VFAT_IOCTL_READDIR_SHORT _IOR('r', 2, struct dirent [2])
・include/linux/dirent.h
struct dirent {
long d_ino;
__kernel_off_t d_off;
unsigned short d_reclen;
char d_name[256];
};
という定義を見つけることが出来ました。
ioctlを使えば、ショートネームが取得出来るのではないかと考え、
以下のようなコードを作成し、実行してみました(乱れたコードで申し訳ありません)。
#include "com_example_shortfilename_ShortFileName.h"
#include <jni.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/dirent.h>
#include <android/log.h>
struct kernel_dirent {
long d_ino;
long d_off;
unsigned short d_reclen;
char d_name[256];
};
#define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct kernel_dirent [2])
#define VFAT_IOCTL_READDIR_SHORT _IOR('r', 2, struct kernel_dirent [2])
jstring Java_com_example_shortfilename_ShortFileName_getShortFileName(
JNIEnv* env,
jobject obj,
jstring longFileName) {
int fd;
struct kernel_dirent de[2] = {0};
jstring name;
fd = open("/sdcard", O_RDONLY);
if(fd == 0) {
name = (*env)->NewStringUTF(env, "open failed!!!");
} else {
int ret = ioctl(fd, VFAT_IOCTL_READDIR_BOTH, (long)de);
// int ret = ioctl(fd, VFAT_IOCTL_READDIR_SHORT, (long)de);
if(ret != -1) {
__android_log_print(
ANDROID_LOG_DEBUG,
"NDK",
"Message : ret: %d de[0]<d_ino: %d, d_off: %d, d_reclen: %d>
d[1]<d_ino: %d, d_off: %d, d_reclen: %d>",
ret,
de[0].d_ino,
de[0].d_off,
de[0].d_reclen,
de[1].d_ino,
de[1].d_off,
de[1].d_reclen);
char dname[1024] = {0};
memcpy(dname, de[0].d_name, de[0].d_reclen);
name = (*env)->NewStringUTF(env, dname);
} else
name = (*env)->NewStringUTF(env, "Failed!!!");
}
close(fd);
return name;
}
このソースファイルをNDKでビルドし、Activity側から本関数を呼び出したところ、
Message : ret: 1 de[0]<d_ino: 0, d_off: 0, d_reclen: 1> d[1]<d_ino: 0,
d_off: 0, d_reclen: 0>
とlogcatに表示されました(de[0].d_nameには'.'が格納されていました)。
ioctlの戻り値が「1」なので、成功しているのだと思われますが(fs/fat/dir.cを見てそう判断しました)、
肝心のディレクトリエントリの情報が取得出来ていません。
試しにディレクトリのパスを
/sdcard/tmp、/mnt/sdcard/tmp、/sdcard/tmp/hogehoge.bin
などいろいろ変えてみたところ(いずれも存在するパス)、ファイルパスを指定するとioctlに失敗し(戻り値:-1)、
ディレクトリパスを指定すると成功はしますが、エントリの情報が取得出来ない、という状況でした(上と同じ)。
-★-★-★-★-★-★-★-★-★-★-★-★-
何かお気づきの点などございましたら、教えて頂ければ幸いです。
よろしくお願いいたします。
よくわかりませんが、readdirのかわりにioctlを使うのではないでしょうか?
man opendir
man readdir
あたりを見るとよいと思います。
'.'がとれるのは、directory entryの最初の2つは'.', '..'だからではないかと。
----------------------------------------------------------------------
木本 雅彦 / Masahiko KIMOTO, Ph. D.
E-mail: kim...@ohnolab.org URL: http://www.ohnolab.org/~kimoto
中井です。
素早い返信ありがとうございます。
> '.'がとれるのは、directory entryの最初の2つは'.', '..'だからではないかと。
たしかにそうですね!
正しく取れているということは、ショートネームも取得出来そうな気がしてきました。
ご指摘を頂いた
> man opendir
> man readdir
をチェックして、再度トライしてみます。
また質問させて頂くかも知れませんが、よろしくお願いいたします。
昨日質問させていただいたショートネーム取得の件、解決しましたので、
報告させていただきます。
木本さまからコメントがあったopendir、readdirの使い方を調べました。
次のようなサンプルを見つけました。
for(dp=readdir(dir);dp!=NULL;dp=readdir(dir)){
printf("%s\n",dp->d_name);
}
イテレータのように繰り返し呼び出して使うんですね。
ioctlでも同じように呼び出せば、ショートネームをリストアップ出来るのではないかと考えて、以下のようなコードを作成しました。
#include "com_example_helloshortname_HelloShortFileName.h"
#include <jni.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <linux/dirent.h>
#include <android/log.h>
#define VFAT_IOCTL_READDIR_BOTH _IOR('r', 1, struct dirent [2])
#define VFAT_IOCTL_READDIR_SHORT _IOR('r', 2, struct dirent [2])
jstring JNICALL
Java_com_example_helloshortname_HelloShortFileName_getShortName(JNIEnv*
env, jobject obj, jstring param) {
int fd;
struct dirent entries[2];
jstring shortName;
const char* longName;
int ioctlRet;
longName = (*env)->GetStringUTFChars(env, param, NULL);
fd = open(longName, O_RDONLY);
if(fd == 0)
return (*env)->NewStringUTF(env, "Open failed.");
for(;;) {
// ioctlRet = ioctl(fd, VFAT_IOCTL_READDIR_SHORT, &entries);
ioctlRet = ioctl(fd, VFAT_IOCTL_READDIR_BOTH, &entries);
if(ioctlRet == -1) {
return (*env)->NewStringUTF(env, "Ioctl failed");
} else {
if(entries[0].d_reclen == 0)
return (*env)->NewStringUTF(env, "Ioctl success but data not exists");
__android_log_print(
ANDROID_LOG_DEBUG,
"NDK",
"Short name: %s, Long name: %s",
entries[0].d_name,
entries[1].d_name);
}
}
close(fd);
return (*env)->NewStringUTF(env, "Ioctl success");
}
呼び出し元としては、
getShortName("/sdcard");
getShortName("/mnt/sdcard");
getShortName("/mnt/sdcard/tmp");
などとしました。手持ちのHT-03Aで動かしてみたところ、
Short name: ., Long name:
Short name: .., Long name:
Short name: LOST.DIR, Long name:
Short name: DCIM, Long name:
Short name: DATA, Long name: data
Short name: ANDROI~1, Long name: .android_secure
Short name: DOLPHI~1, Long name: dolphinbrowser_gestures
Short name: DOWNLO~1, Long name: downloads
Short name: PICTURES, Long name: Pictures
Short name: ANDROID, Long name: Android
Short name: DOWNLOAD, Long name: download
Short name: PING.XML, Long name: ping.xml
Short name: TMP, Long name: tmp
Short name: DROPBO~1, Long name: .dropboxthumbs
Short name: MEDIA, Long name: media
Short name: DOLPHI~2, Long name: Dolphin_Browser_Mini
※ディレクトリパスは/sdcardを指定しています。
というように見事にショートネームとロングネームをリストアップすることに成功しました。取れたときは控えめに歓喜の雄叫びをあげてしまいました。
To 木本さま
コメント、本当にありがとうございました。