Androidアプリからのファイルパーミッションの制御
アプリ間のファイルアクセスの許可/禁止には unix のファイルパーミッションの仕組みが使われています。
Androidではアプリごとにuidとgidが割り当てられていて、パーミッションのうち、otherのread,write,execute ビットを変更することで他アプリからのアクセスを許可/禁止することができます。
パーミッションの設定はネイティブコードならchmod(2)を使うんですが、Javaからだとそのまま呼び出せるようにはなっていません。
そこで今回はアプリからファイルパーミッションを制御するにはどのようなAPIを使えばよいか確認してみます。
Javaの標準APIでの制御
Javaの標準では、他ユーザからのアクセスを制御したい場合は java.io.File のメソッドを使います。
- File#setReadable (boolean readable, boolean ownerOnly)
- File#setWritable (boolean writable, boolean ownerOnly)
- File#setExecutable (boolean executable, boolean ownerOnly)
ただしAndroidではこれらのメソッドが提供されるのは API Level 9 (OS 2.3)以降です。
Android Developers の Device Dashboard の統計を見ると、 2012年3月5日現在 でも 2.1と2.2が合わせて32%近くあります。これらの古い端末を無視してしまうのはまだ難しいです。
AndroidのAPIでの制御
AndroidのAPIでは、以下のメソッドを使うことで他アプリからのアクセスを制限できます。
- Context#openFileOutput(String name, int mode)
- Context#openOrCreateDatabase(String name, int mode, CursorFactory factory)
- Context#getDir(String name, int mode)
ファイルパス指定の制限
これらのメソッドはファイルパスの指定に制限があります。
親ディレクトリのパーミッション
- openFileOutput(),getFilesDir(),getCacheDir() はファイルを格納しているディレクトリのパーミッションを 771 に変更します。
- openOrCreateDatabase がディレクトリを作成した場合、そのパーミッションは771に設定されます。
なのでこれらのAPIで作成したフォルダは、他アプリからもchdirが可能となります。
getDir() の制限
非公開API android.os.FileUtils#setPermissions
前項で説明した3つのメソッドは、ContextImpl#setFilePermissionsFromMode 経由で非公開クラス android.os.FileUtils の ネイティブメソッド setPermissions を呼び出し、このメソッドがchown(2)とchmod(2)を呼び出しています。ただし setFilePermissionsFromMode は uid,gidに-1を設定していて、その場合は FileUtils#setPermissions は chown(2)を呼び出しません。
試しにリフレクションで FileUtils#setPermissions を呼び出してみましょう。
手元の端末で試してみたら1.6, 2.3.6, 2.3.7, 3.1, 4.0.2 で動いてたので実用上問題はないと思いますが、非公開APIなので取り扱いには注意が必要です。
try{ context.openFileOutput("test_file",Context.MODE_PRIVATE).close(); File file = context.getFileStreamPath("test_file"); String path = file.getPath(); // Method setPermissions= Class.forName("android.os.FileUtils").getMethod("setPermissions",String.class ,int.class ,int.class ,int.class); // int rv = ((Integer)(setPermissions.invoke(null,path,0600,-1,-1))).intValue(); Log.d(TAG,"FileUtils.setPermissions rv="+rv); // returns 0 or errno }catch(Throwable ex){ ex.printStackTrace(); }
おまけ: Context APIが作成するファイルの位置
蛇足ですが、実際にファイルやディレクトリが作成される位置をメモしておきます。
なお、確認にはrooted端末を使用しました。
次のようなコードを実行すると
context.openFileOutput("hoge",Context.MODE_PRIVATE).close(); context.getDir("dir1",Context.MODE_PRIVATE); context.openOrCreateDatabase("db1",Context.MODE_PRIVATE,null).close();
/data/data/${package_name} の下に 次のようなファイル階層が作成されます。
# pwd /data/data/jp.juggler.test_app # find . . ./databases ./databases/db1 ./app_dir1 ./files ./files/hoge ./lib
ファイルパスとAPIとの対応はこんな感じです。
API_Level | メソッド名 | 内容 | 関連API |
---|---|---|---|
(private) | Context#getDataDirFile | LoadedApk#getDataDirFile() | 下記すべて |
(private) | Context#getDatabasesDir | getDataDirFile() + "/databases" (ただし特定の条件で /data/system に変更されます) |
databaseList, getDatabasePath, deleteDatabase, openOrCreateDatabase |
1 | Context#getFilesDir | getDataDirFile() + "/files" | openFileInput, openFileOutput, deleteFile, getFileStreamPath, fileList |
1 | Context#getDir(name) | getDataDirFile() + "/app_"+name | なし |