Make SD cards available over web workflow

This changes storage.mount() to require that a mount point exist
on the parent file system.

A bug in background tasks is also fixed where the function
parameter is cleared on pending callbacks during "reset".

Disk usage is shown on the directory listing and changes based on
the mounted file system. Writable is also loaded per-directory.

Fixes #8108. Fixes #8690. Fixes #8107.
This commit is contained in:
Scott Shawcroft 2023-11-30 15:48:49 -08:00
parent 9beefdbe8d
commit 25e862d110
No known key found for this signature in database
GPG key ID: 0DFD512649C052DA
26 changed files with 581 additions and 356 deletions

View file

@ -165,6 +165,13 @@ Returns a JSON representation of the directory.
* `403 Forbidden` - No `CIRCUITPY_WEB_API_PASSWORD` set
* `404 Not Found` - Missing directory
Returns directory information:
* `free`: Count of free blocks on the disk holding this directory.
* `total`: Total blocks that make up the disk holding this directory.
* `block_size`: Size of a block in bytes.
* `writable`: True when CircuitPython and the web workflow can write to the disk. USB may claim a disk instead.
* `files`: Array of objects. One for each file.
Returns information about each file in the directory:
* `name` - File name. No trailing `/` on directory names
@ -179,14 +186,20 @@ curl -v -u :passw0rd -H "Accept: application/json" -L --location-trusted http://
```
```json
[
{
"name": "world.txt",
"directory": false,
"modified_ns": 946934328000000000,
"file_size": 12
}
]
{
"free": 451623,
"total": 973344,
"block_size": 32768,
"writable": true,
"files": [
{
"name": "world.txt",
"directory": false,
"modified_ns": 946934328000000000,
"file_size": 12
}
]
}
```
##### PUT
@ -373,10 +386,10 @@ curl -v -L http://circuitpython.local/cp/devices.json
Returns information about the attached disk(s). A list of objects, one per disk.
* `root`: Filesystem path to the root of the disk.
* `free`: Count of free bytes on the disk.
* `free`: Count of free blocks on the disk.
* `total`: Total blocks that make up the disk.
* `block_size`: Size of a block in bytes.
* `writable`: True when CircuitPython and the web workflow can write to the disk. USB may claim a disk instead.
* `total`: Total bytes that make up the disk.
Example:
```sh
@ -405,7 +418,7 @@ This is an authenticated endpoint in both modes.
Returns information about the device.
* `web_api_version`: Between `1` and `3`. This versions the rest of the API and new versions may not be backwards compatible. See below for more info.
* `web_api_version`: Between `1` and `4`. This versions the rest of the API and new versions may not be backwards compatible. See below for more info.
* `version`: CircuitPython build version.
* `build_date`: CircuitPython build date.
* `board_name`: Human readable name of the board.
@ -467,3 +480,5 @@ Only one WebSocket at a time is supported.
* `1` - Initial version.
* `2` - Added `/cp/diskinfo.json`.
* `3` - Changed `/cp/diskinfo.json` to return a list in preparation for multi-disk support.
* `4` - Changed directory json to an object with additional data. File list is under `files` and is
the same as the old format.

View file

@ -48,6 +48,8 @@
#define MP_BLOCKDEV_FLAG_USB_WRITABLE (0x0010)
// Bit set when the above flag is checked before opening a file for write.
#define MP_BLOCKDEV_FLAG_CONCURRENT_WRITE_PROTECTED (0x0020)
// Bit set when something has claimed the right to mutate the blockdev.
#define MP_BLOCKDEV_FLAG_LOCKED (0x0040)
// constants for block protocol ioctl
#define MP_BLOCKDEV_IOCTL_INIT (1)

View file

