adbfs

package module
v0.0.0-...-31b5a2e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 24, 2023 License: Apache-2.0 Imports: 23 Imported by: 0

README

adbfs Build Status GoDoc

A FUSE filesystem that uses goadb to expose Android devices' filesystems.

Features

  • Read access to Android device filesystems through adb, without root. Of course, this is limited to files that are accessible to whatever user adb shell runs the shell as on the device.
  • Experimental write support (run with --no-readonly). Notably, on OSX, Finder can't copy files into mounted directories (issue #34).
  • Automounter daemon that detects when devices are connected and mounts them under a configurable directory.
  • Communicates directly with adb server using goadb instead of delegating to the adb client command like most adb-based filesystems.

Quick Start

Installation

adbfs depends on fuse. For OS X, install osxfuse. Then run:

$ export GO15VENDOREXPERIMENT=1
$ go get github.com/zach-klippenstein/adbfs
$ cd `go list -f '{{.Dir}}' github.com/zach-klippenstein/adbfs`
$ ./install.sh
Let's mount some devices!

The easiest way to start mounting devices is to use adbfs-automount to watch for newly-connected devices and automatically mount them.

$ mkdir ~/mnt
$ adbfs-automount

You probably want to run this as a service when you login (e.g. create a LaunchAgent on OSX). For example, on OSX, paste this into ~/Library/LaunchAgents/com.adbfs-automount.plist (substituting "zach" for your own username, of course):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key>
  <string>com.adbfs-automount</string>
  <key>ProgramArguments</key>
  <array>
    <string>/Users/zach/go/bin/adbfs-automount</string>
    <string>--adb=/Users/zach/android-sdk/platform-tools/adb</string>
    <string>--adbfs=/Users/zach/go/bin/adbfs</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>/Users/zach/adbfs.log</string>
  <key>StandardErrorPath</key>
  <string>/Users/zach/adbfs.log</string>
</dict>
</plist>

then run

$ launchctl load ~/Library/LaunchAgents/com.adbfs-automount.plist
$ launchctl start com.adbfs-automounter

adfs

Usage

Devices are specified by serial number. To list the serial numbers of all connected devices, run:

adb devices -l

The serial number is the left-most column. To mount a device with serial number 02b5c5a809117c73 on /mnt, run:

adbfs --device 02b5c5a809117c73 --mountpoint /mnt

Example:

$ adb devices -l
List of devices attached 
02b5c5a809117c73       device usb:14100000 product:hammerhead model:Nexus_5 device:hammerhead
$ mkdir ~/mnt
$ adbfs -device 02b5c5a809117c73 -mountpoint ~/mnt
INFO[2015-09-07T16:13:03.386813059-07:00] stat cache ttl: 300ms
INFO[2015-09-07T16:13:03.387113547-07:00] connection pool size: 2
INFO[2015-09-07T16:13:03.400838775-07:00] server ready.
INFO[2015-09-07T16:13:03.400884026-07:00] mounted 02b5c5a809117c73 on /Users/zach/mnt
⋮

adbfs-automount

adbfs-automount listens for new device connections to adb and runs an instance of adbfs for each device to mount it. Most arguments are passed through to adbfs, but there are a few arguments specific to the automounter:

--root: the directory under which to mount devices. If this is not specified, it will try to figure out a good path. On OSX, ~/mnt is used if it exists, else /Volumes. On Linux, it tries ~/mnt then /mnt.

--adbfs: path to the adbfs executable to run. If not specified, will search $PATH. The executable must be built from the same SHA as adbfs-automount, which will exit with an error if this is not the case.

--on-(un)mount: a command to run when a device is (un)mounted. Can be repeated to run multiple commands. E.g. --on-mount 'say $ADBFS_MODEL' --on-mount 'open $ADBFS_PATH' will speak the type of device and open it in Finder.

Running from Source

$ go run cmd/adbfs/main.go …

Documentation

Index

