android-file-transfer-linux icon indicating copy to clipboard operation
android-file-transfer-linux copied to clipboard

ENOSYS or EINVAL when trying to write using FUSE

Open jbeich opened this issue 8 years ago • 15 comments

FreeBSD 12.0-CURRENT amd64 (host) with Android 6.0.1 RR 5.7.3 armv7l (device) here. Android File Transfer's FUSE helper appears to have broken write support. OTOH, simple-mtpfs works fine. Any clues?

$ aft-mtp-mount -o allow_other /mnt

$ ls /mnt/SD\ card/Movies
Foo.mp4
Bar.mp4

$ hd /mnt/SD\ card/Movies/Foo.mp4 | head -1
00000000  00 00 00 20 66 74 79 70  69 73 6f 6d 00 00 02 00  |... ftypisom....|

$ cp Qux.mp4 /mnt/SD\ card/Movies
cp: /mnt/SD card/Movies/Qux.mp4: Function not implemented

$ cp Qux.mp4 /mnt/SD\ card/Movies
cp: /mnt/SD card/Movies/Qux.mp4: Invalid argument

or if mounted with -o debug

unique: 73, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 1844
   Lookup 1009995 Qux.mp4
   unique: 73, error: -2 (No such file or directory), outsize: 16
unique: 74, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 1844
   Lookup 1009995 Qux.mp4
   unique: 74, error: -2 (No such file or directory), outsize: 16
unique: 75, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 1844
   Lookup 1009995 Qux.mp4
   unique: 75, error: -2 (No such file or directory), outsize: 16
unique: 76, opcode: CREATE (35), nodeid: 1009995, insize: 54, pid: 1844
   unique: 76, error: -78 (Function not implemented), outsize: 16



unique: 77, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 3550
   Lookup 1009995 Qux.mp4
   unique: 77, error: -2 (No such file or directory), outsize: 16
unique: 78, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 3550
   Lookup 1009995 Qux.mp4
   unique: 78, error: -2 (No such file or directory), outsize: 16
unique: 79, opcode: LOOKUP (1), nodeid: 1009995, insize: 46, pid: 3550
   Lookup 1009995 Qux.mp4
   unique: 79, error: -2 (No such file or directory), outsize: 16

jbeich avatar Sep 26 '16 06:09 jbeich

I forgot to read Known Problems in README.md. But still, that sentence is a bit vague whether the issue affects only Samsung or any phone past a specific Android version.

jbeich avatar Sep 26 '16 06:09 jbeich

samsung removed google extension (partial writes/truncate and 64 bit version of partial read) from their firmware for no good reason, so there's no way to implement write().

libmtp loads all data from file/object, patch it and write it back, which is completely inefficient in case of sequential writes.

afs-fuse-mount should output: your device does not have android EditObject extension, mounting read-only in this case.

If it's not, then something wrong with fuse integration (probably because of different platform)

whoozle avatar Sep 26 '16 10:09 whoozle

aft-mtp-mount doesn't print anything when mounting. I cannot reproduce on Ubuntu 16.10 amd64. And the device vendor is not Samsung. aft-mtp-cli writes fine which rules out the difference in libusb implementation.

It seems FreeBSD returns ENOSYS if .create() isn't defined via struct fuse_lowlevel_ops, confirmed by adding a stub. What Linux does in this case? simple-mtpfs works fine because it has explicit .create().

jbeich avatar Sep 26 '16 22:09 jbeich

Linux's fuse calls mknod in this case, but I can implement create, no worries.

whoozle avatar Sep 27 '16 16:09 whoozle

added create in 973fe4dc4abf445e3f0379d8c672f4cb38e01bb9 can you test it?

whoozle avatar Sep 27 '16 16:09 whoozle

EDIT: replaced -o debug with -d output as it provides more details.

With 973fe4d files can be successufully created but aft-mtp-mount just hangs then silently exits. Return code is 0, no core dump.

$ aft-mtp-mount -o allow_other /mnt

$ cp Qux.mp4 /mnt/SD\ card/Movies
cp: /mnt/SD card/Movies/Qux.mp4: Operation timed out

$ ls /mnt
$ umount /mnt
umount: /mnt: not a file system root directory

$ adb shell ls -l /sdcard/Movies
-rw-rw---- 1 root sdcard_rw 270243798 2014-05-05 04:02 Foo.mp4
-rw-rw---- 1 root sdcard_rw 639406586 2016-08-23 11:04 Bar.mp4
-rw-rw---- 1 root sdcard_rw         0 2016-09-27 18:32 Qux.mp4

where -d shows what happens just before

