通过设立FatFS隐藏分区,实现系统文件和用户文件的隔离

嘛。。这是一个关于个人使用FatFS文件系统的一点小的经验

我知道大家都会百度和谷歌,关于文件系统有什么用,文件系统怎么移植上自己的平台,看看资料也就懂了,在这里不再详述(打字太慢一分钟50-60字懒得写)。本系列默认已经可以将设备模拟成u盘,并且已经通过修改diskio.c,可以实现ff.c中的各项功能(不能实现的自行面壁)。FatFS项目官网 http://elm-chan.org/fsw/ff/00index_e.html,在这里面可以下载到最新的版本以及查看各API的说明。

在开发过程中为了将存储空间的一大部分给用户存储数据,常常需要把存储器模拟成USB大容量存储设备(比如智能手表)。但是我们也有很多素材文件以及数据库之类的文件,不希望被用户看见和修改(比如widget用到的图片或者字库之类)。

通常我们可以想到可以实现划分和挂载两个分区,第一个分区用来存储(zuo)用户(wei)可见(you)的文件(pan),第二个分区用来存储系统文件

如图为16G的iphone的分区。可见有1.1G的空间用来存储系统数据,此部分普通用户是接触不到的。

废话少说,本系列比起网上当前各种抄来抄去的中文资料,主要是要实现这么几个创新点

普通用户只能看见第一个分区,并且在电脑上也只能接触到第一个分区。(第二个分区根本接触不到,在设备管理器里面也看不到,用U盘量产工具也看不到)

开发者可以看见两个分区,在电脑上都能挂载,方便开发者进行素材的替换、字库的更新等操作。

需要注意的是,本文适合存储芯片不可拆卸的情况,比如外部flash或者EMMC,想用SD卡实现这个那就别看了(没必要模拟成usb大容量存储设备,用户一拆卡,放在读卡器里面,你还模拟成usb大容量存储设备个毛线)。。

那么,我们开始了。

一.分区

首先,我们需要先分成两个区。

uint8_t work[512];                  /* Work area (larger is better for process time) */
DWORD plist[] = {95, 5, 0, 0};     /* Divide drive into two partitions */
res = f_fdisk(0, plist, work);      /* Divide physical drive 0 */

 第一个分区占95%,第二个分区占5%。经过f_mkfs掉两个分区之后,我们将设备模拟成u盘,可以在设备管理器->磁盘管理中,里面看见下面的情景:


(额,这个图是90%对10%的了。。凑合看)

第一个分区是可以正常访问的,但是第二个不能挂载。为什么捏?

原来, 虽然fatfs是支持多个分区的,但windows认为可移动闪存设备只有一个分区(注意,移动硬盘除外)。多余的分区不能挂载。

偷懒的人可能会想,哈哈,这下好,正好实现了第二个分区隐藏的功能。

全文完!

……

且慢,没完,这时候就怕遇见爱折腾的用户。

你是否想过,此时可以打开cmd,在diskpart工具里面clean掉整个磁盘;也可以动用U盘量产工具,挂载出来第二个分区。。总之,这时,第二个分区的文件有覆灭和被改动的风险!!

永远不要低估用户的好奇心!!!

二.让用户只能看见第一个分区

想解决这个问题,就要消除用户在磁盘管理器里面看见这个分区的可能性。

也就是说,在通过USB访问这个磁盘的时候,只访问和第一个分区相关的block。那么,我们只需要知道第一个分区所占的最后一个block即可。

可以使用以下函数:

FRESULT f_getlastblock_vol1(BYTE pdrv, DWORD percentage, DWORD *last_block)
{
    UINT n, sz_cyl, tot_cyl, p_cyl, e_hd;	
    DWORD sz_disk, sz_part;
    UINT retry = 0; //重试次数,确保磁盘在获取容量之前已经初始化
    
    while (retry < 2) 
    {
        if (disk_ioctl(pdrv, GET_SECTOR_COUNT, &sz_disk) != FR_OK) 
        {
            retry ++;
            disk_initialize(pdrv);
        } 
        else 
        {			
            break;
        }
    }
    if (retry >= 2) 
    {
        return FR_NOT_READY;
    }
    
    /* Determine the CHS without any consideration of the drive geometry */
    for (n = 16; n < 256 && sz_disk / n / 63 > 1024; n *= 2);
    if (n == 256) n--;
    e_hd = n - 1;
    sz_cyl = 63 * n;
    tot_cyl = sz_disk / sz_cyl;
    p_cyl = (DWORD)tot_cyl * percentage / 100;	/* Number of cylinders */
    sz_part = (DWORD)sz_cyl * p_cyl;
    *last_block = sz_part - 1; 
    return FR_OK;
}
这样,就可以获得第一个分区所占的最后一个block的位置。在将设备模拟成usb大容量存储设备的时候,只需要访问到第*last_block个block就可以啦。
(别问我怎么写访问哪些block的函数。。。这个每个平台都不一样。一般改改“获取flash容量”的hal函数的返回值对应的函数就好了)

这样下来,挂载在电脑里面的景象是这样的:


三.让开发者能看见两个分区

想解决这个问题,需要看看USB协议。

为什么windows只认u盘的第一个分区,但是可以认移动硬盘的多个分区呢?

原因是windows驱动程序在挂载设备的时候会发一个SCSI指令来查询这是什么设备,发的是INQUIRY命令,返回的状态位有一个Removable Media Bit( RMB),如果是1就认为这是u盘,如果是0就认为这是移动硬盘,区别对待。

(抱歉,有RMB真的是可以为所欲为的)

所以,我们的核心方法就是欺骗windows,让windows认为这是个移动硬盘。

翻一翻你的程序,你会在USB协议相关的地方找到类似这个的玩意:

ATTR_RWDATA_IN_NONCACHED_RAM uint8_t INQUIRE_DATA[] = {
    0x00,
    0x80, //这里非常重要!!!!要认出两个分区的话就改成0x00,一个分区的话就改成0x80
    0x00,
    0x01,
    0x1f, /*length*/
    0x00,
    0x00,
    0x00,
    'M',  /*Vendor Identification*/
    'E',
    'D',
    'I',
    'A',
    'T',
    'E',
    'K',
    ' ', /*Product Identification*/
    'F',
    'L',
    'A',
    'S',
    'H',
    ' ',
    'D',
    'I',
    'S',
    'K',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ',
    ' ', /*Product Revision Level*/
    ' ',
    ' ',
    ' '
};


程序中0x80的地方的最高位就是Removable Media Bit,改成0即可欺骗windows认为这是个移动硬盘。

可以写点程序进行这一位的切换:

USB_DISK_TYPE disk_type = FLASH_DISK;//切换这个设备为u盘(只能在电脑上识别第一个分区)和移动硬盘(所有分区都能识别到)
/* switch udisk type */
typedef enum
{
    EXTERNAL_HARDDRIVE = (uint8_t)0x00,
    FLASH_DISK = (uint8_t)0x80
}USB_DISK_TYPE;
void USBSetDiskType(USB_DISK_TYPE usb_disk_type)
{
    if(usb_disk_type == EXTERNAL_HARDDRIVE)
    {
        INQUIRE_DATA[1] = 0x00;
        disk_type = EXTERNAL_HARDDRIVE;
    }
    if(usb_disk_type == FLASH_DISK)
    {
        INQUIRE_DATA[1] = 0x80;
        disk_type = FLASH_DISK;
    }
}
USB_DISK_TYPE USBGetDiskType(USB_DISK_TYPE usb_disk_type)
{
    return usb_disk_type;
}

改后,记得把访问block的范围改成直到获取全磁盘(两个分区都包含)的最后一个block,确保两个磁盘被正确挂载。

我们可以写个开发者模式or普通用户模式的切换函数。

static kal_bool UsbmsReadCapacityModeSdc(uint32_t *max_lba, uint32_t *sec_len)
{
    /*
     * *max_lba  is the capacity of store the Udisk, the unit is block.
     * *sec_len  is the number of bytes about one block.
     */
    *sec_len = FF_MIN_SS;
    USB_DISK_TYPE usb_disk_type = USBGetDiskType(disk_type); //得知当前的磁盘到底按照u盘(只挂载第一分区)来挂载还是按照移动硬盘来挂载
 
    //cap  is the SD/eMMC card capacity, the unit is bytes.
    uint64_t cap;
    hal_sd_get_capacity(HAL_SD_PORT_0, &cap);
    
    if(usb_disk_type == EXTERNAL_HARDDRIVE)
    {       
        *max_lba = cap / *sec_len - 1;
    }
    else if(usb_disk_type == FLASH_DISK)
    {
        f_getlastblock_vol1(0, 95, (DWORD *)max_lba); //这里的百分比要按照volume1占总磁盘体积的实际百分比来赋值
    }

    return KAL_TRUE;
}

两个分区都挂载好的磁盘是这样的。这样开发者就可以开心的在电脑端修改系统文件啦。

这样,我们就实现了两种模式的切换~达到了类似IOS的封闭效果,既保证了用户对U盘的操作,也保证了系统文件的安全性。

全文完。


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值