Constants

View Source
const (
	// See AdbFileOpenOptions.Perms.
	DontSetPerms = os.FileMode(0)

	// This seems pretty long, but every write that occurs after this period will
	// flush the entire buffer to the device – this could take a while, and if we do it
	// to often we're effectively thrashing. If a file has been written to continuously for
	// this time, it's guaranteed to take at least as long, probably a lot longer, to flush
	// to the device (process->kernel->fuse has lower latency than process->adb->device).
	DefaultDirtyTimeout = 5 * time.Minute
)
View Source
const (
	ReadlinkInvalidArgument  = "readlink: Invalid argument"
	ReadlinkPermissionDenied = "readlink: Permission denied"
)

Error messages returned by the readlink command on Android devices. Should these be moved into goadb instead?

View Source
const CachePurgeInterval = 5 * time.Minute
View Source
const DefaultFilePermissions = os.FileMode(0664)
View Source
const MaxLinkResolveDepth = 64

64 symlinks ought to be deep enough for anybody.

View Source
const OK = syscall.Errno(0)

Variables

View Source
var (
	// A symlink cycle is detected.
	ErrLinkTooDeep = errors.New("link recursion too deep")
	ErrNotALink    = errors.New("not a link")
	// The user doesn't have permission to perform an operation.
	ErrNoPermission = os.ErrPermission
	// The operation is not permitted due to reasons other than user permission.
	ErrNotPermitted = errors.New("operation not permitted")
)

Functions

func NewAdbFile

func NewAdbFile(opts AdbFileOpenOptions) nodefs.File

NewAdbFile returns a File that reads and writes to name on the device. perms should be set from the existing file if it exists, or to the desired new permissions if new.

func NewAdbFileSystem

func NewAdbFileSystem(config Config) (pathfs.FileSystem, error)

Types

type AdbFile

type AdbFile struct {
	nodefs.File
	AdbFileOpenOptions
}

AdbFile is a nodefs.File that is backed by a file on an adb device. There is one AdbFile for each file descriptor. All AdbFiles that point to the same path are backed by the same FileBuffer.

Note: On OSX at least, the OS will automatically map multiple open files to a single AdbFile.

func (*AdbFile) Flush

func (f *AdbFile) Flush() fuse.Status

func (*AdbFile) Fsync

func (f *AdbFile) Fsync(flags int) fuse.Status

Fsync flushes the file to device if the dirty flag is set, else re-reads the file from the device into memory.

func (*AdbFile) GetAttr

func (f *AdbFile) GetAttr(out *fuse.Attr) fuse.Status

func (*AdbFile) InnerFile

func (f *AdbFile) InnerFile() nodefs.File

func (*AdbFile) Read

func (f *AdbFile) Read(buf []byte, off int64) (fuse.ReadResult, fuse.Status)

func (*AdbFile) Release

func (f *AdbFile) Release()

func (*AdbFile) Truncate

func (f *AdbFile) Truncate(size uint64) fuse.Status

func (*AdbFile) Write

func (f *AdbFile) Write(data []byte, off int64) (uint32, fuse.Status)

type AdbFileOpenOptions

type AdbFileOpenOptions struct {
	// If the create flag is set, the file will immediately be created if it does not exist.
	Flags      FileOpenFlags
	FileBuffer *FileBuffer
}

type AdbFileSystem

type AdbFileSystem struct {
	// contains filtered or unexported fields
}

AdbFileSystem is an implementation of fuse.pathfs.FileSystem that exposes the filesystem on an adb device.

Since all operations go through a single adb server, short-lived connections are throttled by using a fixed-size pool of device clients. The pool is initially filled by calling Config.ClientFactory. The pool is not used for long-lived connections such as file transfers, which may be kept open for arbitrary periods of time by processes using the filesystem.

func (*AdbFileSystem) Access

func (fs *AdbFileSystem) Access(name string, mode uint32, context *fuse.Context) fuse.Status