$ aft-mtp-mount -d -o allow_other /mnt
0:0, index: 0, enpoints: 1
0:0, index: 0, enpoints: 1
0:0, index: 0, enpoints: 3
FUSE library version: 2.9.5
unique: 1, opcode: INIT (26), nodeid: 0, insize: 56, pid: 63026
INIT: 7.8
flags=0x00000000
max_readahead=0x00010000
Init: fuse proto version: 7.8, capability: 0x00000000, async read: 0, max readahead: 65536, max write: 131072
   INIT: 7.19
   flags=0x00000000
   max_readahead=0x00010000
   max_write=0x00100000
   max_background=0
   congestion_threshold=0
   unique: 1, success, outsize: 40
[...]
unique: 120, opcode: LOOKUP (1), nodeid: 1009995, insize: 48, pid: 63046
   Lookup 1009995 Qux.mp4
   unique: 120, error: -2 (No such file or directory), outsize: 16
unique: 121, opcode: LOOKUP (1), nodeid: 1009995, insize: 48, pid: 63046
   Lookup 1009995 Qux.mp4
   unique: 121, error: -2 (No such file or directory), outsize: 16
unique: 122, opcode: LOOKUP (1), nodeid: 1009995, insize: 48, pid: 63046
   Lookup 1009995 Qux.mp4
   unique: 122, error: -2 (No such file or directory), outsize: 16
unique: 123, opcode: CREATE (35), nodeid: 1009995, insize: 56, pid: 63046
   Create 1009995 Qux.mp4 0x000081a4
   creating object in storage 10001, parent: 0000270b
   new object id 25961
   unique: 123, success, outsize: 152
unique: 124, opcode: GETATTR (3), nodeid: 1025961, insize: 40, pid: 63046
   GetAttr 1025961
   unique: 124, success, outsize: 112
unique: 125, opcode: GETATTR (3), nodeid: 1025961, insize: 40, pid: 63046
   GetAttr 1025961
   unique: 125, success, outsize: 112

$ echo $?
0

jbeich avatar Sep 27 '16 17:09 jbeich

Looks more or less fine for me(except that double LOOKUP/GETATTR requests, if it matters) Also, can you run it using gdb please? Probably fuse handles SIGSEGV or another signal, and then exits.

gdb ./build/mtp/aft-mtp-mount
r -d -o allow_other /mnt

And if gdb does not catch any signals, can you check if fuse has different main function in its FreeBSD port?

I mean that part:

    if (fuse_parse_cmdline(&args, &mountpoint, &multithreaded, &foreground) != -1 &&
        (ch = fuse_mount(mountpoint, &args)) != NULL) {
        struct fuse_session *se;

        se = fuse_lowlevel_new(&args, &ops,
                       sizeof(ops), NULL);
        if (se != NULL) {
            if (fuse_set_signal_handlers(se) != -1)
            {
                fuse_session_add_chan(se, ch);
                if (fuse_daemonize(foreground) == -1)
                    perror("fuse_daemonize");
                err = (multithreaded? fuse_session_loop_mt: fuse_session_loop)(se);
                fuse_remove_signal_handlers(se);
                fuse_session_remove_chan(ch);
            }
            fuse_session_destroy(se);
        }
        fuse_unmount(mountpoint, ch);
    }
    fuse_opt_free_args(&args);

whoozle avatar Sep 28 '16 10:09 whoozle

