HeavyThing - rwasa/master.inc

Jeff Marrison

	; ------------------------------------------------------------------------
	; HeavyThing x86_64 assembly language library and showcase programs
	; Copyright © 2015-2018 2 Ton Digital 
	; Homepage: https://2ton.com.au/
	; Author: Jeff Marrison <jeff@2ton.com.au>
	;       
	; This file is part of the HeavyThing library.
	;       
	; HeavyThing is free software: you can redistribute it and/or modify
	; it under the terms of the GNU General Public License, or
	; (at your option) any later version.
	;       
	; HeavyThing is distributed in the hope that it will be useful, 
	; but WITHOUT ANY WARRANTY; without even the implied warranty of
	; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
	; GNU General Public License for more details.
	;       
	; You should have received a copy of the GNU General Public License along
	; with the HeavyThing library. If not, see <http://www.gnu.org/licenses/>.
	; ------------------------------------------------------------------------
	;       
	; master.inc: rwasa's startup/forking/master process goods
	;

globals
{
	workers		dq	0

}

	; no arguments, and isn't called, but jumped straight to from _start after
	; all our command line arguments were parsed
falign
masterthread:
	; so, all our configurations/bindings have been done already
	; first step is to change our uid/gid
	mov	eax, syscall_setgid
	mov	rdi, [runasgid]
	syscall
	test	eax, eax
	jnz	.setgidfail
	mov	eax, syscall_setuid
	mov	rdi, [runasuid]
	syscall
	test	eax, eax
	jnz	.setuidfail

	; deal with detaching
	cmp	dword [background], 0
	je	.skip_detach

	mov	eax, syscall_fork
	syscall
	cmp	rax, 0
	jl	.forkfail
	jne	.doexit
	mov	eax, syscall_close
	mov	edi, 0
	syscall
	mov	eax, syscall_close
	mov	edi, 1
	syscall
	mov	eax, syscall_close
	mov	edi, 2
	syscall
	mov	eax, syscall_setsid
	syscall
	call	rng$init

	; reset our syslog pid so they make sense
	mov	eax, syscall_getpid
	syscall
	mov	[syslog_pid], rax
calign
.skip_detach:
	cmp	dword [background], 0
	jne	.nobanner
	mov	rdi, .banner
	call	string$to_stdout
calign
.nobanner:
	; we need our worker children
	call	list$new
	mov	[workers], rax
	push	rbx
	mov	ebx, dword [cpucount]
calign
.childloop:
	mov	rdi, master$vtable
	mov	rsi, workerthread
	call	epoll_child
	test	rax, rax
	jz	.forkfail
	mov	rdi, [workers]
	mov	rsi, rax
	call	list$push_back
	sub	ebx, 1
	jnz	.childloop
	pop	rbx

	; then, we need to close/destroy all our delayed epoll_inbounds
	mov	rdi, [_epoll_inbound_delayed]
	mov	rsi, epoll$fatality
	call	list$clear

if defined newepoll_for_master
	; reinit our epoll layer but only if we did a detach
	cmp	dword [background], 0
	je	.skip_newepoll
	call	epoll$init

	; since we had to call epoll$init, this means we lost our OCSP Stapling timers
	mov	rdi, [tls$pem_byptr]
	mov	rsi, X509$ocsp
	call	unsignedmap$foreach

calign
.skip_newepoll:
end if
if defined children_write_their_own_logs
	cmp	dword [cpucount], 1
	je	.skiplogwriter_timer
end if
	cmp	dword [background], 0
	je	.skiplogwriter_timer
	; we need a new master timer to deal with log buffering
	call	io$new
	mov	qword [rax], logwriter$vtable
	mov	edi, 1500
	mov	rsi, rax
	call	epoll$timer_new
calign
.skiplogwriter_timer:

	; we need to hook the X509 OCSP stapler responses so that we can propagate them
	; to our children
	mov	qword [X509$ocsp_hook], master_ocsp_hook

	; and finally, turn it over to epoll
	call	epoll$run
calign
.doexit:
	mov	eax, syscall_exit
	xor	edi, edi
	syscall
cleartext .banner, 'This is rwasa v1.24 ',0xc2,0xa9,' 2015-2018 2 Ton Digital. Author: Jeff Marrison',10,'A showcase piece for the HeavyThing library. Commercial support available',10,'Proudly made in Cooroy, Australia. More info: https://2ton.com.au/rwasa',10
calign
.forkfail:
	mov	rdi, .err_forkfail
	call	string$to_stdoutln
	mov	eax, syscall_exit
	mov	edi, 1
	syscall
cleartext .err_forkfail, 'Fatal: fork and/or socketpair failed.'
calign
.setgidfail:
	mov	rdi, .err_setgidfail
	call	string$to_stdoutln
	mov	eax, syscall_exit
	mov	edi, 1
	syscall
cleartext .err_setgidfail, 'setgid() failed.'
calign
.setuidfail:
	mov	rdi, .err_setuidfail
	call	string$to_stdoutln
	mov	eax, syscall_exit
	mov	edi, 1
	syscall
cleartext .err_setuidfail, 'setuid() failed.'


dalign
logwriter$vtable:
	dq	io$destroy, io$clone, io$connected, io$send, io$receive, io$error, logwriter$timer

