android vold学习

前言:

这次,主要学习 u盘挂载的问题。目的是实现linux/android双系统下,屏蔽android uDisk的识别以及挂载过程,使用mount指令,将linux的uDisk节点,挂载到android 文件系统的sdcard目录下。

预计涉及到的知识点有:
1,uevent,netlink。
2,socket。
3,文件系统,mount操作。

涉及的文件:
1,main.cpp,vold的入口函数,系统起来会执行/system/bin/vold,调到这个main函数中。
2,NetlinkManager.cpp位于源码位置/system/vold/NetlinkManager.cpp。该类的主要通过引用NetlinkHandler类中的onEvent()方法来接收来自内核的事件消息,NetlinkHandler位于/system/vold/NetlinkHandler.cpp。
3,VolumeManager:位于源码位置/system/vold/VolumeManager.cpp。该类的主要作用是接收经过NetlinkManager处理过后的事件消息。
4,DirectVolume:位于/system/vold/DirectVolume.cpp。该类的是一个工具类,主要负责对传入的事件进行进一步的处理,block事件又可以分为:Add,Removed,Change,Noaction这四种。
5,Volume:Volume.cpp位于/system/vold/Volume.cpp,该类是负责SD卡挂载的主要类。Volume.cpp主要负责检查SD卡格式,以及对复合要求的SD卡进行挂载,并通过Socket将消息SD卡挂载的消息传递给NativeDaemonConnector。

总的讲,vold程序需要分层三部分,
第一部分为NetlinkManager,管理接受来自kernel的UEvent消息,
第二部分为VolumeManager,主要负责处理来自NetlinkManager的消息和来自java层的消息,
第三部分真正的挂载卸载动作就需要volume负责了。

android vold学习_第1张图片

大概流程如上图所示:0.几开头的是系统启动时的部分操作. 1-13就是挂载的流程了.

现在遇到个问题,在linux下mount的目录,终端可以正常打开,但使用android的文件管理器,看不见这个文件的内容。

一,volume, 文件系统, mount操作学习。

这部分是为了弄清楚android vold程序,到底要做什么。也就是忽略了识别,消息处理等过程。我的理解呢:一个path的构成,包括 mnt和dentry。mount的作用,就是在dentry的flag加上mount标志,然后将mnt和dentry加入mnt_hash_table。这样使用dentry找到这个目录后,再判断mnt关系,就能找到挂载在这个目录上的新mount。

在mount命令执行时,VFS会创建一个vfsmount对象,这个对象里包含了整个文件系统所有的mount信息,其中也会包括本次mount中的信息,这个对象是一个HASH值对应表(HASH值通过对路径字符串的计算得来),表里就有 /test1 到 /test2 两个目录的HASH值对应关系
命令执行完后,当访问 /test2下的文件时,系统会告知 /test2 的目录项被屏蔽掉了,自动转到内存里找VFS,通过vfsmount了解到 /test2 和 /test1 的对应关系,从而读取到 /test1 的inode,这样在 /test2 下读到的全是 /test1 目录下的文件
由上述过程可知,mount --bind 和硬连接的重要区别有:
1.mount --bind连接的两个目录的inode号码并不一样,只是被挂载目录的block被屏蔽掉,inode被重定向到挂载目录的inode(被挂载目录的inode和block依然没变)
2.两个目录的对应关系存在于内存里,一旦重启挂载关系就不存在了
mount的详细实现,https://blog.csdn.net/wuruixn/article/details/9619127
版权声明:本文为CSDN博主「dAng1r0Us」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shengxia1999/article/details/52060354    

inode是内核选择用于表示文件内容和相关元数据的方法。

app分析

VolumeManager *vm = VolumeManager::Instance();
I/DevicePath( 5118): /mnt/extsd
I/DevicePath( 5118): extsd add
I/DevicePath( 5118):/mnt/usbhost
I/DevicePath( 5118): usb add
D/chen    ( 5118): item  /mnt/sdcard
rc = vm->mountVolume(argv[2]);
打开全志的FileExpolure App

I/DevicePath( 5118): scs_scs getExternalStorageDirectory().getAbsolutePath() /mnt/sdcard
I/DevicePath( 5118): scs_scs0/mnt/sdcard
I/DevicePath( 5118): flashList add

也就是说 /mnt/sdcard/  /mnt/usbhost /mnt/extsd这三个目录,分别对应的是内置sdcard,u盘,外置sdcard。

Environment.getExternalStorageDirectory().getAbsolutePath()返回的是  /mnt/sdcard

现在要做的,就是在 实现下面这个等式

private StorageManager stmg;