func (*AdbFileSystem) Chmod

func (fs *AdbFileSystem) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status)

func (*AdbFileSystem) Chown

func (fs *AdbFileSystem) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status)

func (*AdbFileSystem) Create

func (fs *AdbFileSystem) Create(name string, rawFlags uint32, perms uint32, context *fuse.Context) (nodefs.File, fuse.Status)

func (*AdbFileSystem) GetAttr

func (fs *AdbFileSystem) GetAttr(name string, _ *fuse.Context) (attr *fuse.Attr, status fuse.Status)

func (*AdbFileSystem) GetXAttr

func (fs *AdbFileSystem) GetXAttr(name string, attribute string, context *fuse.Context) (data []byte, code fuse.Status)
func (fs *AdbFileSystem) Link(oldName string, newName string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) ListXAttr

func (fs *AdbFileSystem) ListXAttr(name string, context *fuse.Context) (attributes []string, code fuse.Status)

func (*AdbFileSystem) Mkdir

func (fs *AdbFileSystem) Mkdir(name string, perms uint32, context *fuse.Context) fuse.Status

Mkdir creates name on the device with the default permissions. perms is ignored.

func (*AdbFileSystem) Mknod

func (fs *AdbFileSystem) Mknod(name string, mode uint32, dev uint32, context *fuse.Context) fuse.Status

func (*AdbFileSystem) OnMount

func (fs *AdbFileSystem) OnMount(nodeFs *pathfs.PathNodeFs)

func (*AdbFileSystem) OnUnmount

func (fs *AdbFileSystem) OnUnmount()

func (*AdbFileSystem) Open

func (fs *AdbFileSystem) Open(name string, flags uint32, context *fuse.Context) (nodefs.File, fuse.Status)

func (*AdbFileSystem) OpenDir

func (fs *AdbFileSystem) OpenDir(name string, _ *fuse.Context) ([]fuse.DirEntry, fuse.Status)
func (fs *AdbFileSystem) Readlink(name string, context *fuse.Context) (target string, status fuse.Status)

func (*AdbFileSystem) RemoveXAttr

func (fs *AdbFileSystem) RemoveXAttr(name string, attr string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) Rename

func (fs *AdbFileSystem) Rename(oldName, newName string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) Rmdir

func (fs *AdbFileSystem) Rmdir(name string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) SetDebug

func (fs *AdbFileSystem) SetDebug(debug bool)

func (*AdbFileSystem) SetXAttr

func (fs *AdbFileSystem) SetXAttr(name string, attr string, data []byte, flags int, context *fuse.Context) fuse.Status

func (*AdbFileSystem) StatFs

func (fs *AdbFileSystem) StatFs(name string) *fuse.StatfsOut

func (*AdbFileSystem) String

func (fs *AdbFileSystem) String() string
func (fs *AdbFileSystem) Symlink(oldName string, newName string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) Truncate

func (fs *AdbFileSystem) Truncate(name string, size uint64, context *fuse.Context) (code fuse.Status)
func (fs *AdbFileSystem) Unlink(name string, context *fuse.Context) fuse.Status

func (*AdbFileSystem) Utimens

func (fs *AdbFileSystem) Utimens(name string, Atime *time.Time, Mtime *time.Time, context *fuse.Context) (code fuse.Status)

type CachedDirEntries

type CachedDirEntries struct {
	InOrder []*adb.DirEntry
	ByName  map[string]*adb.DirEntry
}

func NewCachedDirEntries

func NewCachedDirEntries(entries []*adb.DirEntry) *CachedDirEntries

type CachingDeviceClient

type CachingDeviceClient struct {
	DeviceClient
	Cache DirEntryCache
}

func (*CachingDeviceClient) ListDirEntries

func (c *CachingDeviceClient) ListDirEntries(path string, log *LogEntry) ([]*adb.DirEntry, error)

func (*CachingDeviceClient) OpenWrite

