linuxassemblysystem-callsarm64armv8

Open Syscall 56 ARMv8 fails


I'm trying to open my Xbox Gamepad at /dev/input/js0 using syscall 56. I have verified the devices' existence at the given path. Including through a test program written in C with open(). However, the program continues to branch FailExit.

.global main

.section .text
.align 2
main:
    ldr x0, =GamepadPath
    mov x1, #0
    mov x8, #56
    svc #0

    cmp x0, #0
    blt FailExit 

    mov x0, #1
    ldr x1, =SuccessMsg
    mov x2, #25
    mov x8, #64
    svc #0 

    mov x0, #0
    mov x8, #93
    svc #0

FailExit:
    mov x0, #1
    ldr x1, =ErrorMsg
    mov x2, #25
    mov x8, #64
    svc #0
    mov x0, #0
    mov x8, #93
    svc #0

.section .data

.section .rodata
GamepadPath: .asciz "/dev/input/js0"
SuccessMsg: .asciz "Gamepad Connected\n\n"
ErrorMsg: .asciz "Gamepad Not Available\n\n"

.section .bss

I've attempt to understand several references including:

openat(2) - Linux man page

ARM64.Syscall.sh

What leaves me confused is that Syscall 56, OpenAt's first argument at x0 is suppose to be a return value set by syscall Open, which I cannot find on any reference for ARMv8 Linux.


Solution

  • linux.die.net tends to have outdated versions of the man pages. man7.org is better. Here you can read:

    The dirfd argument is used in conjunction with the pathname argument as follows:

    • If the pathname given in pathname is absolute, then dirfd is ignored.

    This is your situation; /dev/input/js0 is an absolute path (begins with /).

    So just pass whatever you like (e.g. 0 or -1) as the dirfd first argument to the openat system call. This means that the pathname becomes the second argument, and the flags the third argument.

    Try

        mov x0, #-1
        ldr x1, =GamepadPath
        mov x2, #0
        mov x8, #56
        svc #0
    

    The value added by openat is for relative paths like foo/bar/baz. With normal open, this always gets resolved relative to the current working directory; so if your current working directory is /blah, then open("foo/bar/baz") will always open /blah/foo/bar/baz. You can get this same behavior from openat by passing the special value AT_FDCWD (defined in <fcntl.h> as -100) as the first argument. So in your code, if you want to replicate open when using a relative path, just do mov x0, #-100 and populate x1, x2, x3 with the remaining arguments.

    But you can also open a directory first and pass its fd instead. Thus if your current working directory is /blah but you really want to open relative to /gurgle, you can do

    dirfd = openat(-1, "/gurgle", O_RDONLY | O_DIRECTORY);
    fd = openat(dirfd, "foo/bar/baz", O_RDONLY);
    

    This opens /gurgle/foo/bar/baz.

    Using a file descriptor can help avoid race conditions. Suppose you want to create a file /foo/new but only if /foo/old already exists in that directory. If you do the obvious

    fdold = open("/foo/old", O_RDONLY);
    if (fdold >= 0) {
        fdnew = open("/foo/new", O_CREAT | O_WRONLY, 0644)
    }
    

    then the problem is that in between your two calls to open, someone could rename and recreate the directory /foo, or recreate it as a symlink pointing somewhere else. You might then end up creating new in a directory that doesn't contain old.

    openat lets you avoid this:

    dirfd = openat(-1, "/foo", O_RDONLY | O_DIRECTORY);
    fdold = openat(dirfd, "old", O_RDONLY);
    if (fdold) {
        openat(dirfd, "new", O_CREAT | O_WRONLY, 0644)
    }
    

    Now the two files will definitely exist in the same directory, regardless of whether that directory's name has changed from foo to something else in the meantime.