any news? :(

whoozle avatar Nov 27 '16 22:11 whoozle

libfuse hangs if built with -O0 -g which seems like a rabbit hole given the FreeBSD port lacks a maintainer.

can you run it using gdb please?

gdb itself crashes, lldb doesn't catch anything here. Note, lldb cannot follow fork().

Probably fuse handles SIGSEGV or another signal, and then exits.

Well, gdb/lldb handles few signals by default. Do you have a specific one in mind?

can you check if fuse has different main function in its FreeBSD port?

libfuse on FreeBSD uses different helper utility under the hood but the API is same. The snippet looks like example/hello_ll.c in libfuse source which works fine but doesn't implement writing. Maybe list libfuse consumers with similar write ops to aft-mtp-mount for me to check. For now, I can only confirm writing works fine in simple-mtpfs, exfat, gitfs and, IIRC, smbnetfs.

Note, aft-mtp-mount works fine for reading and listing files.

jbeich avatar Nov 28 '16 08:11 jbeich

thanks a lot for the update! I will investigate more. :+1:

whoozle avatar Nov 28 '16 14:11 whoozle

Do you have a specific one in mind?

I think, all signals that kill process by default are handled by gdb/lldb. I thought about SIGSEGV (11)

By the way, I have an idea - can you pull, build and run with -f option, this will prevent fuse from forking and allow you to debug with lldb.

whoozle avatar Mar 10 '17 00:03 whoozle

-d output was provided several posts ago. Not sure how -f would help. The process exits on timeout without receiving a fatal signal to catch using a debugger. However, with small files (a few Kb large) the process sometimes persists over a write operation but is stuck in libfuse for a few minutes.

$ aft-mtp-mount --help
[...]
FUSE options:
    -d   -o debug          enable debug output (implies -f)
[...]

$ (sleep 5 && cp small.txt /mnt/Internal\ shared\ storage/) &
$ (sleep 60 && cp Qux.mp4 /mnt/Internal\ shared\ storage/Movies/) &

$ lldb -- =aft-mtp-mount -f -o allow_other /mnt
(lldb) target create "/usr/local/bin/aft-mtp-mount"
Current executable set to '/usr/local/bin/aft-mtp-mount' (x86_64).
(lldb) settings set -- target.run-args  "-f" "-o" "allow_other" "/mnt"
(lldb) r
Process 71595 launching
Process 71595 launched: '/usr/local/bin/aft-mtp-mount' (x86_64)
Process 71595 stopped
* thread #1, stop reason = signal SIGSTOP
    frame #0: libc.so.7`_umtx_op at _umtx_op.S:3
(lldb) bt
* thread #1, stop reason = signal SIGSTOP
  * frame #0: libc.so.7`_umtx_op at _umtx_op.S:3
    frame #1: libc.so.7`_sem_clockwait_np [inlined] usem_wait(sem=<unavailable>, clock_id=0, rqtp=<unavailable>, rmtp=<unavailable>) at cancelpoints_sem_new.c:370
    frame #2: libc.so.7`_sem_clockwait_np(sem=<unavailable>, clock_id=<unavailable>, flags=<unavailable>, rqtp=<unavailable>, rmtp=<unavailable>) at cancelpoints_sem_new.c:429
    frame #3: libfuse.so.2`fuse_session_loop_mt(se=0x000000080245e050) at fuse_loop_mt.c:242
    frame #4: aft-mtp-mount`main(argc=5, argv=0x00007fffffffe3a0) at fuse_ll.cpp:809
    frame #5: aft-mtp-mount`_start(ap=<unavailable>, cleanup=<unavailable>) at crt1.c:72
(lldb) f 3
frame #3: libfuse.so.2`fuse_session_loop_mt(se=0x000000080245e050) at fuse_loop_mt.c:242
   239          if (!err) {
   240                  /* sem_wait() is interruptible */
   241                  while (!fuse_session_exited(se))
-> 242                          sem_wait(&mt.finish);
   243
   244                  pthread_mutex_lock(&mt.lock);
   245                  for (w = mt.main.next; w != &mt.main; w = w->next)
(lldb) p errno
(int) $1 = 16
(lldb) fr v
(fuse_session *) se = 0x000000080245e050
(fuse_mt) mt = {
  lock = 0x000000080241c6c0
  numworker = 2
  numavail = 2
  se = 0x000000080245e050
  prevch = 0x000000080241b3c0
  main = {
    prev = 0x000000080260d000
    next = 0x00000008024450c0
    thread_id = 0x0000000802416000
    bufsize = 0
    buf = 0x0000000000000000
    mt = 0x0000000000000000
  }
  finish = {
    _magic = 1936026930
    _kern = (_count = 2147483648, _flags = 0)
    _padding = 0
  }
  exit = 0
  error = 0
}
(int) err = 0
(fuse_worker *) w = <variable not available>
(lldb) c
Process 71595 resuming
Process 71595 exited with status = 0 (0x00000000)

$ ls /mnt
$ ls /mnt/Internal\ shared\ storage
ls: /mnt/Internal shared storage: No such file or directory

jbeich avatar Mar 12 '17 02:03 jbeich

@jbeich you're correct that as of your writing FreeBSD's fuse driver didn't fallback to FUSE_MKNOD in the event that the server didn't implement FUSE_CREATE. However, that is fixed in the projects/fuse2 branch and will soon be merged back to head. https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236

asomers avatar Jun 01 '19 16:06 asomers

any news on this one?

whoozle avatar Dec 29 '20 00:12 whoozle

@whoozle the FUSE_MKNOD fallback was merged to head in https://svnweb.freebsd.org/changeset/base/350665 , and released in FreeBSD 12.1-RELEASE in November 2019.

asomers avatar Dec 29 '20 00:12 asomers