madbfs (Modern adb Filesystem, formerly adbfsm) is a userspace filesystem for Android via adb built using libfuse.
out.mp4
What's happening above:
- viewing a video on Waydroid from host and copying it to host, and
- copying a picture from host to Waydroid.
I want to manage my Android phone storage from my computer without using MTP (it's awful).
This project is inspired by the adbfs-rootless project. While adbfs-rootless works most of the time under light load it has reliability issues. Under high load (such as when a thumbnailer service is running) adbfs-rootless tends to crash, making it unusable until it is forcefully unmounted and its cache cleaned.
The file I/O approach of adbfs-rootless is also similar to that of MTP, in which each file is first pulled from device (using adb pull) before any operations like reading and writing performed. If a write operation is performed, the file is then pushed back into the device (using adb push). This approach limits the size of the file that can be handled before the cache is filled (it uses /tmp).
These limitations lead me to decide to rebuild the project from ground up in order to create a more stable, modern, and possibly faster solution.
TLDR: full file and directory traversal with concurrent file streaming approach, partial read/write, and active caching.
-
No root access required
Non-rooted device will have standard file and directory access as regular user. Rooted device will have less constraints on file and directory access.
-
Full file and directory traversal
Browse the entire file tree of your Android device (subject to permission constraints).
-
Read and write support
Open, read, and write files (subject to permission constraints).
-
Create and delete files and directories
Support creating new files and directories as well as deleting them (subject to permission constraints).
-
Rename and move
Rename or move files and directory seamlessly (subject to permission constraints).
-
Modify file timestamps
Update file access and modification times.
-
Automatic in-memory caching
Recently accessed files are cached in memory using an LRU paging mechanism, allowing faster repeated access.
-
Streamed file access (partial read/write)
Read files on-demand without pulling the entire file first. Unlike MTP’s "pull-whole-file" approach, this filesystem streams data over
adb, enabling efficient access to large files or specific file segments. -
Efficient resource use
Loads only what you access, conserving memory usage and bandwidth. You can also control how much the cache stores file data.
-
Concurrent file access
Allows for concurrent access to files and directories without blocking. This ability comes out-of-the-box by virtue of using FUSE and
adb. -
Flexible connection method
madbfsoffers two kinds of transport:-
adbtransportThe simplest transport. It executes all FUSE operations by running
adb shellcommands likedd,stat, andtouch. No additional component is required. -
Proxy transport
Communicates with a lightweight TCP server running natively on the Android device via a custom RPC protocol. It requires a server binary compiled for the phone architecture but has better I/O throughput than the
adbtransport.
Both of the transport can work wired via USB or wireless in your local network.
madbfswill automatically fall back toadbtransport if the proxy transport is not available. -
-
Resilient to disconnections
Stays mounted even when the device disconnects. Cached files and directories remain accessible, and full functionality resumes automatically when the connection is restored.
-
Built with modern C++
Developed in C++23 using coroutine-style asynchronous programming for clean, lightweight, high-performance I/O.
-
madbfsandmadbfs-msgRuntime dependencies
- adb
Build dependencies
- GCC or Clang (support C++23 or above, tested on GCC 15 and GCC 16)
- CMake (version 3.22+)
- Conan
Library dependencies
- Boost (Asio, Process, and JSON component)
- libfuse
- rapidhash
- spdlog
-
madbfs-serverBuild dependencies
- Android NDK (support C++23 or above, tested on NDK version 29 (beta 1))
- CMake (version 3.22+)
- Conan
Library dependencies
- Asio (non-Boost variant)
- spdlog
if you just want a prebuilt binaries, jump to installation
Since the dependencies are managed by Conan, you need to make sure it's installed and configured correctly (consult the documentation on how to do it, here) on your system.
don't forget to run
conan profile detectif you use Conan for the first time
Navigate to the root of the repository then edit package.sh script. This step is necessary to set the Android native app build system and its parameters to be able to compile the servers. You may change these parameters:
you can left the parameters unchanged if you are using Android NDK version 29 (beta 1)
API_LEVEL(see)COMPILER(usually clang)COMPILER_VERSION(not ndk version! check by running the compiler on the NDK path)
Your ANDROID_NDK_HOME variable must be set before compiling and point to the appropriate Android NDK. If your ANDROID_NDK_HOME variable is not set, you can set it this:
export ANDROID_NDK_HOME=/path/to/your/android/ndk/home/Then you compile the project:
./package.sh
- you can optionally pass an argument to this function to specify custom conan profile (example)
The script above will build madbfs-server for all the currently supported Android ABIs (see this). Then just after, will build madbfs and madbfs-msg (and tests). The madbfs client embeds all of the madbfs-server inside itself on compilation. The end result is a tar.gz file in build/package/ directory that contains madbfs and madbfs-msg.
You can download madbfs from the GitHub releases page.
There is no installation step required, as the application is built statically. You can place the binaries wherever you prefer.
madbfs can be installed using your prefered AUR helper, as with every other AUR package. Using yay, you'd run the following command as superuser:
yay -S madbfs-bin
The help message can help you start using this program
usage: madbfs [options] <mountpoint>
Options for madbfs:
--serial=<str> serial number of the device to mount
(you can omit this [detection is similar to adb])
(will prompt if more than one device exists)
--root=<path> directory to mount on device as root of the filesystem
(by default madbfs mounts the root path of the device)
(the path must be absolute and points to an existing path)
(if the path is a symlink, it will be resolved first)
--log-level=<enum> log level to use
(default: "warning")
(enum: "trace", "debug", "info", "warning", "error", "critical", "off")
--log-file=<path> log file to write to
(default: "-" for stdout)
--cache-size=<int> maximum size of the cache in MiB
(default: 256)
(minimum: 128)
(value will be rounded up to the next power of 2)
(ignored if 'no-cache' is provided)
--page-size=<int> page size for cache & transfer in KiB
(default: 128)
(minimum: 64)
(maximum: 4096)
(value will be rounded up to the next power of 2)
(ignored if 'no-cache' is provided)
--ttl=<int> set the TTL of the stat cache of the filesystem in seconds
(default: 60)
(set to 0 to disable it)
--timeout=<int> set the timeout of every remote operation
(default: 2)
(set to 0 to disable it)
--port=<int> set the port number the server will listen on
(default: 23237)
--no-server don't launch server
(will still attempt to connect to specified port)
(fall back to adb shell calls if connection failed)
(useful for debugging the server)
--adb-only don't launch server and don't try to connect
--no-cache don't use data caching
Options for libfuse:
-h --help print help
-V --version print version
-d -o debug enable debug output (implies -f)
-f foreground operation
-s disable multi-threaded operation
-o clone_fd use separate fuse device fd for each thread
(may improve performance)
-o max_idle_threads the maximum number of idle worker threads
allowed (default: -1)
-o max_threads the maximum number of worker threads
allowed (default: 10)
-o allow_other allow access by all users
-o allow_root allow access by root
-o auto_unmount auto unmount on process termination
To unmount the filesystem, you run this command like for any other FUSE filesystem:
fusermount -u <mountpoint>You can also use the IPC operation unmount for unmounting. This will unmount the filesystem on the next operation immediately.
You don't need to specify the device if there is only one device connected via adb on your computer. If there are more than one device then you can specify the serial using --serial option. If you omit the --serial option when there are multiple device connected to the computer, you will be prompted to specify the device you want to mount.
$ madbfs <mountpoint>
[madbfs] checking adb availability...
[madbfs] multiple devices detected,
- 1: 068832516O101622
- 2: 192.168.240.112:5555
[madbfs] please specify which one you would like to use: _madbfs respects the env variable ANDROID_SERIAL (mimicking adb behavior) so you can alternately use it to specify the device.
$ ANDROID_SERIAL=068832516O101622 ./madbfs
[madbfs] checking adb availability...
[madbfs] using serial '068832516O101622' from env variable 'ANDROID_SERIAL'madbfs supports mounting subdirectory. Use --root option to specify it.
$ madbfs --root=<root> <mountpoint>The <root> is the subdirectory on your device you want to treat as the root of the filesystem (effectively mounting a subdirectory). The option only accepts absolute path and if the directory is a symlink, it will resolve it first (/sdcard -> /storage/emulated/0).
Do note that mounting subdirectory might break symbolic links since it is possible that a link may contain components that are unreachable from the specified root. For example if you mount /sdcard/ and have a link in /sdcard/link that points to /storage/ the filesystem can't reach it, because /storage/ is outside of /sdcard/.
only relevant if you want proxy transport support
If you want the filesystem to use only use adb transport, use --adb-only flag. This flag prevents madbfs from pushing the server into your phone and running it. If you rather want to manually run the server yourself (for debugging purpose), use --no-server flag instead.
The proxy communicates with madbfs over TCP enabled by port forwarding and by default it will listen on port 23237 (adbfs on dial pad). If you find this port to be not suitable for your use you can always specify it with --port option.
$ madbfs --server=<path/to/server-with-abi> --port=23237 <mountpoint>By default madbfs caches file stat and file content (data) of files operated by the filesystem. While file stat caching is always active, file content caching can be disabled using --no-cache program option. This option will ignore other cache related options.
$ madbfs --no-cache <mountpoint>The no-cache mode is basically a direct I/O, file content will always be read/written to device immediately via the connection method (proxy or adb transport).
madbfs caches all the read/write operations on the files on the device. This cache is stored in memory. You can control the size of this cache using --cache-size option (in MiB). The default value is 256 (256 MiB).
$ madbfs --cache-size=256 <mountpoint> # 256 MiB of memory will be used as file cacheIn the cache, each file is divided into pages. The --page-size option dictates the size of this page (in KiB). Page size also dictates the size of the buffer used to read/write into the file on the device. You can adjust this value according to your use.
$ madbfs --page-size=128 <mountpoint> # read/write operations are communicated in 128 KiB chunks
The default log file is stdout (specified by "-"; which goes to nowhere when not run in foreground mode). You can manually set the log file using --log-file option and set the log level using --log-level.
$ madbfs --log-file=madbfs.log --log-level=debug <mountpoint>You can always watch the logs of the filesystem at runtime even if you don't specify a log-file beforehand by using IPC logcat operation (see below).
Filesystem parameters can be reconfigured and queried during runtime though IPC using unix socket. The supported operations are:
- help,
- version,
- info,
- invalidate cache,
- set page size,
- set cache size,
- set ttl,
- set timeout,
- set log level, and
- unmount (on next FUSE operation)
- logcat (read
madbfslog in real-time, similar toadb logcat).
The address of the socket in which you can connect to as client is composed of the name of the filesystem and the serial of the device. The socket itself is created in directory defined by XDG_RUNTIME_DIR environment variable (it's usually set to /run/user/<uid>). If the XDG_RUNTIME_DIR is not defined, as fallback, the directory is set to /tmp. The socket will be created when the filesystem initializes.
For example, the socket path for a device with serial 192.168.240.112:5555:
/run/user/1000/madbfs@192.168.240.112:5555.sock
If at initialization this socket file exists, the IPC won't start. This may happen if the filesystem is terminated unexpectedly (crash or kill signal). You need to remove this file manually if that happens.
For the specification of the message protocol used on the IPC and how to use it read IPC.md file. To make it easier for user to use the IPC without having to write their own socket code, I have created another executable: madbfs-msg. The possible operations are explained in IPC.md file as well.
As part of debugging functionality libfuse has provided debug mode through -d flag. You can use this to monitor madbfs operations (if you don't want to use log file or want to see the log in real-time). If the debugging information is too verbose, you can use -f instead to make madbfs run in foreground mode without printing fuse debug information.
$ madbfs --log-file=- --log-level=debug -f <mountpoint> # runs in foreground (not daemonized)
$ madbfs --log-file=- --log-level=debug -d <mountpoint> # this will print the libfuse debug messages and madbfs log messages
$ madbfs --log-file=- --log-level=debug -d <mountpoint> 2> /dev/null # this will print only madbfs log messages since libfuse debug messages are printed to stderrBenchmark is done by writing a 64 MiB file using dd and then reading it back. The statistics printed by dd is used for the speed value so is for adb push and adb pull. The test is done on an Android 11 phone (armv8) using USB cable with proxy transport. As baseline, the speed on which an adb push (write) and an adb pull (read) operation is done on a file with the same size is measured. madbfs is launched using its default parameters (cache size = 256 MiB, page size = 128 KiB).
The write command is as follows:
dd if=/dev/random of=random bs=128K count=1024The read command is as follows:
dd if=random of=/dev/null bs=128K count=1024-
Write
operation speed (MB/s) relative speed adb push 15.3 1.00 write 13.1 0.86 -
Read
operation speed (MB/s) relative speed adb pull 11.7 1.00 read (no cache) 10.8 0.93 read (cache) 2100.0 179.49
-
Write
operation speed (MB/s) relative speed adb push 15.3 1.00 write 12.4 0.81 -
Read
operation speed (MB/s) relative speed adb pull 11.7 1.00 read (no cache) 3.6 0.24 read (cache) 2100.0 179.49