「SDカード向け」ではないSDCardFSの正体


GoogleSD, .

juggly.cn



SD Card FS  SD / Micro SD 

, sdcardfsSD.





. . .

, SDCardFSFS, SD.

"/sdcard"


AndroidSD. SD, "/sdcard". "/sdcard", , .

, AndroidSD*1. , "/sdcard". SD, . , 使, SD.

SDVFAT. , Android (Linux)Ext4(F2FS). VFATExt4. 2.

1, . VFAT, Ext4. , "cat.JPG". VFAT"JPG", "cat.jpg". Ext4, "cat.jpg""JPG", !.

2, . LinuxUNIX使. , , , . , MS-DOSVFAT, .

1, Ext4. , "/sdcard". .

2, VFATAndroid. Android, WRITE_EXTERNAL_STORAGE"/sdcard". VFAT, , OS"/sdcard". , "/sdcard". , SD, .

. , Ext4(Linux)使. VFAT. , .


VFAT?, .

VFATLinux, VFAT. , VFAT, , OS.

.


"/sdcard", 

, 

,  (OS)


f:id:meech:20170121053222p:plain
Android, LinuxFUSE使. FUSE, Linux. Linux, , , C使, (, !). , , , , . , ssh使sshfs, WindowsNTFSntfs3g.

, FUSE使Linux. , "/sdcard"SD(VFAT), (ext4), 2, , .

sdcardfs: FUSE


, .

FUSE使, , . , FUSE.

. , VFS. VFSExt4, .

FUSE, , VFSFUSE. , FUSE. FUSE, ssh, . , .

FUSE, . , FUSE, FUSE.

f:id:meech:20170121044423p:plain
, . AndroidFUSE.

fixbugfix.blogspot.jp

,


(700MB), FUSEExt4, 17%

(5KB x 10000, 50MB), ext417. FUSE1


. , , , FUSEFUSE.

, , Ext4FUSE, , FUSE.

, SDCardFS. FUSE使, 調. ?.

. SDCardFS, FUSE使. VFAT, , , 調.

f:id:meech:20170121044510p:plain
VFAT Ext4/F2FS FUSE SDCardFS
互換性 o x o o
アクセス権限 x o o o
オーバヘッド なし なし 大きい 小さい

結果として, FUSEを使っていたオーバヘッドが小さくなる分だけ性能が向上します. しかしながら, 「SD Card FS」はSDカード専用のファイルシステム, というものではありません. SDカードの特性をうまく使って高速化とか, そういうものではありません. F2FSと比べるとかそういうものではありません. SDカードがいた領域を, あたかもそのままSDカードであったかのように見せる, それを速くしました. そういうものです.

宣伝

ね, ファイルシステムおもしろいですよね. Linuxでは, いろんなファイルシステムが実装されています. そんなファイルシステムたちがどのようなデータ構造で, どのようにデータをディスクに保存しているか気になってきましたよね? ちょうど現在, 発売中のSoftware Design 2017年2月号に「Linuxファイルシステムの教科書」と題して, Ext3, Ext4, XFS, F2FS, Btrfsの実装について特集記事を書きました. ファイルシステムの中身をもっと知りたくなったあなたはぜひ購入してみてください.

参考文献

Androidの"/sdcard"については, 知識がなかったので以下を参考にしています

www.xda-developers.com

Linuxのストレージ・メモリ管理に関する会議の議題として, sdcardfsでやっていることさらにVFSに統合していこうという話が提案されている. case-insensitveな探索ができるようにしようとか, アプリケーションレベルでpermissionのハンドルを行うLSM(Linux Security Module)が実装できないかなどが話されるようだ.

'[LSF/MM TOPIC] Getting rid of Android's FUSE/wrapfs hackery' - MARC

で, おれはコードを読みたいんだ. コードはどこだ

(ここから先は上級向けで飛んでいきます)

よっしゃまかせろ. コードはここだ. 読んでくぞ. (以下ほとんど読む過程のdumpで整理されていない)

fs/sdcardfs - kernel/common.git - Git at Google

まず, ファイルシステムに限らずLinux kernel moduleを読む時の鉄則として"__init"の付いた関数を見つけよう. こいつがmoduleの初期化をする. moduleの初期化で, だいたい大事なデータ構造を作ったりしてるので眺めておくと理解がスムーズに行く.

こいつの場合, main.cのinit_sdcardfs_fsがそれ.