@ -30,12 +30,41 @@
#include "py/mperrno.h"
#include "extmod/vfs.h"
#if CIRCUITPY_SDCARDIO
#include "shared-bindings/sdcardio/SDCard.h"
#endif
#if CIRCUITPY_SDIOIO
#include "shared-bindings/sdioio/SDCard.h"
#endif
#if MICROPY_VFS
void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) {
mp_load_method(bdev, MP_QSTR_readblocks, self->readblocks);
mp_load_method_maybe(bdev, MP_QSTR_writeblocks, self->writeblocks);
mp_load_method_maybe(bdev, MP_QSTR_ioctl, self->u.ioctl);
// CIRCUITPY-CHANGE: Support native SD cards.
#if CIRCUITPY_SDCARDIO
if (mp_obj_get_type(bdev) == &sdcardio_SDCard_type) {
self->flags |= MP_BLOCKDEV_FLAG_NATIVE | MP_BLOCKDEV_FLAG_HAVE_IOCTL;
self->readblocks[0] = mp_const_none;
self->readblocks[1] = bdev;
self->readblocks[2] = (mp_obj_t)sdcardio_sdcard_readblocks; // native version
self->writeblocks[0] = mp_const_none;
self->writeblocks[1] = bdev;
self->writeblocks[2] = (mp_obj_t)sdcardio_sdcard_writeblocks; // native version
self->u.ioctl[0] = mp_const_none;
self->u.ioctl[1] = bdev;
self->u.ioctl[2] = (mp_obj_t)sdcardio_sdcard_ioctl; // native version
}
#endif
#if CIRCUITPY_SDIOIO
if (mp_obj_get_type(bdev) == &sdioio_SDCard_type) {
// TODO: Enable native blockdev for SDIO too.
}
#endif
if (self->u.ioctl[0] != MP_OBJ_NULL) {
// Device supports new block protocol, so indicate it
self->flags |= MP_BLOCKDEV_FLAG_HAVE_IOCTL;
@ -48,8 +77,8 @@ void mp_vfs_blockdev_init(mp_vfs_blockdev_t *self, mp_obj_t bdev) {
int mp_vfs_blockdev_read(mp_vfs_blockdev_t *self, size_t block_num, size_t num_blocks, uint8_t *buf) {
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
mp_uint_t (*f)(uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2];
return f(buf, block_num, num_blocks);
mp_uint_t (*f)(mp_obj_t self, uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->readblocks[2];
return f(self->readblocks[1], buf, block_num, num_blocks);
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, buf};
self->readblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
@ -80,8 +109,8 @@ int mp_vfs_blockdev_write(mp_vfs_blockdev_t *self, size_t block_num, size_t num_
}
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
mp_uint_t (*f)(const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2];
return f(buf, block_num, num_blocks);
mp_uint_t (*f)(mp_obj_t self, const uint8_t *, uint32_t, uint32_t) = (void *)(uintptr_t)self->writeblocks[2];
return f(self->writeblocks[1], buf, block_num, num_blocks);
} else {
mp_obj_array_t ar = {{&mp_type_bytearray}, BYTEARRAY_TYPECODE, 0, num_blocks *self->block_size, (void *)buf};
self->writeblocks[2] = MP_OBJ_NEW_SMALL_INT(block_num);
@ -112,6 +141,15 @@ int mp_vfs_blockdev_write_ext(mp_vfs_blockdev_t *self, size_t block_num, size_t
mp_obj_t mp_vfs_blockdev_ioctl(mp_vfs_blockdev_t *self, uintptr_t cmd, uintptr_t arg) {
if (self->flags & MP_BLOCKDEV_FLAG_HAVE_IOCTL) {
if (self->flags & MP_BLOCKDEV_FLAG_NATIVE) {
size_t out_value;
bool (*f)(mp_obj_t self, uint32_t, uint32_t, size_t *) = (void *)(uintptr_t)self->u.ioctl[2];
bool b = f(self->u.ioctl[1], cmd, arg, &out_value);
if (!b) {
return mp_const_none;
}
return MP_OBJ_NEW_SMALL_INT(out_value);
}
// New protocol with ioctl
self->u.ioctl[2] = MP_OBJ_NEW_SMALL_INT(cmd);
self->u.ioctl[3] = MP_OBJ_NEW_SMALL_INT(arg);

View file

@ -34,6 +34,7 @@ typedef struct _fs_user_mount_t {
mp_obj_base_t base;
mp_vfs_blockdev_t blockdev;
FATFS fatfs;
int8_t lock_count;
} fs_user_mount_t;
extern const byte fresult_to_errno_table[20];

View file

@ -1378,6 +1378,10 @@ msgstr ""
msgid "Missing jmp_pin. %q[%u] jumps on pin"
msgstr ""
#: shared-module/storage/__init__.c
msgid "Mount point missing. Create first (maybe via USB)"
msgstr ""
#: shared-bindings/busio/UART.c shared-bindings/displayio/Group.c
msgid "Must be a %q subclass."
msgstr ""

View file

@ -36,7 +36,8 @@
void board_init(void) {
mp_import_stat_t stat_b = mp_import_stat("boot.py");
if (stat_b != MP_IMPORT_STAT_FILE) {
FATFS *fatfs = filesystem_circuitpy();
fs_user_mount_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fatfs;
FIL fs;
UINT char_written = 0;
const byte buffer[] = "#Serial port upload mode\nimport storage\nstorage.remount(\"/\", False)\nstorage.disable_usb_drive()\n";
@ -44,7 +45,7 @@ void board_init(void) {
f_open(fatfs, &fs, "/boot.py", FA_WRITE | FA_CREATE_ALWAYS);
f_write(&fs, buffer, sizeof(buffer) - 1, &char_written);
f_close(&fs);
// Delete code.Py, use main.py
// Delete code.py, use main.py
mp_import_stat_t stat_c = mp_import_stat("code.py");
if (stat_c == MP_IMPORT_STAT_FILE) {
f_unlink(fatfs, "/code.py");

View file

@ -60,7 +60,8 @@ STATIC uint32_t _cache_lba = 0xffffffff;
#define SECSIZE(fs) ((fs)->ssize)
#endif // FF_MAX_SS == FF_MIN_SS
STATIC DWORD fatfs_bytes(void) {
FATFS *fatfs = filesystem_circuitpy();
fs_user_mount_t *fs_mount = filesystem_circuitpy();
FATFS *fatfs = &fs_mount->fatfs;
return (fatfs->csize * SECSIZE(fatfs)) * (fatfs->n_fatent - 2);
}
STATIC bool storage_extended = true;

View file

@ -131,7 +131,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdcardio_sdcard_deinit_obj, sdcardio_sdcard_deinit);
//|
//| :return: None"""
STATIC mp_obj_t sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
STATIC mp_obj_t _sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
uint32_t start_block = mp_obj_get_int(start_block_in);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE);
@ -143,7 +143,7 @@ STATIC mp_obj_t sdcardio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_bloc
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_readblocks_obj, sdcardio_sdcard_readblocks);
MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_readblocks_obj, _sdcardio_sdcard_readblocks);
//| def sync(self) -> None:
//| """Ensure all blocks written are actually committed to the SD card
@ -171,7 +171,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdcardio_sdcard_sync_obj, sdcardio_sdcard_sync);
//| :return: None"""
//|
STATIC mp_obj_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
STATIC mp_obj_t _sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
uint32_t start_block = mp_obj_get_int(start_block_in);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
@ -182,7 +182,7 @@ STATIC mp_obj_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_blo
}
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_writeblocks_obj, sdcardio_sdcard_writeblocks);
MP_DEFINE_CONST_FUN_OBJ_3(sdcardio_sdcard_writeblocks_obj, _sdcardio_sdcard_writeblocks);
STATIC const mp_rom_map_elem_t sdcardio_sdcard_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_count), MP_ROM_PTR(&sdcardio_sdcard_count_obj) },

View file

@ -27,4 +27,19 @@
#pragma once
#include "shared-module/sdcardio/SDCard.h"
extern const mp_obj_type_t sdcardio_SDCard_type;
void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *spi, const mcu_pin_obj_t *cs, int baudrate);
void common_hal_sdcardio_sdcard_deinit(sdcardio_sdcard_obj_t *self);
void common_hal_sdcardio_sdcard_check_for_deinit(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_get_blockcount(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf);
int common_hal_sdcardio_sdcard_sync(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf);
// Used by native vfs blockdev.
mp_uint_t sdcardio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t buflen);
mp_uint_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t buflen);
bool sdcardio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value);

View file

@ -164,7 +164,7 @@ MP_DEFINE_CONST_FUN_OBJ_1(sdioio_sdcard_count_obj, sdioio_sdcard_count);
//| :param ~circuitpython_typing.WriteableBuffer buf: The buffer to write into. Length must be multiple of 512.
//|
//| :return: None"""
STATIC mp_obj_t sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
STATIC mp_obj_t _sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
uint32_t start_block = mp_obj_get_int(start_block_in);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_WRITE);
@ -176,7 +176,7 @@ STATIC mp_obj_t sdioio_sdcard_readblocks(mp_obj_t self_in, mp_obj_t start_block_
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, sdioio_sdcard_readblocks);
MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, _sdioio_sdcard_readblocks);
//| def writeblocks(self, start_block: int, buf: ReadableBuffer) -> None:
//| """Write one or more blocks to the card
@ -185,7 +185,7 @@ MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_readblocks_obj, sdioio_sdcard_readblocks
//| :param ~circuitpython_typing.ReadableBuffer buf: The buffer to read from. Length must be multiple of 512.
//|
//| :return: None"""
STATIC mp_obj_t sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
STATIC mp_obj_t _sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block_in, mp_obj_t buf_in) {
uint32_t start_block = mp_obj_get_int(start_block_in);
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf_in, &bufinfo, MP_BUFFER_READ);
@ -197,7 +197,7 @@ STATIC mp_obj_t sdioio_sdcard_writeblocks(mp_obj_t self_in, mp_obj_t start_block
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_writeblocks_obj, sdioio_sdcard_writeblocks);
MP_DEFINE_CONST_FUN_OBJ_3(sdioio_sdcard_writeblocks_obj, _sdioio_sdcard_writeblocks);
//| frequency: int
//| """The actual SDIO bus frequency. This may not match the frequency

View file

@ -62,8 +62,12 @@ STATIC bool open_file(const char *name, file_arg *active_file) {
return false;
}
#else
FATFS *fs = filesystem_circuitpy();
FRESULT result = f_open(fs, active_file, name, FA_READ);
fs_user_mount_t *fs_mount = filesystem_circuitpy();
if (fs_mount == NULL) {
return false;
}
FATFS *fatfs = &fs_mount->fatfs;
FRESULT result = f_open(fatfs, active_file, name, FA_READ);
return result == FR_OK;
#endif
}

View file

