sylware / nyanlinux (public) (License: AFFERO GPLv3) (since 2019-09-09) (hash sha1)
scripts for a lean, from scratch, amd hardware, linux distro

/files/asm/inotify.asm (355e07c2aa5382dabca220be20659ae24b433704) (16743 bytes) (mode 100644) (type blob)

; INVOKATION:
; 	inotify /name0/name1/.../name_to_look_for
; RETURN VALUES:
; 	0 if found, an error number if not. See end of this file for specifics.

; to build, an example using nasm:
; nasm -f elf64 inotify.asm
; ld -s inotify.o

; I tried to avoid as much as possible the use of nasm preprocessor (super
; powerful). Worse case scenario, use preprocessor features which are ez to
; implement (not the powerful features with high technical cost of
; implementation for obvious reasons). To help, I used the equ instruction
; instead of a preprocessor macro definition. That to allow ez porting to any
; other/future intel syntax assembler.

; notes:
; - for a string, the 'size' is the whole including the terminating null char
; - for a string, the 'length' is the number of actual char without the
;   terminating null char

; This uses a blocking file descriptor with a select syscall for timeout
; estimation. Should not use select (that binary array passing is weird),
; should not use a blocking file descriptor.

; the kernel copies only a set of whole events in the read buffer, with the
; the name padded to the hdr size with zeros.

; At the time of writing, linux has a default max count of fds which is 1024
; and, on x86_64, a stack sz limit of 8192 bytes, those per userland task.

; you need x86_64 ABI document from AMD
; you need x86_64 ISA doc, from Intel or AMD.

	[section .data]
;******************************************************************************
; the converted linux uapi header
;/*
; * struct inotify_event - structure read from the inotify device for each event
; *
; * When you are watching a directory, you will receive the filename for events
; * such as IN_CREATE, IN_DELETE, IN_OPEN, IN_CLOSE, ..., relative to the wd.
; */
inotify_event_watch_desc:	equ 0	;s32 /* watch descriptor */
inotify_event_mask:		equ 4	;u32 /* watch mask */
inotify_event_cookie:		equ 8	;u32 /* cookie to synchronize two events */
; XXX: the event name + terminating null char has its size zeros padded on
;      inotify_event_hdr_sz!
;      (data padding is common for linux user exchanged data)
inotify_event_name_padded_sz:	equ 12	;u32 /* length (including nulls) of name */
	inotify_event_hdr_sz:	equ 16
inotify_event_name:		equ 16	;char name[] /* stub for possible name */

;/* the following are legal, implemented events that user-space can watch for */
IN_ACCESS:		equ	0x00000001	;/* File was accessed */
IN_MODIFY:		equ	0x00000002	;/* File was modified */
IN_ATTRIB:		equ	0x00000004	;/* Metadata changed */
IN_CLOSE_WRITE:		equ	0x00000008	;/* Writtable file was closed */
IN_CLOSE_NOWRITE:	equ	0x00000010	;/* Unwrittable file closed */
IN_OPEN:		equ	0x00000020	;/* File was opened */
IN_MOVED_FROM:		equ	0x00000040	;/* File was moved from X */
IN_MOVED_TO:		equ	0x00000080	;/* File was moved to Y */
IN_CREATE:		equ	0x00000100	;/* Subfile was created */
IN_DELETE:		equ	0x00000200	;/* Subfile was deleted */
IN_DELETE_SELF:		equ	0x00000400	;/* Self was deleted */
IN_MOVE_SELF:		equ	0x00000800	;/* Self was moved */

;/* the following are legal events.  they are sent as needed to any watch */
IN_UNMOUNT:		equ	0x00002000	;/* Backing fs was unmounted */
IN_Q_OVERFLOW:		equ	0x00004000	;/* Event queued overflowed */
IN_IGNORED:		equ	0x00008000	;/* File was ignored */

;/* helper events */
IN_CLOSE:		equ	(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) ;/* close */
IN_MOVE:		equ	(IN_MOVED_FROM | IN_MOVED_TO) ;/* moves */

;/* special flags */
IN_ONLYDIR:		equ	0x01000000	;/* only watch the path if it is a directory */
IN_DONT_FOLLOW:		equ	0x02000000	;/* don't follow a sym link */
IN_EXCL_UNLINK:		equ	0x04000000	;/* exclude events on unlinked objects */
IN_MASK_ADD:		equ	0x20000000	;/* add to the mask of an already existing watch */
IN_ISDIR:		equ	0x40000000	;/* event occurred against dir */
IN_ONESHOT:		equ	0x80000000	;/* only send event once */

;/*
; * All of the events - we build the list by hand so that we can add flags in
; * the future and not break backward compatibility.  Apps will get only the
; * events that they originally wanted.  Be sure to add new events here!
; */
IN_ALL_EVENTS:		equ	(IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE | \
			 	IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM | \
			 	IN_MOVED_TO | IN_DELETE | IN_CREATE | IN_DELETE_SELF | \
			 	IN_MOVE_SELF)

;/* Flags for sys_inotify_init1.  */
; XXX:linux is reaching common flag values for all archs!
O_CLOEXEC:		equ	0o2000000
O_NONBLOCK:		equ	0o0004000

IN_CLOEXEC:		equ	O_CLOEXEC
IN_NONBLOCK:		equ	O_NONBLOCK
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; the linux syscall ABI on x86_64
; syscall number is in rax register
; syscall parameters in the following ordered registers:
;     rdi
;     rsi
;     rdx
;     r10
;     r8
;     r9
; syscalls clobbers r11, rcx and rax, cause of sysret(syscall) machine instruction
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; input:s32 fd, u64 ptr to buf, u64 count
; output:s64 count
sysc_read:		equ	0
;-------------------------------------------------------------------------------
; input:s32 fd, u64 ptr to buf, u64 count
; output:s64 count
sysc_write:		equ	1
;-------------------------------------------------------------------------------
; input:s32 flags
; output:s32 fd
sysc_inotify_init1:	equ	294
;-------------------------------------------------------------------------------
; input:s32 fd, u64 ptr to pathname, u32 mask
sysc_inotify_add_watch:	equ	254
;-------------------------------------------------------------------------------
; input:s32 exit code
sysc_exit_group: 	equ	231
;-------------------------------------------------------------------------------
; input: s32 max fd + 1, u64 ptr on array of read rdy fd bits, u64 ptr on array
;        of write rdy fd bits, u64 ptr on array of special rdy fd bits, u64 ptr
;        on timeval(tv_sec,tv_usec) timeout
; output:s32 exit code(-EINTR...) and proper fd bits set in their respective
;        array
sysc_select:		equ	23
;-------------------------------------------------------------------------------
;******************************************************************************




;******************************************************************************
;------------------------------------------------------------------------------
; XXX: wrong way of doing this. The right way, query the limits of the mounted
;      file system mounted there. _Usually_ this limit is 256 bytes.
path_sz_max:		equ 1024
;------------------------------------------------------------------------------
time_out:		equ 4		; 4 seconds is huge, should be passed on the command line
events_max		equ 1024	; process a maximum of 1024 events before bailing out, should be passed on the command line
;------------------------------------------------------------------------------
eintr:			equ 4
errno_max:		equ 4095
;===============================================================================
; to reach more readability and ease register manipulation in the main function,
; use the stack for storage
path_start:				equ 8*0	; args[1]
path_null:				equ 8*1
path_last_name_separator:		equ 8*2	; ptr on last '/' (separator of path names) of path
path_last_name_start:			equ 8*3
path_last_name_len			equ 8*4
;-------------------------------------------------------------------------------
inotify_fd:				equ 8*5
;-------------------------------------------------------------------------------
fd_set_read:				equ 8*6	; 64 fds is fairly enough. On linux x86_64, max default is 1024 bits for select fds per process. You could do more since ulimits file_max is dynamic, but you should not with select because you may end up passing huge bits array
timeval_tv_sec:				equ 8*7
timeval_tv_usec:			equ 8*8
;-------------------------------------------------------------------------------
events_remaining:			equ 8*9 ; count to limit the number of processed events
;-------------------------------------------------------------------------------
inotify_event_buf:			equ 8*10	; 512 bytes for the read buffer (should accomodate really nasty filesystems already)
	inotify_event_buf_sz:			equ 8*64
;-------------------------------------------------------------------------------
start_stack_room:			equ 8*10 + inotify_event_buf_sz
;===============================================================================
;******************************************************************************




;******************************************************************************
	[section .text]

; _start is the ELF symbol which defines the program entry point
	[global _start:function]
_start:
	mov rax, [rsp]			; x86_64 ABI->argc
	cmp rax,1			; we have always the name of the program, then we want more than 1 argument
	je .exit_no_error

	mov rbx, [rsp + 2 * 8]		; x86_64 ABI->ptr in stack on array of arg ptrs, here rbx = args[1]

	; make some room on the stack
	sub rsp, start_stack_room
	mov [rsp + path_start], rbx

;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	; rbx = ptr on path start
;-------------------------------------------------------------------------------

	; forward lookup for the the terminating null character which ends the file path
	cld
	xor al,al			; null character
	mov rdi, [rsp + path_start]
	mov rcx, path_sz_max
	repne scasb			; the flags are from the cmp operation, not from counter termination condition
	jne .exit_path_too_big

	dec rdi				; rewind 1 byte to point on the terminating null character

	cmp rdi, [rsp + path_start]	; empty path, we exit here but the following code should not presume a non-empty path
	je .exit_no_error

	mov [rsp + path_null], rdi
	
;-------------------------------------------------------------------------------

	; backward lookup for the last '\' separator for path names
	std
	mov al, '/'
	mov rdi, [rsp + path_null]
	dec rdi						; point on the char right before the terminating null char
	mov rcx, [rsp + path_null]
	sub rcx, [rsp + path_start]			; n chars to compare, len of path (= ptr on terminating null char - ptr on first char)
	repne scasb					; the flags are from the cmp operation, not from counter termination condition
	jne .exit_no_path_separator

	inc rdi						; forward 1 byte to point on '/'
	mov [rsp + path_last_name_separator], rdi

;-------------------------------------------------------------------------------

	mov rax, sysc_inotify_init1
	; arg 0
	xor rdi, rdi

	syscall

	cmp rax, -errno_max
	jnb .exit_inotify_init_failed	

	mov [rsp + inotify_fd], rax

;-------------------------------------------------------------------------------

	mov rax, sysc_inotify_add_watch
	; arg 0
	mov rdi, [rsp + inotify_fd]
	; arg 1
	mov rbx, [rsp + path_last_name_separator]
	mov rsi, [rsp + path_start]
	cmp rbx, rsi
	je .file_at_root
		mov byte [rbx], 0	; do nullify the '/' if not at root
.file_at_root:
	; arg 2
	mov rdx, IN_CREATE|IN_ONLYDIR

	syscall

	cmp rax, -errno_max
	jnb .exit_inotify_add_watch_failed
	
;===============================================================================
; Here start the main loop, we have a event reader and an event scanner.
; Based on linux code, only whole events are read, and there is no short read.
; Linux rounds each event size is a modulo of inotify_event_hdr size, zerop
; added.

	mov rax, [rsp + path_last_name_separator]
	inc rax
	mov [rsp + path_last_name_start], rax

	mov rbx, [rsp + path_null]
	sub rbx, rax				; len of path last name
	mov [rsp + path_last_name_len], rbx	; _not_ including the terminating null char, we use len not size

	mov qword [rsp + events_remaining], events_max	; if the file system is really active in the watched dir, it can last a very long time despite the time out, cap the number of processed events

	; 4 sec timeout granularity, we do not reset it each time we call linux select to wait the same amount of time whatever happens
	mov qword [rsp + timeval_tv_sec], time_out
	mov qword [rsp + timeval_tv_usec], 0

.select:
	; zero the read fd_set, and set the inotify fd bit
	mov rcx, [rsp + inotify_fd]
	mov qword [rsp + fd_set_read], 1
	shl qword [rsp + fd_set_read], cl	; 8 bins cl is enough because the value of our file destriptor is very low

	mov rax, sysc_select
	mov rdi, [rsp + inotify_fd]
	inc rdi					; max fd + 1
	lea rsi, [rsp +  fd_set_read]
	xor rdx, rdx
	xor r10, r10
	lea r8, [rsp + timeval_tv_sec]

	syscall

	cmp rax, -eintr				; got a signal, don't care and keep going
	je .select

	cmp rax, -errno_max
	jnb .exit_select_failed

	test rax, rax
	jz .exit_select_timedout

	; since we are expecting only 1 type of event from select, it must be our inotify events

;-------------------------------------------------------------------------------

.read:
	mov rax, sysc_read
	mov rdi, [rsp + inotify_fd]
	lea rsi, [rsp + inotify_event_buf]
	mov rdx, inotify_event_buf_sz

	syscall

	cmp rax, -eintr	; a signal was received before anything was read, don't care then restart
	je .read

	; error, something is really wrong, bail out
	cmp rax, -errno_max
	jnb .exit_inotify_read_failed

;^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
	; rax = the number of read bytes
;-------------------------------------------------------------------------------

;-------------------------------------------------------------------------------
; next is the scanner of the read events
;-------------------------------------------------------------------------------

.scan:
	mov r8, rax				; the right above number of read bytes
	lea r9, [rsp + inotify_event_buf]
	mov r10, [rsp + path_last_name_start]
	mov r11, [rsp + path_last_name_len]
	cld					; move forward in strings

