unit kern_descrip;

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

interface

uses
 kern_param,
 kern_thr,
 kern_proc,
 kern_id,
 sys_event,
 vfile,
 vstat,
 vuio,
 vcapability,
 vfcntl,
 vfilio,
 vmount,
 vnode,
 vsocketvar;

const
 FGET_GETCAP=$00000001;

function  kern_close(fd:Integer):Integer;

function  sys_getdtablesize():Integer;
function  sys_dup2(from,_to:Integer):Integer;
function  sys_dup(u_fd:Integer):Integer;
function  sys_fcntl(fd,cmd:Integer;arg:QWORD):Integer;
function  sys_close(fd:Integer):Integer;
function  sys_fstat(fd:Integer;sb:Pointer):Integer;
function  sys_fpathconf(fd,name:Integer):Integer;
function  sys_flock(fd,how:Integer):Integer;

function  fget_unlocked(fd:Integer):p_file;
function  falloc_noinstall(resultfp:pp_file):Integer;
function  finstall(fp:p_file;fd:PInteger;flags:Integer):Integer;
function  falloc(resultfp:pp_file;resultfd:PInteger;flags:Integer):Integer;

procedure fhold(fp:p_file);
function  fdrop(fp:p_file):Integer;

function  fget(fd:Integer;
               rights:cap_rights_t;
               fpp:pp_file):Integer;

function  fget_mmap(fd:Integer;
                    rights:cap_rights_t;
                    maxprotp:PByte;
                    fpp:pp_file):Integer;

function  fget_read(fd:Integer;
                    rights:cap_rights_t;
                    fpp:pp_file):Integer;

function  fget_write(fd:Integer;
                     rights:cap_rights_t;
                     fpp:pp_file):Integer;

function  fgetcap(fd:Integer;
                  fpp:pp_file):Integer;

function  fgetvp(fd:Integer;
                 rights:cap_rights_t;
                 vpp:pp_vnode):Integer;

function  fgetvp_rights(fd:Integer;
                        need:cap_rights_t;
                        have:p_cap_rights_t;
                        vpp:pp_vnode):Integer;

function  fgetvp_read(fd:Integer;
                      rights:cap_rights_t;
                      vpp:pp_vnode):Integer;

function  fgetvp_exec(fd:Integer;
                      rights:cap_rights_t;
                      vpp:pp_vnode):Integer;

function  fgetvp_write(fd:Integer;
                       rights:cap_rights_t;
                       vpp:pp_vnode):Integer;

function  fgetsock(fd:Integer;
                   rights:cap_rights_t;
                   spp:pp_socket;
                   fflagp:PDWORD):Integer;

procedure fdclose(fp:p_file;idx:Integer);
function  dupfdopen(indx,dfd,mode,error:Integer):Integer;
procedure finit(fp:p_file;flag:DWORD;_type:Word;data:Pointer;ops:p_fileops);

procedure fdcloseexec();

function  badfo_readwrite(fp:p_file;uio:p_uio;flags:Integer):Integer;
function  badfo_truncate(fp:p_file;length:Int64):Integer;
function  badfo_ioctl(fp:p_file;com:QWORD;data:Pointer):Integer;
function  badfo_poll(fp:p_file;events:Integer):Integer;
function  badfo_kqfilter(fp:p_file;kn:p_knote):Integer;
function  badfo_stat(fp:p_file;sb:p_stat):Integer;
function  badfo_close(fp:p_file):Integer;
function  badfo_chmod(fp:p_file;mode:mode_t):Integer;
function  badfo_chown(fp:p_file;uid:uid_t;gid:gid_t):Integer;

function  invfo_chmod(fp:p_file;mode:mode_t):Integer;
function  invfo_chown(fp:p_file;uid:uid_t;gid:gid_t):Integer;

const
 badfileops:fileops=(
  fo_read    :@badfo_readwrite;
  fo_write   :@badfo_readwrite;
  fo_truncate:@badfo_truncate;
  fo_ioctl   :@badfo_ioctl;
  fo_poll    :@badfo_poll;
  fo_kqfilter:@badfo_kqfilter;
  fo_stat    :@badfo_stat;
  fo_close   :@badfo_close;
  fo_chmod   :@badfo_chmod;
  fo_chown   :@badfo_chown;
  fo_flags   :0
 );

procedure fildesc_drvinit(); //SYSINIT(fildescdev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, fildesc_drvinit, NULL);

implementation

uses
 atomic,
 errno,
 systm,
 vfiledesc,
 vfs_vnops,
 vnode_if,
 sys_resource,
 kern_resource,
 kern_mtx,
 sys_conf;

//

function  cap_funwrap(fp_cap:p_file;rights:cap_rights_t;fpp:pp_file):Integer; external;
function  cap_rights(fp_cap:p_file):cap_rights_t; external;
function  cap_funwrap_mmap(fp_cap:p_file;rights:cap_rights_t;maxprotp:PByte;fpp:pp_file):Integer; external;

//

function badfo_readwrite(fp:p_file;uio:p_uio;flags:Integer):Integer;
begin
 Exit(EBADF);
end;

function badfo_truncate(fp:p_file;length:Int64):Integer;
begin
 Exit(EINVAL);
end;

function badfo_ioctl(fp:p_file;com:QWORD;data:Pointer):Integer;
begin
 Exit(EBADF);
end;

function badfo_poll(fp:p_file;events:Integer):Integer;
begin
 Exit(0);
end;

function badfo_kqfilter(fp:p_file;kn:p_knote):Integer;
begin
 Exit(EBADF);
end;

function badfo_stat(fp:p_file;sb:p_stat):Integer;
begin
 Exit(EBADF);
end;

function badfo_close(fp:p_file):Integer;
begin
 Exit(EBADF);
end;

function badfo_chmod(fp:p_file;mode:mode_t):Integer;
begin
 Exit(EBADF);
end;

function badfo_chown(fp:p_file;uid:uid_t;gid:gid_t):Integer;
begin
 Exit(EBADF);
end;

function invfo_chmod(fp:p_file;mode:mode_t):Integer;
begin
 Exit(EINVAL);
end;

function invfo_chown(fp:p_file;uid:uid_t;gid:gid_t):Integer;
begin
 Exit(EINVAL);
end;

{
 * System calls on descriptors.
 }
function sys_getdtablesize():Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 curkthread^.td_retval[0]:=lim_cur(RLIMIT_NOFILE);
 Exit(0);
end;

// Flags for do_dup()
const
 DUP_FIXED  =$1; // Force fixed allocation
 DUP_FCNTL  =$2; // fcntl()-style errors
 DUP_CLOEXEC=$4; // Atomically set FD_CLOEXEC

function do_dup(flags,old,new:Integer;retval:PQWORD):Integer; forward;

{
 * Duplicate a file descriptor to a particular value.
 *
 * Note: keep in mind that a potential race condition exists when closing
 * descriptors from a shared descriptor table (via rfork).
 }
function sys_dup2(from,_to:Integer):Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 Exit(do_dup(DUP_FIXED, from, _to,  @curkthread^.td_retval));
end;

{
 * Duplicate a file descriptor.
 }
function sys_dup(u_fd:Integer):Integer;
begin
 //priv_check(td,688);
 Exit(EPERM);

 Exit(do_dup(0, u_fd, 0, @curkthread^.td_retval));
end;

function kern_fcntl(fd,cmd:Integer;arg:QWORD):Integer; forward;

{
 * The file control system call.
 }
function sys_fcntl(fd,cmd:Integer;arg:QWORD):Integer;
var
 fl:t_flock;
 ofl:__oflock;
 error:Integer;
begin

 //if (priv_check(td,683) <> 0) then
 if (cmd > 13) or (($3818 shr (cmd and $1f) and 1)=0) then
 begin
  Exit(EINVAL);
 end;

 error:=0;
 case cmd of
  F_OGETLK,
  F_OSETLK,
  F_OSETLKW:
   begin
    {
     * Convert old flock structure to new.
     }
    error:=copyin(Pointer(arg), @ofl, sizeof(ofl));
    fl.l_start :=ofl.l_start;
    fl.l_len   :=ofl.l_len;
    fl.l_pid   :=ofl.l_pid;
    fl.l_type  :=ofl.l_type;
    fl.l_whence:=ofl.l_whence;
    fl.l_sysid :=0;

    case cmd of
     F_OGETLK :cmd:=F_GETLK;
     F_OSETLK :cmd:=F_SETLK;
     F_OSETLKW:cmd:=F_SETLKW;
    end;
    arg:=QWORD(@fl);
   end;

  F_GETLK,
  F_SETLK,
  F_SETLKW,
  F_SETLK_REMOTE:
   begin
    error:=copyin(Pointer(arg), @fl, sizeof(fl));
    arg:=QWORD(@fl);
   end;
  else;
 end;
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=kern_fcntl(fd, cmd, arg);
 if (error<>0) then
 begin
  Exit(error);
 end;
 if (cmd=F_OGETLK) then
 begin
  ofl.l_start :=fl.l_start;
  ofl.l_len   :=fl.l_len;
  ofl.l_pid   :=fl.l_pid;
  ofl.l_type  :=fl.l_type;
  ofl.l_whence:=fl.l_whence;
  error:=copyout(@ofl, Pointer(arg), sizeof(ofl));
 end else
 if (cmd=F_GETLK) then
 begin
  error:=copyout(@fl, Pointer(arg), sizeof(fl));
 end;
 Exit(error);
end;

function fdunwrap(fd:Integer;rights:cap_rights_t;fpp:pp_file):Integer;
var
 fp_fromcap:p_file;
 err:Integer;
begin

 fpp^:=fget_unlocked(fd);
 if (fpp^=nil) then
 begin
  Exit(EBADF);
 end;

 if (fpp^^.f_type=DTYPE_CAPABILITY) then
 begin
  fp_fromcap:=nil;
  err:=cap_funwrap(fpp^, rights, @fp_fromcap);
  if (err<>0) then
  begin
   fdrop(fpp^);
   fpp^:=nil;
   Exit(err);
  end;
  if (fpp^<>fp_fromcap) then
  begin
   fhold(fp_fromcap);
   fdrop(fpp^);
   fpp^:=fp_fromcap;
  end;
 end;

 Exit(0);
end;

function kern_fcntl(fd,cmd:Integer;arg:QWORD):Integer;
label
 _break,
 do_setlk,
 _break2,
 do_readahead,
 readahead_vnlock_fail;
var
 td:p_kthread;
 flp:p_flock;
 fp:p_file;
 vp:p_vnode;
 error,flg,tmp:Integer;
 vfslocked:Integer;
 old,new:DWORD;
 bsize:QWORD;
 foffset:Int64;
begin
 td:=curkthread;
 vfslocked:=0;
 error:=0;
 flg:=F_POSIX;
 fp:=nil;

 case cmd of
  F_DUPFD:
   begin
    tmp:=arg;
    error:=do_dup(DUP_FCNTL, fd, tmp,@td^.td_retval);
   end;

  F_DUPFD_CLOEXEC:
   begin
    tmp:=arg;
    error:=do_dup(DUP_FCNTL or DUP_CLOEXEC, fd, tmp,@td^.td_retval);
   end;

  F_DUP2FD:
   begin
    tmp:=arg;
    error:=do_dup(DUP_FIXED, fd, tmp,@td^.td_retval);
   end;

  F_DUP2FD_CLOEXEC:
   begin
    tmp:=arg;
    error:=do_dup(DUP_FIXED or DUP_CLOEXEC, fd, tmp,@td^.td_retval);
   end;

  F_GETFD:
   begin
    fp:=fget_unlocked(fd);
    if (fp=nil) then
    begin
     error:=EBADF;
     goto _break;
    end;
    if ((fp^.f_exclose and UF_EXCLOSE)<>0) then
    begin
     td^.td_retval[0]:=FD_CLOEXEC;
    end else
    begin
     td^.td_retval[0]:=0;
    end;
   end;

  F_SETFD:
   begin
    fp:=fget_unlocked(fd);
    if (fp=nil) then
    begin
     error:=EBADF;
     goto _break;
    end;
    if ((arg and FD_CLOEXEC)<>0) then
    begin
     atomic_set_int(@fp^.f_exclose,UF_EXCLOSE);
    end else
    begin
     atomic_clear_int(@fp^.f_exclose,UF_EXCLOSE);
    end;
   end;

  F_GETFL:
   begin
    error:=fdunwrap(fd, CAP_FCNTL, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    td^.td_retval[0]:=OFLAGS(fp^.f_flag);
   end;

  F_SETFL:
   begin
    error:=fdunwrap(fd, CAP_FCNTL, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    repeat
     flg:=fp^.f_flag;
     tmp:=flg;
     tmp:=tmp and (not FCNTLFLAGS);
     tmp:=tmp or FFLAGS(arg and (not O_ACCMODE)) and FCNTLFLAGS;
    until (System.InterlockedCompareExchange(fp^.f_flag,tmp,flg)=flg);
    tmp:=fp^.f_flag and FNONBLOCK;
    error:=fo_ioctl(fp, FIONBIO, @tmp);
    if (error<>0) then
    begin
     goto _break;
    end;
    tmp:=fp^.f_flag and FASYNC;
    error:=fo_ioctl(fp, FIOASYNC, @tmp);
    if (error=0) then
    begin
     goto _break;
    end;
    atomic_clear_int(@fp^.f_flag, FNONBLOCK);
    tmp:=0;
    fo_ioctl(fp, FIONBIO, @tmp);
   end;

  F_GETOWN:
   begin
    error:=fdunwrap(fd, CAP_FCNTL, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    error:=fo_ioctl(fp, FIOGETOWN, @tmp);
    if (error=0) then
    begin
     td^.td_retval[0]:=tmp;
    end;
   end;

  F_SETOWN:
   begin
    error:=fdunwrap(fd, CAP_FCNTL, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    tmp:=arg;
    error:=fo_ioctl(fp, FIOSETOWN, @tmp);
   end;

  F_SETLK_REMOTE:
   begin
    error:=EPERM;
    //error:=priv_check(td, PRIV_NFS_LOCKD);
    if (error<>0) then
     Exit(error);
    flg:=F_REMOTE;
    goto do_setlk;
   end;

  F_SETLKW:
  begin
   flg:=flg or F_WAIT;
   goto do_setlk;
  end;

  F_SETLK:
   begin
    do_setlk:
    error:=fdunwrap(fd, CAP_FLOCK, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    if (fp^.f_type<>DTYPE_VNODE) then
    begin
     error:=EBADF;
     goto _break;
    end;
    flp:=p_flock(arg);
    if (flp^.l_whence=SEEK_CUR) then
    begin
     FILEDESC_SLOCK(@fd_table);
     foffset:=foffset_get(fp);
     if (foffset < 0) or
        ((flp^.l_start > 0) and
         (foffset > High(Int64) - flp^.l_start)) then
     begin
      FILEDESC_SUNLOCK(@fd_table);
      error:=EOVERFLOW;
      goto _break;
     end;
     Inc(flp^.l_start,foffset);
     FILEDESC_SUNLOCK(@fd_table);
    end;

    {
     * VOP_ADVLOCK() may block.
     }
    vp:=fp^.f_vnode;
    vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
    case flp^.l_type of
     F_RDLCK:
      begin
       if ((fp^.f_flag and FREAD)=0) then
       begin
        error:=EBADF;
        goto _break2;
       end;
       PROC_LOCK();
       p_proc.p_flag:=p_proc.p_flag or P_ADVLOCK;
       PROC_UNLOCK();
       error:=VOP_ADVLOCK(vp, @p_proc, F_SETLK, flp, flg);
      end;
     F_WRLCK:
      begin
       if ((fp^.f_flag and FWRITE)=0) then
       begin
        error:=EBADF;
        goto _break2;
       end;
       PROC_LOCK();
       p_proc.p_flag:=p_proc.p_flag or P_ADVLOCK;
       PROC_UNLOCK();
       error:=VOP_ADVLOCK(vp, @p_proc, F_SETLK, flp, flg);
      end;
     F_UNLCK:
      begin
       error:=VOP_ADVLOCK(vp, @p_proc, F_UNLCK, flp, flg);
      end;
     F_UNLCKSYS:
      begin
       {
        * Temporary api for testing remote lock
        * infrastructure.
        }
       if (flg<>F_REMOTE) then
       begin
        error:=EINVAL;
         goto _break2;
       end;
       error:=VOP_ADVLOCK(vp, @p_proc, F_UNLCKSYS, flp, flg);
      end;
     else
      error:=EINVAL;
    end;
    _break2:
    VFS_UNLOCK_GIANT(vfslocked);
    vfslocked:=0;
   end;

  F_GETLK:
   begin
    error:=fdunwrap(fd, CAP_FLOCK, @fp);
    if (error<>0) then
    begin
     goto _break;
    end;
    if (fp^.f_type<>DTYPE_VNODE) then
    begin
     error:=EBADF;
     goto _break;
    end;
    flp:=p_flock(arg);
    if (flp^.l_type<>F_RDLCK) and
       (flp^.l_type<>F_WRLCK) and
       (flp^.l_type<>F_UNLCK) then
    begin
     error:=EINVAL;
     goto _break;
    end;
    if (flp^.l_whence=SEEK_CUR) then
    begin
     FILEDESC_SLOCK(@fd_table);
     foffset:=foffset_get(fp);
     if ((flp^.l_start > 0) and
         (foffset > High(Int64) - flp^.l_start)) or
        ((flp^.l_start < 0) and
         (foffset < Low(Int64) - flp^.l_start)) then
     begin
      FILEDESC_SUNLOCK(@fd_table);
      error:=EOVERFLOW;
      goto _break;
     end;
     Inc(flp^.l_start,foffset);
     FILEDESC_SUNLOCK(@fd_table);
    end;
    {
     * VOP_ADVLOCK() may block.
     }
    vp:=fp^.f_vnode;
    vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
    error:=VOP_ADVLOCK(vp, @p_proc, F_GETLK, flp, F_POSIX);
    VFS_UNLOCK_GIANT(vfslocked);
    vfslocked:=0;
   end;

  F_RDAHEAD:
   begin
    if (arg<>0) then
     arg:=128 * 1024
    else
     arg:=0;
    goto do_readahead;
   end;
  F_READAHEAD:
   begin
    do_readahead:
    fp:=fget_unlocked(fd);
    if (fp=nil) then
    begin
     error:=EBADF;
     goto _break;
    end;
    if (fp^.f_type<>DTYPE_VNODE) then
    begin
     error:=EBADF;
     goto _break;
    end;
    if (arg<>0) then
    begin
     vp:=fp^.f_vnode;
     vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
     error:=vn_lock(vp, LK_SHARED,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
     if (error<>0) then
      goto readahead_vnlock_fail;
     bsize:=p_mount(fp^.f_vnode^.v_mount)^.mnt_stat.f_iosize;
     VOP_UNLOCK(vp, 0);
     fp^.f_seqcount:=(arg + bsize - 1) div bsize;
     repeat
      old:=fp^.f_flag;
      new:=old;
      new:=new or FRDAHEAD;
     until (System.InterlockedCompareExchange(fp^.f_flag,new,old)=old);
     readahead_vnlock_fail:
     VFS_UNLOCK_GIANT(vfslocked);
     vfslocked:=0;
    end else
    begin
     repeat
      old:=fp^.f_flag;
      new:=old;
      new:=new and (not FRDAHEAD);
     until (System.InterlockedCompareExchange(fp^.f_flag,new,old)=old);
    end;
   end;

  else
   error:=EINVAL;
 end;
 _break:

 if (fp<>nil) then
 begin
  fdrop(fp);
 end;

 VFS_UNLOCK_GIANT(vfslocked);
 Exit(error);
end;

function getmaxfd():Integer; inline;
begin
 Result:=lim_cur(RLIMIT_NOFILE);
end;

function closef(fp:p_file):Integer; forward;

{
 * Common code for dup, dup2, fcntl(F_DUPFD) and fcntl(F_DUP2FD).
 }
function do_dup(flags,old,new:Integer;retval:PQWORD):Integer;
var
 fp,delfp:p_file;
 maxfd:Integer;
begin
 {
  * Verify we have a valid descriptor to dup from and possibly to
  * dup to. Unlike dup() and dup2(), fcntl()'s F_DUPFD should
  * Return EINVAL when the new descriptor is out of bounds.
  }
 if (old < 0) then
 begin
  Exit(EBADF);
 end;

 if (new < 0) then
 begin
  if (flags and DUP_FCNTL)<>0 then
   Exit(EINVAL)
  else
   Exit(EBADF);
 end;

 maxfd:=getmaxfd();
 if (new >= maxfd) then
 begin
  if (flags and DUP_FCNTL)<>0 then
   Exit(EINVAL)
  else
   Exit(EBADF);
 end;

 if ((flags and DUP_FIXED)<>0) and (old=new) then
 begin
  retval^:=new;
  if ((flags and DUP_CLOEXEC)<>0) then
  begin
   fp:=fget_unlocked(new);
   if (fp=nil) then
   begin
    Exit(EBADF);
   end;
   atomic_set_int(@fp^.f_exclose,UF_EXCLOSE);
   fdrop(fp);
  end;
  Exit(0);
 end;

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

 Assert(old<>new,'new fd is same as old');

 {
  * If the caller specified a file descriptor, make sure the file
  * table is large enough to hold it, and grab it.  Otherwise, just
  * allocate a new descriptor the usual way.  Since the filedesc
  * lock may be temporarily dropped in the process, we have to look
  * out for a race.
  }
 delfp:=nil;
 if ((flags and DUP_FIXED)<>0) then
 begin
  if not id_set(@fd_table.fd_ofiles,@fp^.desc,new,@delfp) then
  begin
   fdrop(fp);
   Exit(EBADF);
  end;
 end else
 begin
  if not id_new(@fd_table.fd_ofiles,@fp^.desc,@new) then
  begin
   fdrop(fp);
   Exit(ENFILE);
  end;
  fdrop(fp); //<-id_new
 end;
 fdrop(fp);


 retval^:=new;

 {
  * If we dup'd over a valid file, we now own the reference to it
  * and must dispose of it using closef() semantics (as if a
  * close() were performed on it).
  *
  * XXX this duplicates parts of close().
  }
 if (delfp<>nil) then
 begin
  knote_fdclose(new);
  if (delfp^.f_type=DTYPE_MQUEUE) then
  begin
   //mq_fdclose(td, new, delfp);
  end;
  closef(delfp);
 end;
 Exit(0);
end;

function kern_close(fd:Integer):Integer; public;
var
 fp,fp_object:p_file;
begin
 fp:=nil;
 if not id_del(@fd_table.fd_ofiles,fd,@fp) then
 begin
  Exit(EBADF);
 end;

 {
  * We now hold the fp reference that used to be owned by the
  * descriptor array.  We have to unlock the FILEDESC *AFTER*
  * knote_fdclose to prevent a race of the fd getting opened, a knote
  * added, and deleteing a knote for the new fd.
  }
 knote_fdclose(fd);

 {
  * When we're closing an fd with a capability, we need to notify
  * mqueue if the underlying object is of type mqueue.
  }
 if (cap_funwrap(fp, 0, @fp_object)=0) then
 begin
  if (fp_object^.f_type=DTYPE_MQUEUE) then
  begin
   //mq_fdclose(td, fd, fp_object);
  end;
 end;

 Exit(closef(fp));
end;

{
 * Close a file descriptor.
 }
function sys_close(fd:Integer):Integer;
begin
 Exit(kern_close(fd));
end;

{
 * Internal form of close.  Decrement reference count on file structure.
 * Note: td may be nil when closing a file that was being passed in a
 * message.
 *
 * XXXRW: Giant is not required for the caller, but often will be held; this
 * makes it moderately likely the Giant will be recursed in the VFS case.
 }
function closef(fp:p_file):Integer;
var
 vp:p_vnode;
 lf:t_flock;
 fp_object:p_file;
 vfslocked:Integer;
begin
 {
  * POSIX record locking dictates that any close releases ALL
  * locks owned by this process.  This is handled by setting
  * a flag in the unlock to free ONLY locks obeying POSIX
  * semantics, and not to free BSD-style file locks.
  * If the descriptor was in a message, POSIX-style locks
  * aren't passed with the descriptor, and the thread pointer
  * will be nil.  Callers should be careful only to pass a
  * nil thread pointer when there really is no owning
  * context that might have locks, or the locks will be
  * leaked.
  *
  * If this is a capability, we do lock processing under the underlying
  * node, not the capability itself.
  }
 if (cap_funwrap(fp, 0, @fp_object)=0) then
 begin
  if (fp_object^.f_type=DTYPE_VNODE) then
  begin
   vp:=fp_object^.f_vnode;
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   if ((p_proc.p_flag and P_ADVLOCK)<>0) then
   begin
    lf.l_whence:=SEEK_SET;
    lf.l_start :=0;
    lf.l_len   :=0;
    lf.l_type  :=F_UNLCK;
    VOP_ADVLOCK(vp, @p_proc, F_UNLCK, @lf, F_POSIX);
   end;

   VFS_UNLOCK_GIANT(vfslocked);
  end;
 end;
 Exit(fdrop(fp));
end;

{
 * If a specific file object occupies a specific file descriptor, close the
 * file descriptor entry and drop a reference on the file object.  This is a
 * convenience function to handle a subsequent error in a function that calls
 * falloc() that handles the race that another thread might have closed the
 * file descriptor out from under the thread creating the file object.
}
procedure fdclose(fp:p_file;idx:Integer);
var
 tmp:p_file;
begin
 tmp:=p_file(id_get(@fd_table.fd_ofiles,idx));
 if (tmp=fp) then
 begin
  fdrop(tmp);
  fdrop(tmp);
 end else
 if (tmp<>nil) then
 begin
  fdrop(tmp);
 end;
end;

function kern_fstat(fd:Integer;sbp:p_stat):Integer;
var
 fp:p_file;
 error:Integer;
begin
 error:=fget(fd, CAP_FSTAT, @fp);
 if (error<>0) then
  Exit(error);

 error:=fo_stat(fp, sbp);
 fdrop(fp);

 Exit(error);
end;

{
 * Exitstatus information about a file descriptor.
 }
function sys_fstat(fd:Integer;sb:Pointer):Integer;
var
 ub:t_stat;
 error:Integer;
begin
 error:=kern_fstat(fd, @ub);
 if (error=0) then
 begin
  error:=copyout(@ub, sb, sizeof(ub));
 end;
 Exit(error);
end;

{
 * Exitpathconf information about a file descriptor.
 }
function sys_fpathconf(fd,name:Integer):Integer;
label
 _out;
var
 td:p_kthread;
 fp:p_file;
 vp:p_vnode;
 error:Integer;
 vfslocked:Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 td:=curkthread;

 error:=fget(fd, CAP_FPATHCONF, @fp);
 if (error<>0) then
 begin
  Exit(error);
 end;

 { If asynchronous I/O is available, it works for all descriptors. }
 if (name=_PC_ASYNC_IO) then
 begin
  td^.td_retval[0]:=async_io_version;
  goto _out;
 end;
 vp:=fp^.f_vnode;
 if (vp<>nil) then
 begin
  vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
  vn_lock(vp, LK_SHARED or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  error:=VOP_PATHCONF(vp, name, td^.td_retval);
  VOP_UNLOCK(vp, 0);
  VFS_UNLOCK_GIANT(vfslocked);
 end else
 if (fp^.f_type=DTYPE_PIPE) or (fp^.f_type=DTYPE_SOCKET) then
 begin
  if (name<>_PC_PIPE_BUF) then
  begin
   error:=EINVAL;
  end else
  begin
   td^.td_retval[0]:=PIPE_BUF;
   error:=0;
  end;
 end else
 begin
  error:=EOPNOTSUPP;
 end;
_out:
 fdrop(fp);
 Exit(error);
end;

{
 * Apply an advisory lock on a file descriptor.
 *
 * Just attempt to get a record lock of the requested type on the entire file
 * (l_whence:=SEEK_SET, l_start:=0, l_len:=0).
 }
function sys_flock(fd,how:Integer):Integer;
label
 done2;
var
 fp:p_file;
 vp:p_vnode;
 lf:t_flock;
 vfslocked:Integer;
 error:Integer;
begin

 error:=fget(fd, CAP_FLOCK, @fp);
 if (error<>0) then
  Exit(error);
 if (fp^.f_type<>DTYPE_VNODE) then
 begin
  fdrop(fp);
  Exit(EOPNOTSUPP);
 end;

 vp:=fp^.f_vnode;
 vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
 lf.l_whence:=SEEK_SET;
 lf.l_start:=0;
 lf.l_len:=0;
 if ((how and LOCK_UN)<>0) then
 begin
  lf.l_type:=F_UNLCK;
  atomic_clear_int(@fp^.f_flag, FHASLOCK);
  error:=VOP_ADVLOCK(vp, fp, F_UNLCK, @lf, F_FLOCK);
  goto done2;
 end;
 if ((how and LOCK_EX)<>0) then
  lf.l_type:=F_WRLCK
 else
 if ((how and LOCK_SH)<>0) then
  lf.l_type:=F_RDLCK
 else
 begin
  error:=EBADF;
  goto done2;
 end;
 atomic_set_int(@fp^.f_flag, FHASLOCK);

 if ((how and LOCK_NB)<>0) then
 begin
  error:=VOP_ADVLOCK(vp, fp, F_SETLK, @lf, F_FLOCK);
 end else
 begin
  error:=VOP_ADVLOCK(vp, fp, F_SETLK, @lf, F_FLOCK or F_WAIT);
 end;

done2:
 fdrop(fp);
 VFS_UNLOCK_GIANT(vfslocked);
 Exit(error);
end;

{
 * Duplicate the specified descriptor to a free descriptor.
 }
function dupfdopen(indx,dfd,mode,error:Integer):Integer;
var
 wfp,fp:p_file;
begin
 {
  * If the to-be-dup'd fd number is greater than the allowed number
  * of file descriptors, or the fd to be dup'd has already been
  * closed, then reject.
  }

 wfp:=fget_unlocked(dfd);
 if (wfp=nil) then
 begin
  Exit(EBADF);
 end;

 {
  * There are two cases of interest here.
  *
  * For ENODEV simply dup (dfd) to file descriptor (indx) and return.
  *
  * For ENXIO steal away the file structure from (dfd) and store it in
  * (indx).  (dfd) is effectively closed by this operation.
  *
  * Any other error code is just returned.
  }
 case error of
  ENODEV:
   begin
    {
     * Check that the mode the file is being opened for is a
     * subset of the mode of the existing descriptor.
     }
    if (((mode and (FREAD or FWRITE)) or wfp^.f_flag)<>wfp^.f_flag) then
    begin
     fdrop(wfp);
     Exit(EACCES);
    end;

    fp:=nil;
    if not id_set(@fd_table.fd_ofiles,@wfp^.desc,indx,@fp) then
    begin
     fdrop(wfp);
     Exit(EBADF);
    end;

    if (fp<>nil) then
    begin
     fdrop(fp);
    end;

    fdrop(wfp);
    Exit(0);
   end;

  ENXIO:
   begin
    {
     * Steal away the file pointer from dfd and stuff it into indx.
     }
    fp:=nil;
    if not id_set(@fd_table.fd_ofiles,@wfp^.desc,indx,@fp) then
    begin
     fdrop(wfp);
     Exit(EBADF);
    end;

    if not id_del(@fd_table.fd_ofiles,dfd,nil) then
    begin
     fdrop(wfp);
     Exit(EBADF);
    end;

    if (fp<>nil) then
    begin
     fdrop(fp);
    end;

    fdrop(wfp);
    Exit(0);
   end;

  else
   begin
    fdrop(wfp);
    Exit(error);
   end;
 end;
 { NOTREACHED }
end;

{
 * Initialize the file pointer with the specified properties.
 *
 * The ops are set with release semantics to be certain that the flags, type,
 * and data are visible when ops is.  This is to prevent ops methods from being
 * called with bad data.
}
procedure finit(fp:p_file;flag:DWORD;_type:Word;data:Pointer;ops:p_fileops);
begin
 fp^.f_data:=data;
 fp^.f_flag:=flag;
 fp^.f_type:=_type;
 System.InterlockedExchange(fp^.f_ops,ops);
end;

procedure _fdrop(data:pointer); forward;

function falloc_noinstall(resultfp:pp_file):Integer;
var
 fp:p_file;
begin
 Assert(resultfp<>nil,'resultfp=nil');

 System.InterlockedIncrement(openfiles);

 fp:=AllocMem(SizeOf(t_file));
 if (fp=nil) then Exit(ENOMEM);

 fp^.desc.refs:=1;
 fp^.desc.free:=@_fdrop;
 fp^.f_ops    :=@badfileops;
 fp^.f_data   :=nil;
 fp^.f_vnode  :=nil;

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

function finstall(fp:p_file;fd:PInteger;flags:Integer):Integer;
begin
 Assert(fd<>nil,'fd=nil');
 Assert(fp<>nil,'fp=nil');

 if not id_new(@fd_table.fd_ofiles,@fp^.desc,fd) then
 begin
  Exit(ENFILE);
 end;

 if ((flags and O_CLOEXEC)<>0) then
 begin
  atomic_set_int(@fp^.f_exclose,UF_EXCLOSE);
 end;

 Exit(0);
end;

function falloc(resultfp:pp_file;resultfd:PInteger;flags:Integer):Integer;
var
 fp:p_file;
 error,fd:Integer;
begin
 error:=falloc_noinstall(@fp);
 if (error<>0) then
 begin
  Exit(error);  { no reference held on error }
 end;

 error:=finstall(fp,@fd,flags);
 if (error<>0) then
 begin
  _fdrop(fp);  { one reference (fp only) }
  Exit(error);
 end;

 if (resultfp<>nil) then
  resultfp^:=fp  { copy out result }
 else
  fdrop(fp);  { release local reference }

 if (resultfd<>nil) then
 begin
  resultfd^:=fd;
 end;

 Exit(0);
end;

procedure _fdrop(data:pointer);
var
 fp:p_file;
begin
 fp:=data;

 if (fp^.f_ops<>@badfileops) then
 begin
  fo_close(fp);
 end;

 System.InterlockedDecrement(openfiles);

 FreeMem(fp^.f_advice);
 FreeMem(fp);
end;

procedure fhold(fp:p_file);
begin
 id_acqure(@fp^.desc);
end;

function fdrop(fp:p_file):Integer;
begin
 id_release(@fp^.desc);
 Result:=0;
end;

function fget_unlocked(fd:Integer):p_file;
begin
 if (fd<0) then Exit(nil);
 Result:=p_file(id_get(@fd_table.fd_ofiles,fd));
end;

function _fget(fd:Integer;
               fpp:pp_file;
               flags:Integer;
               needrights:cap_rights_t;
               haverightsp:p_cap_rights_t;
               maxprotp:PByte;
               fget_flags:Integer):Integer;
var
 fp:p_file;
 fp_fromcap:p_file;
 error:Integer;
begin
 fpp^:=nil;

 fp:=fget_unlocked(fd);
 if (fp=nil) then
  Exit(EBADF);

 if (fp^.f_ops=@badfileops) then
 begin
  fdrop(fp);
  Exit(EBADF);
 end;

 {
  * If this is a capability, what rights does it have?
  }
 if (haverightsp<>nil) then
 begin
  if (fp^.f_type=DTYPE_CAPABILITY) then
   haverightsp^:=cap_rights(fp)
  else
   haverightsp^:=CAP_MASK_VALID;
 end;

 {
  * If a capability has been requested, Exitthe capability directly.
  * Otherwise, check capability rights, extract the underlying object,
  * and check its access flags.
  }
 if ((fget_flags and FGET_GETCAP)<>0) then
 begin
  if (fp^.f_type<>DTYPE_CAPABILITY) then
  begin
   fdrop(fp);
   Exit(EINVAL);
  end;
 end else
 begin
  if (maxprotp=nil) then
   error:=cap_funwrap(fp, needrights,@fp_fromcap)
  else
   error:=cap_funwrap_mmap(fp, needrights, maxprotp,@fp_fromcap);

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

  {
   * If we've unwrapped a file, drop the original capability
   * and hold the new descriptor.  fp after this point refers to
   * the actual (unwrapped) object, not the capability.
   }
  if (fp<>fp_fromcap) then
  begin
   fhold(fp_fromcap);
   fdrop(fp);
   fp:=fp_fromcap;
  end;
 end;

 {
  * FREAD and FWRITE failure return EBADF as per POSIX.
  }
 error:=0;

 case (flags) of
  FREAD,
  FWRITE:
   begin
    if ((fp^.f_flag and flags)=0) then
     error:=EBADF;
   end;
  FEXEC:
   begin
    if ((fp^.f_flag and (FREAD or FEXEC))=0) or
       ((fp^.f_flag and FWRITE)<>0) then
    error:=EBADF;
   end;
  0:;
 else
  Assert(false,'wrong flags');
 end;

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

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

function fget(fd:Integer;
              rights:cap_rights_t;
              fpp:pp_file):Integer;
begin
 Exit(_fget(fd, fpp, 0, rights, nil, nil, 0));
end;

function fget_mmap(fd:Integer;
                   rights:cap_rights_t;
                   maxprotp:PByte;
                   fpp:pp_file):Integer;
begin
 Exit(_fget(fd, fpp, 0, rights, nil, maxprotp, 0));
end;

function fget_read(fd:Integer;
                   rights:cap_rights_t;
                   fpp:pp_file):Integer;
begin
 Exit(_fget(fd, fpp, FREAD, rights, nil, nil, 0));
end;

function fget_write(fd:Integer;
                    rights:cap_rights_t;
                    fpp:pp_file):Integer;
begin
 Exit(_fget(fd, fpp, FWRITE, rights, nil, nil, 0));
end;

{
 * Unlike the other fget() calls, which accept and check capability rights
 * but never Exitcapabilities, fgetcap() Exits the capability but doesn't
 * check capability rights.
}
function fgetcap(fd:Integer;
                 fpp:pp_file):Integer;
begin
 Exit(_fget(fd, fpp, 0, 0, nil, nil, FGET_GETCAP));
end;


{
 * Like fget() but loads the underlying vnode, or Exits an error if the
 * descriptor does not represent a vnode.  Note that pipes use vnodes but
 * never have VM objects.  The Exited vnode will be vref()'d.
 *
 * XXX: what about the unused flags ?
}
function _fgetvp(fd:Integer;
                 flags:Integer;
                 needrights:cap_rights_t;
                 haverightsp:p_cap_rights_t;
                 vpp:pp_vnode):Integer;
var
 fp:p_file;
 error:Integer;
begin
 vpp^:=nil;
 error:=_fget(fd, @fp, flags, needrights, haverightsp,nil, 0);
 if (error<>0) then
  Exit(error);
 if (fp^.f_vnode=nil) then
 begin
  error:=EINVAL;
 end else
 begin
  vpp^:=fp^.f_vnode;
  vref(vpp^);
 end;
 fdrop(fp);

 Exit(error);
end;

function fgetvp(fd:Integer;
                rights:cap_rights_t;
                vpp:pp_vnode):Integer;
begin
 Exit(_fgetvp(fd, 0, rights, nil, vpp));
end;

function fgetvp_rights(fd:Integer;
                       need:cap_rights_t;
                       have:p_cap_rights_t;
                       vpp:pp_vnode):Integer;
begin
 Exit(_fgetvp(fd, 0, need, have, vpp));
end;

function fgetvp_read(fd:Integer;
                     rights:cap_rights_t;
                     vpp:pp_vnode):Integer;
begin
 Exit(_fgetvp(fd, FREAD, rights, nil, vpp));
end;

function fgetvp_exec(fd:Integer;
                     rights:cap_rights_t;
                     vpp:pp_vnode):Integer;
begin
 Exit(_fgetvp(fd, FEXEC, rights, nil, vpp));
end;

function fgetvp_write(fd:Integer;
                      rights:cap_rights_t;
                      vpp:pp_vnode):Integer;
begin
 Exit(_fgetvp(fd, FWRITE, rights, nil, vpp));
end;

{
 * Like fget() but loads the underlying socket, or Exits an error if the
 * descriptor does not represent a socket.
 *
 * We bump the ref count on the Exited socket.  XXX Also obtain the SX lock
 * in the future.
 *
 * Note: fgetsock() and fputsock() are deprecated, as consumers should rely
 * on their file descriptor reference to prevent the socket from being free'd
 * during use.
}
function fgetsock(fd:Integer;
                  rights:cap_rights_t;
                  spp:pp_socket;
                  fflagp:PDWORD):Integer;
var
 fp:p_file;
 error:Integer;
begin
 spp^:=nil;
 if (fflagp<>nil) then
  fflagp^:=0;
 error:=_fget(fd, @fp, 0, rights, nil, nil, 0);
 if (error<>0) then
  Exit(error);
 if (fp^.f_type<>DTYPE_SOCKET) then
 begin
  error:=ENOTSOCK;
 end else
 begin
  spp^:=fp^.f_data;
  if (fflagp<>nil) then
   fflagp^:=fp^.f_flag;
  SOCK_LOCK(spp^);
  soref(spp^);
  SOCK_UNLOCK(spp^);
 end;
 fdrop(fp);

 Exit(error);
end;


{
* Close any files on exec?
}
procedure fdcloseexec();
var
 i,count:Integer;
 fp:p_file;
begin
 FILEDESC_XLOCK(@fd_table);

 count:=fd_table.fd_lastfile;
 if (count<>0) then
 For i:=0 to count-1 do
 begin
  fp:=p_file(id_get(@fd_table.fd_ofiles,i));

  if (fp<>nil) then
  begin
   if (fp^.f_type=DTYPE_MQUEUE) or
      ((fp^.f_exclose and UF_EXCLOSE)<>0) then
   begin
    fdrop(fp);

    knote_fdclose(i);

    fp:=nil;
    if id_del(@fd_table.fd_ofiles,i,@fp) then
    if (fp<>nil) then
    begin
     if (fp^.f_type=DTYPE_MQUEUE) then
     begin
      //mq_fdclose(td, i, fp);
     end;

     FILEDESC_XUNLOCK(@fd_table);
     closef(fp);
     FILEDESC_XLOCK(@fd_table);
    end;

   end;

  end;

 end;

 FILEDESC_XUNLOCK(@fd_table);
end;


{
 * File Descriptor pseudo-device driver (/dev/fd/).
 *
 * Opening minor device N dup()s the file (if any) connected to file
 * descriptor N belonging to the calling process.  Note that this driver
 * consists of only the ``open()'' routine, because all subsequent
 * references to this file will be direct to the other driver.
 *
 * XXX: we could give this one a cloning event handler if necessary.
 }

{ ARGSUSED }
function fdopen(dev:p_cdev;mode,_type:Integer):Integer;
var
 td:p_kthread;
begin
 td:=curkthread;
 {
  * XXX Kludge: set curthread^.td_dupfd to contain the value of the
  * the file descriptor being sought for duplication. The error
  * Exitensures that the vnode for this device will be released
  * by vn_open. Open will detect this special error and take the
  * actions in dupfdopen below. Other callers of vn_open or VOP_OPEN
  * will simply report the error.
  }
 td^.td_dupfd:=dev2unit(dev);
 Exit(ENODEV);
end;

const
 fildesc_cdevsw:t_cdevsw=(
  d_version    :D_VERSION;
  d_flags      :0;
  d_name       :'FD';
  d_open       :@fdopen;
 );

procedure fildesc_drvinit();
var
 dev:p_cdev;
begin
 dev:=make_dev_credf(MAKEDEV_ETERNAL, @fildesc_cdevsw, 0, UID_ROOT, GID_WHEEL, &0666, 'fd/0',[]);
 make_dev_alias(dev, 'stdin',[]);
 dev:=make_dev_credf(MAKEDEV_ETERNAL, @fildesc_cdevsw, 1, UID_ROOT, GID_WHEEL, &0666, 'fd/1',[]);
 make_dev_alias(dev, 'stdout',[]);
 dev:=make_dev_credf(MAKEDEV_ETERNAL, @fildesc_cdevsw, 2, UID_ROOT, GID_WHEEL, &0666, 'fd/2',[]);
 make_dev_alias(dev, 'stderr',[]);
end;


end.