@ -26,6 +26,8 @@
// This implementation largely follows the structure of adafruit_sdcard.py
#include "extmod/vfs.h"
#include "shared-bindings/busio/SPI.h"
#include "shared-bindings/digitalio/DigitalInOut.h"
#include "shared-bindings/sdcardio/SDCard.h"
@ -358,23 +360,24 @@ STATIC int readinto(sdcardio_sdcard_obj_t *self, void *buf, size_t size) {
return 0;
}
STATIC int readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) {
uint32_t nblocks = buf->len / 512;
mp_uint_t sdcardio_sdcard_readblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t nblocks) {
sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (!lock_and_configure_bus(self)) {
return MP_EAGAIN;
}
int r = 0;
size_t buflen = 512 * nblocks;
if (nblocks == 1) {
// Use CMD17 to read a single block
return block_cmd(self, 17, start_block, buf->buf, buf->len, true, true);
r = block_cmd(self, 17, start_block, buf, buflen, true, true);
} else {
// Use CMD18 to read multiple blocks
int r = block_cmd(self, 18, start_block, NULL, 0, true, true);
if (r < 0) {
return r;
}
uint8_t *ptr = buf->buf;
while (nblocks--) {
r = block_cmd(self, 18, start_block, NULL, 0, true, true);
uint8_t *ptr = buf;
while (nblocks-- && r >= 0) {
r = readinto(self, ptr, 512);
if (r < 0) {
return r;
if (r != 0) {
break;
}
ptr += 512;
}
@ -387,12 +390,13 @@ STATIC int readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buff
uint8_t single_byte;
common_hal_busio_spi_read(self->bus, &single_byte, 1, 0xff);
if (single_byte & 0x80) {
return r;
break;
}
r = single_byte;
}
}
return 0;
extraclock_and_unlock_bus(self);
return r;
}
int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) {
@ -401,10 +405,7 @@ int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t
mp_raise_ValueError(MP_ERROR_TEXT("Buffer length must be a multiple of 512"));
}
lock_and_configure_bus(self);
int r = readblocks(self, start_block, buf);
extraclock_and_unlock_bus(self);
return r;
return sdcardio_sdcard_readblocks(MP_OBJ_FROM_PTR(self), buf->buf, start_block, buf->len / 512);
}
STATIC int _write(sdcardio_sdcard_obj_t *self, uint8_t token, void *buf, size_t size) {
@ -452,17 +453,20 @@ STATIC int _write(sdcardio_sdcard_obj_t *self, uint8_t token, void *buf, size_t
return 0;
}
STATIC int writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf) {
mp_uint_t sdcardio_sdcard_writeblocks(mp_obj_t self_in, uint8_t *buf, uint32_t start_block, uint32_t nblocks) {
sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in);
common_hal_sdcardio_check_for_deinit(self);
uint32_t nblocks = buf->len / 512;
DEBUG_PRINT("cmd25? %d next_block %d start_block %d\n", self->in_cmd25, self->next_block, start_block);
if (!lock_and_configure_bus(self)) {
return MP_EAGAIN;
}
if (!self->in_cmd25 || start_block != self->next_block) {
DEBUG_PRINT("entering CMD25 at %d\n", (int)start_block);
// Use CMD25 to write multiple block
int r = block_cmd(self, 25, start_block, NULL, 0, true, true);
if (r < 0) {
extraclock_and_unlock_bus(self);
return r;
}
self->in_cmd25 = true;
@ -470,17 +474,19 @@ STATIC int writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buf
self->next_block = start_block;
uint8_t *ptr = buf->buf;
uint8_t *ptr = buf;
while (nblocks--) {
int r = _write(self, TOKEN_CMD25, ptr, 512);
if (r < 0) {
self->in_cmd25 = false;
extraclock_and_unlock_bus(self);
return r;
}
self->next_block++;
ptr += 512;
}
extraclock_and_unlock_bus(self);
return 0;
}
@ -498,7 +504,29 @@ int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t
mp_raise_ValueError(MP_ERROR_TEXT("Buffer length must be a multiple of 512"));
}
lock_and_configure_bus(self);
int r = writeblocks(self, start_block, buf);
int r = sdcardio_sdcard_writeblocks(MP_OBJ_FROM_PTR(self), buf->buf, start_block, buf->len / 512);
extraclock_and_unlock_bus(self);
return r;
}
bool sdcardio_sdcard_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value) {
sdcardio_sdcard_obj_t *self = MP_OBJ_TO_PTR(self_in);
*out_value = 0;
switch (cmd) {
case MP_BLOCKDEV_IOCTL_DEINIT:
common_hal_sdcardio_sdcard_sync(self);
break; // TODO properly
case MP_BLOCKDEV_IOCTL_SYNC:
common_hal_sdcardio_sdcard_sync(self);
break;
case MP_BLOCKDEV_IOCTL_BLOCK_COUNT:
*out_value = common_hal_sdcardio_sdcard_get_blockcount(self);
break;
case MP_BLOCKDEV_IOCTL_BLOCK_SIZE:
*out_value = 512;
break;
default:
return false;
}
return true;
}

View file