.scan_loop:
	xor r15, r15
	mov r15d, [r9 + inotify_event_name_padded_sz]

	test r15, r15
	jz .next_event

.event_have_name:
	; look for the terminating null char
	xor al, al	; '\0'

	; r14 = rdi = inotify_event_name_start
	mov r14, r9
	add r14, inotify_event_hdr_sz
	mov rdi, r14

	mov rcx, r15	; don't go beyond the event name upper bound
	repne scasb
	jne .exit_event_name_is_corrupted	; no null char? fishy fishy, bail out

	dec rdi		; rewind rdi one byte to point on terminator null char
	sub rdi, r14	; (ptr on terminating null char - ptr on first char) = str len, _not_ str sz

	cmp rdi, r11
	jne .next_event

	; cmp the names
	mov rsi, r14	; inotify_event_name_start
	mov rdi, r10	; path_last_name_start
	mov rcx, r11	; inotify_event_name_len = path_last_name_len
	repe cmpsb
	jne .next_event

	; **** FOUND *****
	jmp .exit_no_error 

.next_event:	
	dec qword [rsp + events_remaining]
	jz .exit_too_many_events
	
	sub r8, inotify_event_hdr_sz
	sub r8, r15
	jz .select

	add r9, inotify_event_hdr_sz
	add r9, r15
	jmp .scan_loop

;******************************************************************************
.exit_no_error:
	xor edi, edi		; ret = 0
	jmp .exit_group
;******************************************************************************
;******************************************************************************
.exit_path_too_big:
	mov edi, 1		; ret = 1
	jmp .exit_group
;******************************************************************************
.exit_no_path_separator:
	mov edi, 2		; ret = 2
	jmp .exit_group
;******************************************************************************
.exit_inotify_init_failed:
	mov edi, 3		; ret = 3
	jmp .exit_group
;******************************************************************************
.exit_inotify_add_watch_failed:
	mov edi, 4		; ret = 4
	jmp .exit_group
;******************************************************************************
.exit_select_failed:
	mov edi, 5		; ret = 5
	jmp .exit_group
;******************************************************************************
.exit_select_timedout:
	mov edi, 6		; ret = 6
	jmp .exit_group
;******************************************************************************
.exit_inotify_read_failed:
	mov edi, 7		; ret = 7
	jmp .exit_group
;******************************************************************************
.exit_event_name_is_corrupted:
	mov edi, 8		; ret = 8
	jmp .exit_group
;******************************************************************************
.exit_too_many_events:
	mov edi, 9		; ret = 9
	jmp .exit_group
;******************************************************************************
;******************************************************************************
.exit_group:
	mov rax, sysc_exit_group
	syscall			; never returning syscall
;******************************************************************************



Mode Type Size Ref File
100644 blob 5 8eba6c8dd4dcaf6166bd22285ed34625f38a84ff .gitignore
100755 blob 1587 57fa4264b9ee0ae0a6f678f2527a05d3b22dda78 00-bootstrap-build.sh
100755 blob 848 a30f443bf405d56682efe3b4c5d3a19d5f7eb45d 01-re-bootstrap-build.sh
100644 blob 2142 f19c2d6b293244bb11a3f74ee77c10675cadc7d6 INSTALL
100644 blob 30 c9b735fa1332286f4b3f5f81fa10527fd7506b6e LICENSE
040000 tree - 206d6a23693cdd25c4309f53d5017899c0668d34 builders
100644 blob 1773 ef1551089a803bde37e36edc8d61bb819d06f793 conf.bootstrap.sh
100644 blob 479 8cc15efe46965ac7750fe304460f5a2b0aa4201c conf.sh
040000 tree - 90a6c62877b0c53ac1007d8dd55422cacb39ef99 files
100755 blob 333 06859f922e41c1e691c72ada1be3f981ef05f602 pkg-build
100644 blob 22800641 e9e6291054c857401f6835c728f31541dae4311e steam.tar.bz2
100644 blob 173 2047af328b22f9d146585cd9e759edbc18122250 utils.sh
040000 tree - b7a22de7f5cbd97650dd45412ef7d4246e395eb8 x86
Hints:
Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://rocketgit.com/user/sylware/nyanlinux

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@ssh.rocketgit.com/user/sylware/nyanlinux

Clone this repository using git:
git clone git://git.rocketgit.com/user/sylware/nyanlinux

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a merge request:
... clone the repository ...
... make some changes and some commits ...
git push origin main