stmg = (StorageManager) context.getSystemService(context.STORAGE_SERVICE);if(Environment.MEDIA_MOUNTED.equals(stmg.getVolumeState(dev)))   (//  /mnt/sdcard  /mnt/extsd  /mnt/usbhost)
也就是切换目录后,getHomeDir 返回的情况。
I/FileManager(26600):  getHomeDir 1[/mnt/extsd]
I/FileManager(26600):  getHomeDir 0[/mnt/sdcard]
I/DevicePath(26600): scs log dev/mnt/sdcard
D/chen    (26600): item  /mnt/sdcard
I/FileManager(26600):  getHomeDir 2[/mnt/usbhost]

目前看来,只有getHomeDir 0[/mnt/sdcard]走下去了。因此,只要找到这个走下去的原因,就能查看mount到底干啥了。
找到StorageManager 的 getVolumeState 方法,看一下到底做了什么。
在 frameworks/base/core/java/android/os/storage/StorageManager.java 中  不同版本android,还不一样哈。
    /**     * Gets the state of a volume via its mountpoint.
     * @hide     */
    public String getVolumeState(String mountPoint) {
         if (mMountService == null) return Environment.MEDIA_REMOVED;
        try {
            return mMountService.getVolumeState(mountPoint);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get volume state", e);
            return null;
        }
    }

Mountservice.java中。public String getVolumeState(String mountPoint) {
        synchronized (mVolumesLock) {
            String state = mVolumeStates.get(mountPoint);
            if (state == null) {
                Slog.e(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
                if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
                    state = Environment.MEDIA_REMOVED;
                } else {
                    throw new IllegalArgumentException();
                }
            }
            Slog.e(TAG, "getVolumeState(" + mountPoint + "): state:" + state);
            return state;
        }
    }
E/MountService( 1980): getVolumeState(/mnt/extsd): state:unmounted
E/MountService( 1980): getVolumeState(/mnt/usbhost): state:unmounted
E/MountService( 1980): getVolumeState(/mnt/sdcard): state:mounted
很明显嘛,在mountService中,state需要变成mounted 没有改变。
直接linux执行mount命令,发现结果仍然是umount。好吧,这块得看android的处理方式了。
首先简单的将返回值强行赋值成mounted。然后手动将U盘挂载到/mnt/usbhost目录下。
D/chen    ( 3578): item  /mnt/usbhost
I/FileManager( 3578):  getNextDir /mnt/usbhost
D/chen    ( 3578): /mnt/usbhost has multi partition
报了一个问题,就是, /mnt/usbhost has multi partition
/mnt/usbhost这个目录,通过串口在里面创建文件夹,然后例如/mnt/usbhost/dsb。mount之后,串口终端就看不到这个了。只能看到mount后,文件夹的内容。但是,android却能够看到/mnt/usbhost/dsb这个文件夹,并且看不到mount后的目录。

在app中加入打印,执行./sbin/busybox命令,执行ls /mnt/usbhost命令。
W/System.err( 3247): java.io.IOException: Error running exec(). Command: [./sbin/busybox] Working Directory: null Environment: null 
I/System.out( 3247): dsb 
代码中加入xbin/su。 修改权限,并执行。结果是app挂了。。。
现在怀疑的点,是 app非root权限,导致终端看到的结果,与app看到的结果不一致。
因此现在的想法是,想办法用软件进行mount,这样权限应该就是普通用户的了。

刚好,android的挂载进程vold,学一下。

init的脚本中,有一个 export EXTERNAL_STORAGE /mnt/sdcard ,这个是内置存储的。那么对于外置存储,有一个SECONDARY_STORAGE,不知道能否使用。听说后面api把它删除了

vold的挂载流程,

NetlinkListener::onDataAvailable之后,传建一个 NetlinkEvent *evt = new NetlinkEvent(); 然后evt->decode(mBuffer, count, mFormat);把消息处理后,通过 netlinkHandler onEvent传递消息,发送给VolumeManager,然后VolumeManager处理消息DirectVolume 》handleBlockEvent-》handleDiskAdded(dp, evt);-》setState(Volume::State_Pending);-》 mVm->getBroadcaster()->sendBroadcast;

上面的过程写简单了,实际上,发送消息分为两次,一次挂载disk,一次是partition。
第一步,uevent消息是:event { 'add', '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda', 'block', '', 8, 0 }
vold进行处理,并且将状态改变成pending。这个时候,挂载的是disk。
Volume usbhost state changing 0 (No-Media) -> 2 (Pending)
Device '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda' event handled by volume usbhost

第二次,uevent:event{ 'add', '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda/sda1', 'block', '', 8, 1 }
 Volume usbhost state changing 2 (Pending) -> 1 (Idle-Unmounted)
 Device '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda/sda1' event handled by volume usbhost
D/MountService( 1759): volume state changed for /mnt/usbhost (removed -> unmounted)
D/MountService( 1759): sendStorageIntent Intent { act=android.intent.action.MEDIA_UNMOUNTED dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
I/Vold    ( 1244): Volume::mountVol: getMountpoint() /mnt/usbhost
I/Vold    ( 1244): /dev/block/vold/8:1 being considered for volume usbhost
D/Vold    ( 1244): Volume usbhost state changing 1 (Idle-Unmounted) -> 3 (Checking)
I/Vold    ( 1244): Exfat::check
D/MountService( 1759): volume state changed for /mnt/usbhost (unmounted -> checking)
D/MountService( 1759): sendStorageIntent Intent { act=android.intent.action.MEDIA_CHECKING dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
I/fsck.exfat( 1244): exfatfsck 0.9.5
I/fsck.exfat( 1244): ERROR: exFAT file system is not found.
I/fsck.exfat( 1244): fsck.exfat terminated by exit(1)
E/Vold    ( 1244): Filesystem check failed (unknown exit code 1)
I/Vold    ( 1244): Fat::check /dev/block/vold/8:1

这里挂载的是Fat::  /dev/block/vold/8:1,这个与 /dev/block/sda1 有啥关系,是同一个节点不?是的,主设备号都是8,次设备号1.
I/Vold    ( 1244): Filesystem check completed OK
D/Vold    ( 1244): Volume usbhost state changing 3 (Checking) -> 4 (Mounted)
I/Vold    ( 1244): Volume::mountVol: getState=4, State_Mounted=4
D/MountService( 1759): volume state changed for /mnt/usbhost (checking -> mounted)

EVENT的消息使用的有,

底层uevent消息:Ascii add@/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda,总长279,不想打印了。下面有一个解析代码,可以作为参考。

 event { 'add', '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda/sda1', 'block', '', 8, 1 }

    while(*msg) {
        if(!strncmp(msg, "ACTION=", 7)) {
            msg += 7;            uevent->action = msg;
        } else if(!strncmp(msg, "DEVPATH=", 8)) {
            msg += 8;            uevent->path = msg;
        } else if(!strncmp(msg, "SUBSYSTEM=", 10)) {
            msg += 10;            uevent->subsystem = msg;
        } else if(!strncmp(msg, "FIRMWARE=", 9)) {
            msg += 9;            uevent->firmware = msg;
        } else if(!strncmp(msg, "MAJOR=", 6)) {
            msg += 6;            uevent->major = atoi(msg);
        } else if(!strncmp(msg, "MINOR=", 6)) {
            msg += 6;            uevent->minor = atoi(msg);
        } else if(!strncmp(msg, "PARTN=", 6)) {
            msg += 6;            uevent->partition_num = atoi(msg);
        } else if(!strncmp(msg, "PARTNAME=", 9)) {
            msg += 9;            uevent->partition_name = msg;
        } else if(!strncmp(msg, "DEVNAME=", 8)) {
            msg += 8;            uevent->device_name = msg;
        }

上层解析:NL param 'DEVPATH=/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host1/target1:0:0/1:0:0:0/block/sda'
NL param 'MAJOR=8'  NL param 'MINOR=0'  NL param 'DEVNAME=sda'  NL param 'DEVTYPE=disk'  NL param 'NPARTS=1',

以及最后一个 evt->getAction();好吧,NetlinkEvent的消息,应该可以伪造了。

查看一下vold的挂载log

D/DirectVolume( 1219): Dv::diskIns - waiting for 1 partitions (mask 0x2)

D/Vold    ( 1219): Volume usbhost state changing 0 (No-Media) -> 2 (Pending)
I/USB3G   ( 1229):  ---- event { 'add', '/devices/platform/sw-ehci.1/usb2/2-1/2-1.1/2-1.1:1.0/host0/target0:0:0/0:0:0:0/block/sda/sda1', 'block', '', 8, 1 }
D/DirectVolume( 1219): Dv:partAdd: part_num = 1, minor = 1
D/DirectVolume( 1219): Dv:partAdd: Got all partitions - ready to rock!
D/Vold    ( 1219): Volume usbhost state changing 2 (Pending) -> 1 (Idle-Unmounted)
D/MountService( 1668): volume state changed for /mnt/usbhost (removed -> unmounted)
D/MountService( 1668): sendStorageIntent Intent { act=android.intent.action.MEDIA_UNMOUNTED dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
D/dalvikvm( 2416): Late-enabling CheckJNI
I/Vold    ( 1219): Volume::mountVol: getMountpoint() /mnt/usbhost
I/Vold    ( 1219): /dev/block/vold/8:1 being considered for volume usbhost
D/Vold    ( 1219): Volume usbhost state changing 1 (Idle-Unmounted) -> 3 (Checking)
I/Vold    ( 1219): Exfat::check
I/ActivityManager( 1668): Start proc com.hiinfo.broadcast for broadcast com.hiinfo.broadcast/.USBReceiver: pid=2416 uid=10062 gids={50062}
D/MountService( 1668): volume state changed for /mnt/usbhost (unmounted -> checking)
D/MountService( 1668): sendStorageIntent Intent { act=android.intent.action.MEDIA_CHECKING dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
I/fsck.exfat( 1219): exfatfsck 0.9.5
I/fsck.exfat( 1219): ERROR: exFAT file system is not found.
I/fsck.exfat( 1219): fsck.exfat terminated by exit(1)
E/Vold    ( 1219): Filesystem check failed (unknown exit code 1)
I/Vold    ( 1219): Fat::check /dev/block/vold/8:1
I/fsck_msdos( 1219): ** /dev/block/vold/8:1
I/fsck_msdos( 1219): Primary/Backup bootblock miscompare
I/fsck_msdos( 1219): Primary:
I/fsck_msdos( 1219): 00 02 08 20 00 02 00 00 00 00 f0 00 00 00 00 00 00 00 00 00 00 21 75 df 00 d0 37 00 00 00 00 00 00 02 00 00 00 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 80 01 29 6f 00 00 00 20 20 20 20 20 20 20 20 20 20 20 46 41 54 33 32 20 20 20 
I/fsck_msdos( 1219): Backup:
I/fsck_msdos( 1219): 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
I/fsck_msdos( 1219): ** Phase 1 - Read FAT (compare skipped)
I/fsck_msdos( 1219): Attempting to allocate 7144 KB for FAT
D/EventBus( 2416): No subscribers registered for event class com.hiinfo.broadcast.BroadcastEvent
D/EventBus( 2416): No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
D/USBReceiver( 2416): onReceive: U盘移除了
D/MediaScannerReceiver( 1830): action: android.intent.action.MEDIA_UNMOUNTED path: /mnt/usbhost
I/fsck_msdos( 1219): ** Phase 2 - Check Cluster Chains
I/ActivityManager( 1668): Start proc dev.dworks.apps.anexplorer.pro for broadcast dev.dworks.apps.anexplorer.pro/dev.dworks.apps.anexplorer.receiver.MountReceiver: pid=2436 uid=10063 gids={50063, 1028, 1015, 3003}
I/fsck_msdos( 1219): ** Phase 3 - Checking Directories
I/fsck_msdos( 1219): ** Phase 4 - Checking for Lost Files
I/fsck_msdos( 1219): 130 files, 1857692 free (1512999 clusters)

I/Vold    ( 1219): Filesystem check completed OK
D/Vold    ( 1219): Volume usbhost state changing 3 (Checking) -> 4 (Mounted)
I/Vold    ( 1219): Volume::mountVol: getState=4, State_Mounted=4
D/MountService( 1668): volume state changed for /mnt/usbhost (checking -> mounted)
W/ResourceType( 1752): No known package when getting value for resource number 0x7f0a00c6
D/MountService( 1668): sendStorageIntent Intent { act=android.intent.action.MEDIA_MOUNTED dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
E/VoldConnector( 1668): NDC Command {5 volume mount /mnt/usbhost} took too long (1289ms)
D/ActivityThread( 2436): Loading provider dev.dworks.apps.anexplorer.pro.explorer: dev.dworks.apps.anexplorer.provider.ExplorerProvider
I/dalvikvm( 2436): Could not find method android.view.Window.setStatusBarColor, referenced from method dev.dworks.apps.anexplorer.setting.SettingsActivity.setUpStatusBar
W/dalvikvm( 2436): VFY: unable to resolve virtual method 13813: Landroid/view/Window;.setStatusBarColor (I)V
D/dalvikvm( 2436): VFY: replacing opcode 0x6e at 0x0012
I/dalvikvm( 2436): Could not find method android.text.Html.fromHtml, referenced from method dev.dworks.apps.anexplorer.misc.Utils.fromHtml
W/dalvikvm( 2436): VFY: unable to resolve static method 13102: Landroid/text/Html;.fromHtml (Ljava/lang/String;I)Landroid/text/Spanned;
D/dalvikvm( 2436): VFY: replacing opcode 0x71 at 0x0007

D/ExternalStorage( 2436): After updating volumes, found 5 active roots

I/dalvikvm( 2436): Could not find method android.os.storage.StorageManager.getStorageVolume, referenced from method dev.dworks.apps.anexplorer.misc.SAFManager.takeCardUriPermission
W/dalvikvm( 2436): VFY: unable to resolve virtual method 1411: Landroid/os/storage/StorageManager;.getStorageVolume (Ljava/io/File;)Landroid/os/storage/StorageVolume;
D/dalvikvm( 2436): VFY: replacing opcode 0x6e at 0x0016
D/ExternalStorage( 2436): After updating volumes, found 5 active roots
D/EventBus( 2416): No subscribers registered for event class com.hiinfo.broadcast.BroadcastEvent
D/EventBus( 2416): No subscribers registered for event class org.greenrobot.eventbus.NoSubscriberEvent
D/USBReceiver( 2416): mountPath = /mnt/usbhost
D/RootsCache( 2436): Updating roots due to change at content://dev.dworks.apps.anexplorer.pro.externalstorage.documents/root
D/MediaScannerReceiver( 1830): action: android.intent.action.MEDIA_MOUNTED path: /mnt/usbhost

mountedVolume::uid 1000,gid 1015,permMask 0x1c2 mPartIdx-1 n 1

重新整理一下,首先是/sys/devices/.....usb...sda这个是设备目录,会发送uevent。然后vold接收到uevent后,进行处理,确定是/mnt/usbhost这个volume来处理这个消息,mknod创建/dev/block/vold/8:0 /dev/block/vold/8:1设备节点 。发送消息给mount service。

MountService回复消息,

 I/MountService( 1671): onEvent:: raw= 605 Volume usbhost /mnt/usbhost state changed from 0 (No-Media) to 2 (Pending) cooked =  605 Volume usbhost /mnt/usbhost state changed from 0 (No-Media) to 2 (Pending)

I/MountService( 1671): onEvent:: raw= 605 Volume usbhost /mnt/usbhost state changed from 2 (Pending) to 1 (Idle-Unmounted) cooked =  605 Volume usbhost /mnt/usbhost state changed from 2 (Pending) to 1 (Idle-Unmounted)
E/MountService( 1671): getVolumeState(/mnt/usbhost): state:removed
D/MountService( 1671): volume state changed for /mnt/usbhost (removed -> unmounted)
D/MountService( 1671): sendStorageIntent Intent { act=android.intent.action.MEDIA_UNMOUNTED dat=file:///mnt/usbhost (has extras) } to UserHandle{-1}
I/MountService( 1671): onEvent:: raw= 630 Volume usbhost /mnt/usbhost disk inserted (8:0) cooked =  630 Volume usbhost /mnt/usbhost disk inserted (8:0)

使得 CommandListener接收到消息。然后调用::VolumeCmd::runCommand进行mount操作。rc = vm->mountVolume(argv[2]);

然后mount /dev/block/vold/8:1 /mnt/usbhost这个路径。

所以,如果想要挂载一个已经在本地的upan,可以尝试一下,仿造下面这条指令。

                if (getState() == Volume::State_Idle) {
                    char msg[255];

                    snprintf(msg, sizeof(msg),
                             "Volume %s %s disk inserted (%d:%d)", getLabel(),
                             getFuseMountpoint(), mDiskMajor, mDiskMinor);
                    mVm->getBroadcaster()->sendBroadcast(ResponseCode::VolumeDiskInserted,
                                                         msg, false);
                }

发送了多条命令,伪造了mount消息,最终发现,还是存在linux能看到,android看不到的情况。。。

二,问题分析

再回忆一下问题:

1:通过vold进程,调用system函数,在mount前 /mnt/usbhost目录下创建一个文件。然后在mount 后的 /mnt/usbhost目录下创建一个文件。android访问不到后面那个文件。创建的挂载目录,无论是mode, owner都是对的,android就是访问不到。相反,vold进程可以访问的到。所以初步判断,问题在java这部分。

2:vold重启后,MountService收不到消息。通过VoldConnector加log后发现,换个地方sendMsg就行了,,,奇怪。。

先解决问题1吧.

问题,和下面这个很像啊

https://zhidao.baidu.com/question/1496007111880305179.html   //最后发现,完全不是一回事。

参考这篇文章继续分析。http://www.tasfa.cn/index.php/2017/05/25/android_file_open_read_write/

https://blog.csdn.net/yachl882828/article/details/53351116

跟踪到native层,libcore_io_Posix.cpp

其实到这一步之后,就不用跟踪了。因为vold是可以看到文件的,所以只是native以上,java那部分看不到。

现在要做的,是把中间的流程看一下,也就是下面这段,由下往上。

W/System.err( 3413): Caused by: libcore.io.ErrnoException: open failed: EACCES (Permission denied)
W/System.err( 3413):    at libcore.io.Posix.open(Native Method)
W/System.err( 3413):    at libcore.io.BlockGuardOs.open(BlockGuardOs.java:110)
W/System.err( 3413):    at java.io.File.createNewFile(File.java:939)

贴上代码createNewFile(File.java:939)   fd = Libcore.os.open(path, O_RDWR | O_CREAT | O_EXCL, 0600);//line 939

然后 BlockGuardOs.open(BlockGuardOs.java:110)

    @Override public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
        BlockGuard.getThreadPolicy().onReadFromDisk();
        if ((mode & O_ACCMODE) != O_RDONLY) {
            BlockGuard.getThreadPolicy().onWriteToDisk();
        }
        return os.open(path, flags, mode);  //line 110
    }

public native FileDescriptor open(String path, int flags, int mode) throws ErrnoException;  //不知道多少line。

往下到native了libcore_io_Posix.cpp

static jobject Posix_open(JNIEnv* env, jobject, jstring javaPath, jint flags, jint mode) {
    ScopedUtfChars path(env, javaPath);
    if (path.c_str() == NULL) {
        return NULL;
    }
    int fd = throwIfMinusOne(env, "open", TEMP_FAILURE_RETRY(open(path.c_str(), flags, mode)));
    return fd != -1 ? jniCreateFileDescriptor(env, fd) : NULL;}

通过mountService调用mConnector.execute("volume", "mkdirs", "/mnt/usbhost/scs_mountService/");是可以在mount之后的usbhost中创建目录的。但这个同样是通过vold进程操作的。

发现java中,根本不能进入这个目录

File file1 = new File("/mnt/usbhost");
            if(file1.canRead()) {
                Slog.d(TAG, "usbHost can read");
            } else {
                Slog.d(TAG, "usbHost can not read"); //很明显,打印的是这个log.
            }

    public boolean canRead() {
        return doAccess(R_OK);
    }

   private boolean doAccess(int mode) {
        try {
            return Libcore.os.access(path, mode);
        } catch (ErrnoException errnoException) {
            return false;
        }
    }

public final class Posix implements Os {
    public native boolean access(String path, int mode) throws ErrnoException;

进入native阶段static jboolean Posix_access(JNIEnv* env, jobject, jstring javaPath, jint mode) {
    ScopedUtfChars path(env, javaPath);
    if (path.c_str() == NULL) {
        ALOGI("scs:: NULL");
        return JNI_FALSE;
    }
    ALOGI("scs:: %s,mode %d",path.c_str(),mode);
    int rc = TEMP_FAILURE_RETRY(access(path.c_str(), mode));
    if (rc == -1) {
        throwErrnoException(env, "access");
    }
    return (rc == 0);
}在Posix里面加了log I/Posix   ( 1983): scs:: /mnt/usbhost,mode 4。这里的mode  4应该代表的是read吧。都走到这一步来了。android怎么判断权限的呢??直接在底层加log查看吧。
在do_filp_open加入log,发现 app与进程,分别访问修改前,修改后的目录,路径没啥问题。
app是在/mnt/usbhost目录下创建aaa.txt。打印的log是,pathname /mnt/usbhost/aaa.txt。这个可以创建成功,但是位置错了。是mount前的目录。
hal层与framework层的cpp文件,创建的是/mnt/usbhost/aa.txt。ps,这个成功过,但是后面都创建失败了。。但至少找对了位置。
结果是,app找的mount之前的目录,cpp找的mount之后的目录。底层输出的log,都是相同的path.
因此判断,是在最后查找mount关系的时候,出现了差异。
在linux系统中,path的构成包括dentry和vfsmount。struct path {    struct vfsmount *mnt;     struct dentry *dentry; };
如果要判断两个path是否相等,return path1->mnt == path2->mnt && path1->dentry == path2->dentry;
猜测是,没有识别到 DCACHE_MOUNTED 这个dir特征,没有执行lookup_mnt找到相应路径。


打印出所有的差异,
dfd,pathname,op->open_flag,op->mode,op->acc_mode,op->intent,flag
vold 访问 S dfd -100,path /mnt/usbhost/aaa.txt,oflg 131650,md 33206,amd 38,it 768,flag 1          //成功
cpp 访问  S dfd -100,path /mnt/usbhost/aaa.txt,oflg 131650,md 33206,amd 38,it 768,flag 1          //成功
app 访问  S dfd -100,path /mnt/usbhost/aaa.txt,oflg 131649,md 33152,amd 34,it 768,flag 1          //失败
app 访问  S dfd -100,path /mnt/usbhost/aaa.txt,oflg 131266,md 33152,amd 38,it 1792,flag 1        //失败
app 访问  S dfd -100,path /mnt/usbhost/aaa.txt,oflg 147456,md 0,amd 36,it 256,flag 3                  //失败 操作类似ls
好的,进入 filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);查看不同
filp->f_flags = op->open_flag;
nd->intent.open.file = filp;
nd->intent.open.flags = open_to_namei_flags(op->open_flag);
nd->intent.open.create_mode = op->mode;error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);
{nd->last_type = LAST_ROOT;nd->flags = flags | LOOKUP_PARENT | LOOKUP_JUMPED;nd->depth = 0;
nd->root.mnt = NULL;set_root(nd);//这里出现了一个current静态全局变量,有点怂。nd->path = nd->root;//当前path,为root的path,这个app与cpp访问方式都一样。
nd->inode = nd->path.dentry->d_inode;//nd的inode初始化为根目录的inode}。
current->total_link_count = 0;
error = link_path_walk(pathname, nd);  //这是一个基本的路径名解析函数,将一个路径名最终转化为一个dentry;
成功返回0,并将分量保存在nd中。{struct dentry *parent = nd->path.dentry;len = hash_name(name, &this.hash);//解析出‘/’或者NULL之前的dir名字。nd->flags &= ~LOOKUP_JUMPED;if (!name[len]) goto last_component;//解析名字完毕。如果没有解析完毕,则从下一个字段开始 name += len; err = walk_component(nd, &next, &this, type, LOOKUP_FOLLOW)///*这个函数执行真正的dentry缓存的查找,在dentry_hashtable中查找@this的名字,找不到则使用inode的inode_operations->lookup中的操作从底层设备载入进来,这个哈希表用父目录的dentry和当前目录名字的hash值也就是this->hash作为键值。而且这个函数还处理目录的权限以及目录是装载点的情况,由于一个目录下可以装载多个文件系统,最新装载的文件系统隐藏以前的装载,若是装载点,则顺着装载点一直查找,直到最上层的装载点也就是当前可以看到的文件系统,当这个函数返回1,则表示这个目录是符号链接,下面进行特殊处理。函数调用成功则 @nd->path 表示this.name这个名字所表示的目录,也是就当前解析成功的目录,然后下一次循环解析下一个部分时候,这个目录就当做父目录在dentry缓存中查找,直至所有的部分全部完成*/  {err = do_lookup(nd, name, path, &inode){err = follow_managed(path, nd->flags){nd->flags |= LOOKUP_JUMPED;
    *inode = path->dentry->d_inode;};就是这个函数了,他的返回值,关系到当前目录是否应该mount了,这里应该有bug.}}}

参照这篇文章https://www.jianshu.com/p/f3f5a33f2c59 

再打印nameidata结构体

struct nameidata {
    struct path path;       /* 当前搜索的目录 path里保存着dentry指针和挂载信息vfsmount */
    struct qstr last;       /* 下一个待处理的component。只有last_type是LAST_NORM时这个字段才有用*/
    struct path root;       /* 保存根目录的信息 */
    struct inode    *inode; /* path.dentry.d_inode */
    unsigned int    flags;  /* 查找相关的标志位 */
    unsigned    seq;        /* 目录项的顺序锁序号 */
    int     last_type;      /* This is one of LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT, or LAST_BIND. */
    unsigned    depth;      /* 解析符号链接过程中的递归深度 */
    char *saved_names[MAX_NESTED_LINKS + 1]; /* 相应递归深度的符号链接的路径 */
};

打印了path的mnt flag。正常的MNT_RELATIME|MNT_NOEXEC|MNT_NODEV|MNT_NOSUID  39
异常的MNT_NOATIME|MNT_NODIRATIME 24
这明显不一样了。可以认为这个时候的nd->path是不一样的。
看一下入参的差异 
open flag
131650 成功     131649  131266  147456失败
open mode
33206 成功     33152失败
open|read dir 成功
S path /mnt/usbhost/,oflg 147456,md 0,amd 36,it 256,flag 67
open|read|write aaa.txt 算是成功。
S path /mnt/usbhost/aaa.txt,oflg 131650,md 33206,amd 38,it 768,flag 65

open|read dir 失败
S path /mnt/usbhost,oflg 147456,md 0,amd 36,it 256,flag 67
S r error:0,nflags:0x1051,name:android,mnt_flags:0x18
S n error:0,nflags:0x51,name:/,(打印的是nd->path.dentry->d_iname)mnt_flags:0x27,lastn:aaa.txt
open|read|write aaa.txt 失败
S path /mnt/usbhost/aaa.txt,oflg 131649,md 33152,amd 34,it 768,flag 65
S path /mnt/usbhost/aaa.txt,oflg 131266,md 33152,amd 38,it 1792,flag 65
S r error:0,nflags:0x1051,name:android,mnt_flags:0x18
S n error:0,nflags:0x51,name:usbhost,(打印的是nd->path.dentry->d_iname)mnt_flags:0x18,lastn:aaa.txt
0x18与0x27的差异,看不出啥。
但是nd->path.dentry->d_iname 一个是usbhost,一个是/。差异就比较大了,看出一个是mount之前的,一个是之后的。
所以问题,就出在link_path_walk这个函数中,里面获取的 nd->path有差异。

再帖一条log
正常的,"S b name:%s,mntr:%s,lastn:%s",nd->path.dentry->d_iname,nd->path.mnt->mnt_root->d_iname,nd->path.mnt->mnt_sb->s_id
"S c err %d,name:%s,mntr:%s,lastn:%s",err,nd->path.dentry->d_iname,nd->path.mnt->mnt_root->d_iname,nd->path.mnt->mnt_sb->s_id
S a name:/mnt/usbhost/aaa.txt
S b name:android,mntr:/,lastn:mmcblk0p9
S c err 0,name:mnt,mntr:/,lastn:mmcblk0p9
S b name:mnt,mntr:/,lastn:mmcblk0p9
S c err 0,name:/,mntr:/,lastn:sda1    //没错,就应该是sda1,entyrflag= DCACHE_OP_HASH|DCACHE_OP_COMPARE |DCACHE_OP_REVALIDATE    0x0004
异常的如下:
S a name:/mnt/usbhost/aaa.txt
S b name:android,mntr:/,lastn:mmcblk0p9
S c err 0,name:mnt,mntr:/,lastn:mmcblk0p9
S b name:mnt,mntr:/,lastn:mmcblk0p9
S c err 0,name:usbhost,mntr:/,lastn:mmcblk0p9    //这个明显没有切换mount.这个时候,dentry-flags = 0x100c0,也就是 DCACHE_REFERENCED|DCACHE_RCUACCESS|DCACHE_MOUNTED。就是说这个时候,其实已经知道了这个目录是mount节点了,可以看到mnt_sb的->s_id。这个不一样。为啥访问有问题呢?感觉问题出在 __lookup_mnt函数了。

这个函数,是通过 mnt与dentry来找挂载点的。网上分析这段函数的很多,我们只需要关注输入输出就行了。
现在打印了log,发现hash(mnt,dentry)有差异,具体点,就是正常和错误的访问时,传入的mnt不一样。导致这个函数结果不同。
再回溯一下,发现这个问题在 error = path_init(dfd, pathname, flags | LOOKUP_PARENT, nd, &base);就出现了。
这个函数,传入的 dfd ,pathname ,flags 都是相同的。nd稍有不同。
再回溯一下,发现current->fs->root就是不一样的。妈蛋,这怎么跟踪啊。
这个current,定义在linux-3.4/arch/arm/include/asm/current.h,#define current (get_current()) return current_thread_info()->task;
再回溯一遍,看一下最后的nd->path->mnt传入的过程中,有没有变化过。在link_path_walk加个log试试。
这个时候的猜测,是由于namespace隔离的问题导致的。每个进程的task namespace不一样。
看了一下zygote进程和init进程,发现mount 之后,只有init进程获得到了mount消息,而zygote并没有。
分析代码发现,这是由于
static bool initZygote()
{
    /* zygote goes into its own process group */
    setpgid(0,0);
    // See storage config details at http://source.android.com/tech/storage/
    // Create private mount namespace shared by all children
    //此处调用是为了创建新的命名空间,所以在此之后的进程,就看不到之前的挂载点了.
    //CLONE_NEWNS表示mount类型的命名空间,
    //这样子进程和父进程的mount命名空间不再是同一个了
    if (unshare(CLONE_NEWNS) == -1) {
从Android init.rc中,可以看到Android解决这个问题的方法,就是将根目录 / 设置为shared属性.

在访问u盘(/mnt/usbhost)的时候,将父目录mnt的属性,设置为shared。这样usbhost在挂载后,各个namespace都能看到。类似于mount --bind。原理是在mnt_hashtable中再加入了一组mnt,两个namespace处于mnt_hashtable的不同位置,但是存储着同一个mnt。

你可能感兴趣的:(linux_drv,android)