unit fdesc_vnops;

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

interface

uses
 mqueue,
 kern_param,
 vmount,
 vnode,
 vfs_default,
 vnode_if,
 fdescfs;

{
 * /dev/fd Filesystem
 }
function fdesc_getattr(ap:p_vop_getattr_args):Integer;
function fdesc_lookup(ap:p_vop_lookup_args):Integer;
function fdesc_open(ap:p_vop_open_args):Integer;
function fdesc_readdir(ap:p_vop_readdir_args):Integer;
function fdesc_reclaim(ap:p_vop_reclaim_args):Integer;
function fdesc_setattr(ap:p_vop_setattr_args):Integer;

const
 fdesc_vnodeops:vop_vector=(
  vop_default       :@default_vnodeops;
  vop_bypass        :nil;

  vop_islocked      :nil;
  vop_lookup        :@fdesc_lookup;
  vop_create        :nil;
  vop_whiteout      :nil;
  vop_mknod         :nil;
  vop_open          :@fdesc_open;
  vop_close         :nil;
  vop_access        :@VOP_NULL;
  vop_accessx       :nil;
  vop_getattr       :@fdesc_getattr;
  vop_setattr       :@fdesc_setattr;
  vop_markatime     :nil;
  vop_read          :nil;
  vop_write         :nil;
  vop_ioctl         :nil;
  vop_poll          :nil;
  vop_kqfilter      :nil;
  vop_revoke        :nil;
  vop_fsync         :nil;
  vop_remove        :nil;
  vop_link          :nil;
  vop_rename        :nil;
  vop_mkdir         :nil;
  vop_rmdir         :nil;
  vop_symlink       :nil;
  vop_readdir       :@fdesc_readdir;
  vop_readlink      :nil;
  vop_inactive      :nil;
  vop_reclaim       :@fdesc_reclaim;
  vop_lock1         :nil;
  vop_unlock        :nil;
  vop_bmap          :nil;
  vop_strategy      :nil;
  vop_getwritemount :nil;
  vop_print         :nil;
  vop_pathconf      :@vop_stdpathconf;
  vop_advlock       :nil;
  vop_advlockasync  :nil;
  vop_advlockpurge  :nil;
  vop_reallocblks   :nil;
  vop_getpages      :nil;
  vop_putpages      :nil;
  vop_vptofh        :nil;
  vop_vptocnp       :nil;
  vop_allocate      :nil;
  vop_unp_bind      :nil;
  vop_unp_connect   :nil;
  vop_unp_detach    :nil;
 );

procedure fdesc_insmntque_dtr(vp:p_vnode;arg:Pointer);
procedure fdesc_remove_entry(fd:p_fdescnode);

function  fdesc_allocvp(ftype:fdntype;fd_fd,ix:Integer;mp:p_mount;vpp:pp_vnode):Integer;

implementation

uses
 errno,
 time,
 vfile,
 vfiledesc,
 vnamei,
 vstat,
 vdirent,
 vcapability,
 vuio,
 subr_uio,
 vfs_vnops,
 vfs_subr,
 vfs_syscalls,
 kern_mtx,
 kern_descrip,
 kern_thr;

function VFSTOFDESC(mp:p_mount):p_fdescmount; inline;
begin
 Result:=mp^.mnt_data;
end;

function VTOFDESC(vp:p_vnode):p_fdescnode; inline;
begin
 Result:=vp^.v_data;
end;

{
 * If allocating vnode fails, call this.
 }
procedure fdesc_insmntque_dtr(vp:p_vnode;arg:Pointer);
begin
 vgone(vp);
 vput(vp);
end;

{
 * Remove an entry from the hash if it exists.
 }
procedure fdesc_remove_entry(fd:p_fdescnode);
var
 fc:p_fdhashhead;
 fd2:p_fdescnode;
begin
 fc:=FD_NHASH(fd^.fd_ix);
 mtx_lock(fdesc_hashmtx);
 fd2:=LIST_FIRST(fc);
 while (fd2<>nil) do
 begin
  if (fd=fd2) then
  begin
   LIST_REMOVE(fd,@fd^.fd_hash);
   break;
  end;
  fd2:=LIST_NEXT(fd2,@fd2^.fd_hash);
 end;
 mtx_unlock(fdesc_hashmtx);
end;

function fdesc_allocvp(ftype:fdntype;fd_fd,ix:Integer;mp:p_mount;vpp:pp_vnode):Integer;
label
 _or,
 loop;
var
 fmp:p_fdescmount;
 fc:p_fdhashhead;
 fd,fd2:p_fdescnode;
 vp,vp2:p_vnode;
 error:Integer;
