Created
February 8, 2026 13:04
-
-
Save pavelrevak/8074f843cb7b57fa5f14290b2c56879c to your computer and use it in GitHub Desktop.
Simple dict based Filesystem for micropython
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # boot_ram.py - Dict-based RAM filesystem for MicroPython | |
| # No block device, no FAT - files stored directly in nested dicts | |
| # Copy to device as boot.py or run via exec | |
| import gc | |
| import os | |
| _IFDIR = 0x4000 | |
| _IFREG = 0x8000 | |
| class DictFile: | |
| def __init__(self, parent, name, mode): | |
| self._parent = parent | |
| self._name = name | |
| if 'w' in mode: | |
| self._data = bytearray() | |
| parent[name] = self._data | |
| elif 'a' in mode: | |
| self._data = parent.get(name, bytearray()) | |
| parent[name] = self._data | |
| else: | |
| self._data = parent[name] | |
| self._pos = len(self._data) if 'a' in mode else 0 | |
| def read(self, n=-1): | |
| if n < 0 or n > len(self._data) - self._pos: | |
| n = len(self._data) - self._pos | |
| result = self._data[self._pos:self._pos + n] | |
| self._pos += n | |
| return bytes(result) | |
| def readinto(self, buf): | |
| n = min(len(buf), len(self._data) - self._pos) | |
| buf[:n] = self._data[self._pos:self._pos + n] | |
| self._pos += n | |
| return n | |
| def readline(self): | |
| start = self._pos | |
| end = len(self._data) | |
| while self._pos < end: | |
| self._pos += 1 | |
| if self._data[self._pos - 1] == 0x0a: | |
| break | |
| return bytes(self._data[start:self._pos]) | |
| def write(self, buf): | |
| n = len(buf) | |
| if self._pos == len(self._data): | |
| self._data.extend(buf) | |
| else: | |
| end = self._pos + n | |
| if end > len(self._data): | |
| self._data.extend(bytearray(end - len(self._data))) | |
| self._data[self._pos:self._pos + n] = buf | |
| self._pos += n | |
| return n | |
| def seek(self, offset, whence=0): | |
| if whence == 0: | |
| self._pos = offset | |
| elif whence == 1: | |
| self._pos += offset | |
| elif whence == 2: | |
| self._pos = len(self._data) + offset | |
| if self._pos < 0: | |
| self._pos = 0 | |
| return self._pos | |
| def tell(self): | |
| return self._pos | |
| def flush(self): | |
| pass | |
| def close(self): | |
| pass | |
| def __enter__(self): | |
| return self | |
| def __exit__(self, *a): | |
| pass | |
| class DictFS: | |
| def __init__(self): | |
| self._root = {} | |
| self._cwd = '/' | |
| def _split(self, path): | |
| if not path.startswith('/'): | |
| path = self._cwd.rstrip('/') + '/' + path | |
| return [p for p in path.split('/') if p] | |
| def _find(self, path): | |
| node = self._root | |
| for p in self._split(path): | |
| if not isinstance(node, dict) or p not in node: | |
| raise OSError(2) | |
| node = node[p] | |
| return node | |
| def _parent(self, path): | |
| parts = self._split(path) | |
| if not parts: | |
| raise OSError(2) | |
| node = self._root | |
| for p in parts[:-1]: | |
| if not isinstance(node, dict) or p not in node: | |
| raise OSError(2) | |
| node = node[p] | |
| if not isinstance(node, dict): | |
| raise OSError(20) | |
| return node, parts[-1] | |
| def mount(self, readonly, mkfs): | |
| pass | |
| def umount(self): | |
| pass | |
| def open(self, path, mode): | |
| parent, name = self._parent(path) | |
| if 'r' in mode and name not in parent: | |
| raise OSError(2) | |
| if name in parent and isinstance(parent[name], dict): | |
| raise OSError(21) | |
| return DictFile(parent, name, mode) | |
| def stat(self, path): | |
| node = self._find(path) | |
| if isinstance(node, dict): | |
| return (_IFDIR, 0, 0, 0, 0, 0, 0, 0, 0, 0) | |
| return (_IFREG, 0, 0, 0, 0, 0, len(node), 0, 0, 0) | |
| def statvfs(self, path): | |
| gc.collect() | |
| used = self._du(self._root) | |
| free = gc.mem_free() | |
| total = used + free | |
| return (1, 1, total, free, free, 0, 0, 0, 0, 255) | |
| def _du(self, node): | |
| if isinstance(node, dict): | |
| return sum(self._du(v) for v in node.values()) | |
| return len(node) | |
| def ilistdir(self, path): | |
| node = self._find(path) | |
| if not isinstance(node, dict): | |
| raise OSError(20) | |
| for name, val in node.items(): | |
| if isinstance(val, dict): | |
| yield (name, _IFDIR, 0, 0) | |
| else: | |
| yield (name, _IFREG, 0, len(val)) | |
| def mkdir(self, path): | |
| parent, name = self._parent(path) | |
| if name in parent: | |
| raise OSError(17) | |
| parent[name] = {} | |
| def rmdir(self, path): | |
| parent, name = self._parent(path) | |
| if name not in parent or not isinstance(parent[name], dict): | |
| raise OSError(2) | |
| if parent[name]: | |
| raise OSError(39) | |
| del parent[name] | |
| def remove(self, path): | |
| parent, name = self._parent(path) | |
| if name not in parent: | |
| raise OSError(2) | |
| if isinstance(parent[name], dict): | |
| raise OSError(21) | |
| del parent[name] | |
| def rename(self, old, new): | |
| op, on = self._parent(old) | |
| np, nn = self._parent(new) | |
| if on not in op: | |
| raise OSError(2) | |
| np[nn] = op.pop(on) | |
| def chdir(self, path): | |
| parts = self._split(path) | |
| node = self._root | |
| for p in parts: | |
| if not isinstance(node, dict) or p not in node: | |
| raise OSError(2) | |
| node = node[p] | |
| if not isinstance(node, dict): | |
| raise OSError(20) | |
| self._cwd = '/' + '/'.join(parts) if parts else '/' | |
| def getcwd(self): | |
| return self._cwd | |
| os.mount(DictFS(), '/ramfs') | |
| os.chdir('/ramfs') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment