unit kern_sysctl;

{$mode ObjFPC}{$H+}
{$CALLING SysV_ABI_CDecl}

interface


const
 CTL_MAXNAME=24; // largest number of components supported

 CTLTYPE       =$f; // Mask for the type
 CTLTYPE_NODE  =1;  // name is a node
 CTLTYPE_INT   =2;  // name describes an integer
 CTLTYPE_STRING=3;  // name describes a string
 CTLTYPE_S64   =4;  // name describes a signed 64-bit number
 CTLTYPE_OPAQUE=5;  // name describes a structure
 CTLTYPE_STRUCT=CTLTYPE_OPAQUE; // name describes a structure
 CTLTYPE_UINT  =6;  // name describes an unsigned integer
 CTLTYPE_LONG  =7;  // name describes a long
 CTLTYPE_ULONG =8;  // name describes an unsigned long
 CTLTYPE_U64   =9;  // name describes an unsigned 64-bit number

 CTLFLAG_RD     =$80000000; // Allow reads of variable
 CTLFLAG_WR     =$40000000; // Allow writes to the variable
 CTLFLAG_RW     =(CTLFLAG_RD or CTLFLAG_WR);
 CTLFLAG_ANYBODY=$10000000; // All users can set this var
 CTLFLAG_SECURE =$08000000; // Permit set only if securelevel<=0
 CTLFLAG_PRISON =$04000000; // Prisoned roots can fiddle
 CTLFLAG_DYN    =$02000000; // Dynamic oid - can be freed
 CTLFLAG_SKIP   =$01000000; // Skip this sysctl when listing
 CTLMASK_SECURE =$00F00000; // Secure level
 CTLFLAG_TUN    =$00080000; // Tunable variable
 CTLFLAG_RDTUN  =(CTLFLAG_RD or CTLFLAG_TUN);
 CTLFLAG_RWTUN  =(CTLFLAG_RW or CTLFLAG_TUN);
 CTLFLAG_MPSAFE =$00040000; // Handler is MP safe
 CTLFLAG_VNET   =$00020000; // Prisons with vnet can fiddle
 CTLFLAG_DYING  =$00010000; // oid is being removed
 CTLFLAG_CAPRD  =$00008000; // Can be read in capability mode
 CTLFLAG_CAPWR  =$00004000; // Can be written in capability mode
 CTLFLAG_CAPRW  =(CTLFLAG_CAPRD or CTLFLAG_CAPWR);

 OID_AUTO=(-1);

 CTL_AUTO_START=$100;

//Top-level identifiers
 CTL_UNSPEC  = 0; // unused
 CTL_KERN    = 1; // "high kernel": proc, limits
 CTL_VM      = 2; // virtual memory
 CTL_VFS     = 3; // filesystem, mount type is next
 CTL_NET     = 4; // network, see socket.h
 CTL_DEBUG   = 5; // debugging parameters
 CTL_HW      = 6; // generic cpu/io
 CTL_MACHDEP = 7; // machine dependent
 CTL_USER    = 8; // user-level
 CTL_P1003_1B= 9; // POSIX 1003.1B
 CTL_MAXID   =10; // number of valid top-level ids


//CTL_KERN identifiers
 KERN_PROC_     =14;
 KERN_USRSTACK  =33;
 KERN_ARND      =37;
 KERN_SDKVERSION=38; //SDK version
 KERN_FSST_PARAM=40; //file system service threads (FSST)
 KERN_CPUMODE   =41;
 KERN_RNG_PSEUDO=47;

 KERN_SMP     =$485; //(OID_AUTO) Kernel SMP
 KERN_SCHED   =$2A0; //(OID_AUTO) Scheduler
 KERN_NEOMODE =$50F; //(OID_AUTO) Neo mode

//CTL_VM subtypes
 KERN_VM_PS4DEV=1;     //vm parameters for PS4 (DevKit only)

 KERN_VM_BUDGETS=$139; //(OID_AUTO) VM budgets

//KERN_PROC subtypes
 KERN_PROC_APPINFO     =35; //Application information
 KERN_PROC_SDK_VERSION =36; //SDK version of the executable file
 KERN_PROC_IDTABLE     =37; //ID table information

 KERN_PROC_SANITIZER   =41; //kern_sanitizer (Sanitizing mode)
 KERN_PROC_PTC         =43; //Process time counter (value at program start)
 KERN_PROC_TEXT_SEGMENT=44; //kern_dynlib_get_libkernel_text_segment