fs/sdcardfs/main.c - kernel/common.git - Git at Google

 err = sdcardfs_init_inode_cache();
    if (err)
        goto out;
    err = sdcardfs_init_dentry_cache();
    if (err)
        goto out;
    err = packagelist_init();
    if (err)
        goto out;
    err = register_filesystem(&sdcardfs_fs_type);

ざっと見ると, 以下の3つのデータ構造が使われてそう. package listってなんだろね.

  • inode cache
  • dentry cache
  • packagelist

ファイルシステムの場合, 次に見ていきたいのは, struct file_operations. こいつらがファイルシステム上のファイルの挙動を決める. (mount処理は, だいたいoptionをなめていろいろ初期化してるだけなので, 頭から読むようなものではない, と思う)

const struct file_operations sdcardfs_main_fops = {
    .llseek     = generic_file_llseek,
    .read       = sdcardfs_read,
    .write      = sdcardfs_write,
    .unlocked_ioctl = sdcardfs_unlocked_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = sdcardfs_compat_ioctl,
#endif
    .mmap       = sdcardfs_mmap,
    .open       = sdcardfs_open,
    .flush      = sdcardfs_flush,
    .release    = sdcardfs_file_release,
    .fsync      = sdcardfs_fsync,
    .fasync     = sdcardfs_fasync,
};

/* trimmed directory options */
const struct file_operations sdcardfs_dir_fops = {
    .llseek     = generic_file_llseek,
    .read       = generic_read_dir,
    .iterate    = sdcardfs_readdir,
    .unlocked_ioctl = sdcardfs_unlocked_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = sdcardfs_compat_ioctl,
#endif
    .open       = sdcardfs_open,
    .release    = sdcardfs_file_release,
    .flush      = sdcardfs_flush,
    .fsync      = sdcardfs_fsync,
    .fasync     = sdcardfs_fasync,
};

変数名から, それぞれファイルとディレクトリに対してのoperationsだとわかる. case-insensitve searchしてくれるんだよな, という知識で眺めると, .iteratesdcardfs_readdirが気になる.

static int sdcardfs_readdir(struct file *file, struct dir_context *ctx)
{
    int err;
    struct file *lower_file = NULL;
    struct dentry *dentry = file->f_path.dentry;

    lower_file = sdcardfs_lower_file(file);

    lower_file->f_pos = file->f_pos;
    err = iterate_dir(lower_file, ctx);
    file->f_pos = lower_file->f_pos;
    if (err >= 0)        /* copy the atime */
        fsstack_copy_attr_atime(d_inode(dentry),
                    file_inode(lower_file));
    return err;
}

意外と面白いところはない. lower_fileで下位のFSのファイルをとってきて, そいつをiterate_dir()でなめる. atimeを上のディレクトリにcopy upする. それだけっぽい

じゃあ, 多分openだな, ということで, sdcardfs_openに行く.

 if(!check_caller_access_to_name(parent->d_inode, dentry->d_name.name)) {
        printk(KERN_INFO "%s: need to check the caller's gid in packages.list\n"
                         "  dentry: %s, task:%s\n",
                         __func__, dentry->d_name.name, current->comm);
        err = -EACCES;
        goto out_err;
    }

    /* save current_cred and override it */
    OVERRIDE_CRED(sbi, saved_cred);

check_caller_access_to_name()に親ディレクトリと, アクセスしようとしているファイル名を渡してアクセス可能かチェックし, OVERRIDE_CREDで権限を書きかえるという流れだろう.

/* Kernel has already enforced everything we returned through
 * derive_permissions_locked(), so this is used to lock down access
 * even further, such as enforcing that apps hold sdcard_rw. */
int check_caller_access_to_name(struct inode *parent_node, const char* name)
{
    /* Always block security-sensitive files at root */
    if (parent_node && SDCARDFS_I(parent_node)->perm == PERM_ROOT) {
        if (!strcasecmp(name, "autorun.inf")
            || !strcasecmp(name, ".android_secure")
            || !strcasecmp(name, "android_secure")) {
            return 0;
        }
    }

    /* Root always has access; access for any other UIDs should always
     * be controlled through packages.list. */
    if (from_kuid(&init_user_ns, current_fsuid()) == 0) {
        return 1;
    }

    /* No extra permissions to enforce */
    return 1;
}

ところがcheck_caller_access_to_nameはあまり大したことをしていない. "autorun.inf"とかのやばそうなファイルへのアクセスを消り, rootにアクセス権を与え, それ以外は弾いているだけ. ふ〜ん…コメントが気になるけど, 一度OVERRIDE_CREDを見る.

OVERRIDE_CREDは結局以下の関数でstruct credを書きかえる.