func (c *CachingDeviceClient) OpenWrite(name string, perms os.FileMode, mtime time.Time, log *LogEntry) (io.WriteCloser, error)

func (*CachingDeviceClient) Stat

func (c *CachingDeviceClient) Stat(name string, log *LogEntry) (*adb.DirEntry, error)

type Config

type Config struct {
	// Serial number of the device for which ClientFactory returns clients.
	DeviceSerial string

	// Absolute path to mountpoint, used to resolve symlinks.
	Mountpoint string

	// Directory on device to consider root.
	DeviceRoot string

	// Used to initially populate the device client pool, and create clients for open files.
	ClientFactory DeviceClientFactory

	// Maximum number of concurrent connections for short-lived connections (does not restrict
	// the number of concurrently open files).
	// Values <1 are treated as 1.
	ConnectionPoolSize int

	ReadOnly bool
}

Config stores arguments used by AdbFileSystem.

type DeviceClient

type DeviceClient interface {
	OpenRead(path string, log *LogEntry) (io.ReadCloser, error)
	OpenWrite(path string, perms os.FileMode, mtime time.Time, log *LogEntry) (io.WriteCloser, error)
	Stat(path string, log *LogEntry) (*adb.DirEntry, error)
	ListDirEntries(path string, log *LogEntry) ([]*adb.DirEntry, error)

	RunCommand(cmd string, args ...string) (string, error)
}

DeviceClient wraps adb.DeviceClient for testing.

type DeviceClientFactory

type DeviceClientFactory func() DeviceClient

func NewCachingDeviceClientFactory

func NewCachingDeviceClientFactory(cache DirEntryCache, factory DeviceClientFactory) DeviceClientFactory

func NewGoadbDeviceClientFactory

func NewGoadbDeviceClientFactory(server *adb.Adb, deviceSerial string, deviceDisconnectedHandler func()) DeviceClientFactory

type DirEntryCache

type DirEntryCache interface {
	GetOrLoad(path string, loader DirEntryLoader) (entries *CachedDirEntries, err error, hit bool)
	Get(path string) (entries *CachedDirEntries, found bool)
	// Removes the entry for path from the cache without blocking on other cache operations.
	RemoveEventually(path string)
}

DirEntryCache is a key-value cache of normalized directory paths to slices of *adb.FileEntries.

func NewDirEntryCache

func NewDirEntryCache(ttl time.Duration) DirEntryCache

type DirEntryLoader

type DirEntryLoader func(path string) (*CachedDirEntries, error)

type FileBuffer

type FileBuffer struct {
	FileBufferOptions
	// contains filtered or unexported fields
}

FileBuffer loads, provides read/write access to, and saves a file on the device. A single FileBuffer backs all the open files for a given path to provide as consistent a view of that file as possible. 1 or more AdbFiles may point to a single FileBuffer.

Note: On OSX at least, the OS will automatically map multiple open files to a single AdbFile. Still, this type is still useful because it separates the file model and logic from the go-fuse-specific integration code.

func NewFileBuffer

func NewFileBuffer(initialFlags FileOpenFlags, opts FileBufferOptions, logEntry *LogEntry) (file *FileBuffer, err error)

NewFileBuffer returns a File that reads and writes to name on the device. initialFlags are the flags being used to open the file the first time, and are only used to determine if the buffer needs to be read into memory when initializing.

func (*FileBuffer) Contents

func (f *FileBuffer) Contents() string

func (*FileBuffer) DecRefCount

func (f *FileBuffer) DecRefCount() int

func (*FileBuffer) Flush

func (f *FileBuffer) Flush(logEntry *LogEntry) error

Flush saves the buffer to the device if dirty, else does nothing. Like Sync, but without the read.

func (*FileBuffer) IncRefCount

func (f *FileBuffer) IncRefCount() int

func (*FileBuffer) IsDirty

func (f *FileBuffer) IsDirty() bool

