Search code examples
linuxassemblyx86-64nasmsystem-calls

How to append to a file using x86-64 Linux system calls?


I'm creating a file and then opening it using system calls.

Creating:

mov rax, 85
mov rdi, path
mov rsi, 400
syscall

Opening:

mov rax, 2
mov rdi, path
mov rsi, 2 ; I found somewhere that I should use 400 to append
syscall
mov r8, rax ; save the file handle

Writing:

mov rax, 1
mov rdi, r8
mov rsi, output
mov rdx, length
syscall

After I complete these steps I close the file naturally, however I am unable to append to it and each time I perform these actions it just rewrites the content of the file, but for example I calculated the first n prime numbers and stored them away in some chunk of memory and now I'd like to write those n prime numbers down in the file, but I'm not able to do so without appending more lines.

Also is there a way to not specify the length of the string for write in rdx, but instead have the string be terminated by a /0?


Solution

  • O_APPEND by itself would imply O_RDONLY (because it's 0), not O_WRONLY or O_RDWR. Opening read-only for append isn't usually useful. As in C, you need to OR together multiple flags to get a file descriptor you can use to write at the end of the file, even if its length is changed by something other than than your writes.


    For appending to a file use the O_APPEND = 0x400 flag as int flags parameter in rsi. So you were close: it's a value of hexadecimal 400 and not decimal 400 as you attempted.

    Here is a list of the raw int flags values in hexadecimal:

    O_ACCMODE                        = 0x3
    O_APPEND                         = 0x400
    O_ASYNC                          = 0x2000
    O_CLOEXEC                        = 0x80000
    O_CREAT                          = 0x40
    O_DIRECT                         = 0x4000
    O_DIRECTORY                      = 0x10000
    O_DSYNC                          = 0x1000
    O_EXCL                           = 0x80
    O_FSYNC                          = 0x101000
    O_LARGEFILE                      = 0x0
    O_NDELAY                         = 0x800
    O_NOATIME                        = 0x40000
    O_NOCTTY                         = 0x100
    O_NOFOLLOW                       = 0x20000
    O_NONBLOCK                       = 0x800
    O_RDONLY                         = 0x0
    O_RDWR                           = 0x2
    O_RSYNC                          = 0x101000
    O_SYNC                           = 0x101000
    O_TRUNC                          = 0x200
    O_WRONLY                         = 0x1
    

    So this should work:

    mov   rax, 2
    lea   rdi, [rel path]
    mov   rsi, 0x441        ; O_CREAT| O_WRONLY | O_APPEND
    mov   edx, 0q666        ; octal permissions in case O_CREAT has to create it
    syscall
    mov   r8, rax      ; save the file descriptor
    

    I added the three values to create the file if it doesn't exist or open it in write_only mode if it does exist. You could define them as assemble-time equ constants so you could actually write mov esi, O_CREAT| O_WRONLY | O_APPEND in your source.

    Also, make sure to supply a value for the 3rd arg (permissions) any time you include O_CREAT. Otherwise the file could get created with random garbage for its permissions, including possibly setuid + exec. Normally you pass octal 666 (NASM 0q666) and let the user's umask knock off write permission for other and optionally group.

    If you don't actually want open to create it if it doesn't already exist, omit O_CREAT. Then it will return -ENOENT.


    Also is there a way to not specify the length of the string in rdx, but instead have the string be terminated by a /0?

    In short: No.
    Create a strlen function and pass the result in rdx. Kernel system calls take data to be read/written on file descriptors as buffer + length, only pathnames as C implicit-length strings.