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

标签: 文件系统  存储系统  智能手表  u盘  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盘的操作,也保证了系统文件的安全性。

全文完。


版权声明:本文为zc314401480原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zc314401480/article/details/78707266

智能推荐

Rtthread学习笔记(十三)RT-Thread Studio开启硬件看门狗Watchdog

一、开启硬件看门狗Watchdog 1、配置RT-Thread Settings 2、开启stm32f1xx_hal_conf.h中的宏定义 3.使用RT接口函数初始化硬件看门狗...

TYVJ 4864 天天去哪吃 || 清北学堂金秋杯大奖赛

题目描述: 记录一下i这个值上次出现的位置在哪里,就是pre...

java反编译

jvm 把Boolean类型的值flag当做int类型处理。​​​ Foo.java: 由 class 文件生成 jasm 文件:java -jar asmtools.jar jdis Foo.class > Foo.jasm  修改jasm文件: 执行反编译: java -jar jd-gui-1.6.6.jar File 打开Foo.class文件:b修改为2 重新执行java...

【学习笔记】03-v-html的学习和示例

v-html的认识和使用 示例: 显示结果: 注意:v-html是有复制的...

Java实现在线考试系统(系统介绍)

1.和现在有的考试系统有以下几种优势: a.和现在有的系统比较起来,本系统有科目、章节、老师、学生、班级等信息的管理,还有批阅试卷查看已批阅试卷等。传统的考试系统划分并不细,业务功能简单。 b.和学校的考试系统还有外面的考试系统比较起来,本系统是B/S结构,学校的考试系统一般为C/S结构,性能方面不如B/S结构,并且C/S接口需要安装客户端,客户端压力很大,我的系统只需要电脑具有浏览器,在同一局域...

猜你喜欢

计算机视觉--多视几何初步尝试

基础矩阵的原理 K和K’分别是两个相机的参数矩阵。p和p’是X在平面π的坐标表示。所以可以得出 具体计算过程 代码: #!/usr/bin/env python coding: utf-8 from PIL import Image from numpy import * from pylab import * import numpy as np from imp ...

java初学者怎么学习才可以快速入门

java初学者怎么学习才可以快速入门 一、了解JAVA 我们要知道:Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言。 Java之父:詹姆斯·高斯林 1.1 java的三个体系 Java SE(Java Platform Standard Edition)。Java SE 以前称为 J2SE。它允许开发和部署在桌面、服务器、嵌入式环境...

字段属性之主键&增删改查&自增长&唯一键约束

字段属性之主键&自增长&唯一键约束 主键 主键:primary key 主要的键 一张表中只有一个字段可以使用对应的键,用来唯一的约束该字段里面的数据,不能重复,这种称之为主键 一张表只能最多一个主键 增加主键 SQL操作中有多种方式增加主键大体分为三种 1.在创建表的时候直接在字段之后跟primary key关键字(主键本身不允许为空) 优点:非常直接:缺点:只能使用一个字段作为...

linux下 基于libmad的socket多用户mp3音频在线播放服务器

在众多大神的帮助下,这个在线播放流媒体服务器终于完成啦。。。。 这个mp3流媒体服务器设计的思路是,服务器程序server用多线程实现和多个客户端的通信(这是必然的),然后发送给客户端当前的音频列表公客户端选择,之后根据k客户端的选择给多个客户端传输相应mp3文件的数据,同时,客户端进行实时地音频解码并播放。 关于libmad开源mp3音频解码库的使用,见上一篇博客吧。。。。 在服务器程序这一端,...

Nginx

Nginx Nginx简介: Nginx是一个高性能的http和反向代理服务器,特点是有内存少,并发能力强,事实上Nginx的并发能力确实在同类型网页服务器中表现较好, Nginx用作web服务器:Nginx可以作为静态页面的web服务器,同时还支持CGI语言,但不支持java,java程序只能通过Tomcat配合完成。Nginx专为性能优化而开发,性能是其最重要的考量,实现上非常注重效率,能经受...