unit uipc_syscalls;

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

interface

function sys_socket     (domain,stype,protocol:Integer):Integer;
function sys_socketex   (name:pchar;domain,stype,protocol:Integer):Integer;
function sys_socketclose(fd:Integer):Integer;
function sys_bind       (s:Integer;name:Pointer;namelen:Integer):Integer;
function sys_listen     (s,backlog:Integer):Integer;
function sys_accept     (s:Integer;aname,anamelen:Pointer):Integer;
function sys_connect    (fd:Integer;name:Pointer;namelen:Integer):Integer;
function sys_setsockopt (s,level,name:Integer;val:Pointer;valsize:Integer):Integer;
function sys_netabort   (fd,flags:Integer):Integer;

implementation

uses
 errno,
 mqueue,
 systm,
 md_sleep,
 kern_thr,
 kern_descrip,
 sys_conf,
 kern_socket,
 vsocket,
 sockopt,
 vsocketvar,
 vuio,
 vfile,
 vfcntl,
 vcapability;

{
 Convert a user file descriptor to a kernel file entry and check that, if
 it is a capability, the right rights are present. A reference on the file
 entry is held upon returning.
}
function getsock_cap(fd    :Integer;
                     rights:cap_rights_t;
                     fpp   :pp_file;
                     fflagp:PDWORD):Integer;
var
 fp:p_file;
begin
 fp:=fget_unlocked(fd);

 if (fp=nil) then
 begin
  Exit(EBADF);
 end;

 if (fp^.f_type<>DTYPE_SOCKET) and
    (fp^.f_type<>DTYPE_IPCSOCKET) then
 begin
  fdrop(fp);
  Exit(ENOTSOCK);
 end;

 if (fflagp<>nil) then
 begin
  fflagp^:=fp^.f_flag;
 end;

 fpp^:=fp;
 Exit(0);
end;

function sys_socket(domain,stype,protocol:Integer):Integer;
var
 td:p_kthread;
 so:Pointer;
 fp:p_file;
 fd:Integer;
 dtype:Integer;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 fd:=0;
 Result:=falloc(@fp,@fd,0);
 if (Result<>0) then Exit();

 so:=nil; /////socreate

 dtype:=DTYPE_SOCKET;
 if (domain=PF_UNIX) then
 begin
  dtype:=DTYPE_IPCSOCKET;
 end;

 finit(fp,FREAD or FWRITE, dtype, so, @socketops);

 fdrop(fp);

 td^.td_retval[0]:=fd;
end;

function sys_socketex(name:pchar;domain,stype,protocol:Integer):Integer;
var
 td:p_kthread;
 so:Pointer;
 fp:p_file;
 fd:Integer;
 dtype:Integer;
begin
 td:=curkthread;
 if (td=nil) then Exit(-1);

 fd:=0;
 Result:=falloc(@fp,@fd,0);
 if (Result<>0) then Exit();

 so:=nil; /////socreate

 dtype:=DTYPE_SOCKET;
 if (domain=PF_UNIX) then
 begin
  dtype:=DTYPE_IPCSOCKET;
 end;

 finit(fp,FREAD or FWRITE, dtype, so, @socketops);

 fdrop(fp);

 td^.td_retval[0]:=fd;
end;

function sys_socketclose(fd:Integer):Integer;
begin
 Result:=kern_close(fd);
end;

function getsockaddr(namp:pp_sockaddr;uaddr:Pointer;len:size_t):Integer;
var
 sa:p_sockaddr;
begin
 sa:=nil;

 if (len>SOCK_MAXADDRLEN) then
 begin
  Exit(ENAMETOOLONG);
 end;

 if (len<size_t(@p_sockaddr(nil)^.sa_data[0])) then
 begin
  Exit(EINVAL);
 end;

 sa:=AllocMem(len);

 Result:=copyin(uaddr, sa, len);

 if (Result<>0) then
 begin
  FreeMem(sa);
 end else
 begin
  sa^.sa_len:=len;
  namp^:=sa;
 end;
end;

function kern_bind(fd:Integer;sa:p_sockaddr):Integer;
var
 //so:p_socket;
 fp:p_file;
 error:Integer;
begin
 error:=getsock_cap(fd, CAP_BIND, @fp, nil);
 if (error<>0) then
 begin
  Exit(error);
 end;
 //so:=fp^.f_data;
 //error:=sobind(so, sa, td);
 fdrop(fp);
 Exit(error);
end;

function sys_bind(s:Integer;name:Pointer;namelen:Integer):Integer;
var
 sa:p_sockaddr;
 error:Integer;
begin
 error:=getsockaddr(@sa, name, namelen);

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

 error:=kern_bind(s, sa);

 FreeMem(sa);

 Exit(error);
end;

function kern_connect(fd:Integer;sa:p_sockaddr):Integer;
var
 fp:p_file;
begin
 fp:=nil;
 Result:=getsock_cap(fd, CAP_CONNECT, @fp, nil);
 if (Result<>0) then Exit;

 Result:=ECONNREFUSED;

 fdrop(fp);
end;

function sys_listen(s,backlog:Integer):Integer;
var
 //so:p_socket;
 fp:p_file;
 error:Integer;
begin
 error:=getsock_cap(s, CAP_LISTEN, @fp, nil);
 if (error=0) then
 begin
  //so:=fp^.f_data;
  //error:=solisten(so, uap^.backlog, td);
  fdrop(fp);
 end;
 Exit(error);
end;

function kern_accept(s:Integer;name:pp_sockaddr;
                     namelen:p_socklen_t;fp:pp_file):Integer;
label
 done,
 noconnection;
var
 td:p_kthread;
 headfp,nfp:p_file;
 sa:p_sockaddr;
 error:Integer;
 //so,head:p_socket;
 fd:Integer;
 fflag:DWORD;
 pgid:Integer; //pid_t
 tmp:Integer;
begin
 td:=curkthread;
 headfp:=nil;
 nfp   :=nil;
 sa    :=nil;

 if (name<>nil) then
 begin
  name^:=nil;
  if (namelen^ < 0) then
  begin
   Exit(EINVAL);
  end;
 end;

 error:=getsock_cap(s, CAP_ACCEPT, @headfp, @fflag);

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

 {
 head:=headfp^.f_data;
 if ((head^.so_options and SO_ACCEPTCONN)=0) then
 begin
  error:=EINVAL;
  goto done;
 end;
 }

 error:=falloc(@nfp, @fd, 0);

 if (error<>0) then
 begin
  goto done;
 end;

 //////////////////
 msleep_td(0);
 error:=EWOULDBLOCK;
 goto noconnection;
  //////////////////

 ACCEPT_LOCK();

 {
 if (((head^.so_state and SS_NBIO)<>0) and TAILQ_EMPTY(@head^.so_comp)) then
 begin
  ACCEPT_UNLOCK();
  error:=EWOULDBLOCK;
  goto noconnection;
 end;
 }

 {
 while ((TAILQ_EMPTY(@head^.so_comp)) and (head^.so_error=0)) do
 begin
  if (head^.so_rcv.sb_state & SBS_CANTRCVMORE) then
  begin
   head^.so_error:=ECONNABORTED;
   break;
  end;
  error:=msleep(@head^.so_timeo, @accept_mtx, PSOCK or PCATCH, 'accept', 0);
  if (error) then
  begin
   ACCEPT_UNLOCK();
   goto noconnection;
  end;
 end;
 }

 {
 if (head^.so_error<>0) then
 begin
  error:=head^.so_error;
  head^.so_error:=0;
  ACCEPT_UNLOCK();
  goto noconnection;
 end;
 }

 {
 so:=TAILQ_FIRST(@head^.so_comp);
 Assert(not (so^.so_qstate and SQ_INCOMP), 'accept1: so SQ_INCOMP');
 Assert(so^.so_qstate and SQ_COMP, 'accept1: so not SQ_COMP');
 }

 {
  * Before changing the flags on the socket, we have to bump the
  * reference count.  Otherwise, if the protocol calls sofree(),
  * the socket will be released due to a zero refcount.
  }

 {
 SOCK_LOCK(so);   { soref() and so_state update }
 soref(so);   { file descriptor reference }

 TAILQ_REMOVE(@head^.so_comp, so, so_list);
 Dec(head^.so_qlen);
 so^.so_state :=so^.so_state or (head^.so_state and SS_NBIO);
 so^.so_qstate:=so^.so_qstate and (not SQ_COMP);
 so^.so_head  :=nil;

 SOCK_UNLOCK(so);
 }

 ACCEPT_UNLOCK();


 { An extra reference on `nfp' has been held for us by falloc(). }
 td^.td_retval[0]:=fd;

 {
 { connection has been removed from the listen queue }
 KNOTE_UNLOCKED(@head^.so_rcv.sb_sel.si_note, 0);

 pgid:=fgetown(@head^.so_sigio);
 if (pgid<>0) then
 begin
  fsetown(pgid, @so^.so_sigio);
 end;

 finit(nfp, fflag, DTYPE_SOCKET, so, @socketops);

 { Sync socket nonblocking/async state with file flags }
 tmp:=fflag and FNONBLOCK;
 fo_ioctl(nfp, FIONBIO, @tmp, td^.td_ucred, td);

 tmp:=fflag and FASYNC;
 fo_ioctl(nfp, FIOASYNC, @tmp, td^.td_ucred, td);

 sa:=0;
 error:=soaccept(so, @sa);
 }

 if (error<>0) then
 begin
  {
   * Exit a namelen of zero for older code which might
   * ignore the Exit value from accept.
   }
  if (name<>nil) then
  begin
   namelen^:=0;
  end;
  goto noconnection;
 end;

 if (sa=nil) then
 begin
  if (name<>nil) then
  begin
   namelen^:=0;
  end;
  goto done;
 end;

 if (name<>nil) then
 begin
  { check sa_len before it is destroyed }
  if (namelen^ > sa^.sa_len) then
  begin
   namelen^:=sa^.sa_len;
  end;

  name^:=sa;
  sa:=nil;
 end;

noconnection:
 if (sa<>nil) then
 begin
  FreeMem(sa);
 end;

 {
  * close the new descriptor, assuming someone hasn't ripped it
  * out from under us.
  }
 if (error<>0) then
 begin
  fdclose(nfp, fd);
 end;

 {
  * Release explicitly held references before Exiting.  We Exit
  * a reference on nfp to the caller on success if they request it.
  }
done:
 if (fp<>nil) then
 begin
  if (error=0) then
  begin
   fp^:=nfp;
   nfp:=nil;
  end else
  begin
   fp^:=nil;
  end;
 end;
 //
 if (nfp<>nil) then
 begin
  fdrop(nfp);
 end;
 fdrop(headfp);
 //
 Exit(error);
end;

function accept1(s:Integer;aname:p_sockaddr;anamelen:p_socklen_t):Integer;
var
 td:p_kthread;
 name:p_sockaddr;
 namelen:socklen_t;
 fp:p_file;
 error:Integer;
begin
 td:=curkthread;

 if (aname=nil) then
 begin
  Exit(kern_accept(s, nil, nil, nil));
 end;

 error:=copyin(anamelen, @namelen, sizeof(namelen));

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

 error:=kern_accept(s, @name, @namelen, @fp);

 {
  * Exit a namelen of zero for older code which might
  * ignore the Exit value from accept.
  }
 if (error<>0) then
 begin
  copyout(@namelen, anamelen, sizeof(anamelen^));
  Exit(error);
 end;

 if (error=0) and (name<>nil) then
 begin
  error:=copyout(name, name, namelen);
 end;

 if (error=0) then
 begin
  error:=copyout(@namelen,anamelen,sizeof(namelen));
 end;

 if (error<>0) then
 begin
  fdclose(fp, td^.td_retval[0]);
 end;

 fdrop(fp);
 FreeMem(name);

 Exit(error);
end;

function sys_accept(s:Integer;aname,anamelen:Pointer):Integer;
begin
 Exit(accept1(s,aname,anamelen));
end;

function sys_connect(fd:Integer;name:Pointer;namelen:Integer):Integer;
var
 sa:p_sockaddr;
begin
 sa:=nil;
 Result:=getsockaddr(@sa, name, namelen);
 if (Result<>0) then Exit;

 Result:=kern_connect(fd, sa);

 FreeMem(sa);
end;

function kern_setsockopt(s,level,name:Integer;val:Pointer;valseg:uio_seg;valsize:Integer):Integer;
var
 error:Integer;
 //so:p_socket;
 fp:p_file;
 sopt:t_sockopt;
begin
 if (val=nil) and (valsize<>0) then
 begin
  Exit(EFAULT);
 end;

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

 sopt.sopt_dir    :=SOPT_SET;
 sopt.sopt_level  :=level;
 sopt.sopt_name   :=name;
 sopt.sopt_val    :=val;
 sopt.sopt_valsize:=valsize;

 case (valseg) of
  UIO_USERSPACE:sopt.sopt_td:=curkthread;
  UIO_SYSSPACE :sopt.sopt_td:=nil;
  else
   Assert(false,'kern_setsockopt called with bad valseg');
 end;

 error:=getsock_cap(s, CAP_SETSOCKOPT, @fp, nil);
 if (error=0) then
 begin
  //so:=fp->f_data;
  //error:=sosetopt(so, @sopt);
  fdrop(fp);
 end;

 Exit(error);
end;

function sys_setsockopt(s,level,name:Integer;val:Pointer;valsize:Integer):Integer;
begin
 Result:=kern_setsockopt(s,level,name,val,UIO_USERSPACE,valsize);
end;

const
 SOCKET_ABORT_FLAG_RCV      =$00000001;
 SOCKET_ABORT_FLAG_SND      =$00000002;
 SOCKET_ABORT_FLAG_SND_AGAIN=$00000004;

function sys_netabort(fd,flags:Integer):Integer;
var
 error:Integer;
 fp:p_file;
begin

 if ((flags and $1000000)=0) then
 begin
  error:=getsock_cap(fd, flags, @fp, nil);
  if (error=0) then
  begin
   //error:=bnet_netabort(td,fp->f_data,flags);
   fdrop(fp);
  end;
 end else
 begin
  flags:=flags and $feffffff;
  error:=0;
  //error:=kern_epollabort(td,fd,flags);
 end;

 Exit(error);
end;

end.

