unit vsys_generic;

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

interface

uses
 mqueue,
 kern_param,
 kern_mtx,
 kern_mtxpool,
 kern_condvar,
 kern_thr,
 vselinfo,
 vioccom,
 vpoll,
 vselect,
 vuio,
 vfile,
 vcapability,
 vnode,
 vfcntl,
 vfilio,
 vfiledesc,
 time,
 signal,
 vm,
 vmparam;

const
 SYS_IOCTL_SMALL_SIZE =128; { bytes }
 SYS_IOCTL_SMALL_ALIGN=8; { bytes }

 SELTD_PENDING=$0001;   { We have pending events. }
 SELTD_RESCAN =$0002;   { Doing a rescan. }

type
{
 * One seltd per-thread allocated on demand as needed.
 *
 * t - protected by st_mtx
 *  k - Only accessed by curthread or read-only
 }
 p_selfd=^t_selfd;

 p_seltd=^t_seltd;
 t_seltd=packed record
  st_selq :STAILQ_HEAD; { (k) List of selfds. }
  st_free1:p_selfd;     { (k) free fd for read set. }
  st_free2:p_selfd;     { (k) free fd for write set. }
  st_mtx  :mtx;         { Protects struct seltd }
  st_wait :t_cv;        { (t) Wait channel. }
  st_flags:Integer;     { (t) SELTD_ flags. }
 end;

 {
  * One selfd allocated per-thread per-file-descriptor.
  * f - protected by sf_mtx
  }
 t_selfd=packed record
  sf_link   :STAILQ_ENTRY; { (k) fds owned by this td. }
  sf_threads:TAILQ_ENTRY;  { (f) fds on this selinfo. }
  sf_si     :p_selinfo;    { (f) selinfo when linked. }
  sf_mtx    :p_mtx;        { Pointer to selinfo mtx. }
  sf_td     :p_seltd;      { (k) owning seltd. }
  sf_cookie :Pointer;      { (k) fd or pollfd. }
 end;

function  _pollout(td:p_kthread;fds,ufds:p_pollfd;nfd:DWORD):Integer;
function  pollscan(td:p_kthread;fds:p_pollfd;nfd:DWORD):Integer;
function  pollrescan(td:p_kthread):Integer;
function  selscan(td:p_kthread;ibits,obits:pp_fd_mask;nfd:Integer):Integer;
function  selrescan(td:p_kthread;ibits,obits:pp_fd_mask):Integer;
procedure selfdalloc(td:p_kthread;cookie:Pointer);
procedure selfdfree(stp:p_seltd;sfp:p_selfd);
procedure seldrain(sip:p_selinfo);
procedure selrecord(selector:p_kthread;sip:p_selinfo);
procedure selwakeup(sip:p_selinfo);
procedure selwakeuppri(sip:p_selinfo;pri:Integer);
function  dofileread(fd:Integer;fp:p_file;auio:p_uio;offset:Int64;flags:Integer):Integer;
function  dofilewrite(fd:Integer;fp:p_file;auio:p_uio;offset:Int64;flags:Integer):Integer;
procedure doselwakeup(sip:p_selinfo;pri:Integer);
procedure seltdinit(td:p_kthread);
function  seltdwait(td:p_kthread;timo:Int64):Integer;
procedure seltdclear(td:p_kthread);
function  poll_no_poll(events:Integer):Integer;

//

function  sys_read(fd:Integer;buf:Pointer;nbyte:QWORD):Integer;
function  sys_pread(fd:Integer;buf:Pointer;nbyte:QWORD;offset:Int64):Integer;
function  sys_readv(fd:Integer;iovp:Pointer;iovcnt:DWORD):Integer;
function  sys_preadv(fd:Integer;iovp:Pointer;iovcnt:DWORD;offset:Int64):Integer;
function  sys_write(fd:Integer;buf:Pointer;nbyte:QWORD):Integer;
function  sys_pwrite(fd:Integer;buf:Pointer;nbyte:QWORD;offset:Int64):Integer;
function  sys_writev(fd:Integer;iovp:Pointer;iovcnt:DWORD):Integer;
function  sys_pwritev(fd:Integer;iovp:Pointer;iovcnt:DWORD;offset:Int64):Integer;
function  sys_ftruncate(fd:Integer;length:Int64):Integer;
function  sys_ioctl(fd:Integer;com:QWORD;data:Pointer):Integer;
function  sys_pselect(nd:Integer;uin,uou,uex,uts,sm:Pointer):Integer;
function  sys_select(nd:Integer;uin,uou,uex,utv:Pointer):Integer;
function  sys_poll(fds:Pointer;nfds:DWORD;timeout:Integer):Integer;

procedure selectinit(); //SYSINIT(select, SI_SUB_SYSCALLS, SI_ORDER_ANY, selectinit, NULL);

implementation

uses
 atomic,
 systm,
 errno,
 kern_descrip,
 subr_uio,
 kern_proc,
 md_time,
 sys_capability;

var
 mtxpool_select:p_mtx_pool;

function kern_readv(fd:Integer;auio:p_uio):Integer;
var
 fp:p_file;
 error:Integer;