func (*FileBuffer) ReadAt

func (f *FileBuffer) ReadAt(buf []byte, off int64) (n int, err error)

ReadAt implements the io.ReaderAt interface.

func (*FileBuffer) RefCount

func (f *FileBuffer) RefCount() int

func (*FileBuffer) SetSize

func (f *FileBuffer) SetSize(size int64)

func (*FileBuffer) Size

func (f *FileBuffer) Size() int64

func (*FileBuffer) Sync

func (f *FileBuffer) Sync(logEntry *LogEntry) error

Sync saves the buffer to the device if dirty, else reloads the buffer from the device. Like Flush, but reloads the buffer if not dirty.

func (*FileBuffer) SyncIfTooDirty

func (f *FileBuffer) SyncIfTooDirty(logEntry *LogEntry) error

SyncIfTooDirty performs a sync if the buffer has been dirty for longer than the timeout specified in FileBufferOptions.

func (*FileBuffer) WriteAt

func (f *FileBuffer) WriteAt(data []byte, off int64) (int, error)

type FileBufferOptions

type FileBufferOptions struct {
	Path         string
	Client       DeviceClient
	Clock        Clock
	DirtyTimeout time.Duration

	// The permissions to set on the file when flushing.
	// If this is DontSetPerms, the file's existing permissions will be used.
	// Set from the existing file if it exists, or to the desired new permissions if new.
	Perms os.FileMode

	// Function called when ref count hits 0.
	// Note that, because concurrency, the ref count may be incremented again by the time
	// this function is executed.
	ZeroRefCountHandler func(*FileBuffer)
}

type FileOpenFlags

type FileOpenFlags uint32

Helper methods around the flags passed to the Open call.

func (FileOpenFlags) CanRead

func (f FileOpenFlags) CanRead() bool

func (FileOpenFlags) CanWrite

func (f FileOpenFlags) CanWrite() bool

func (FileOpenFlags) Contains

func (f FileOpenFlags) Contains(bits FileOpenFlags) bool

Contains returns true if the current flags contain any of the bits in bits.

type LogEntry

type LogEntry struct {
	// contains filtered or unexported fields
}

LogEntry reports results, errors, and statistics for an individual operation. Each method can only be called once, and will panic on subsequent calls.

If an error is reported, it is logged as a separate entry.

Example Usage

func DoTheThing(path string) fuse.Status {
	logEntry := StartOperation("DoTheThing", path)
	defer FinishOperation(log) // Where log is a logrus logger.

	result, err := perform(path)
	if err != nil {
		logEntry.Error(err)
		return err
	}

	logEntry.Result(result)
	return logEntry.Status(fuse.OK)
}

func StartFileOperation

func StartFileOperation(name, path string, args string) *LogEntry

func StartOperation

func StartOperation(name string, path string) *LogEntry

StartOperation creates a new LogEntry with the current time. Should be immediately followed by a deferred call to FinishOperation.

func (*LogEntry) CacheUsed

func (r *LogEntry) CacheUsed(hit bool)

CacheUsed records that a cache was used to attempt to retrieve a result.

func (*LogEntry) Error

func (r *LogEntry) Error(err error)

Error records a failure result. Panics if called more than once.

func (*LogEntry) ErrorMsg

func (r *LogEntry) ErrorMsg(err error, msg string, args ...interface{})

ErrorMsg records a failure result. Panics if called more than once.

func (*LogEntry) FinishOperation

func (r *LogEntry) FinishOperation()

FinishOperation should be deferred. It will log the duration of the operation, as well as any results and/or errors.

func (*LogEntry) Result

func (r *LogEntry) Result(msg string, args ...interface{})

Result records a non-failure result. Panics if called more than once.

func (*LogEntry) Status

func (r *LogEntry) Status(status syscall.Errno) syscall.Errno

Status records the fuse.Status result of an operation.

func (*LogEntry) SuppressFinishOperation