const struct cred * override_fsids(struct sdcardfs_sb_info* sbi)
{
    struct cred * cred;
    const struct cred * old_cred;

    cred = prepare_creds();
    if (!cred)
        return NULL;

    cred->fsuid = make_kuid(&init_user_ns, sbi->options.fs_low_uid);
    cred->fsgid = make_kgid(&init_user_ns, sbi->options.fs_low_gid);

    old_cred = override_creds(cred);

    return old_cred;
}

cred->fsuid というのがVFSでのUID, fsgidは言うまでもなくGID. FUSE版の方はファイルのattributeをアプリの所有に書きかえる感じだったのが, kernel版では実行主体のcredentialを書きかえてるのが面白い.

コメントで気になったderive_permissions_locked()を見ていく……と思ったけど, そんな関数はない. 似た名前のget_derive_permissions()というのがあやしい. 多分移植の時にコメント書き変えてないんでしょ. よくある. よくする.

結局get_derived_permission_newにたどりつく. 親ディレクトparentと, アクセスするファイルdentryで呼ばれる. newdentryはrenameの時のため.

void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, struct dentry *newdentry)
{
…
    inherit_derived_state(parent->d_inode, dentry->d_inode);

    /* Derive custom permissions based on parent and current node */
    switch (parent_info->perm) {
        case PERM_INHERIT:
            /* Already inherited above */
            break;
…         
        case PERM_ANDROID_DATA:
        case PERM_ANDROID_OBB:
        case PERM_ANDROID_MEDIA:
            appid = get_appid(sbi->pkgl_id, newdentry->d_name.name);
            if (appid != 0) {
                info->d_uid = multiuser_get_uid(parent_info->userid, appid);
            }
            break;
    }
}

inherit_derived_stateで, 親からなんか引き継ぐ. ほとんどそのままだが, 親がPERM_ANDROID_DATAなどだとなんかしている.

    /* This node is "/Android/data" */
    PERM_ANDROID_DATA,
    /* This node is "/Android/obb" */
    PERM_ANDROID_OBB,
    /* This node is "/Android/media" */
    PERM_ANDROID_MEDIA,

なるほどね〜, "/Android/data"とかの直下のファイルだった場合ね. この時, ファイル名から"appid"をとってきて, それをinfo_uidに設定している. multiuser_get_uidは, Androidのマルチユーザ対応っぽい. ここでアプリ個別のディレクトリの所有者が記録されるのかな.

inherit_derived_state()に行く

static void inherit_derived_state(struct inode *parent, struct inode *child)
{
    struct sdcardfs_inode_info *pi = SDCARDFS_I(parent);
    struct sdcardfs_inode_info *ci = SDCARDFS_I(child);

    ci->perm = PERM_INHERIT;
    ci->userid = pi->userid;
    ci->d_uid = pi->d_uid;
    ci->under_android = pi->under_android;
}

いかにもsdcardfs専用構造っぽいstruct sdcardfs_inode_infoが出てきた. SDCARDFS_Iは, struct inode*からstruct sdcardfs_inode_info*に変換するマクロだろ. FS, だいたいこんな書き方する.

permPERM_INHERIT, userdid, d_uid, under_androidは上から引き継ぎ. すなわち, こいつの下もこいつの親からの情報をひきついでいくことになる. それっぽい.

ここまでは, struct sdcardfs_inode_info しかいじられていない. これではアクセス制御になってないな.

get_derived_permissionの呼びだしあたりがあやしいのでgrepする.

struct dentry *sdcardfs_lookup(struct inode *dir, struct dentry *dentry,
                 unsigned int flags)
{
…
    /* save current_cred and override it */
    OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred);
…
    ret = __sdcardfs_lookup(dentry, flags, &lower_parent_path, SDCARDFS_I(dir)->userid);
    if (IS_ERR(ret))
    {
        goto out;
    }
    if (ret)
        dentry = ret;
    if (dentry->d_inode) {
        fsstack_copy_attr_times(dentry->d_inode,
                    sdcardfs_lower_inode(dentry->d_inode));
        /* get drived permission */
        mutex_lock(&dentry->d_inode->i_mutex);
        get_derived_permission(parent, dentry);
        fix_derived_permission(dentry->d_inode);
        mutex_unlock(&dentry->d_inode->i_mutex);
    }

ここでもcredを変えて, __sdcardfs_lookupして, attributeとかをcopy upして, get_derived_permissionしてる. fix_derived_permissionかなあ