begin
 error:=fget_read(fd, CAP_READ or CAP_SEEK, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=dofileread(fd, fp, auio, -1, 0);
 fdrop(fp);
 Exit(error);
end;

function kern_preadv(fd:Integer;auio:p_uio;offset:Int64):Integer;
var
 fp:p_file;
 error:Integer;
begin
 error:=fget_read(fd, CAP_READ, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 if ((fp^.f_ops^.fo_flags and DFLAG_SEEKABLE)=0) then
  error:=ESPIPE
 else
 if (offset < 0) and (fp^.f_vnode^.v_type<>VCHR) then
  error:=EINVAL
 else
  error:=dofileread(fd, fp, auio, offset, FOF_OFFSET);

 fdrop(fp);
 Exit(error);
end;

function kern_writev(fd:Integer;auio:p_uio):Integer;
var
 fp:p_file;
 error:Integer;
begin
 error:=fget_write(fd, CAP_WRITE or CAP_SEEK, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=dofilewrite(fd, fp, auio, -1, 0);
 fdrop(fp);
 Exit(error);
end;

function kern_pwritev(fd:Integer;auio:p_uio;offset:Int64):Integer;
var
 fp:p_file;
 error:Integer;
begin
 error:=fget_write(fd, CAP_WRITE, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 if ((fp^.f_ops^.fo_flags and DFLAG_SEEKABLE)=0) then
  error:=ESPIPE
 else
 if (offset < 0) and (fp^.f_vnode^.v_type<>VCHR) then
  error:=EINVAL
 else
  error:=dofilewrite(fd, fp, auio, offset, FOF_OFFSET);

 fdrop(fp);
 Exit(error);
end;

//

function sys_read(fd:Integer;buf:Pointer;nbyte:QWORD):Integer;
var
 auio:t_uio;
 aiov:iovec;
 error:Integer;
begin
 if (nbyte > IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;
 aiov.iov_base  :=buf;
 aiov.iov_len   :=nbyte;
 auio.uio_iov   :=@aiov;
 auio.uio_iovcnt:=1;
 auio.uio_resid :=nbyte;
 auio.uio_segflg:=UIO_USERSPACE;
 error:=kern_readv(fd, @auio);
 Exit(error);
end;

{
 * Positioned read system call
 }
function sys_pread(fd:Integer;buf:Pointer;nbyte:QWORD;offset:Int64):Integer;
var
 auio:t_uio;
 aiov:iovec;
 error:Integer;
begin
 if (nbyte > IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;
 aiov.iov_base  :=buf;
 aiov.iov_len   :=nbyte;
 auio.uio_iov   :=@aiov;
 auio.uio_iovcnt:=1;
 auio.uio_resid :=nbyte;
 auio.uio_segflg:=UIO_USERSPACE;
 error:=kern_preadv(fd, @auio, offset);
 Exit(error);
end;

{
 * Scatter read system call.
 }
function sys_readv(fd:Integer;iovp:Pointer;iovcnt:DWORD):Integer;
var
 auio:p_uio;
 error:Integer;
begin
 error:=copyinuio(iovp, iovcnt, @auio);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=kern_readv(fd, auio);
 FreeMem(auio);
 Exit(error);
end;

{
 * Scatter positioned read system call.
 }
function sys_preadv(fd:Integer;iovp:Pointer;iovcnt:DWORD;offset:Int64):Integer;
var
 auio:p_uio;
 error:Integer;
begin
 error:=copyinuio(iovp, iovcnt, @auio);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=kern_preadv(fd, auio, offset);
 FreeMem(auio);
 Exit(error);
end;

{
 * Common code for readv and preadv that reads data in
 * from a file using the passed in uio, offset, and flags.
 }
function dofileread(fd:Integer;fp:p_file;auio:p_uio;offset:Int64;flags:Integer):Integer;
var
 td:p_kthread;
 cnt:Int64;
 error:Integer;
begin
 td:=curkthread;
 { Finish zero length reads right here }
 if (auio^.uio_resid=0) then
 begin
  td^.td_retval[0]:=0;
  Exit(0);
 end;
 auio^.uio_rw    :=UIO_READ;
 auio^.uio_offset:=offset;
 auio^.uio_td    :=td;

 cnt:=auio^.uio_resid;
 error:=fo_read(fp, auio, flags);
 if (error<>0) then
 begin
  if (auio^.uio_resid<>cnt) and ((error=ERESTART) or (error=EINTR) or (error=EWOULDBLOCK)) then
  begin
   error:=0;
  end;
 end;
 Dec(cnt,auio^.uio_resid);

 td^.td_retval[0]:=cnt;
 Exit(error);
end;

function sys_write(fd:Integer;buf:Pointer;nbyte:QWORD):Integer;
var
 auio:t_uio;
 aiov:iovec;
 error:Integer;
begin
 if (nbyte > IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;
 aiov.iov_base  :=buf;
 aiov.iov_len   :=nbyte;
 auio.uio_iov   :=@aiov;
 auio.uio_iovcnt:=1;
 auio.uio_resid :=nbyte;
 auio.uio_segflg:=UIO_USERSPACE;
 error:=kern_writev(fd, @auio);
 Exit(error);
end;

{
 * Positioned write system call.
 }
function sys_pwrite(fd:Integer;buf:Pointer;nbyte:QWORD;offset:Int64):Integer;
var
 auio:t_uio;
 aiov:iovec;
 error:Integer;
begin
 if (nbyte > IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;
 aiov.iov_base  :=buf;
 aiov.iov_len   :=nbyte;
 auio.uio_iov   :=@aiov;
 auio.uio_iovcnt:=1;
 auio.uio_resid :=nbyte;
 auio.uio_segflg:=UIO_USERSPACE;
 error:=kern_pwritev(fd, @auio, offset);
 Exit(error);
end;

{
 * Gather write system call.
 }
function sys_writev(fd:Integer;iovp:Pointer;iovcnt:DWORD):Integer;
var
 auio:p_uio;
 error:Integer;
begin
 error:=copyinuio(iovp, iovcnt, @auio);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=kern_writev(fd, auio);
 FreeMem(auio);
 Exit(error);
end;

{
 * Gather positioned write system call.
 }
function sys_pwritev(fd:Integer;iovp:Pointer;iovcnt:DWORD;offset:Int64):Integer;
var
 auio:p_uio;
 error:Integer;
begin
 error:=copyinuio(iovp, iovcnt, @auio);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=kern_pwritev(fd, auio, offset);
 FreeMem(auio);
 Exit(error);
end;

{
 * Common code for writev and pwritev that writes data to
 * a file using the passed in uio, offset, and flags.
 }
function dofilewrite(fd:Integer;fp:p_file;auio:p_uio;offset:Int64;flags:Integer):Integer;
var
 td:p_kthread;
 cnt:Int64;
 error:Integer;
begin
 td:=curkthread;

 auio^.uio_rw    :=UIO_WRITE;
 auio^.uio_td    :=td;
 auio^.uio_offset:=offset;

 cnt:=auio^.uio_resid;
 //if (fp^.f_type=DTYPE_VNODE) and
 //   ((fp^.f_vnread_flags and FDEVFS_VNODE)=0) then
 // bwillwrite();
 error:=fo_write(fp, auio, flags);
 if (error<>0) then
 begin
  if (auio^.uio_resid<>cnt) and ((error=ERESTART) or (error=EINTR) or (error=EWOULDBLOCK)) then
  begin
   error:=0;
  end;
  { Socket layer is responsible for issuing SIGPIPE. }
  if (fp^.f_type<>DTYPE_SOCKET) and (error=EPIPE) then
  begin
   PROC_LOCK();
   tdsignal(td, SIGPIPE);
   PROC_UNLOCK();
  end;
 end;
 Dec(cnt,auio^.uio_resid);

 td^.td_retval[0]:=cnt;
 Exit(error);
end;

{
 * Truncate a file given a file descriptor.
 *
 * Can't use fget_write() here, since must ExitEINVAL and not EBADF if the
 * descriptor isn't writable.
 }
function kern_ftruncate(fd:Integer;length:Int64):Integer;
var
 fp:p_file;
 error:Integer;
begin
 if (length < 0) then
 begin
  Exit(EINVAL);
 end;
 error:=fget(fd, CAP_FTRUNCATE, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 if ((fp^.f_flag and FWRITE)=0) then
 begin
  fdrop(fp);
  Exit(EINVAL);
 end;
 error:=fo_truncate(fp, length);
 fdrop(fp);
 Exit(error);
end;

function sys_ftruncate(fd:Integer;length:Int64):Integer;
begin
 Exit(kern_ftruncate(fd, length));
end;

function kern_ioctl(fd:Integer;com:QWORD;data:Pointer):Integer;
label
 _out;
var
 fp:p_file;
 error:Integer;
 tmp:Integer;
begin
 error:=fget(fd, CAP_IOCTL, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;
 if ((fp^.f_flag and (FREAD or FWRITE))=0) then
 begin
  fdrop(fp);
  Exit(EBADF);
 end;
 case com of
  FIONCLEX:
   begin
    atomic_clear_int(@fp^.f_exclose,UF_EXCLOSE);
    goto _out;
   end;
  FIOCLEX:
   begin
    atomic_set_int(@fp^.f_exclose,UF_EXCLOSE);
    goto _out;
   end;
  FIONBIO:
   begin
    tmp:=PInteger(data)^;

    if (tmp<>0) then
     atomic_set_int(@fp^.f_flag, FNONBLOCK)
    else
     atomic_clear_int(@fp^.f_flag, FNONBLOCK);

    data:=@tmp;
   end;
  FIOASYNC:
   begin
    tmp:=PInteger(data)^;

    if (tmp<>0) then
     atomic_set_int(@fp^.f_flag, FASYNC)
    else
     atomic_clear_int(@fp^.f_flag, FASYNC);

    data:=@tmp;
   end;
 end;
 error:=fo_ioctl(fp, com, data);
_out:
 fdrop(fp);
 Exit(error);
end;

function sys_ioctl(fd:Integer;com:QWORD;data:Pointer):Integer;
label
 _out;
var
 smalldata:array[0..SYS_IOCTL_SMALL_SIZE-1] of Byte; //__aligned(SYS_IOCTL_SMALL_ALIGN)
 arg,error:Integer;
 size:DWORD;
 kern_data:Pointer;
begin
 if (com > $ffffffff) then
 begin
  Writeln('WARNING pid %d (%s): ioctl sign-extension ioctl ',com);
  com:=com and $ffffffff;
 end;

 {
  * Interpret high order word to find amount of data to be
  * copied to/from the user's address space.
  }
 size:=IOCPARM_LEN(com);
 if (size > IOCPARM_MAX) or
    ((com and (IOC_VOID or IOC_IN or IOC_OUT))=0) or
    (((com and (IOC_IN or IOC_OUT))<>0) and (size=0)) or
    (((com and IOC_VOID)<>0) and (size > 0) and (size<>sizeof(Integer))) then
 begin
  Writeln('com:0x',HexStr(com,16),':ENOTTY');
  Exit(ENOTTY);
 end;

 if (size > 0) then
 begin
  if ((com and IOC_VOID)<>0) then
  begin
   { Integer argument. }
   arg:=ptrint(data);
   kern_data:=@arg;
   size:=0;
  end else
  begin
   if (size > SYS_IOCTL_SMALL_SIZE) then
    kern_data:=AllocMem(size)
   else
    kern_data:=@smalldata;
  end;
 end else
 begin
  kern_data:=@curkthread^.td_frame.tf_rsi;
 end;
 if ((com and IOC_IN)<>0) then
 begin
  error:=copyin(data, kern_data, size);
  if (error<>0) then
  begin
   goto _out;
  end;
 end else
 if ((com and IOC_OUT)<>0) then
 begin
  {
   * Zero the buffer so the user always
   * gets back something deterministic.
   }
  FillChar(kern_data^,size,0);
 end;

 error:=kern_ioctl(fd, com, kern_data);

 if (error=0) and ((com and IOC_OUT)<>0) then
 begin
  error:=copyout(kern_data, data, size);
 end;

_out:
 if (size > SYS_IOCTL_SMALL_SIZE) then
 begin
  FreeMem(kern_data);
 end;
 Exit(error);
end;

function poll_no_poll(events:Integer):Integer;
begin
 {
  * return true for read/write.  If the user asked for something
  * special, return POLLNVAL, so that clients have a way of
  * determining reliably whether or not the extended
  * functionality is present without hard-coding knowledge
  * of specific filesystem implementations.
  }
 if (events and (not POLLSTANDARD))<>0 then
 begin
  Exit(POLLNVAL);
 end;

 Exit(events and (POLLIN or POLLOUT or POLLRDNORM or POLLWRNORM));
end;

function kern_select(nd:Integer;
                     fd_in,fd_ou,fd_ex:p_fd_set;
                     tvp:p_timeval;
                     abi_nfdbits:Integer):Integer; forward;

function kern_pselect(nd:Integer;
                      uin,uou,uex:p_fd_set;
                      tvp:p_timeval;
                      uset:p_sigset_t;
                      abi_nfdbits:Integer):Integer;
var
 td:p_kthread;
 error:Integer;
begin
 td:=curkthread;
 if (uset<>nil) then
 begin
  error:=kern_sigprocmask(td, SIG_SETMASK, uset, @td^.td_oldsigmask, 0);
  if (error<>0) then
  begin
   Exit(error);
  end;
  td^.td_pflags:=td^.td_pflags or TDP_OLDMASK;
  {
   * Make sure that ast() is called on Exitto
   * usermode and TDP_OLDMASK is cleared, restoring old
   * sigmask.
   }
  thread_lock(td);
  td^.td_flags:=td^.td_flags or TDF_ASTPENDING;
  thread_unlock(td);
 end;
 error:=kern_select(nd, uin, uou, uex, tvp, abi_nfdbits);
 Exit(error);
end;

function sys_pselect(nd:Integer;uin,uou,uex,uts,sm:Pointer):Integer;
var
 ts:timespec;
 tv:timeval;
 tvp:p_timeval;
 _set:sigset_t;
 uset:p_sigset_t;
 error:Integer;
begin
 if (uts<>nil) then
 begin
  error:=copyin(uts, @ts, sizeof(ts));
  if (error<>0) then
  begin
   Exit(error);
  end;
  TIMESPEC_TO_TIMEVAL(@tv, @ts);
  tvp:=@tv;
 end else
 begin
  tvp:=nil;
 end;
 if (sm<>nil) then
 begin
  error:=copyin(sm, @_set, sizeof(_set));
  if (error<>0) then
  begin
   Exit(error);
  end;
  uset:=@_set;
 end else
 begin
  uset:=nil;
 end;
 Exit(kern_pselect(nd, uin, uou, uex, tvp, uset, NFDBITS));
end;

function sys_select(nd:Integer;uin,uou,uex,utv:Pointer):Integer;
var
 tv:timeval;
 tvp:p_timeval;
 error:Integer;
begin
 if (utv<>nil) then
 begin
  error:=copyin(utv, @tv, sizeof(tv));
  if (error<>0) then
  begin
   Exit(error);
  end;
  tvp:=@tv;
 end else
 begin
  tvp:=nil;
 end;

 Exit(kern_select(nd, uin, uou, uex, tvp, NFDBITS));
end;

{
 * In the unlikely case when user specified n greater then the last
 * open file descriptor, check that no bits are set after the last
 * valid fd.  We must ExitEBADF if any is set.
 *
 * There are applications that rely on the behaviour.
 *
 * nd is fd_lastfile + 1.
 }
function select_check_badfd(fd_in:p_fd_set;nd,ndu,abi_nfdbits:Integer):Integer;
var
 addr,oaddr:PByte;
 b,i,res:Integer;
 bits:Byte;
begin
 if (nd >= ndu) or (fd_in=nil) then
 begin
  Exit(0);
 end;

 oaddr:=nil;
 bits:=0; { silence gcc }
 For i:=nd to ndu-1 do
 begin
  b:=i div NBBY;
  addr:=PByte(fd_in) + b;
  if (addr<>oaddr) then
  begin
   res:=fubyte(addr^);
   if (res=-1) then
   begin
    Exit(EFAULT);
   end;
   oaddr:=addr;
   bits:=res;
  end;
  if ((bits and (1 shl (i mod NBBY)))<>0) then
  begin
   Exit(EBADF);
  end;
 end;
 Exit(0);
end;

function kern_select(nd:Integer;
                     fd_in,fd_ou,fd_ex:p_fd_set;
                     tvp:p_timeval;
                     abi_nfdbits:Integer):Integer;
label
 done;
var
 td:p_kthread;
 {
  * The magic 2048 here is chosen to be just enough for FD_SETSIZE
  * infds with the new FD_SETSIZE of 1024, and more than enough for
  * FD_SETSIZE infds, outfds and exceptfds with the old FD_SETSIZE
  * of 256.
  }
 s_selbits:array[0..((2048 + (NFDBITS - 1)) div NFDBITS)-1] of fd_mask;
 ibits,obits:array[0..2] of p_fd_mask;
 selbits,sbp:p_fd_mask;
 _atv:timeval;
 atv,rtv,ttv,timo:Int64;
 error,lf,ndu:Integer;
 nbufbytes,ncpbytes,ncpubytes,_nfdbits:DWORD;

 function getbits(name:p_fd_set;x:Integer):Integer;
 begin
  if (name=nil) then
  begin
   ibits[x]:=nil;
   obits[x]:=nil;
  end else
  begin
   ibits[x]:=sbp + (nbufbytes div 2 div sizeof(fd_mask));
   obits[x]:=sbp;

   Inc(sbp,ncpbytes div sizeof(fd_mask));

   Result:=copyin(name, ibits[x], ncpubytes);

   if (Result=0) and (ncpbytes<>ncpubytes) then
   begin
    FillChar((PByte(ibits[x]) + ncpubytes)^,ncpbytes - ncpubytes,0);
   end;
  end;
 end;

 procedure putbits(name:p_fd_set;x:Integer);
 var
  error2:Integer;
 begin
  if (name<>nil) then
  begin
   error2:=copyout(@obits[x], name, ncpubytes);
   if (error2<>0) then
   begin
    error:=error2;
   end;
  end;
 end;

begin
 if (nd < 0) then
 begin
  Exit(EINVAL);
 end;

 td:=curkthread;
 ndu:=nd;

 lf:=fd_table.fd_lastfile;
 if (nd > lf + 1) then
 begin
  nd:=lf + 1;
 end;

 error:=select_check_badfd(fd_in, nd, ndu, abi_nfdbits);
 if (error<>0) then
 begin
  Exit(error);
 end;

 error:=select_check_badfd(fd_ou, nd, ndu, abi_nfdbits);
 if (error<>0) then
 begin
  Exit(error);
 end;

 error:=select_check_badfd(fd_ex, nd, ndu, abi_nfdbits);
 if (error<>0) then
 begin
  Exit(error);
 end;

 {
  * Allocate just enough bits for the non-nil fd_sets.  Use the
  * preallocated auto buffer if possible.
  }
 _nfdbits:=roundup(nd, NFDBITS);
 ncpbytes:=_nfdbits div NBBY;
 ncpubytes:=roundup(nd, abi_nfdbits) div NBBY;
 nbufbytes:=0;

 if (fd_in<>nil) then
 begin
  Inc(nbufbytes,2 * ncpbytes);
 end;
 if (fd_ou<>nil) then
 begin
  Inc(nbufbytes,2 * ncpbytes);
 end;
 if (fd_ex<>nil) then
 begin
  Inc(nbufbytes,2 * ncpbytes);
 end;

 if (nbufbytes <= sizeof(s_selbits)) then
  selbits:=@s_selbits[0]
 else
  selbits:=AllocMem(nbufbytes);

 {
  * Assign pointers into the bit buffers and fetch the input bits.
  * Put the output buffers together so that they can be bzeroed
  * together.
  }

 sbp:=selbits;

 error:=getbits(fd_in, 0);
 if (error<>0) then
 begin
  goto done;
 end;

 error:=getbits(fd_ou, 1);
 if (error<>0) then
 begin
  goto done;
 end;

 error:=getbits(fd_ex, 2);
 if (error<>0) then
 begin
  goto done;
 end;

 if (nbufbytes<>0) then
 begin
  FillChar(selbits^, nbufbytes div 2,0);
 end;

 if (tvp<>nil) then
 begin
  _atv:=tvp^;
  if (itimerfix(@_atv)<>0) then
  begin
   error:=EINVAL;
   goto done;
  end;
  atv:=TIMEVAL_TO_UNIT(@_atv);
  rtv:=get_unit_uptime;
  atv:=atv+rtv;
 end else
 begin
  atv:=0;
 end;
 timo:=0;
 seltdinit(td);
 { Iterate until the timeout expires or descriptors become ready. }
 repeat
  error:=selscan(td, @ibits, @obits, nd);
  if (error<>0) or (td^.td_retval[0]<>0) then
  begin
   break;
  end;
  if (atv<>0) then
  begin
   rtv:=get_unit_uptime;
   if (rtv>=atv) then
   begin
    break;
   end;
   ttv:=atv-rtv;

   if (ttv>24*60*60*hz) then
    timo:=24*60*60*hz
   else
    timo:=tvtohz(ttv);
  end;
  error:=seltdwait(td, timo);
  if (error<>0) then
  begin
   break;
  end;
  error:=selrescan(td, @ibits, @obits);
  if (error<>0) or (td^.td_retval[0]<>0) then
  begin
   break;
  end;
 until false;
 seltdclear(td);

done:
 { select is not restarted after signals... }
 case error of
  ERESTART   :error:=EINTR;
  EWOULDBLOCK:error:=0;
  else;
 end;

 if (error=0) then
 begin
  putbits(fd_in, 0);
  putbits(fd_ou, 1);
  putbits(fd_ex, 2);
 end;

 if (selbits<>@s_selbits[0]) then
 begin
  FreeMem(selbits);
 end;

 Exit(error);
end;
{
 * Convert a select bit set to poll flags.
 *
 * The backend always Exits POLLHUP/POLLERR if appropriate and we
 * Exitthis as a set bit in any set.
 }
const
 select_flags:array[0..2] of Integer=(
  POLLRDNORM or POLLHUP or POLLERR,
  POLLWRNORM or POLLHUP or POLLERR,
  POLLRDBAND or POLLERR);

{
 * Compute the fo_poll flags required for a fd given by the index and
 * bit position in the fd_mask array.
 }
function selflags(ibits:pp_fd_mask;idx:Integer;bit:fd_mask):Integer;
var
 flags,msk:Integer;
begin
 flags:=0;
 For msk:=0 to 2 do
 begin
  if (ibits[msk]=nil) then
  begin
   continue;
  end;
  if ((ibits[msk][idx] and bit)=0) then
  begin
   continue;
  end;
  flags:=flags or select_flags[msk];
 end;
 Exit(flags);
end;

{
 * Set the appropriate output bits given a mask of fired events and the
 * input bits originally requested.
 }
function selsetbits(ibits,obits:pp_fd_mask;idx:Integer;bit:fd_mask;events:Integer):Integer;
var
 msk,n:Integer;
begin
 n:=0;
 For msk:=0 to 2 do
 begin
  if ((events and select_flags[msk])=0) then
  begin
   continue;
  end;
  if (ibits[msk]=nil) then
  begin
   continue;
  end;
  if ((ibits[msk][idx] and bit)=0) then
  begin
   continue;
  end;
  {
   * XXX Check for a duplicate set.  This can occur because a
   * socket calls selrecord() twice for each poll() call
   * resulting in two selfds per real fd.  selrescan() will
   * call selsetbits twice as a result.
   }
  if ((obits[msk][idx] and bit)<>0) then
  begin
   continue;
  end;
  obits[msk][idx]:=obits[msk][idx] or bit;
  Inc(n);
 end;

 Exit(n);
end;

function getselfd_cap(fd:Integer;fpp:pp_file):Integer;
var
 fp:p_file;
 fp_fromcap:p_file;
 error:Integer;
begin
 fp:=fget_unlocked(fd);
 if (fp=nil) then
 begin
  Exit(EBADF);
 end;
 {
  * If the file descriptor is for a capability, test rights and use
  * the file descriptor references by the capability.
  }
 error:=cap_funwrap(fp, CAP_POLL_EVENT, @fp_fromcap);
 if (error<>0) then
 begin
  fdrop(fp);
  Exit(error);
 end;
 if (fp<>fp_fromcap) then
 begin
  fhold(fp_fromcap);
  fdrop(fp);
  fp:=fp_fromcap;
 end;
 fpp^:=fp;
 Exit(0);
end;

{
 * Traverse the list of fds attached to this thread's seltd and check for
 * completion.
 }
function selrescan(td:p_kthread;ibits,obits:pp_fd_mask):Integer;
var
 si:p_selinfo;
 stp:p_seltd;
 sfp:p_selfd;
 sfn:p_selfd;
 fp:p_file;
 bit:fd_mask;
 fd,ev,n,idx:Integer;
 error:Integer;
begin
 stp:=td^.td_sel;
 n:=0;

 sfp:=STAILQ_FIRST(@stp^.st_selq);
 while (sfp<>nil) do
 begin
  sfn:=STAILQ_NEXT(sfp,@sfp^.sf_link);
  //
  fd:=ptrint(sfp^.sf_cookie);
  si:=sfp^.sf_si;
  selfdfree(stp, sfp);
  { If the selinfo wasn't cleared the event didn't fire. }
  if (si<>nil) then
  begin
   sfp:=sfn;
   //
   continue;
  end;
  error:=getselfd_cap(fd, @fp);
  if (error<>0) then
  begin
   Exit(error);
  end;
  idx:=fd div NFDBITS;
  bit:=fd_mask(1) shl (fd mod NFDBITS);
  ev:=fo_poll(fp, selflags(ibits, idx, bit));
  fdrop(fp);
  if (ev<>0) then
  begin
   Inc(n,selsetbits(ibits, obits, idx, bit, ev));
  end;
  //
  sfp:=sfn;
 end;
 stp^.st_flags:=0;
 td^.td_retval[0]:=n;
 Exit(0);
end;

{
 * Perform the initial filedescriptor scan and register ourselves with
 * each selinfo.
 }
function selscan(td:p_kthread;ibits,obits:pp_fd_mask;nfd:Integer):Integer;
label
 _continue;
var
 fp:p_file;
 bit:fd_mask;
 ev,flags,_end,fd:Integer;
 n,idx:Integer;
 error:Integer;
begin
 n:=0;
 idx:=0;
 fd:=0;
 while (fd < nfd) do
 begin
  _end:=fd+NFDBITS;
  if (_end>nfd) then _end:=nfd;
  bit:=1;
  while (fd < _end) do
  begin
   { Compute the list of events we're interested in. }
   flags:=selflags(ibits, idx, bit);
   if (flags=0) then
   begin
    goto _continue;
   end;
   error:=getselfd_cap(fd, @fp);
   if (error<>0) then
   begin
    Exit(error);
   end;
   selfdalloc(td, Pointer(ptrint(fd)));
   ev:=fo_poll(fp, flags);
   fdrop(fp);
   if (ev<>0) then
   begin
    Inc(n,selsetbits(ibits, obits, idx, bit, ev));
   end;
   //
   _continue:
   bit:=bit shl 1;
   Inc(fd);
  end;
  //
  Inc(idx);
 end;

 td^.td_retval[0]:=n;
 Exit(0);
end;

function sys_poll(fds:Pointer;nfds:DWORD;timeout:Integer):Integer;
label
 done,
 _out;
var
 td:p_kthread;
 bits:p_pollfd;
 smallbits:array[0..31] of t_pollfd;
 atv,rtv,ttv,timo:Int64;
 error:Integer;
 ni:QWORD;
begin
 if (nfds > maxfilesperproc) and (nfds > FD_SETSIZE) then
 begin
  Exit(EINVAL);
 end;
 td:=curkthread;
 ni:=nfds * sizeof(t_pollfd);

 if (ni > sizeof(smallbits)) then
  bits:=AllocMem(ni)
 else
  bits:=@smallbits;

 error:=copyin(fds, bits, ni);
 if (error<>0) then
 begin
  goto done;
 end;
 if (timeout<>INFTIM) then
 begin
  atv:=USEC_TO_UNIT(_msec2usec(timeout));
  rtv:=get_unit_uptime;
  atv:=atv+rtv;
 end else
 begin
  atv:=0;
 end;
 timo:=0;
 seltdinit(td);
 { Iterate until the timeout expires or descriptors become ready. }
 repeat
  error:=pollscan(td, bits, nfds);
  if (error<>0) or (td^.td_retval[0]<>0) then
  begin
   break;
  end;
  if (atv<>0) then
  begin
   rtv:=get_unit_uptime;
   if (rtv>=atv) then
   begin
    break;
   end;
   ttv:=atv-rtv;

   if (ttv>24*60*60*hz) then
    timo:=24*60*60*hz
   else
    timo:=tvtohz(ttv);
  end;
  error:=seltdwait(td, timo);
  if (error<>0) then
  begin
   break;
  end;
  error:=pollrescan(td);
  if (error<>0) or (td^.td_retval[0]<>0) then
  begin
   break;
  end;
 until false;
 seltdclear(td);

done:
 { poll is not restarted after signals... }
 if (error=ERESTART) then
 begin
  error:=EINTR;
 end;
 if (error=EWOULDBLOCK) then
 begin
  error:=0;
 end;
 if (error=0) then
 begin
  error:=_pollout(td, bits, fds, nfds);
  if (error<>0) then
  begin
   goto _out;
  end;
 end;
_out:
 if (ni > sizeof(smallbits)) then
 begin
  FreeMem(bits);
 end;
 Exit(error);
end;

function pollrescan(td:p_kthread):Integer;
var
 stp:p_seltd;
 sfp:p_selfd;
 sfn:p_selfd;
 si:p_selinfo;
 fp:p_file;
 fd:p_pollfd;
 n:Integer;
begin
 n:=0;
 stp:=td^.td_sel;
 FILEDESC_SLOCK(@fd_table);
 sfp:=STAILQ_FIRST(@stp^.st_selq);
 while (sfp<>nil) do
 begin
  sfn:=STAILQ_NEXT(sfp,@sfp^.sf_link);
  //
  fd:=p_pollfd(sfp^.sf_cookie);
  si:=sfp^.sf_si;
  selfdfree(stp, sfp);
  { If the selinfo wasn't cleared the event didn't fire. }
  if (si<>nil) then
  begin
   sfp:=sfn;
   //
   continue;
  end;
  fp:=fget_unlocked(fd^.fd);
  if (cap_funwrap(fp, CAP_POLL_EVENT, @fp)<>0) then
  begin
   if (fp<>nil) then
   begin
    fdrop(fp);
   end;
   //
   fd^.revents:=POLLNVAL;
   Inc(n);
   //
   sfp:=sfn;
   //
   continue;
  end;
  {
   * Note: backend also Exits POLLHUP and
   * POLLERR if appropriate.
   }
  fd^.revents:=fo_poll(fp, fd^.events);
  if (fd^.revents<>0) then
  begin
   Inc(n);
  end;
  //
  fdrop(fp);
  //
  sfp:=sfn;
 end;
 FILEDESC_SUNLOCK(@fd_table);
 stp^.st_flags:=0;
 td^.td_retval[0]:=n;
 Exit(0);
end;

function _pollout(td:p_kthread;fds,ufds:p_pollfd;nfd:DWORD):Integer;
var
 error:Integer;
 i,n:DWORD;
begin
 error:=0;
 i:=0;
 n:=0;
 For i:=0 to nfd-1 do
 begin
  error:=copyout(@fds^.revents, @ufds^.revents, sizeof(ufds^.revents));
  if (error<>0) then
  begin
   Exit(error);
  end;
  if (fds^.revents<>0) then
  begin
   Inc(n);
  end;
  Inc(fds);
  Inc(ufds);
 end;
 td^.td_retval[0]:=n;
 Exit(0);
end;

function pollscan(td:p_kthread;fds:p_pollfd;nfd:DWORD):Integer;
var
 i:Integer;
 fp:p_file;
 n:Integer;
begin
 n:=0;
 i:=0;
 FILEDESC_SLOCK(@fd_table);
 While (i < nfd) do
 begin
  if (fds^.fd >= fd_table.fd_nfiles) then
  begin
   fds^.revents:=POLLNVAL;
   Inc(n);
  end else
  if (fds^.fd < 0) then
  begin
   fds^.revents:=0;
  end else
  begin
   fp:=fget_unlocked(fds^.fd);
   if (cap_funwrap(fp, CAP_POLL_EVENT, @fp)<>0) then
   begin
    fds^.revents:=POLLNVAL;
    Inc(n);
   end else
   begin
    {
     * Note: backend also Exits POLLHUP and
     * POLLERR if appropriate.
     }
    selfdalloc(td, fds);
    fds^.revents:=fo_poll(fp, fds^.events);
    {
     * POSIX requires POLLOUT to be never
     * set simultaneously with POLLHUP.
     }
    if ((fds^.revents and POLLHUP)<>0) then
    begin
     fds^.revents:=fds^.revents and (not POLLOUT);
    end;

    if (fds^.revents<>0) then
    begin
     Inc(n);
    end;
   end;
   //
   if (fp<>nil) then
   begin
    fdrop(fp);
   end;
  end;
  //
  Inc(i);
  Inc(fds);
 end;
 FILEDESC_SUNLOCK(@fd_table);
 td^.td_retval[0]:=n;
 Exit(0);
end;

{
 * Preallocate two selfds associated with 'cookie'.  Some fo_poll routines
 * have two select sets, one for read and another for write.
 }
procedure selfdalloc(td:p_kthread;cookie:Pointer);
var
 stp:p_seltd;
begin
 stp:=td^.td_sel;
 if (stp^.st_free1=nil) then
 begin
  stp^.st_free1:=AllocMem(SizeOf(t_selfd));
 end;
 stp^.st_free1^.sf_td:=stp;
 stp^.st_free1^.sf_cookie:=cookie;
 if (stp^.st_free2=nil) then
 begin
  stp^.st_free2:=AllocMem(SizeOf(t_selfd));
 end;
 stp^.st_free2^.sf_td:=stp;
 stp^.st_free2^.sf_cookie:=cookie;
end;

procedure selfdfree(stp:p_seltd;sfp:p_selfd);
begin
 STAILQ_REMOVE(@stp^.st_selq,sfp,@sfp^.sf_link);
 mtx_lock(sfp^.sf_mtx^);
 if (sfp^.sf_si<>nil) then
 begin
  TAILQ_REMOVE(@sfp^.sf_si^.si_tdlist,sfp,@sfp^.sf_threads);
 end;
 mtx_unlock(sfp^.sf_mtx^);
 FreeMem(sfp);
end;

{ Drain the waiters tied to all the selfd belonging the specified selinfo. }
procedure seldrain(sip:p_selinfo);
begin
 {
  * This feature is already provided by doselwakeup(), thus it is
  * enough to go for it.
  * Eventually, the context, should take care to avoid races
  * between thread calling select()/poll() and file descriptor
  * detaching, but, again, the races are just the same as
  * selwakeup().
  }
 doselwakeup(sip, -1);
end;

{
 * Record a select request.
 }
procedure selrecord(selector:p_kthread;sip:p_selinfo);
var
 sfp:p_selfd;
 stp:p_seltd;
 mtxp:p_mtx;
begin
 stp:=selector^.td_sel;
 {
  * Don't record when doing a rescan.
  }
 if ((stp^.st_flags and SELTD_RESCAN)<>0) then
 begin
  Exit;
 end;
 {
  * Grab one of the preallocated descriptors.
  }
 sfp:=stp^.st_free1;
 if (sfp<>nil) then
 begin
  stp^.st_free1:=nil
 end else
 begin
  sfp:=stp^.st_free2;
  if (sfp<>nil) then
  begin
   stp^.st_free2:=nil
  end else
  begin
   Assert(false,'selrecord: No free selfd on selq');
   Exit;
  end;
 end;
 mtxp:=sip^.si_mtx;
 if (mtxp=nil) then
 begin
  mtxp:=mtx_pool_find(mtxpool_select, sip);
 end;
 {
  * Initialize the sfp and queue it in the thread.
  }
 sfp^.sf_si:=sip;
 sfp^.sf_mtx:=mtxp;
 STAILQ_INSERT_TAIL(@stp^.st_selq,sfp,@sfp^.sf_link);
 {
  * Now that we've locked the sip, check for initialization.
  }
 mtx_lock(mtxp^);
 if (sip^.si_mtx=nil) then
 begin
  sip^.si_mtx:=mtxp;
  TAILQ_INIT(@sip^.si_tdlist);
 end;
 {
  * Add this thread to the list of selfds listening on this selinfo.
  }
 TAILQ_INSERT_TAIL(@sip^.si_tdlist,sfp,@sfp^.sf_threads);
 mtx_unlock(sip^.si_mtx^);
end;

{ Wake up a selecting thread. }

procedure selwakeup(sip:p_selinfo);
begin
 doselwakeup(sip, -1);
end;

{ Wake up a selecting thread, and set its priority. }
procedure selwakeuppri(sip:p_selinfo;pri:Integer);
begin
 doselwakeup(sip, pri);
end;

{
 * Do a wakeup when a selectable event occurs.
 }
procedure doselwakeup(sip:p_selinfo;pri:Integer);
var
 sfp:p_selfd;
 sfn:p_selfd;
 stp:p_seltd;
begin
 { If it's not initialized there can't be any waiters. }
 if (sip^.si_mtx=nil) then
 begin
  Exit;
 end;
 {
  * Locking the selinfo locks all selfds associated with it.
  }
 mtx_lock(sip^.si_mtx^);
 sfp:=TAILQ_FIRST(@sip^.si_tdlist);
 while (sfp<>nil) do
 begin
  sfn:=TAILQ_NEXT(sfp,@sfp^.sf_threads);
  {
   * Once we remove this sfp from the list and clear the
   * sf_si seltdclear will know to ignore this si.
   }
  TAILQ_REMOVE(@sip^.si_tdlist,sfp,@sfp^.sf_threads);
  sfp^.sf_si:=nil;
  stp:=sfp^.sf_td;
  mtx_lock(stp^.st_mtx);
  stp^.st_flags:=stp^.st_flags or SELTD_PENDING;
  cv_broadcastpri(@stp^.st_wait, pri);
  mtx_unlock(stp^.st_mtx);
  //
  sfp:=sfn;
 end;
 mtx_unlock(sip^.si_mtx^);
end;

procedure seltdinit(td:p_kthread);
label
 _out;
var
 stp:p_seltd;
begin
 stp:=td^.td_sel;
 if (stp<>nil) then
 begin
  goto _out;
 end;
 stp:=AllocMem(sizeof(t_seltd));
 td^.td_sel:=stp;
 mtx_init(stp^.st_mtx, 'sellck');
 cv_init(@stp^.st_wait,'select');
_out:
 stp^.st_flags:=0;
 STAILQ_INIT(@stp^.st_selq);
end;

function seltdwait(td:p_kthread;timo:Int64):Integer;
var
 stp:p_seltd;
 error:Integer;
begin
 stp:=td^.td_sel;
 {
  * An event of interest may occur while we do not hold the seltd
  * locked so check the pending flag before we sleep.
  }
 mtx_lock(stp^.st_mtx);
 {
  * Any further calls to selrecord will be a rescan.
  }
 stp^.st_flags:=stp^.st_flags or SELTD_RESCAN;
 if ((stp^.st_flags and SELTD_PENDING)<>0) then
 begin
  mtx_unlock(stp^.st_mtx);
  Exit(0);
 end;

 if (timo > 0) then
  error:=_cv_timedwait_sig(@stp^.st_wait, @stp^.st_mtx, timo)
 else
  error:=_cv_wait_sig(@stp^.st_wait, @stp^.st_mtx);

 mtx_unlock(stp^.st_mtx);

 Exit(error);
end;

procedure seltdfini(td:p_kthread);
var
 stp:p_seltd;
begin
 stp:=td^.td_sel;
 if (stp=nil) then
 begin
  Exit;
 end;
 if (stp^.st_free1<>nil) then
 begin
  FreeMem(stp^.st_free1);
 end;
 if (stp^.st_free2<>nil) then
 begin
  FreeMem(stp^.st_free2);
 end;
 td^.td_sel:=nil;
 FreeMem(stp);
end;

{
 * Remove the references to the thread from all of the objects we were
 * polling.
 }
procedure seltdclear(td:p_kthread);
var
 stp:p_seltd;
 sfp:p_selfd;
 sfn:p_selfd;
begin
 stp:=td^.td_sel;
 sfp:=STAILQ_FIRST(@stp^.st_selq);
 while (sfp<>nil) do
 begin
  sfn:=STAILQ_NEXT(sfp,@sfp^.sf_link);
  //
  selfdfree(stp, sfp);
  //
  sfp:=sfn;
 end;
 stp^.st_flags:=0;
end;

procedure selectinit();
begin
 mtxpool_select:=mtx_pool_create('select mtxpool', 128);
end;

end.