func (r *LogEntry) SuppressFinishOperation()

type OpenFiles

type OpenFiles struct {
	OpenFilesOptions
	// contains filtered or unexported fields
}

OpenFiles tracks and manages the set of all open files in a filesystem.

func NewOpenFiles

func NewOpenFiles(opts OpenFilesOptions) *OpenFiles

func (*OpenFiles) GetOrLoad

func (f *OpenFiles) GetOrLoad(path string, openFlags FileOpenFlags, perms os.FileMode, logEntry *LogEntry) (file *FileBuffer, err error)

type OpenFilesOptions

type OpenFilesOptions struct {
	DeviceSerial       string
	DefaultPermissions os.FileMode
	ClientFactory      DeviceClientFactory

	// The length of time the file can be dirty before the next write will force a flush.
	DirtyTimeout time.Duration
}

type WrappingFile

type WrappingFile struct {
	nodefs.File

	BeforeCall func(fs *WrappingFile, method string, args ...interface{}) (call interface{})

	// AfterCall is called after every operation on the file with the method receiver,
	// the name of the method, and slices of all the passed and returned values.
	AfterCall func(fs *WrappingFile, call interface{}, status *fuse.Status, results ...interface{})
}

WrappingFile is an implementation of nodefs.File that invokes a callback after every method call.

func (*WrappingFile) Allocate

func (f *WrappingFile) Allocate(off uint64, size uint64, mode uint32) (code fuse.Status)

func (*WrappingFile) Chmod

func (f *WrappingFile) Chmod(perms uint32) (code fuse.Status)

func (*WrappingFile) Chown

func (f *WrappingFile) Chown(uid uint32, gid uint32) (code fuse.Status)

func (*WrappingFile) Flush

func (f *WrappingFile) Flush() (code fuse.Status)

Flush is called for close() call on a file descriptor. In case of duplicated descriptor, it may be called more than once for a file.

func (*WrappingFile) Fsync

func (f *WrappingFile) Fsync(flags int) (code fuse.Status)

func (*WrappingFile) GetAttr

func (f *WrappingFile) GetAttr(out *fuse.Attr) (code fuse.Status)

func (*WrappingFile) InnerFile

func (f *WrappingFile) InnerFile() (file nodefs.File)

Wrappers around other File implementations, should return the inner file here.

func (*WrappingFile) Read

func (f *WrappingFile) Read(dest []byte, off int64) (result fuse.ReadResult, code fuse.Status)

func (*WrappingFile) Release

func (f *WrappingFile) Release()

This is called to before the file handle is forgotten. This method has no return value, so nothing can synchronizes on the call. Any cleanup that requires specific synchronization or could fail with I/O errors should happen in Flush instead.

func (*WrappingFile) SetInode

func (f *WrappingFile) SetInode(inode *nodefs.Inode)

Called upon registering the filehandle in the inode.

func (*WrappingFile) String

func (f *WrappingFile) String() string

The String method is for debug printing.

func (*WrappingFile) Truncate

func (f *WrappingFile) Truncate(size uint64) (code fuse.Status)

The methods below may be called on closed files, due to concurrency. In that case, you should return EBADF.

func (*WrappingFile) Utimens

func (f *WrappingFile) Utimens(atime *time.Time, mtime *time.Time) (code fuse.Status)

func (*WrappingFile) Write

func (f *WrappingFile) Write(data []byte, off int64) (written uint32, code fuse.Status)

Directories

Path Synopsis
cmd
adbfs
Another FUSE filesystem that can mount any device visible to your adb server.
Another FUSE filesystem that can mount any device visible to your adb server.
adbfs-automount
Connects to the adb server to listen for new devices, and mounts devices under a certain directory when connected.
Connects to the adb server to listen for new devices, and mounts devices under a certain directory when connected.
internal
cli
Command-line options and utilities used by multiple cmds.
Command-line options and utilities used by multiple cmds.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL