/*  Perry Kivolowitz
    Example of file operations.
*/
        .text
        .global main
        .align  2

/*  This program will
    * open() a file in the current directory,
    * write() some text to it, 
    * seek back to the beginning of the file,
    * read() each line, printing it
    * close() the file
*/

retval  .req    w27
fd 		.req	w28

main:   stp     x29, x30, [sp, -16]!
        stp     x27, x28, [sp, -16]!
        bl      open_file

        // w0 will contain either the file descriptor of the new
        // file or -1 for a failure. Note that the value in w0
        // has also been copied to "fd" - a register alias.
        cmp 	w0, wzr
        bge 	1f

        // If we get here, the open has failed. Use perror() to
        // print a meaningful error and branch to exit. The return
        // code of the program will be set to non-zero inside fail.
        ldr     x0, =fname
        bl      fail
        b       99f

1:		// When we get here, the file is open. Write some data to it.
        // If write_file returns non-zero, it signifies an error. If
        // so, branch to the file closing code since the file is open
        // after printing an error message.
        bl		write_data
        cbz	    w0, 10f

        // If we get here, there was an error in write_data. Print
        // a reasonable error message then branch to the clean usleep
        // code.
        ldr     x0, =wf     // load legend
        bl      fail        // print error
        b       50f         // branch to clean up.

        // Seek back to position zero preparing to read the file back.
        // The return value in x0 (off_t) is the return value of
        // lseek(). 
10:     bl      seek_zero
        cbz     x0, 20f

        // If we get here, the seek failed. Cause a reasonable
        // message to be printed then branch to the clean up code.
        ldr     x0, =sf
        bl      fail
        b       50f

20:     // When we get here, we have to read from the file and print
        // the results. To ignore the complexity of memory allocation
        // and buffer overrun potential, we'll read one character at a 
        // time looking the end-of-file.

        // ssize_t read(int fildes, void *buf, size_t nbyte);
        mov     w0, fd
        ldr     x1, =buffer
        mov     x2, 1
        bl      read
        // Check the return value - should be 1.
        cbz     x0,50f      // zero means EOF - that's OK.
        // If x0 is negative, that IS a problem.
        cmp     x0, xzr
        bge     25f
        // The return value is negative - this is an error.
        ldr     x0, =rf
        bl      fail
        b       99f

25:     // Write the character sitting in buffer to the console.
        mov     w0, 1
        ldr     x1, =buffer
        mov     x2, 1
        bl      write
        // We will ignore the return value for the sake of brevity.
        // There are plenty of examples of handling a potential error
        // elsewhere in this code.
        // --
        b       20b

        // When we get here, we are done. Close the file.
50:		mov		w0, fd
        bl 		close
        mov 	retval, wzr

99:     ldp     x27, x28, [sp], 16
        ldp     x29, x30, [sp], 16
        mov     w0, retval
        ret

/*	open_file()
    This function attempts to open a file for both reading and
    writing. Return values will be checked to ensure the file is
    opened. If successful, the fd is returned (and is squirreled
    away in register "fd"). If unsuccessful, the -1 returned by
    open() is passed back to the caller.

    Explanation of the magic numbers:

    int open(const char *pathname, int flags, mode_t mode);

    octal 102 for flags is O_RDRW | O_CREAT
    octal 600 for mode is rw------- i.e. read and write for
        the owner but no permissions for anyone else.

	There is a version of open() that takes two parameters. However,
	if O_CREAT is specified, the three parameter version is required.
*/

        .equ    O_FLAGS, 0102
        .equ    O_MODE, 0600

open_file:
        stp      x29, x30, [sp, -16]!
        ldr      x0, =fname
        mov      w1, O_FLAGS
        mov      w2, O_MODE
        bl       open
        mov      fd, w0
        ldp      x29, x30, [sp], 16
        ret


/*  This function uses perror() to print a meaningful error
    message in the event of a failure. The string value
    passed to perror() arrives to us as a pointer in x0.
*/

fail:
        stp     x29, x30, [sp, -16]!
        bl 		perror
        mov		retval, 1
        ldp     x29, x30, [sp], 16
        ret

/*  ssize_t write(int fd, const void *buf, size_t count);

	This function will write a string to the file descriptor contained
	in "fd" (a register alias).
*/

write_data:
        stp     x29, x30, [sp, -16]!
        str     x20, [sp, -16]!
        mov     w0, fd              // file descriptor
        ldr     x1, =txt            // address to print from
        ldr     x2, =txt_s          // load pointer to size
        ldr     x2, [x2]            // dereference the pointer
        mov     w20, w2             // need this value for error check.
        bl      write
        cmp     x0, x20             // Did we write the expected amount?
        bne     90f
        // successful write - return 0
        mov     x0, xzr
        b       99f
90:     // failure - ensure we return non-zero!
        mov     x0, 1
99:     ldr     x20, [sp], 16
        ldp     x29, x30, [sp], 16
        ret

/*  off_t lseek(int fd, off_t offset, int whence);
*/
seek_zero:
        stp     x29, x30, [sp, -16]!
        mov     w0, fd          // file descriptor
        mov     x1, xzr         // beginning of file
        mov     w2, wzr         // SEEK_SET - absolute offset
        bl      lseek
        ldp     x29, x30, [sp], 16
        ret

        .data
prog:	.asciz	"file_ops"
wf:     .asciz  "write failed"
rf:     .asciz  "read failed"
sf:     .asciz  "lseek failed"
fname:	.asciz	"test.txt"
txt:	.asciz	"some data\n"
txt_s:	.word	txt_s - txt - 1		// strlen(txt)
buffer: .word   0
        .end