dalign
master$vtable:
	dq	epoll$destroy, epoll$clone, io$connected, epoll$send, master$receive, io$error, io$timeout


falign
master$receive:
	prolog	master$receive
	; got data from one of our workers
	push	rbx r12 r13 r14 r15
	mov	rbx, rdi
	mov	r12, [rdi+epoll_inbuf_ofs]
calign
.outer:
	mov	rdx, [r12+buffer_length_ofs]
	mov	rcx, [r12+buffer_itself_ofs]
	cmp	rdx, 8
	jb	.needmore
	mov	eax, [rcx+4]
	; sanity only
	cmp	rdx, rax
	jb	.needmore
	cmp	dword [rcx], linkmessage_log
	je	.logmessage
	cmp	dword [rcx], linkmessage_tlsupdate
	jne	.insanity
	; otherwise, we are sitting on a TLS session cache update item
	; we need to broadcast this to all other workers
	mov	r13, rcx	; send buffer
	mov	r14, rax	; total send length
	mov	rdi, [workers]
	mov	r15, [rdi+_list_first_ofs]
calign
.tlsbroadcast:
	mov	rdi, [r15+_list_valueofs]
	cmp	rdi, rbx
	je	.tlsbroadcast_skip
	mov	rsi, r13
	mov	rdx, r14
	mov	rcx, [rdi]
	call	qword [rcx+io_vsend]
calign
.tlsbroadcast_skip:
	mov	r15, [r15+_list_nextofs]
	test	r15, r15
	jnz	.tlsbroadcast
	; consume and continue
	mov	rdi, r12
	mov	rsi, r14
	call	buffer$consume
	jmp	.outer
calign
.logmessage:
	; [rcx+8] == 8 byte webservercfg ptr (which is valid, even though it originated in a child process)
	; [rcx+16] == 4 byte logtype (0 == normal, 1 == error)
	; [rcx+20] == string itself
	mov	rdi, [rcx+8]
	lea	rsi, [rcx+20]
	cmp	dword [rcx+16], 0
	jne	.logmessage_error
	call	webservercfg$log
	; consume and continue
	mov	rcx, [r12+buffer_itself_ofs]
	mov	rdi, r12
	mov	esi, [rcx+4]
	call	buffer$consume
	jmp	.outer
calign
.logmessage_error:
	call	webservercfg$logerror
	; consume and continue
	mov	rcx, [r12+buffer_itself_ofs]
	mov	rdi, r12
	mov	esi, [rcx+4]
	call	buffer$consume
	jmp	.outer
calign
.needmore:
	xor	eax, eax
	pop	r15 r14 r13 r12 rbx
	epilog
calign
.insanity:
	; should NOT happen
	mov	rdi, r12
	call	buffer$reset
	xor	eax, eax
	pop	r15 r14 r13 r12 rbx
	epilog



	; single argument in rdi == an X509cert object that just got its ocspresponse buffer updated
falign
master_ocsp_hook:
	prolog	master_ocsp_hook
	; we need to propagate the ocspresponse buffer object to our children
	mov	rsi, [rdi+X509cert_ocspresponse_ofs]
	mov	r8, [rdi+X509cert_subjectcn_ofs]
	mov	rdx, [rsi+buffer_length_ofs]
	mov	r9, [r8]
if string_bits = 32
	shl	r9, 2
else
	shl	r9, 1
end if
	add	rdx, 16		; +8 for the preface, +8 for string length
	add	rdx, r9
	cmp	rdx, 4096	; 4k == atomic oneshot
	ja	.skipit
	push	rbx r12 r13
	sub	rsp, 4096
	mov	dword [rsp], linkmessage_ocsp
	mov	dword [rsp+4], edx
	mov	rbx, rdx	; save our total length
	mov	r12, rsi
	mov	r13, r9
	lea	rdi, [rsp+8]
	mov	rsi, r8
	mov	edx, r9d
	add	edx, 8
	call	memcpy
	lea	rdi, [rsp+r13+16]
	mov	rsi, [r12+buffer_itself_ofs]
	mov	rdx, [r12+buffer_length_ofs]
	call	memcpy

	mov	rdi, [workers]
	mov	r12, [rdi+_list_first_ofs]
calign
.sendloop:
	mov	rdi, [r12+_list_valueofs]
	mov	rsi, rsp
	mov	rdx, rbx
	mov	rcx, [rdi]
	call	qword [rcx+io_vsend]
	mov	r12, [r12+_list_nextofs]
	test	r12, r12
	jnz	.sendloop
	add	rsp, 4096
	pop	r13 r12 rbx
	epilog
calign
.skipit:
	epilog


falign
logwriter$timer:
	prolog	logwriter$timer
	; fired once every 1.5 seconds to dump our logbuffers
	; bit of evil trickery here, we basically do the same thing as ordinary webservercfg objects do
	; if background=0, then our original config-parsed webservercfg objects still have valid timers
	; so we can just leave them be, and in which case this timer never gets created
	; if cpus=1, then the child writes the logs directly and never sends them to us
	; and otherwise, we just need to iterate through the webservercfg and call its timer handler
	mov	rdi, [configs]
	mov	rsi, webservercfg$timer
	call	list$foreach
	xor	eax, eax
	epilog