|
| 1 | +# Linux USB Mount Notes (CIRCUITPY) |
| 2 | + |
| 3 | +If you use the CircuitPython Web Editor on Linux with the **USB workflow** |
| 4 | +and you see |
| 5 | + |
| 6 | +``` |
| 7 | +OSError: [Errno 5] Input/output error |
| 8 | +``` |
| 9 | + |
| 10 | +in the serial terminal after pressing <kbd>Ctrl</kbd>+<kbd>D</kbd> following |
| 11 | +a save, this page is for you. The cause is in the host operating system, |
| 12 | +not the editor and not your CircuitPython device. |
| 13 | + |
| 14 | +## What is happening |
| 15 | + |
| 16 | +When the browser writes to `code.py` (or any other file on the CIRCUITPY |
| 17 | +drive), it goes through the operating system's filesystem layer. On Linux, |
| 18 | +the default behavior of the `vfat` filesystem on a USB Mass Storage Class |
| 19 | +(MSC) device is to **buffer those writes in the kernel's page cache** and |
| 20 | +only push them to the device some time later (the kernel default is up to |
| 21 | +~30 seconds). |
| 22 | + |
| 23 | +When you then press <kbd>Ctrl</kbd>+<kbd>D</kbd>, CircuitPython on the |
| 24 | +device tries to import `code.py` immediately. If the host hasn't yet |
| 25 | +flushed its writes, the device sees an inconsistent filesystem and |
| 26 | +returns `OSError: [Errno 5] Input/output error`. |
| 27 | + |
| 28 | +This only affects **Linux** with the **USB MSC workflow**. macOS and |
| 29 | +Windows do not have this issue. Network and Bluetooth workflows are also |
| 30 | +unaffected. |
| 31 | + |
| 32 | +## Quick fix (current session) |
| 33 | + |
| 34 | +Remount the CIRCUITPY drive with the `sync` mount option so writes are |
| 35 | +sent to the device synchronously: |
| 36 | + |
| 37 | +```sh |
| 38 | +udisksctl unmount -b /dev/sdX1 |
| 39 | +udisksctl mount -b /dev/sdX1 -o sync |
| 40 | +``` |
| 41 | + |
| 42 | +Replace `/dev/sdX1` with the actual block device. Find it with: |
| 43 | + |
| 44 | +```sh |
| 45 | +lsblk |
| 46 | +# or |
| 47 | +mount | grep CIRCUITPY |
| 48 | +``` |
| 49 | + |
| 50 | +After this, return to the editor, **reconnect** (the previous filesystem |
| 51 | +handle is invalidated by the remount), and resume editing. |
| 52 | + |
| 53 | +## Permanent fix: udev rule |
| 54 | + |
| 55 | +To make every CircuitPython device automount with `sync` going forward, |
| 56 | +add a `udev` rule. |
| 57 | + |
| 58 | +1. Find your board's USB Vendor ID (`idVendor`) and Product ID |
| 59 | + (`idProduct`) using `lsusb`. CircuitPython boards often share VID |
| 60 | + `239a` (Adafruit) but PIDs vary by board. |
| 61 | + |
| 62 | + ```sh |
| 63 | + lsusb |
| 64 | + ``` |
| 65 | + |
| 66 | +2. Create `/etc/udev/rules.d/99-circuitpython-sync.rules` with: |
| 67 | + |
| 68 | + ``` |
| 69 | + # CircuitPython CIRCUITPY drive: mount synchronously to avoid |
| 70 | + # OSError: [Errno 5] Input/output error from web-editor saves. |
| 71 | + ENV{ID_FS_LABEL}=="CIRCUITPY", ENV{UDISKS_MOUNT_OPTIONS}+="sync" |
| 72 | + ``` |
| 73 | + |
| 74 | + The label-based match catches any CircuitPython board, regardless of |
| 75 | + VID/PID. If you'd rather scope it tighter, you can add additional |
| 76 | + `ATTRS{idVendor}=="..."` clauses. |
| 77 | + |
| 78 | +3. Reload udev rules: |
| 79 | + |
| 80 | + ```sh |
| 81 | + sudo udevadm control --reload-rules |
| 82 | + sudo udevadm trigger |
| 83 | + ``` |
| 84 | + |
| 85 | +4. Replug your CircuitPython board (or simply unmount/remount the |
| 86 | + CIRCUITPY drive). It should now appear with `sync` in its mount |
| 87 | + options. Verify with: |
| 88 | + |
| 89 | + ```sh |
| 90 | + mount | grep CIRCUITPY |
| 91 | + ``` |
| 92 | + |
| 93 | + You should see something like: |
| 94 | + |
| 95 | + ``` |
| 96 | + /dev/sda1 on /media/<user>/CIRCUITPY type vfat (rw,...,sync,...) |
| 97 | + ``` |
| 98 | + |
| 99 | +## Trade-offs |
| 100 | + |
| 101 | +The `sync` mount option means every write to CIRCUITPY blocks until the |
| 102 | +device has accepted the data. For interactive editing of small files |
| 103 | +this is generally not noticeable. For copying large files (firmware |
| 104 | +updates, large libraries) it will be slower than the default async |
| 105 | +behavior, but the data is more reliably on the device when the copy |
| 106 | +returns. |
| 107 | + |
| 108 | +## See also |
| 109 | + |
| 110 | +- [Issue #229](https://github.com/circuitpython/web-editor/issues/229) |
| 111 | + — original report and discussion |
| 112 | +- `man 8 mount` — see the `sync` option under FILESYSTEM-INDEPENDENT |
| 113 | + MOUNT OPTIONS |
0 commit comments