begin
 error:=0;

 fc:=FD_NHASH(ix);
loop:
 mtx_lock(fdesc_hashmtx);
 {
  * If a forced unmount is progressing, we need to drop it. The flags are
  * protected by the hashmtx.
  }
 fmp:=p_fdescmount(mp^.mnt_data);

 if (fmp=nil) then
 begin
  mtx_unlock(fdesc_hashmtx);
  Exit(-1);
 end;

 if ((fmp^.flags and FMNT_UNMOUNTF)<>0) then
 begin
  mtx_unlock(fdesc_hashmtx);
  Exit(-1);
 end;

 fd:=LIST_FIRST(fc);

 while (fd<>nil) do
 begin
  if (fd^.fd_ix=ix) and (fd^.fd_vnode^.v_mount=mp) then
  begin
   { Get reference to vnode in case it's being free'd }
   vp:=fd^.fd_vnode;
   VI_LOCK(vp);
   mtx_unlock(fdesc_hashmtx);
   if (vget(vp, LK_EXCLUSIVE or LK_INTERLOCK)<>0) then
   begin
    goto loop;
   end;
   vpp^:=vp;
   Exit(0);
  end;
  fd:=LIST_NEXT(fd,@fd^.fd_hash);
 end;
 mtx_unlock(fdesc_hashmtx);

 fd:=AllocMem(sizeof(t_fdescnode));

 error:=getnewvnode('fdescfs', mp, @fdesc_vnodeops, @vp);
 if (error<>0) then
 begin
  FreeMem(fd);
  Exit(error);
 end;

 vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});

 vp^.v_data  :=fd;
 fd^.fd_vnode:=vp;
 fd^.fd_type :=ftype;
 fd^.fd_fd   :=fd_fd;
 fd^.fd_ix   :=ix;

 error:=insmntque1(vp, mp, @fdesc_insmntque_dtr, nil);
 if (error<>0) then
 begin
  vpp^:=nil;
  Exit(error);
 end;

 { Make sure that someone didn't beat us when inserting the vnode. }
 mtx_lock(fdesc_hashmtx);
 {
  * If a forced unmount is progressing, we need to drop it. The flags are
  * protected by the hashmtx.
  }
 fmp:=p_fdescmount(mp^.mnt_data);

 if (fmp=nil) then goto _or;

 if ((fmp^.flags and FMNT_UNMOUNTF)<>0) then
 begin
  _or:
  mtx_unlock(fdesc_hashmtx);
  vgone(vp);
  vput(vp);
  vpp^:=nil;
  Exit(-1);
 end;

 fd2:=LIST_FIRST(fc);
 while (fd2<>nil) do
 begin
  if (fd2^.fd_ix=ix) and (fd2^.fd_vnode^.v_mount=mp) then
  begin
   { Get reference to vnode in case it's being free'd }
   vp2:=fd2^.fd_vnode;
   VI_LOCK(vp2);
   mtx_unlock(fdesc_hashmtx);
   error:=vget(vp2, LK_EXCLUSIVE or LK_INTERLOCK);
   { Someone beat us, dec use count and wait for reclaim }
   vgone(vp);
   vput(vp);
   { If we didn't get it, return no vnode. }
   if (error<>0) then
   begin
    vp2:=nil;
   end;
   vpp^:=vp2;
   Exit(error);
  end;
  fd2:=LIST_NEXT(fd2,@fd2^.fd_hash);
 end;

 { If we came here, we can insert it safely. }
 LIST_INSERT_HEAD(fc,fd,@fd^.fd_hash);
 mtx_unlock(fdesc_hashmtx);
 vpp^:=vp;
 Exit(0);
end;

{
 * vp is the current namei directory
 * ndp is the name to locate in that directory...
 }
function fdesc_lookup(ap:p_vop_lookup_args):Integer;
label
 bad;
var
 vpp:pp_vnode;
 dvp:p_vnode;
 cnp:p_componentname;
 pname:PChar;
 fp:p_file;
 nlen:Integer;
 fd,fd1:Integer;
 error:Integer;
 fvp:p_vnode;
begin
 vpp:=ap^.a_vpp;
 dvp:=ap^.a_dvp;
 cnp:=ap^.a_cnp;
 pname:=cnp^.cn_nameptr;
 nlen:=cnp^.cn_namelen;

 if ((cnp^.cn_flags and ISLASTCN)<>0) and
    ((cnp^.cn_nameiop=DELETE) or (cnp^.cn_nameiop=RENAME)) then
 begin
  error:=EROFS;
  goto bad;
 end;

 if (cnp^.cn_namelen=1) and (pname^='.') then
 begin
  vpp^:=dvp;
  VREF(dvp);
  Exit(0);
 end;

 if (VTOFDESC(dvp)^.fd_type<>_Froot) then
 begin
  error:=ENOTDIR;
  goto bad;
 end;

 fd:=0;
 { the only time a leading 0 is acceptable is if it's '0' }
 if (pname^='0') and (nlen<>1) then
 begin
  error:=ENOENT;
  goto bad;
 end;

 while (nlen<>0) do
 begin
  Dec(nlen);
  if (pname^ < '0') or (pname^ > '9') then
  begin
   error:=ENOENT;
   goto bad;
  end;
  fd1:=10 * fd + ord(pname^) - ord('0');
  Inc(pname);
  if (fd1 < fd) then
  begin
   error:=ENOENT;
   goto bad;
  end;
  fd:=fd1;
 end;

 {
  * No rights to check since 'fp' isn't actually used.
  }
 error:=fget(fd, 0, @fp);
 if (error<>0) then
 begin
  goto bad;
 end;

 { Check if we're looking up ourselves. }
 if (VTOFDESC(dvp)^.fd_ix=FD_DESC + fd) then
 begin
  {
   * In case we're holding the last reference to the file, the dvp
   * will be re-acquired.
   }
  vhold(dvp);
  VOP_UNLOCK(dvp, 0);
  fdrop(fp);

  { Re-aquire the lock afterwards. }
  vn_lock(dvp, LK_RETRY or LK_EXCLUSIVE,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  vdrop(dvp);
  fvp:=dvp;
 end else
 begin
  {
   * Unlock our root node (dvp) when doing this, since we might
   * deadlock since the vnode might be locked by another thread
   * and the root vnode lock will be obtained afterwards (in case
   * we're looking up the fd of the root vnode), which will be the
   * opposite lock order. Vhold the root vnode first so we don't
   * lose it.
   }
  vhold(dvp);
  VOP_UNLOCK(dvp, 0);
  error:=fdesc_allocvp(_Fdesc, fd, FD_DESC + fd, dvp^.v_mount, @fvp);
  fdrop(fp);
  {
   * The root vnode must be locked last to prevent deadlock condition.
   }
  vn_lock(dvp, LK_RETRY or LK_EXCLUSIVE,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  vdrop(dvp);
 end;

 if (error<>0) then
 begin
  goto bad;
 end;
 vpp^:=fvp;
 Exit(0);

bad:
 vpp^:=nil;
 Exit(error);
end;

function fdesc_open(ap:p_vop_open_args):Integer;
var
 vp:p_vnode;
 td:p_kthread;
begin
 vp:=ap^.a_vp;

 if (VTOFDESC(vp)^.fd_type=_Froot) then
  Exit(0);

 {
  * XXX Kludge: set td^.td_proc^.p_dupfd to contain the value of 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.
  * Other callers of vn_open or VOP_OPEN will simply report the
  * error.
  }
 td:=curkthread;
 if (td<>nil) then
 begin
  td^.td_dupfd:=VTOFDESC(vp)^.fd_fd; { XXX }
 end;

 Exit(ENODEV);
end;

function fdesc_getattr(ap:p_vop_getattr_args):Integer;
var
 vp:p_vnode;
 vap:p_vattr;
begin
 vp:=ap^.a_vp;
 vap:=ap^.a_vap;

 vap^.va_mode     :=S_IRUSR or S_IXUSR or S_IRGRP or S_IXGRP or S_IROTH or S_IXOTH;
 vap^.va_fileid   :=VTOFDESC(vp)^.fd_ix;
 vap^.va_uid      :=0;
 vap^.va_gid      :=0;
 vap^.va_blocksize:=DEV_BSIZE;
 vap^.va_atime.tv_sec :=boottime.tv_sec;
 vap^.va_atime.tv_nsec:=0;
 vap^.va_mtime    :=vap^.va_atime;
 vap^.va_ctime    :=vap^.va_mtime;
 vap^.va_gen      :=0;
 vap^.va_flags    :=0;
 vap^.va_bytes    :=0;
 vap^.va_filerev  :=0;

 case VTOFDESC(vp)^.fd_type of
  _Froot:
   begin
    vap^.va_type :=VDIR;
    vap^.va_nlink:=2;
    vap^.va_size :=DEV_BSIZE;
    vap^.va_rdev :=NODEV;
   end;
  _Fdesc:
   begin
    vap^.va_type :=VCHR;
    vap^.va_nlink:=1;
    vap^.va_size :=0;
    vap^.va_rdev :=makedev(0, vap^.va_fileid);
   end;
 end;

 vp^.v_type:=vap^.va_type;
 Exit(0);
end;

function fdesc_setattr(ap:p_vop_setattr_args):Integer;
var
 vap:p_vattr;
 vp:p_vnode;
 mp:p_mount;
 fp:p_file;
 fd:Integer;
 error:Integer;
begin
 vap:=ap^.a_vap;

 {
  * Can't mess with the root vnode
  }
 if (VTOFDESC(ap^.a_vp)^.fd_type=_Froot) then
 begin
  Exit(EACCES);
 end;

 fd:=VTOFDESC(ap^.a_vp)^.fd_fd;

 {
  * Allow setattr where there is an underlying vnode.
  }
 error:=getvnode(fd, CAP_EXTATTR_SET, @fp);
 if (error<>0) then
 begin
  {
   * getvnode() Exits EINVAL if the file descriptor is not
   * backed by a vnode.  Silently drop all changes except
   * chflags(2) in this case.
   }
  if (error=EINVAL) then
  begin
   if (vap^.va_flags<>VNOVAL) then
    error:=EOPNOTSUPP
   else
    error:=0;
  end;
  Exit(error);
 end;
 vp:=fp^.f_vnode;

 error:=vn_start_write(vp, @mp, V_WAIT or PCATCH);
 if (error=0) then
 begin
  vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  error:=VOP_SETATTR(vp, ap^.a_vap);
  VOP_UNLOCK(vp, 0);
  vn_finished_write(mp);
 end;
 fdrop(fp);
 Exit(error);
end;

const
 UIO_MX=16;

function fdesc_readdir(ap:p_vop_readdir_args):Integer;
label
 _break,
 done;
var
 uio:p_uio;
 d:t_dirent;
 dp:p_dirent;
 error,i,off,fcnt:Integer;
 tmp:p_file;
begin
 uio:=ap^.a_uio;
 dp:=@d;

 if (VTOFDESC(ap^.a_vp)^.fd_type<>_Froot) then
 begin
  Assert(False,'fdesc_readdir: not dir');
 end;

 if (ap^.a_ncookies<>nil) then
  ap^.a_ncookies^:=0;

 off:=uio^.uio_offset;

 if (off<>uio^.uio_offset) or
    (off < 0) or
    ((off mod UIO_MX)<>0) or
    (uio^.uio_resid < UIO_MX) then
 begin
  Exit(EINVAL);
 end;

 i:=off div UIO_MX;

 error:=0;

 fcnt:=i - 2;  { The first two nodes are `.' and `..' }

 FILEDESC_SLOCK(@fd_table);

 while (i < fd_table.fd_nfiles + 2) and (uio^.uio_resid >= UIO_MX) do
 begin
  FillChar(dp^,UIO_MX,0);
  case i of
   0, { `.' }
   1: { `..' }
    begin
     dp^.d_fileno:=i + FD_ROOT;
     dp^.d_namlen:=i + 1;
     dp^.d_reclen:=UIO_MX;
     Move(PChar('..')^, dp^.d_name, dp^.d_namlen);
     dp^.d_name[i + 1]:=#0;
     dp^.d_type:=DT_DIR;
    end;
   else
    begin
     tmp:=fget_unlocked(fcnt); //check exists
     if (tmp=nil) then
     begin
      goto _break;
     end;
     fdrop(tmp);

     Str(fcnt,dp^.d_name);
     dp^.d_namlen:=strlen(dp^.d_name);
     dp^.d_reclen:=UIO_MX;
     dp^.d_type  :=DT_CHR;
     dp^.d_fileno:=i + FD_DESC;
    end;
  end;

  _break:
  if (dp^.d_namlen<>0) then
  begin
   {
    * And ship to userland
    }
   FILEDESC_SUNLOCK(@fd_table);
   error:=uiomove(dp, UIO_MX, uio);
   if (error<>0) then
   begin
    goto done;
   end;
   FILEDESC_SLOCK(@fd_table);
  end;
  Inc(i);
  Inc(fcnt);
 end;
 FILEDESC_SUNLOCK(@fd_table);

done:
 uio^.uio_offset:=i * UIO_MX;
 Exit(error);
end;

function fdesc_reclaim(ap:p_vop_reclaim_args):Integer;
var
 vp:p_vnode;
 fd:p_fdescnode;
begin
 vp:=ap^.a_vp;
 fd:=VTOFDESC(vp);
 fdesc_remove_entry(fd);
 FreeMem(vp^.v_data);
 vp^.v_data:=nil;
 Exit(0);
end;


end.