#define fix_derived_permission(x)    \
    do {                        \
        (x)->i_uid = make_kuid(&init_user_ns, SDCARDFS_I(x)->d_uid);  \
        (x)->i_gid = make_kgid(&init_user_ns, get_gid(SDCARDFS_I(x)));   \
        (x)->i_mode = ((x)->i_mode & S_IFMT) | get_mode(SDCARDFS_I(x));\
    } while (0)

あぁ〜確かに, inodeのuidとかgidが, struct sdcardfs_inode_infoに入ってたものに書きかえられてる. さっき, こっちではcredを書きかえるのか〜と思ったけれど, あれはlower FSを読む権限を獲得してるだけで, やっぱりファイルの所有情報書き変えてんのね.

アクセス権限のとこはわかったけど, case-insensitiveのとこはどうなってんだろ. やっぱdentry.cかな.

const struct dentry_operations sdcardfs_ci_dops = {
    .d_revalidate   = sdcardfs_d_revalidate,
    .d_release  = sdcardfs_d_release,
    .d_hash     = sdcardfs_hash_ci,
    .d_compare  = sdcardfs_cmp_ci,
    .d_canonical_path = sdcardfs_canonical_path,
};

専用の dentry_operationsがある. .d_compare = sdcardfs_cmp_ci とかそれっぽい.

static int sdcardfs_cmp_ci(const struct dentry *parent,
        const struct dentry *dentry,
        unsigned int len, const char *str, const struct qstr *name)
{
    if (name->len == len) {
        if (strncasecmp(name->name, str, len) == 0)
            return 0;
    }
    return 1;
}

なるほど見たまんま.

最後に, get_appidを見ておくか…

appid_t get_appid(void *pkgl_id, const char *app_name)
{
    struct packagelist_data *pkgl_dat = pkgl_data_all;
    struct hashtable_entry *hash_cur;
    unsigned int hash = str_hash(app_name);
    appid_t ret_id;

    mutex_lock(&pkgl_dat->hashtable_lock);
    hash_for_each_possible(pkgl_dat->package_to_appid, hash_cur, hlist, hash) {
        if (!strcasecmp(app_name, hash_cur->key)) {
            ret_id = (appid_t)hash_cur->value;
            mutex_unlock(&pkgl_dat->hashtable_lock);
            return ret_id;
        }
    }
    mutex_unlock(&pkgl_dat->hashtable_lock);
    return 0;
}

まあそりゃhashtableひくだけだよね. tableはpackage_to_appidってやつ. insert_str_to_int_lock()が追加している.

このpackagelist.cを見ているとどうやらconfigfsを使ってアプリ名->appidのマッピングが入っているっぽい.

static struct configfs_subsystem sdcardfs_packages_subsys = {
    .su_group = {
        .cg_item = {
            .ci_namebuf = "sdcardfs",
            .ci_type = &sdcardfs_packages_type,
        },
    },
};

static int configfs_sdcardfs_init(void)
{
    int ret;
    struct configfs_subsystem *subsys = &sdcardfs_packages_subsys;

    config_group_init(&subsys->su_group);
    mutex_init(&subsys->su_mutex);
    ret = configfs_register_subsystem(subsys);

configfs中のrootは"sdcardfs"

static struct configfs_group_operations sdcardfs_packages_group_ops = {
    .make_item  = sdcardfs_packages_make_item,
};

static struct config_item_type sdcardfs_packages_type = {
    .ct_item_ops    = &sdcardfs_packages_item_ops,
    .ct_group_ops   = &sdcardfs_packages_group_ops,
    .ct_attrs   = sdcardfs_packages_attrs,
    .ct_owner   = THIS_MODULE,
};

その中にディレクトリを作ることができる (ct_group_ops から)

static struct configfs_attribute package_appid_attr_add_pid = {
    .ca_owner = THIS_MODULE,
    .ca_name = "appid",
    .ca_mode = S_IRUGO | S_IWUGO,
    .show = package_appid_attr_show,
    .store = package_appid_attr_store,
};

static struct configfs_attribute *package_appid_attrs[] = {
    &package_appid_attr_add_pid,
    NULL,
};

その中に"appid"というファイルがある. ここに読み書きできる.

まとめると, "/sys/kernel/config/sdcardfs/app.name/appid"というファイルに数字を書くと, "app.name"->42といったマッピングがさっきのhashtableに登録される.

てことは, 誰かがユーザランド側でこれをやってくれるんだね. なるほどね.

ここまでコードがんがんはしてないけれど、データ構造がわかる感じになるので, もいちどよろしく.

*1:実際どの程度かわからぬですが, 確かに手元のNexus7にはSDカードスロットがない