@ -44,11 +44,3 @@ typedef struct {
uint32_t next_block;
bool in_cmd25;
} sdcardio_sdcard_obj_t;
void common_hal_sdcardio_sdcard_construct(sdcardio_sdcard_obj_t *self, busio_spi_obj_t *spi, const mcu_pin_obj_t *cs, int baudrate);
void common_hal_sdcardio_sdcard_deinit(sdcardio_sdcard_obj_t *self);
void common_hal_sdcardio_sdcard_check_for_deinit(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_get_blockcount(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_readblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf);
int common_hal_sdcardio_sdcard_sync(sdcardio_sdcard_obj_t *self);
int common_hal_sdcardio_sdcard_writeblocks(sdcardio_sdcard_obj_t *self, uint32_t start_block, mp_buffer_info_t *buf);

View file

@ -177,15 +177,16 @@ void common_hal_storage_mount(mp_obj_t vfs_obj, const char *mount_path, bool rea
args[0] = readonly ? mp_const_true : mp_const_false;
args[1] = mp_const_false; // Don't make the file system automatically when mounting.
// Check that there's no file or directory with the same name as the mount point.
// Check that there's file or directory with the same name as the mount point.
// But it's ok to mount '/' in any case.
if (strcmp(vfs->str, "/") != 0) {
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
common_hal_os_stat(mount_path);
nlr_pop();
// Something with the same name exists.
mp_raise_OSError(MP_EEXIST);
} else {
// Something with the same name doesn't exists.
mp_raise_RuntimeError(MP_ERROR_TEXT("Mount point missing. Create first (maybe via USB)"));
}
}

View file

@ -24,8 +24,7 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H
#define MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H
#pragma once
#include <stdbool.h>
@ -45,6 +44,16 @@ void filesystem_set_concurrent_write_protection(fs_user_mount_t *vfs, bool concu
bool filesystem_is_writable_by_python(fs_user_mount_t *vfs);
bool filesystem_is_writable_by_usb(fs_user_mount_t *vfs);
FATFS *filesystem_circuitpy(void);
fs_user_mount_t *filesystem_circuitpy(void);
fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount);
bool filesystem_native_fatfs(fs_user_mount_t *fs_mount);
#endif // MICROPY_INCLUDED_SUPERVISOR_FILESYSTEM_H
// We have two levels of locking. filesystem_* calls grab a shared blockdev lock to allow
// CircuitPython's fatfs code edit the blocks. blockdev_* class grab a lock to mutate blocks
// directly, excluding any filesystem_* locks.
bool filesystem_lock(fs_user_mount_t *fs_mount);
void filesystem_unlock(fs_user_mount_t *fs_mount);
bool blockdev_lock(fs_user_mount_t *fs_mount);
void blockdev_unlock(fs_user_mount_t *fs_mount);

View file

@ -126,13 +126,21 @@ void background_callback_reset() {
background_callback_t *cb = (background_callback_t *)callback_head;
while (cb) {
background_callback_t *next = cb->next;
if (gc_ptr_on_heap((void *)cb)) {
*previous_next = cb;
previous_next = &cb->next;
cb->next = NULL;
new_tail = cb;
cb->next = NULL;
// Unlink any callbacks that are allocated on the python heap or if they
// reference data on the python heap. The python heap will be disappear
// soon after this.
if (gc_ptr_on_heap((void *)cb) || gc_ptr_on_heap(cb->data)) {
cb->prev = NULL; // Used to indicate a callback isn't queued.
} else {
memset(cb, 0, sizeof(*cb));
// Set .next of the previous callback.
*previous_next = cb;
// Set our .next for the next callback.
previous_next = &cb->next;
// Set our prev to the last callback.
cb->prev = new_tail;
// Now we're the tail of the list.
new_tail = cb;
}
cb = next;
}

View file

@ -151,6 +151,7 @@ STATIC uint64_t truncate_time(uint64_t input_time, DWORD *fattime) {
// Used by read and write.
STATIC FIL active_file;
STATIC fs_user_mount_t *active_mount;
STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) {
struct read_command *command = (struct read_command *)raw_buf;
size_t header_size = sizeof(struct read_command);
@ -170,11 +171,19 @@ STATIC uint8_t _process_read(const uint8_t *raw_buf, size_t command_len) {
return THIS_COMMAND;
}
char *path = (char *)((uint8_t *)command) + header_size;
path[command->path_length] = '\0';
char *full_path = (char *)((uint8_t *)command) + header_size;
full_path[command->path_length] = '\0';
FATFS *fs = filesystem_circuitpy();
FRESULT result = f_open(fs, &active_file, path, FA_READ);
const char *mount_path;
active_mount = filesystem_for_path(full_path, &mount_path);
if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) {
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0);
return ANY_COMMAND;
}
FATFS *fs = &active_mount->fatfs;
FRESULT result = f_open(fs, &active_file, mount_path, FA_READ);
if (result != FR_OK) {
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, response_size, NULL, 0);
@ -250,22 +259,6 @@ STATIC uint8_t _process_read_pacing(const uint8_t *raw_buf, size_t command_len)
STATIC size_t total_write_length;
STATIC uint64_t _truncated_time;
// Returns true if usb is active and replies with an error if so. If not, it grabs
// the USB mass storage lock and returns false. Make sure to release the lock with
// usb_msc_unlock() when the transaction is complete.
STATIC bool _usb_active(void *response, size_t response_size) {
// Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done.
#if CIRCUITPY_USB && CIRCUITPY_USB_MSC
if (storage_usb_enabled() && !usb_msc_lock()) {
// Status is always the second byte of the response.
((uint8_t *)response)[1] = STATUS_ERROR_READONLY;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)response, response_size, NULL, 0);
return true;
}
#endif
return false;
}
STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) {
struct write_command *command = (struct write_command *)raw_buf;
size_t header_size = sizeof(struct write_command);
@ -284,23 +277,31 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) {
}
total_write_length = command->total_length;
char *path = (char *)command->path;
path[command->path_length] = '\0';
if (_usb_active(&response, sizeof(struct write_pacing))) {
char *full_path = (char *)command->path;
full_path[command->path_length] = '\0';
const char *mount_path;
active_mount = filesystem_for_path(full_path, &mount_path);
if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) {
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0);
return ANY_COMMAND;
}
if (!filesystem_lock(active_mount)) {
response.status = STATUS_ERROR_READONLY;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0);
return ANY_COMMAND;
}
FATFS *fs = filesystem_circuitpy();
FATFS *fs = &active_mount->fatfs;
DWORD fattime;
_truncated_time = truncate_time(command->modification_time, &fattime);
override_fattime(fattime);
FRESULT result = f_open(fs, &active_file, path, FA_WRITE | FA_OPEN_ALWAYS);
FRESULT result = f_open(fs, &active_file, mount_path, FA_WRITE | FA_OPEN_ALWAYS);
if (result != FR_OK) {
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(active_mount);
override_fattime(0);
return ANY_COMMAND;
}
@ -315,9 +316,7 @@ STATIC uint8_t _process_write(const uint8_t *raw_buf, size_t command_len) {
f_truncate(&active_file);
f_close(&active_file);
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(active_mount);
}
response.offset = offset;
response.free_space = chunk_size;
@ -342,9 +341,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) {
// TODO: throw away any more packets of path.
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(active_mount);
override_fattime(0);
return ANY_COMMAND;
}
@ -360,9 +357,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) {
// TODO: throw away any more packets of path.
response.status = STATUS_ERROR;
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct write_pacing), NULL, 0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(active_mount);
override_fattime(0);
return ANY_COMMAND;
}
@ -377,9 +372,7 @@ STATIC uint8_t _process_write_data(const uint8_t *raw_buf, size_t command_len) {
f_truncate(&active_file);
f_close(&active_file);
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(active_mount);
// Don't reload until everything is written out of the packet buffer.
common_hal_bleio_packet_buffer_flush(&_transfer_packet_buffer);
return ANY_COMMAND;
@ -399,29 +392,19 @@ STATIC uint8_t _process_delete(const uint8_t *raw_buf, size_t command_len) {
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct delete_status), NULL, 0);
return ANY_COMMAND;
}
if (_usb_active(&response, sizeof(struct delete_status))) {
return ANY_COMMAND;
}
// We need to receive another packet to have the full path.
if (command_len < header_size + command->path_length) {
return THIS_COMMAND;
}
FATFS *fs = filesystem_circuitpy();
char *path = (char *)((uint8_t *)command) + header_size;
path[command->path_length] = '\0';
FILINFO file;
FRESULT result = f_stat(fs, path, &file);
if (result == FR_OK) {
if ((file.fattrib & AM_DIR) != 0) {
result = supervisor_workflow_delete_directory_contents(fs, path);
}
if (result == FR_OK) {
result = f_unlink(fs, path);
}
char *full_path = (char *)((uint8_t *)command) + header_size;
full_path[command->path_length] = '\0';
FRESULT result = supervisor_workflow_delete_recursive(full_path);
if (result == FR_WRITE_PROTECTED) {
response.status = STATUS_ERROR_READONLY;
}
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
if (result != FR_OK) {
response.status = STATUS_ERROR;
}
@ -456,25 +439,16 @@ STATIC uint8_t _process_mkdir(const uint8_t *raw_buf, size_t command_len) {
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct mkdir_status), NULL, 0);
return ANY_COMMAND;
}
if (_usb_active(&response, sizeof(struct mkdir_status))) {
return ANY_COMMAND;
}
// We need to receive another packet to have the full path.
if (command_len < header_size + command->path_length) {
return THIS_COMMAND;
}
FATFS *fs = filesystem_circuitpy();
char *path = (char *)command->path;
_terminate_path(path, command->path_length);
char *full_path = (char *)command->path;
_terminate_path(full_path, command->path_length);
DWORD fattime;
response.truncated_time = truncate_time(command->modification_time, &fattime);
override_fattime(fattime);
FRESULT result = supervisor_workflow_mkdir_parents(fs, path);
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
FRESULT result = supervisor_workflow_mkdir(fattime, full_path);
if (result != FR_OK) {
response.status = STATUS_ERROR;
}
@ -520,12 +494,21 @@ STATIC uint8_t _process_listdir(uint8_t *raw_buf, size_t command_len) {
return THIS_COMMAND;
}
FATFS *fs = filesystem_circuitpy();
char *path = (char *)&command->path;
_terminate_path(path, command->path_length);
// mp_printf(&mp_plat_print, "list %s\n", path);
char *full_path = (char *)&command->path;
_terminate_path(full_path, command->path_length);
const char *mount_path;
active_mount = filesystem_for_path(full_path, &mount_path);
if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) {
entry->command = LISTDIR_ENTRY;
entry->status = STATUS_ERROR_NO_FILE;
send_listdir_entry_header(entry, max_packet_size);
return ANY_COMMAND;
}
FATFS *fs = &active_mount->fatfs;
FF_DIR dir;
FRESULT res = f_opendir(fs, &dir, path);
FRESULT res = f_opendir(fs, &dir, mount_path);
entry->command = LISTDIR_ENTRY;
entry->status = STATUS_OK;
@ -601,27 +584,21 @@ STATIC uint8_t _process_move(const uint8_t *raw_buf, size_t command_len) {
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0);
return ANY_COMMAND;
}
if (_usb_active(&response, sizeof(struct move_status))) {
return ANY_COMMAND;
}
// We need to receive another packet to have the full path.
if (command_len < header_size + total_path_length) {
return THIS_COMMAND;
}
FATFS *fs = filesystem_circuitpy();
char *old_path = (char *)command->paths;
old_path[command->old_path_length] = '\0';
char *new_path = old_path + command->old_path_length + 1;
new_path[command->new_path_length] = '\0';
// mp_printf(&mp_plat_print, "move %s to %s\n", old_path, new_path);
FRESULT result = f_rename(fs, old_path, new_path);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
if (result != FR_OK) {
FRESULT result = supervisor_workflow_move(old_path, new_path);
if (result == FR_WRITE_PROTECTED) {
response.status = STATUS_ERROR_READONLY;
} else if (result != FR_OK) {
response.status = STATUS_ERROR;
}
common_hal_bleio_packet_buffer_write(&_transfer_packet_buffer, (const uint8_t *)&response, sizeof(struct move_status), NULL, 0);

View file

@ -221,9 +221,56 @@ bool filesystem_present(void) {
return _mp_vfs.len > 0;
}
FATFS *filesystem_circuitpy(void) {
fs_user_mount_t *filesystem_circuitpy(void) {
if (!filesystem_present()) {
return NULL;
}
return &_internal_vfs.fatfs;
return &_internal_vfs;
}
fs_user_mount_t *filesystem_for_path(const char *path_in, const char **path_under_mount) {
mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path_in, path_under_mount);
if (vfs == MP_VFS_NONE) {
return NULL;
}
fs_user_mount_t *fs_mount;
*path_under_mount = path_in;
if (vfs == MP_VFS_ROOT) {
fs_mount = filesystem_circuitpy();
} else {
fs_mount = MP_OBJ_TO_PTR(vfs->obj);
*path_under_mount += strlen(vfs->str);
}
return fs_mount;
}
bool filesystem_native_fatfs(fs_user_mount_t *fs_mount) {
return fs_mount->base.type == &mp_fat_vfs_type && (fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) != 0;
}
bool filesystem_lock(fs_user_mount_t *fs_mount) {
if (fs_mount->lock_count == 0 && !blockdev_lock(fs_mount)) {
return false;
}
fs_mount->lock_count += 1;
return true;
}
void filesystem_unlock(fs_user_mount_t *fs_mount) {
fs_mount->lock_count -= 1;
if (fs_mount->lock_count == 0) {
blockdev_unlock(fs_mount);
}
}
bool blockdev_lock(fs_user_mount_t *fs_mount) {
if ((fs_mount->blockdev.flags & MP_BLOCKDEV_FLAG_LOCKED) != 0) {
return false;
}
fs_mount->blockdev.flags |= MP_BLOCKDEV_FLAG_LOCKED;
return true;
}
void blockdev_unlock(fs_user_mount_t *fs_mount) {
fs_mount->blockdev.flags &= ~MP_BLOCKDEV_FLAG_LOCKED;
}

View file

@ -87,7 +87,7 @@ static void build_partition(uint8_t *buf, int boot, int type, uint32_t start_blo
buf[15] = num_blocks >> 24;
}
static mp_uint_t flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
static mp_uint_t flash_read_blocks(mp_obj_t self, uint8_t *dest, uint32_t block_num, uint32_t num_blocks) {
if (block_num == 0) {
// fake the MBR so we can decide on our own partition table
@ -117,7 +117,7 @@ static mp_uint_t flash_read_blocks(uint8_t *dest, uint32_t block_num, uint32_t n
static volatile bool filesystem_dirty = false;
static mp_uint_t flash_write_blocks(const uint8_t *src, uint32_t block_num, uint32_t num_blocks) {
static mp_uint_t flash_write_blocks(mp_obj_t self, const uint8_t *src, uint32_t block_num, uint32_t num_blocks) {
if (block_num == 0) {
if (num_blocks > 1) {
return 1; // error
@ -150,7 +150,7 @@ void PLACE_IN_ITCM(supervisor_flash_flush)(void) {
STATIC mp_obj_t supervisor_flash_obj_readblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_WRITE);
mp_uint_t ret = flash_read_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE);
mp_uint_t ret = flash_read_blocks(self, bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE);
return MP_OBJ_NEW_SMALL_INT(ret);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_readblocks_obj, supervisor_flash_obj_readblocks);
@ -158,13 +158,15 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_readblocks_obj, supervisor
STATIC mp_obj_t supervisor_flash_obj_writeblocks(mp_obj_t self, mp_obj_t block_num, mp_obj_t buf) {
mp_buffer_info_t bufinfo;
mp_get_buffer_raise(buf, &bufinfo, MP_BUFFER_READ);
mp_uint_t ret = flash_write_blocks(bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE);
mp_uint_t ret = flash_write_blocks(self, bufinfo.buf, mp_obj_get_int(block_num), bufinfo.len / FILESYSTEM_BLOCK_SIZE);
return MP_OBJ_NEW_SMALL_INT(ret);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(supervisor_flash_obj_writeblocks_obj, supervisor_flash_obj_writeblocks);
static bool flash_ioctl(size_t cmd, mp_int_t *out_value) {
*out_value = 0;
STATIC bool flash_ioctl(mp_obj_t self_in, size_t cmd, size_t arg, mp_int_t *out_value) {
if (out_value != NULL) {
*out_value = 0;
}
switch (cmd) {
case MP_BLOCKDEV_IOCTL_INIT:
supervisor_flash_init();
@ -189,8 +191,9 @@ static bool flash_ioctl(size_t cmd, mp_int_t *out_value) {
STATIC mp_obj_t supervisor_flash_obj_ioctl(mp_obj_t self, mp_obj_t cmd_in, mp_obj_t arg_in) {
mp_int_t cmd = mp_obj_get_int(cmd_in);
mp_int_t arg = mp_obj_get_int(arg_in);
mp_int_t out_value;
if (flash_ioctl(cmd, &out_value)) {
if (flash_ioctl(self, cmd, arg, &out_value)) {
return MP_OBJ_NEW_SMALL_INT(out_value);
}
return mp_const_none;

View file

@ -41,58 +41,7 @@
#define MSC_FLASH_BLOCK_SIZE 512
static bool ejected[1] = {true};
// Lock to track if something else is using the filesystem when USB is plugged in. If so, the drive
// will be made available once the lock is released.
static bool _usb_msc_lock = false;
static bool _usb_connected_while_locked = false;
STATIC void _usb_msc_uneject(void) {
for (uint8_t i = 0; i < sizeof(ejected); i++) {
ejected[i] = false;
}
}
void usb_msc_mount(void) {
// Reset the ejection tracking every time we're plugged into USB. This allows for us to battery
// power the device, eject, unplug and plug it back in to get the drive.
if (_usb_msc_lock) {
_usb_connected_while_locked = true;
return;
}
_usb_msc_uneject();
_usb_connected_while_locked = false;
}
void usb_msc_umount(void) {
}
bool usb_msc_ejected(void) {
bool all_ejected = true;
for (uint8_t i = 0; i < sizeof(ejected); i++) {
all_ejected &= ejected[i];
}
return all_ejected;
}
bool usb_msc_lock(void) {
if ((storage_usb_enabled() && !usb_msc_ejected()) || _usb_msc_lock) {
return false;
}
_usb_msc_lock = true;
return true;
}
void usb_msc_unlock(void) {
if (!_usb_msc_lock) {
// Mismatched unlock.
return;
}
if (_usb_connected_while_locked) {
_usb_msc_uneject();
}
_usb_msc_lock = false;
}
static bool locked[1] = {false};
// The root FS is always at the end of the list.
static fs_user_mount_t *get_vfs(int lun) {
@ -111,6 +60,36 @@ static fs_user_mount_t *get_vfs(int lun) {
return current_mount->obj;
}
STATIC void _usb_msc_uneject(void) {
for (uint8_t i = 0; i < sizeof(ejected); i++) {
ejected[i] = false;
locked[i] = false;
}
}
void usb_msc_mount(void) {
_usb_msc_uneject();
}
void usb_msc_umount(void) {
for (uint8_t i = 0; i < sizeof(ejected); i++) {
fs_user_mount_t *vfs = get_vfs(i + 1);
if (vfs == NULL) {
continue;
}
blockdev_unlock(vfs);
locked[i] = false;
}
}
bool usb_msc_ejected(void) {
bool all_ejected = true;
for (uint8_t i = 0; i < sizeof(ejected); i++) {
all_ejected &= ejected[i];
}
return all_ejected;
}
// Callback invoked when received an SCSI command not in built-in list below
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, TEST_UNIT_READY, START_STOP_UNIT, MODE_SENSE6, REQUEST_SENSE
// - READ10 and WRITE10 have their own callbacks
@ -164,6 +143,11 @@ bool tud_msc_is_writable_cb(uint8_t lun) {
if (vfs->blockdev.writeblocks[0] == MP_OBJ_NULL || !filesystem_is_writable_by_usb(vfs)) {
return false;
}
// Lock the blockdev once we say we're writable.
if (!locked[lun] && !blockdev_lock(vfs)) {
return false;
}
locked[lun] = true;
return true;
}
@ -267,7 +251,9 @@ bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, boo
if (disk_ioctl(current_mount, CTRL_SYNC, NULL) != RES_OK) {
return false;
} else {
blockdev_unlock(current_mount);
ejected[lun] = true;
locked[lun] = false;
}
} else {
// We can only load if it hasn't been ejected.

View file

@ -20,5 +20,6 @@
<label>Upload progress:<progress value="0"></progress></label>
<hr>
+📁&nbsp;<input type="text" id="name"><button type="submit" id="mkdir">Create Directory</button>
<label>Disk usage:<span id="usage"></span></label>
<script src="/directory.js" defer=true></script>
</body></html>

View file

@ -4,7 +4,28 @@ let dirs = document.getElementById("dirs");
var url_base = window.location;
var current_path;
var editable = undefined;
// From: https://stackoverflow.com/questions/10420352/converting-file-size-in-bytes-to-human-readable-string
function humanFileSize(bytes) {
const thresh = 1000;
if (Math.abs(bytes) < thresh) {
return bytes + ' B';
}
const units = ['kB', 'MB', 'GB', 'TB'];
let u = -1;
const r = 10;
do {
bytes /= thresh;
++u;
} while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1);
return bytes.toFixed(1) + ' ' + units[u];
}
function compareValues(a, b) {
if (a.directory == b.directory && a.name.toLowerCase() === b.name.toLowerCase()) {
@ -42,21 +63,18 @@ async function refresh_list() {
var path = document.querySelector("#path");
path.textContent = current_path;
var template = document.querySelector('#row');
var disk_usage = document.querySelector('#usage');
if (editable === undefined) {
const status = await fetch(new URL("/fs/", url_base),
{
method: "OPTIONS",
credentials: "include"
}
);
editable = status.headers.get("Access-Control-Allow-Methods").includes("DELETE");
new_directory_name.disabled = !editable;
set_upload_enabled(editable);
if (!editable) {
let usbwarning = document.querySelector("#usbwarning");
usbwarning.style.display = "block";
}
let used = humanFileSize((data.total - data.free) * data.block_size);
let total = humanFileSize(data.total * data.block_size);
disk_usage.textContent = `${used} out of ${total}`;
let editable = data.writable;
new_directory_name.disabled = !editable;
set_upload_enabled(editable);
if (!editable) {
let usbwarning = document.querySelector("#usbwarning");
usbwarning.style.display = "block";
}
if (current_path != "/") {
@ -74,9 +92,9 @@ async function refresh_list() {
new_children.push(clone);
}
data.sort(compareValues);
data.files.sort(compareValues);
for (const f of data) {
for (const f of data.files) {
// Clone the new row and insert it into the table
var clone = template.content.cloneNode(true);
var td = clone.querySelectorAll("td");
@ -106,7 +124,7 @@ async function refresh_list() {
text_file = true;
}
td[0].textContent = icon;
td[1].textContent = f.file_size;
td[1].textContent = humanFileSize(f.file_size);
var path = clone.querySelector("a.path");
path.href = file_path;
path.textContent = f.name;

View file

@ -525,17 +525,6 @@ static bool _origin_ok(_request *request) {
return false;
}
STATIC bool _usb_active(void) {
// Check to see if USB has already been mounted. If not, then we "eject" from USB until we're done.
#if CIRCUITPY_USB && CIRCUITPY_USB_MSC
if (storage_usb_enabled() && !usb_msc_lock()) {
return true;
}
#endif
return false;
}
static const char *OK_JSON = "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\nContent-Type: application/json\r\n";
static void _cors_header(socketpool_socket_obj_t *socket, _request *request) {
@ -574,13 +563,7 @@ static void _reply_access_control(socketpool_socket_obj_t *socket, _request *req
"Content-Length: 0\r\n",
"Access-Control-Expose-Headers: Access-Control-Allow-Methods\r\n",
"Access-Control-Allow-Headers: X-Timestamp, X-Destination, Content-Type, Authorization\r\n",
"Access-Control-Allow-Methods:GET, OPTIONS", NULL);
if (!_usb_active()) {
_send_str(socket, ", PUT, DELETE, MOVE");
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
}
"Access-Control-Allow-Methods:GET, OPTIONS, PUT, DELETE, MOVE", NULL);
_send_str(socket, "\r\n");
_cors_header(socket, request);
_send_final_str(socket, "\r\n");
@ -687,17 +670,47 @@ static void _reply_redirect(socketpool_socket_obj_t *socket, _request *request,
}
#endif
static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, FF_DIR *dir, const char *request_path, const char *path) {
static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, FF_DIR *dir, const char *request_path, const char *path) {
FILINFO file_info;
char *fn = file_info.fname;
FRESULT res = f_readdir(dir, &file_info);
if (res != FR_OK) {
_reply_missing(socket, request);
return;
}
socketpool_socket_send(socket, (const uint8_t *)OK_JSON, strlen(OK_JSON));
_cors_header(socket, request);
_send_str(socket, "\r\n");
mp_print_t _socket_print = {socket, _print_chunk};
_send_chunk(socket, "[");
// Send mount info.
DWORD free_clusters = 0;
FATFS *fatfs = &fs_mount->fatfs;
f_getfree(fatfs, &free_clusters);
size_t ssize;
#if FF_MAX_SS != FF_MIN_SS
ssize = fatfs->ssize;
#else
ssize = FF_MIN_SS;
#endif
uint32_t cluster_size = fatfs->csize * ssize;
uint32_t total_clusters = fatfs->n_fatent - 2;
const char *writable = "false";
if (filesystem_is_writable_by_python(fs_mount)) {
writable = "true";
}
mp_printf(&_socket_print,
"{\"free\": %u, "
"\"total\": %u, "
"\"block_size\": %u, "
"\"writable\": %s, ", free_clusters, total_clusters, cluster_size, writable);
// Send file list
_send_chunk(socket, "\"files\": [");
bool first = true;
FILINFO file_info;
char *fn = file_info.fname;
FRESULT res = f_readdir(dir, &file_info);
while (res == FR_OK && fn[0] != 0) {
if (!first) {
_send_chunk(socket, ",");
@ -733,7 +746,7 @@ static void _reply_directory_json(socketpool_socket_obj_t *socket, _request *req
first = false;
res = f_readdir(dir, &file_info);
}
_send_chunk(socket, "]");
_send_chunk(socket, "]}");
_send_chunk(socket, "");
}
@ -851,7 +864,7 @@ static void _reply_with_version_json(socketpool_socket_obj_t *socket, _request *
_update_encoded_ip();
// Note: this leverages the fact that C concats consecutive string literals together.
mp_printf(&_socket_print,
"{\"web_api_version\": 3, "
"{\"web_api_version\": 4, "
"\"version\": \"" MICROPY_GIT_TAG "\", "
"\"build_date\": \"" MICROPY_BUILD_DATE "\", "
"\"board_name\": \"%s\", "
@ -880,27 +893,46 @@ static void _reply_with_diskinfo_json(socketpool_socket_obj_t *socket, _request
_cors_header(socket, request);
_send_str(socket, "\r\n");
mp_print_t _socket_print = {socket, _print_chunk};
_send_chunk(socket, "[");
DWORD free_clusters;
FATFS *fs = filesystem_circuitpy();
FRESULT blk_result = f_getfree(fs, &free_clusters);
uint16_t block_size;
if (blk_result == FR_OK) {
disk_ioctl(fs, GET_SECTOR_SIZE, &block_size);
mp_vfs_mount_t *vfs = MP_STATE_VM(vfs_mount_table);
size_t i = 0;
while (vfs != NULL) {
if (i > 0) {
_send_chunk(socket, ",");
}
fs_user_mount_t *fs = MP_OBJ_TO_PTR(vfs->obj);
// Skip non-fat and non-native block file systems.
if (fs->base.type != &mp_fat_vfs_type || (fs->blockdev.flags & MP_BLOCKDEV_FLAG_NATIVE) == 0) {
vfs = vfs->next;
continue;
}
DWORD free_clusters = 0;
FATFS *fatfs = &fs->fatfs;
f_getfree(fatfs, &free_clusters);
size_t ssize;
#if FF_MAX_SS != FF_MIN_SS
ssize = fatfs->ssize;
#else
ssize = FF_MIN_SS;
#endif
size_t block_size = fatfs->csize * ssize;
size_t total_size = fatfs->n_fatent - 2;
const char *writable = "false";
if (filesystem_is_writable_by_python(fs)) {
writable = "true";
}
mp_printf(&_socket_print,
"{\"root\": \"%s\", "
"\"free\": %u, "
"\"total\": %u, "
"\"block_size\": %u, "
"\"writable\": %s}", vfs->str, free_clusters, total_size, block_size, writable);
i++;
vfs = vfs->next;
}
uint16_t total_size = fs->n_fatent - 2;
const char *writable = "false";
if (!_usb_active()) {
writable = "true";
}
mp_printf(&_socket_print,
"[{\"root\": \"/\", "
"\"free\": %d, "
"\"block_size\": %d, "
"\"writable\": %s, "
"\"total\": %d}]", free_clusters * block_size, block_size, writable, total_size * block_size);
_send_chunk(socket, "]");
// Empty chunk signals the end of the response.
_send_chunk(socket, "");
@ -937,10 +969,10 @@ STATIC void _discard_incoming(socketpool_socket_obj_t *socket, size_t amount) {
}
}
static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, FATFS *fs, const TCHAR *path) {
static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *request, fs_user_mount_t *fs_mount, const TCHAR *path) {
FIL active_file;
if (_usb_active()) {
if (!filesystem_lock(fs_mount)) {
_discard_incoming(socket, request->content_length);
_reply_conflict(socket, request);
return;
@ -951,6 +983,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
override_fattime(fattime);
}
FATFS *fs = &fs_mount->fatfs;
FRESULT result = f_open(fs, &active_file, path, FA_WRITE);
bool new_file = false;
size_t old_length = 0;
@ -963,18 +996,14 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
if (result == FR_NO_PATH) {
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(fs_mount);
_discard_incoming(socket, request->content_length);
_reply_missing(socket, request);
return;
}
if (result != FR_OK) {
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(fs_mount);
_discard_incoming(socket, request->content_length);
_reply_server_error(socket, request);
return;
@ -994,9 +1023,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
f_unlink(fs, path);
}
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(fs_mount);
// Too large.
if (request->expect) {
_reply_expectation_failed(socket, request);
@ -1034,9 +1061,7 @@ static void _write_file_and_reply(socketpool_socket_obj_t *socket, _request *req
}
f_close(&active_file);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
filesystem_unlock(fs_mount);
override_fattime(0);
if (error) {
@ -1164,8 +1189,28 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
_decode_percents(request->path);
char *path = request->path + 3;
const char *path_out = NULL;
size_t pathlen = strlen(path);
FATFS *fs = filesystem_circuitpy();
mp_vfs_mount_t *vfs = mp_vfs_lookup_path(path, &path_out);
if (vfs == MP_VFS_NONE) {
_reply_missing(socket, request);
return false;
}
fs_user_mount_t *fs_mount;
if (vfs == MP_VFS_ROOT) {
fs_mount = filesystem_circuitpy();
} else {
fs_mount = MP_OBJ_TO_PTR(vfs->obj);
// Skip non-fat and non-native block file systems.
if (!filesystem_native_fatfs(fs_mount)) {
_reply_missing(socket, request);
return false;
}
path += strlen(vfs->str);
pathlen = strlen(path);
}
FATFS *fs = &fs_mount->fatfs;
// Trailing / is a directory.
bool directory = false;
if (path[pathlen - 1] == '/') {
@ -1178,25 +1223,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
// Delete is almost identical for files and directories so share the
// implementation.
if (strcasecmp(request->method, "DELETE") == 0) {
if (_usb_active()) {
_reply_conflict(socket, request);
return false;
}
FILINFO file;
FRESULT result = f_stat(fs, path, &file);
if (result == FR_OK) {
if ((file.fattrib & AM_DIR) != 0) {
result = supervisor_workflow_delete_directory_contents(fs, path);
}
if (result == FR_OK) {
result = f_unlink(fs, path);
}
}
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
FRESULT result = supervisor_workflow_delete_recursive(path);
if (result == FR_NO_PATH || result == FR_NO_FILE) {
_reply_missing(socket, request);
} else if (result != FR_OK) {
@ -1206,11 +1233,6 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
return true;
}
} else if (strcasecmp(request->method, "MOVE") == 0) {
if (_usb_active()) {
_reply_conflict(socket, request);
return false;
}
_decode_percents(request->destination);
char *destination = request->destination + 3;
size_t destinationlen = strlen(destination);
@ -1218,11 +1240,10 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
destination[destinationlen - 1] = '\0';
}
FRESULT result = f_rename(fs, path, destination);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
if (result == FR_EXIST) { // File exists and won't be overwritten.
FRESULT result = supervisor_workflow_move(path, destination);
if (result == FR_WRITE_PROTECTED) {
_reply_conflict(socket, request);
} else if (result == FR_EXIST) { // File exists and won't be overwritten.
_reply_precondition_failed(socket, request);
} else if (result == FR_NO_PATH || result == FR_NO_FILE) { // Missing higher directories or target file.
_reply_missing(socket, request);
@ -1245,7 +1266,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
return false;
}
if (request->json) {
_reply_directory_json(socket, request, &dir, request->path, path);
_reply_directory_json(socket, request, fs_mount, &dir, request->path, path);
} else if (pathlen == 1) {
_REPLY_STATIC(socket, request, directory_html);
} else {
@ -1254,22 +1275,14 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
f_closedir(&dir);
} else if (strcasecmp(request->method, "PUT") == 0) {
if (_usb_active()) {
_reply_conflict(socket, request);
return false;
}
DWORD fattime = 0;
if (request->timestamp_ms > 0) {
DWORD fattime;
truncate_time(request->timestamp_ms * 1000000, &fattime);
override_fattime(fattime);
}
FRESULT result = supervisor_workflow_mkdir_parents(fs, path);
override_fattime(0);
#if CIRCUITPY_USB_MSC
usb_msc_unlock();
#endif
if (result == FR_EXIST) {
FRESULT result = supervisor_workflow_mkdir_parents(fattime, path);
if (result == FR_WRITE_PROTECTED) {
_reply_conflict(socket, request);
} else if (result == FR_EXIST) {
_reply_no_content(socket, request);
} else if (result == FR_NO_PATH) {
_reply_missing(socket, request);
@ -1293,7 +1306,7 @@ static bool _reply(socketpool_socket_obj_t *socket, _request *request) {
f_close(&active_file);
} else if (strcasecmp(request->method, "PUT") == 0) {
_write_file_and_reply(socket, request, fs, path);
_write_file_and_reply(socket, request, fs_mount, path);
return true;
}
}

View file

@ -29,6 +29,8 @@
#include "py/mpstate.h"
#include "py/stackctrl.h"
#include "supervisor/background_callback.h"
#include "supervisor/fatfs.h"
#include "supervisor/filesystem.h"
#include "supervisor/workflow.h"
#include "supervisor/serial.h"
#include "supervisor/shared/workflow.h"
@ -118,24 +120,65 @@ void supervisor_workflow_start(void) {
#endif
}
FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path) {
FRESULT supervisor_workflow_move(const char *old_path, const char *new_path) {
const char *old_mount_path;
const char *new_mount_path;
fs_user_mount_t *active_mount = filesystem_for_path(old_path, &old_mount_path);
fs_user_mount_t *new_mount = filesystem_for_path(new_path, &new_mount_path);
if (active_mount == NULL || new_mount == NULL || active_mount != new_mount || !filesystem_native_fatfs(active_mount)) {
return FR_NO_PATH;
}
if (!filesystem_lock(active_mount)) {
return FR_WRITE_PROTECTED;
}
FATFS *fs = &active_mount->fatfs;
FRESULT result = f_rename(fs, old_path, new_path);
filesystem_unlock(active_mount);
return result;
}
FRESULT supervisor_workflow_mkdir(DWORD fattime, const char *full_path) {
const char *mount_path;
fs_user_mount_t *active_mount = filesystem_for_path(full_path, &mount_path);
if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) {
return FR_NO_PATH;
}
if (!filesystem_lock(active_mount)) {
return FR_WRITE_PROTECTED;
}
FATFS *fs = &active_mount->fatfs;
override_fattime(fattime);
FRESULT result = f_mkdir(fs, mount_path);
override_fattime(0);
filesystem_unlock(active_mount);
return result;
}
FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path) {
override_fattime(fattime);
FRESULT result = FR_OK;
// Make parent directories.
for (size_t j = 1; j < strlen(path); j++) {
if (path[j] == '/') {
path[j] = '\0';
result = f_mkdir(fs, path);
result = supervisor_workflow_mkdir(fattime, path);
path[j] = '/';
if (result != FR_OK && result != FR_EXIST) {
return result;
break;
}
}
}
// Make the target directory.
return f_mkdir(fs, path);
if (result != FR_OK && result != FR_EXIST) {
result = supervisor_workflow_mkdir(fattime, path);
}
override_fattime(0);
return result;
}
FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) {
STATIC FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path) {
FF_DIR dir;
FILINFO file_info;
// Check the stack since we're putting paths on it.
@ -174,3 +217,27 @@ FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *pa
f_closedir(&dir);
return res;
}
FRESULT supervisor_workflow_delete_recursive(const char *full_path) {
const char *mount_path;
fs_user_mount_t *active_mount = filesystem_for_path(full_path, &mount_path);
if (active_mount == NULL || !filesystem_native_fatfs(active_mount)) {
return FR_NO_PATH;
}
if (!filesystem_lock(active_mount)) {
return FR_WRITE_PROTECTED;
}
FATFS *fs = &active_mount->fatfs;
FILINFO file;
FRESULT result = f_stat(fs, full_path, &file);
if (result == FR_OK) {
if ((file.fattrib & AM_DIR) != 0) {
result = supervisor_workflow_delete_directory_contents(fs, full_path);
}
if (result == FR_OK) {
result = f_unlink(fs, full_path);
}
}
filesystem_unlock(active_mount);
return result;
}

View file

@ -31,5 +31,7 @@
extern bool supervisor_workflow_connecting(void);
// File system helpers for workflow code.
FRESULT supervisor_workflow_mkdir_parents(FATFS *fs, char *path);
FRESULT supervisor_workflow_delete_directory_contents(FATFS *fs, const TCHAR *path);
FRESULT supervisor_workflow_move(const char *old_path, const char *new_path);
FRESULT supervisor_workflow_mkdir(DWORD fattime, const char *full_path);
FRESULT supervisor_workflow_mkdir_parents(DWORD fattime, char *path);
FRESULT supervisor_workflow_delete_recursive(const char *full_path);

View file

@ -24,8 +24,7 @@
* THE SOFTWARE.
*/
#ifndef MICROPY_INCLUDED_SUPERVISOR_USB_H
#define MICROPY_INCLUDED_SUPERVISOR_USB_H
#pragma once
#include <stdbool.h>
#include <stddef.h>
@ -85,11 +84,6 @@ void usb_setup_with_vm(void);
void usb_msc_mount(void);
void usb_msc_umount(void);
bool usb_msc_ejected(void);
// Locking MSC prevents presenting the drive on plug-in when in use by something
// else (likely BLE.)
bool usb_msc_lock(void);
void usb_msc_unlock(void);
#endif
#if CIRCUITPY_USB_KEYBOARD_WORKFLOW
@ -102,5 +96,3 @@ void usb_keyboard_detach(uint8_t dev_addr, uint8_t interface);
void usb_keyboard_attach(uint8_t dev_addr, uint8_t interface);
void usb_keymap_set(const uint8_t *buf, size_t len);
#endif
#endif // MICROPY_INCLUDED_SUPERVISOR_USB_H