//KERN_SMP subtypes
 KERN_CPUS=$48A; //(OID_AUTO) Number of CPUs online

//KERN_SCHED subtypes
 KERN_SCHED_CPUSETSIZE=$4E4; //(OID_AUTO) sizeof(cpuset_t)

//CTL_HW identifiers
 HW_MACHINE     = 1; // string: machine class
 HW_MODEL       = 2; // string: specific machine model
 HW_NCPU        = 3; // int: number of cpus
 HW_BYTEORDER   = 4; // int: machine byte order
 HW_PHYSMEM     = 5; // int: total memory
 HW_USERMEM     = 6; // int: non-kernel memory
 HW_PAGESIZE    = 7; // int: software page size
 HW_DISKNAMES   = 8; // strings: disk drive names
 HW_DISKSTATS   = 9; // struct: diskstats[]
 HW_FLOATINGPT  =10; // int: has HW floating point?
 HW_MACHINE_ARCH=11; // string: machine architecture
 HW_REALMEM     =12; // int: 'real' memory
 HW_MAXID       =13; // number of valid hw ids

//MACHDEP subtypes
 MACHDEP_TSC_FREQ        =$1EC; //(OID_AUTO) Time Stamp Counter frequency
 MACHDEP_BOOTPARAMS      =$14D; //(OID_AUTO) orbis bootparams
 MACHDEP_CPUMODE_PLATFORM=$600; //(OID_AUTO) CPU mode platform (PS4/PS5)

//BOOTPARAMS subtypes
 BOOTPARAMS_IS_MAIN_ON_STANDBY=$100; //(OID_AUTO) Is main on standby mode
 BOOTPARAMS_BASE_PS4_MODE     =$151; //(OID_AUTO) base ps4 mode

//KERN_VM_PS4DEV subtypes
 KERN_VM_PS4DEV_TRCMEM_TOTAL=$23B; //(OID_AUTO) trace memory total
 KERN_VM_PS4DEV_TRCMEM_AVAIL=$23C; //(OID_AUTO) trace memory available

//KERN_VM_BUDGETS
 KERN_VM_BUDGETS_MLOCK_AVAIL=$13A; //(OID_AUTO) Available MLOCK budget
 KERN_VM_BUDGETS_MLOCK_TOTAL=$13B; //(OID_AUTO) Total MLOCK budget

//SYSCTL_HANDLER_ARGS oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req

type
 p_sysctl_req=^t_sysctl_req;

 t_sysctl_func=function(req:p_sysctl_req;p:Pointer;s:QWORD):Integer;

 t_sysctl_req=record
  td      :Pointer; //p_kthread
  lock    :Integer;
  oldptr  :Pointer;
  oldlen  :QWORD;
  oldidx  :QWORD;
  oldfunc :t_sysctl_func;
  newptr  :Pointer;
  newlen  :QWORD;
  newidx  :QWORD;
  newfunc :t_sysctl_func;
  validlen:QWORD;
  flags   :Integer;
 end;

 p_sysctl_oid=^t_sysctl_oid;

 t_oid_handler=function(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;

 t_sysctl_oid=record
  oid_arg1   :Pointer;
  oid_arg2   :Integer;
  oid_kind   :DWORD;
  oid_name   :PInteger;
  oid_handler:t_oid_handler;
 end;

function sys___sysctl(name   :PInteger;
                      namelen:DWORD;
                      old    :Pointer;
                      oldlenp:PQWORD;
                      new    :Pointer;
                      newlen :QWORD):Integer;

procedure sysctl_register_all(); //SYSINIT(sysctl, SI_SUB_KMEM, SI_ORDER_ANY, sysctl_register_all, 0);

implementation

uses
 sysutils,
 errno,
 systm,
 vmparam,
 vm_map,
 kern_thr,
 kern_sx,
 time,
 elf64,
 subr_dynlib,
 kern_authinfo,
 md_arc4random,
 sys_bootparam,
 kern_proc,
 md_proc,
 kern_budget,
 subr_backtrace;

var
 sysctllock   :t_sx;
 sysctlmemlock:t_sx;

procedure sysctl_register_all();
begin
 sx_init(@sysctlmemlock, 'sysctl mem');
 sx_init(@sysctllock   , 'sysctl lock');
end;

procedure SYSCTL_XLOCK(); inline;
begin
 sx_xlock(@sysctllock)
end;

procedure SYSCTL_XUNLOCK(); inline;
begin
 sx_xunlock(@sysctllock);
end;

procedure SYSCTL_ASSERT_XLOCKED(); inline;
begin
 sx_assert(@sysctllock)
end;

function SYSCTL_IN(req:p_sysctl_req;p:Pointer;s:QWORD):Integer; inline;
begin
 Result:=req^.newfunc(req,p,s);
end;

function SYSCTL_OUT(req:p_sysctl_req;p:Pointer;s:QWORD):Integer; inline;
begin
 Result:=req^.oldfunc(req,p,s);
end;

function SYSCTL_HANDLE(noid:p_sysctl_oid;
                       name:PInteger;
                       kind:DWORD;
                       func:Pointer):Integer; inline;
begin
 noid^.oid_name   :=name+1;
 noid^.oid_kind   :=kind;
 noid^.oid_arg1   :=nil;
 noid^.oid_arg2   :=0;
 noid^.oid_handler:=t_oid_handler(func);
 Result:=0
end;

function SYSCTL_HANDLE(noid:p_sysctl_oid;
                       name:PInteger;
                       kind:DWORD;
                       arg1:Pointer;
                       func:Pointer):Integer; inline;
begin
 noid^.oid_name   :=name+1;
 noid^.oid_kind   :=kind;
 noid^.oid_arg1   :=arg1;
 noid^.oid_arg2   :=0;
 noid^.oid_handler:=t_oid_handler(func);
 Result:=0
end;

function SYSCTL_HANDLE(noid:p_sysctl_oid;
                       name:PInteger;
                       kind:DWORD;
                       arg2:Integer;
                       func:Pointer):Integer; inline;
begin
 noid^.oid_name   :=name+1;
 noid^.oid_kind   :=kind;
 noid^.oid_arg1   :=nil;
 noid^.oid_arg2   :=arg2;
 noid^.oid_handler:=t_oid_handler(func);
 Result:=0
end;

function SYSCTL_HANDLE(noid:p_sysctl_oid;
                       name:PInteger;
                       kind:DWORD;
                       arg1:Pointer;
                       arg2:Integer;
                       func:Pointer):Integer; inline;
begin
 noid^.oid_name   :=name+1;
 noid^.oid_kind   :=kind;
 noid^.oid_arg1   :=arg1;
 noid^.oid_arg2   :=arg2;
 noid^.oid_handler:=t_oid_handler(func);
 Result:=0
end;

//Transfer function to/from user space.
function sysctl_old_user(req:p_sysctl_req;p:Pointer;l:QWORD):Integer;
var
 i,len,origidx:QWORD;
 error:Integer;
begin
 origidx:=req^.oldidx;
 Inc(req^.oldidx,l);

 if (req^.oldptr=nil) then
 begin
  Exit(0);
 end;

 i:=l;
 len:=req^.validlen;
 if (len <= origidx) then
 begin
  i:=0;
 end else
 begin
  if (i > (len - origidx)) then
  begin
   i:=len - origidx;
  end;
  //if (req^.lock=REQ_WIRED) then
  //begin
  // error:=copyout_nofault(p, req^.oldptr + origidx, i);
  //end else
  begin
   error:=copyout(p, req^.oldptr + origidx, i);
  end;
  if (error<>0) then
  begin
   Exit(error);
  end;
 end;
 if (i < l) then
 begin
  Exit(ENOMEM);
 end;
 Exit(0);
end;

function sysctl_new_user(req:p_sysctl_req;p:Pointer;l:QWORD):Integer;
var
 error:Integer;
begin
 if (req^.newptr=nil) then
 begin
  Exit(0);
 end;

 if ((req^.newlen - req^.newidx) < l) then
 begin
  Exit(EINVAL);
 end;

 error:=copyin(req^.newptr + req^.newidx, p, l);

 Inc(req^.newidx,l);
 Exit(error);
end;

//Exit(ENOTDIR);
//Exit(ENOENT);

function sysctl_kern_proc_idtable(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
begin
 //get idtable key count
 Exit(ENOENT); //sceSblACMgrIsSystemUcred
end;

function sysctl_kern_usrstack(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 vms:p_vmspace;
begin
 vms:=p_proc.p_vmspace;
 Result:=SYSCTL_OUT(req,@vms^.sv_usrstack,SizeOf(Pointer));
end;

function sysctl_kern_arandom(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
type
 t_data_256=array[0..255] of Byte;
var
 len:Integer;
 data:t_data_256;
begin
 len:=256;
 if (req^.oldlen < 256)  then
 begin
  len:=req^.oldlen;
 end;

 data:=Default(t_data_256);
 arc4rand(@data,len,0); //ASLR?

 Result:=SYSCTL_OUT(req,@data,len);
end;

type
 t_set_fsst_param=packed record
  mask :QWORD;
  prio :Integer;
  align:Integer;
 end;

function sysctl_kern_fsst_param(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 params:t_set_fsst_param;
begin
 params:=Default(t_set_fsst_param);

 Result:=SYSCTL_IN(req,@params,SizeOf(t_set_fsst_param));
 if (Result<>0) then Exit;

 if (params.prio<255) or
    (params.prio>767) then
 begin
  Exit(EINVAL);
 end;

 if (params.mask=0) then
 begin
  Exit(EDEADLK);
 end;

 Exit(0);
end;

function sysctl_kern_rng_pseudo(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
type
 t_data_64=array[0..63] of Byte;
var
 data:t_data_64;
begin
 data:=Default(t_data_64);
 arc4rand(@data,64,0);

 Result:=SYSCTL_OUT(req,@data,64);
end;

function sysctl_kern_proc_appinfo(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 pid:Integer;
 flags:Integer;
 appinfo:t_appinfo;
begin
 if (req^.oldlen > 72) then Exit(EINVAL);

 pid:=PInteger(arg1)^;

 if (pid<>p_proc.p_pid) then Exit(EINVAL);

 //sceSblACMgrIsSystemUcred()!=0 -> any proc
 //sceSblACMgrIsSystemUcred()==0 -> cur proc

 Result:=SYSCTL_OUT(req,@g_appinfo,SizeOf(t_appinfo));

 if (Result=0) and (req^.newlen=SizeOf(t_appinfo)) then
 begin
  Result:=SYSCTL_IN(req,@appinfo,SizeOf(t_appinfo));
  if (Result=0) then
  begin
   flags:=g_appinfo.mmap_flags;
   g_appinfo:=appinfo;
   g_appinfo.mmap_flags:=g_appinfo.mmap_flags or (flags and 2);
  end;
 end;

end;

function sysctl_kern_proc_sanitizer(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 Sanitizer:Integer;
begin
 Sanitizer:=0;
 Result:=SYSCTL_OUT(req,@Sanitizer,SizeOf(Integer));
end;

function sysctl_kern_proc_ptc(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
begin
 Result:=SYSCTL_OUT(req,@p_proc.p_ptc,SizeOf(Int64));
 //
 if (Result=0) then
 begin
  //save a ptr to guest memory (libkernel) to modify later
  //when the process resume (support in debug/pause?)
  p_proc.p_guest_ptc:=req^.oldptr;
 end;
end;

function _copy_libkernel_addr(req:p_sysctl_req):Integer;
var
 addr_out:array[0..1] of Pointer;
begin
 addr_out[0]:=p_proc.p_libkernel_start_addr;
 addr_out[1]:=p_proc.p_libkernel___end_addr;

 Result:=SYSCTL_OUT(req,@addr_out,SizeOf(addr_out));
end;

function sysctl_kern_text_segment(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 addr_out:array[0..1] of Pointer;
 libc_param:TSceLibcParam;
 sceLibcHeapDebugFlags:Integer;
begin
 Result:=0;

 addr_out[0]:=nil;
 addr_out[1]:=nil;

 if false then //sceSblACMgrIsShellcoreProces
               //sceSblACMgrIsSystemUcred     && sceRegMgrSrvGetQAFforReg   && sceRegMgrGetInt(sys_heap_trace)
               //sceSblACMgrIsWebcoreProcess  && sceKernelIsDevelopmentMode
               //sceRegMgrSrvGetQAFforReg     && sceRegMgrGetInt(game_heap_trace)
               //sceKernelIsDevelopmentMode   && sceKernelIsAssistMode      && sceRegMgrGetInt(game_intmem_dbg)
 begin
  Exit(_copy_libkernel_addr(req));
 end;

 Result:=copy_libc_param(@libc_param);

 if (Result=0) then
 if (libc_param.entry_count>8) then
 begin

  if (libc_param.SceLibcInternalHeap=1) then
  begin
   Result:=copyin(libc_param.sceLibcHeapDebugFlags,@sceLibcHeapDebugFlags,4);
   if (Result=0) and ((sceLibcHeapDebugFlags and 8)<>0) then
   begin
    Exit(_copy_libkernel_addr(req));
   end else
   begin
    Result:=0;
   end;
  end;

  if (libc_param.entry_count > 11) and (libc_param.SceLibcInternalHeap=1) then
  begin
   Result:=copyin(libc_param.sceKernelInternalMemoryDebugFlags,@sceLibcHeapDebugFlags,4);
   if (Result=0) and ((sceLibcHeapDebugFlags and 8)<>0) then
   begin
    Exit(_copy_libkernel_addr(req));
   end else
   begin
    Result:=0;
   end;
  end;

 end;

 SYSCTL_OUT(req,@addr_out,SizeOf(addr_out));
end;

function sysctl_handle_int(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 tmpout:Integer;
begin
 if (arg1<>nil) then
 begin
  tmpout:=PInteger(arg1)^;
 end else
 begin
  tmpout:=arg2;
 end;

 Result:=SYSCTL_OUT(req,@tmpout,SizeOf(Integer));

 if (Result<>0) or (req^.newptr=nil) then Exit;

 if (arg1=nil) then Exit(EPERM);

 Result:=SYSCTL_IN(req, arg1, sizeof(Integer));
end;

function sysctl_handle_64(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 tmpout:QWORD;
begin
 if (arg1=nil) then
 begin
  Exit(EINVAL);
 end;

 tmpout:=PInteger(arg1)^;

 Result:=SYSCTL_OUT(req,@tmpout,SizeOf(QWORD));

 if (Result<>0) or (req^.newptr=nil) then Exit;

 if (arg1=nil) then Exit(EPERM);

 Result:=SYSCTL_IN(req, arg1, sizeof(QWORD));
end;

function name2oid(name:pchar;oid,len:PInteger):Integer;
begin
 Result:=0;

 case RawByteString(name) of
  'kern.sdk_version':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_SDKVERSION;
     len^  :=2;
    end;
  'kern.fsst_param':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_FSST_PARAM;
     len^  :=2;
    end;
  'kern.cpumode':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_CPUMODE;
     len^  :=2;
    end;
  'kern.rng_pseudo':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_RNG_PSEUDO;
     len^  :=2;
    end;
  'kern.smp.cpus':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_SMP;
     oid[2]:=KERN_CPUS;
     len^  :=3;
    end;
  'kern.proc.ptc':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_PROC_;
     oid[2]:=KERN_PROC_PTC;
     len^  :=3;
    end;
  'kern.sched.cpusetsize':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_SCHED;
     oid[2]:=KERN_SCHED_CPUSETSIZE;
     len^  :=3;
    end;
  'kern.neomode':
    begin
     oid[0]:=CTL_KERN;
     oid[1]:=KERN_NEOMODE;
     len^  :=2;
    end;
  'machdep.tsc_freq':
    begin
     oid[0]:=CTL_MACHDEP;
     oid[1]:=MACHDEP_TSC_FREQ;
     len^  :=2;
    end;
  'machdep.bootparams.base_ps4_mode':
    begin
     oid[0]:=CTL_MACHDEP;
     oid[1]:=MACHDEP_BOOTPARAMS;
     oid[2]:=BOOTPARAMS_BASE_PS4_MODE;
     len^  :=3;
    end;
  'machdep.cpumode_platform':
    begin
     oid[0]:=CTL_MACHDEP;
     oid[1]:=MACHDEP_CPUMODE_PLATFORM;
     len^  :=2;
    end;
  'vm.ps4dev.trcmem_total':
    begin
     oid[0]:=CTL_VM;
     oid[1]:=KERN_VM_PS4DEV;
     oid[2]:=KERN_VM_PS4DEV_TRCMEM_TOTAL;
     len^  :=3;
    end;
  'vm.ps4dev.trcmem_avail':
    begin
     oid[0]:=CTL_VM;
     oid[1]:=KERN_VM_PS4DEV;
     oid[2]:=KERN_VM_PS4DEV_TRCMEM_AVAIL;
     len^  :=3;
    end;
  'vm.budgets.mlock_avail':
    begin
     oid[0]:=CTL_VM;
     oid[1]:=KERN_VM_BUDGETS;
     oid[2]:=KERN_VM_BUDGETS_MLOCK_AVAIL;
     len^  :=3;
    end;
  'vm.budgets.mlock_total':
    begin
     oid[0]:=CTL_VM;
     oid[1]:=KERN_VM_BUDGETS;
     oid[2]:=KERN_VM_BUDGETS_MLOCK_TOTAL;
     len^  :=3;
    end;


  else
   print_error_td('Unhandled name2oid:'+name);
   Assert(False);
   Result:=ENOENT;
 end;
end;

function sysctl_sysctl_name2oid(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
type
 t_path=array[0..MAXPATHLEN] of AnsiChar;
var
 new:t_path;
 oid:array[0..CTL_MAXNAME-1] of Integer;
 len:Integer;
begin
 if (req^.newlen=0) then Exit(ENOENT);

 if (req^.newlen >= MAXPATHLEN) then Exit(ENAMETOOLONG);

 new:=Default(t_path);

 Result:=SYSCTL_IN(req, @new, req^.newlen);
 if (Result<>0) then Exit;

 Result:=name2oid(@new, @oid, @len);
 if (Result<>0) then Exit;

 Result:=SYSCTL_OUT(req, @oid, len*sizeof(Integer));
end;

function sysctl_kern_proc(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  KERN_PROC_APPINFO     :Result:=SYSCTL_HANDLE(noid,name,$C0040001,@sysctl_kern_proc_appinfo);
  KERN_PROC_SANITIZER   :Result:=SYSCTL_HANDLE(noid,name,$80040001,@sysctl_kern_proc_sanitizer);
  KERN_PROC_PTC         :Result:=SYSCTL_HANDLE(noid,name,$90040009,@sysctl_kern_proc_ptc);
  KERN_PROC_TEXT_SEGMENT:Result:=SYSCTL_HANDLE(noid,name,$80040001,@sysctl_kern_text_segment);
  else
   begin
    print_error_td('Unhandled sysctl_kern_proc:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_kern_smp(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
const
 smp_cpus=8;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  KERN_CPUS:Result:=SYSCTL_HANDLE(noid,name,$80048002,smp_cpus,@sysctl_handle_int);

  else
   begin
    print_error_td('Unhandled sysctl_kern_smp:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_kern_sched(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  KERN_SCHED_CPUSETSIZE:Result:=SYSCTL_HANDLE(noid,name,$80040002,8,@sysctl_handle_int);

  else
   begin
    print_error_td('Unhandled sysctl_kern_sched:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_kern(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  KERN_PROC_     :Result:=sysctl_kern_proc(name+1,namelen-1,noid,req);

  KERN_USRSTACK  :Result:=SYSCTL_HANDLE(noid,name,$80008008,@sysctl_kern_usrstack);
  KERN_ARND      :Result:=SYSCTL_HANDLE(noid,name,$80048005,@sysctl_kern_arandom);
  KERN_SDKVERSION:Result:=SYSCTL_HANDLE(noid,name,$80048006,p_system_sdk_version,@sysctl_handle_int);
  KERN_FSST_PARAM:Result:=SYSCTL_HANDLE(noid,name,$50040005,@sysctl_kern_fsst_param);
  KERN_CPUMODE   :Result:=SYSCTL_HANDLE(noid,name,$80040002,p_cpumode,@sysctl_handle_int);
  KERN_RNG_PSEUDO:Result:=SYSCTL_HANDLE(noid,name,$80040005,@sysctl_kern_rng_pseudo);


  KERN_SMP       :Result:=sysctl_kern_smp  (name+1,namelen-1,noid,req);
  KERN_SCHED     :Result:=sysctl_kern_sched(name+1,namelen-1,noid,req);
  KERN_NEOMODE   :Result:=SYSCTL_HANDLE(noid,name,$80040002,p_neomode,@sysctl_handle_int);
  else
   begin
    print_error_td('Unhandled sysctl_kern:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_mlock_avail(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 val:QWORD;
begin
 val:=get_mlock_avail;
 Result:=SYSCTL_OUT(req,@val,SizeOf(QWORD));
end;

function sysctl_mlock_total(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 val:QWORD;
begin
 val:=get_mlock_total;
 Result:=SYSCTL_OUT(req,@val,SizeOf(QWORD));
end;

function sysctl_vm_budgets(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of

  KERN_VM_BUDGETS_MLOCK_AVAIL:Result:=SYSCTL_HANDLE(noid,name,$80000008,@sysctl_mlock_avail); //sceKernelAvailableFlexibleMemorySize
  KERN_VM_BUDGETS_MLOCK_TOTAL:Result:=SYSCTL_HANDLE(noid,name,$80000008,@sysctl_mlock_total); //sceKernelConfiguredFlexibleMemorySize

  else
   begin
    print_error_td('Unhandled sysctl_vm_budgets:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_vm(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of

  KERN_VM_BUDGETS:Result:=sysctl_vm_budgets(name+1,namelen-1,noid,req);

  else
   begin
    print_error_td('Unhandled sysctl_vm:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_sysctl(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  3:Result:=SYSCTL_HANDLE(noid,name,$D004C002,@sysctl_sysctl_name2oid);

  else
   begin
    print_error_td('Unhandled sysctl_sysctl:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_hw(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  HW_PAGESIZE:Result:=SYSCTL_HANDLE(noid,name,$80048002,PAGE_SIZE,@sysctl_handle_int);

  else
   begin
    print_error_td('Unhandled sysctl_hw:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_machdep_tsc_freq(oidp:p_sysctl_oid;arg1:Pointer;arg2:ptrint;req:p_sysctl_req):Integer;
var
 freq:QWORD;
begin
 freq:=System.InterlockedExchangeAdd64(tsc_freq,0);
 if (freq=0) then Exit(EOPNOTSUPP);

 Result:=sysctl_handle_64(oidp, @freq, 0, req);
 if (Result=0) and (req^.newptr<>nil) then
 begin
  System.InterlockedExchange64(tsc_freq,freq);
 end;
end;

function sysctl_bootparams(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  BOOTPARAMS_BASE_PS4_MODE:Result:=SYSCTL_HANDLE(noid,name,$80040002,p_base_ps4_mode,@sysctl_handle_int);
  else
   begin
    print_error_td('Unhandled sysctl_bootparams:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_machdep(name:PInteger;namelen:DWORD;noid:p_sysctl_oid;req:p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOTDIR);
 Result:=ENOENT;

 case name[0] of
  MACHDEP_TSC_FREQ        :Result:=SYSCTL_HANDLE(noid,name,$C0000009,@sysctl_machdep_tsc_freq);
  MACHDEP_BOOTPARAMS      :Result:=sysctl_bootparams(name+1,namelen-1,noid,req);
  MACHDEP_CPUMODE_PLATFORM:Result:=SYSCTL_HANDLE(noid,name,$80000002,0,@sysctl_handle_int);

  else
   begin
    print_error_td('Unhandled sysctl_machdep:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_find_oid(name   :PInteger;
                         namelen:DWORD;
                         noid   :p_sysctl_oid;
                         req    :p_sysctl_req):Integer;
begin
 if (namelen=0) then Exit(ENOENT);
 Result:=ENOENT;

 case name[0] of
  CTL_UNSPEC :Result:=sysctl_sysctl (name+1,namelen-1,noid,req);
  CTL_KERN   :Result:=sysctl_kern   (name+1,namelen-1,noid,req);
  CTL_VM     :Result:=sysctl_vm     (name+1,namelen-1,noid,req);
  CTL_HW     :Result:=sysctl_hw     (name+1,namelen-1,noid,req);
  CTL_MACHDEP:Result:=sysctl_machdep(name+1,namelen-1,noid,req);
  else
   begin
    print_error_td('Unhandled sysctl_root:'+IntToStr(name[0]));
    Assert(False);
   end;
 end;
end;

function sysctl_root(oidp:p_sysctl_oid;
                     arg1:PInteger;
                     arg2:DWORD;
                     req :p_sysctl_req):Integer;
var
 oid:t_sysctl_oid;
 indx:Integer;
begin
 oid:=Default(t_sysctl_oid);

 Result:=sysctl_find_oid(arg1, arg2, @oid, req);
 if (Result<>0) then Exit;

 if (oid.oid_handler=nil) then Exit(EINVAL);
 if (oid.oid_name   =nil) then Exit(EINVAL);

 // Is this sysctl writable?
 if (req^.newptr<>nil) and ((oid.oid_kind and CTLFLAG_WR)=0) then
 begin
  Exit(EPERM);
 end;

 // Is this sysctl writable by only privileged users?
 if (req^.newptr<>nil) and ((oid.oid_kind and CTLFLAG_ANYBODY)=0) then
 begin
  //if (oid^.oid_kind and CTLFLAG_PRISON)
  // priv:=PRIV_SYSCTL_WRITEJAIL;
  //else
  // priv:=PRIV_SYSCTL_WRITE;
  //
  //error:=priv_check(req^.td, priv);
  //
  //if (error<>0) then Ext(error);

  Exit(EPERM);
 end;

 indx:=oid.oid_name-arg1;

 if ((oid.oid_kind and CTLTYPE)=CTLTYPE_NODE) then
 begin
  arg1:=arg1 + indx;
  arg2:=arg2 - indx;
 end else
 begin
  arg1:=oid.oid_arg1;
  arg2:=oid.oid_arg2;
 end;

 Result:=oid.oid_handler(@oid, arg1, arg2, req);

end;

function userland_sysctl(name    :PInteger;
                         namelen :DWORD;
                         old     :Pointer;
                         oldlenp :PQWORD;
                         inkernel:Integer;
                         new     :Pointer;
                         newlen  :QWORD;
                         retval  :PQWORD;
                         flags   :Integer):Integer;
var
 error,memlocked:Integer;
 req:t_sysctl_req;
begin
 error:=0;

 req:=Default(t_sysctl_req);

 req.td   :=curkthread;
 req.flags:=flags;

 if (oldlenp<>nil) then
 begin
  if (inkernel<>0) then
  begin
   req.oldlen:=oldlenp^;
  end else
  begin
   error:=copyin(oldlenp, @req.oldlen, sizeof(Pointer));
   if (error<>0) then Exit(error);
  end;
 end;
 req.validlen:=req.oldlen;

 if (old<>nil) then
 begin
  //if (!useracc(old, req.oldlen, VM_PROT_WRITE))
  // Exit(EFAULT);
  req.oldptr:=old;
 end;

 if (new<>nil) then
 begin
  //if (!useracc(new, newlen, VM_PROT_READ))
  // Exit(EFAULT);
  req.newlen:=newlen;
  req.newptr:=new;
 end;

 req.oldfunc:=@sysctl_old_user;
 req.newfunc:=@sysctl_new_user;
 //req.lock:=REQ_UNWIRED;

 if (req.oldlen > PAGE_SIZE) then
 begin
  memlocked:=1;
  sx_xlock(@sysctlmemlock);
 end else
 begin
  memlocked:=0;
 end;

 repeat
  req.oldidx:=0;
  req.newidx:=0;
  SYSCTL_XLOCK();
  error:=sysctl_root(nil, name, namelen, @req);
  SYSCTL_XUNLOCK();
  if (error<>EAGAIN) then
  begin
   break;
  end;
  //kern_yield(PRI_USER);
 until false;

 //if (req.lock=REQ_WIRED) and (req.validlen > 0) then
 //begin
 // vsunlock(req.oldptr, req.validlen);
 //end;

 if (memlocked<>0) then
 begin
  sx_xunlock(@sysctlmemlock);
 end;

 if (error<>0) and (error<>ENOMEM) then
 begin
  Exit(error);
 end;

 if (retval<>nil) then
 begin
  if (req.oldptr<>nil) and (req.oldidx > req.validlen) then
   retval^:=req.validlen
  else
   retval^:=req.oldidx;
 end;
 Exit(error);
end;

function sys___sysctl(name   :PInteger;
                      namelen:DWORD;
                      old    :Pointer;
                      oldlenp:PQWORD;
                      new    :Pointer;
                      newlen :QWORD):Integer;
var
 error,i:Integer;
 _name:array[0..CTL_MAXNAME-1] of Integer;
 j:QWORD;
begin
 if (namelen > CTL_MAXNAME) or (namelen < 2) then
 begin
  Exit(EINVAL);
 end;

 error:=copyin(name, @_name, namelen * sizeof(Integer));
 if (error<>0) then Exit(error);

 error:=userland_sysctl(@_name,
                        namelen,
                        old,
                        oldlenp,
                        0,
                        new,
                        newlen,
                        @j,
                        0);

 if (error<>0) and (error<>ENOMEM) then
 begin
  Exit(error);
 end;

 if (oldlenp<>nil) then
 begin
  i:=copyout(@j, oldlenp, sizeof(j));
  if (i<>0) then
  begin
   Exit(i);
  end;
 end;

 Exit(error);
end;






end.

