unit devfs_vnops;

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

interface

uses
 mqueue,
 kern_param,
 time,
 sys_event,
 vfile,
 vfilio,
 vttycom,
 vmount,
 vdirent,
 vstat,
 vuio,
 vfcntl,
 vnode,
 vnamei,
 vnode_if,
 vfs_default,
 vfs_vnops,
 devfs_int,
 devfs,
 kern_mtx,
 kern_sx,
 sys_conf;

function  devfs_fp_check(fp:p_file;devp:pp_cdev;dswp:pp_cdevsw;ref:PInteger):Integer;
function  devfs_get_cdevpriv(datap:PPointer):Integer;
function  devfs_set_cdevpriv(priv:Pointer;priv_dtr:cdevpriv_dtr_t):Integer;
procedure devfs_destroy_cdevpriv(p:p_cdev_privdata);
procedure devfs_fpdrop(fp:p_file);
procedure devfs_clear_cdevpriv();
function  devfs_populate_vp(vp:p_vnode):Integer;
function  devfs_vptocnp(ap:p_vop_vptocnp_args):Integer;
function  devfs_fqpn(buf:PChar;dmp:p_devfs_mount;dd:p_devfs_dirent;cnp:p_componentname):PChar;
function  devfs_allocv_drop_refs(drop_dm_lock:Integer;dmp:p_devfs_mount;de:p_devfs_dirent):Integer;
function  devfs_allocv(de:p_devfs_dirent;mp:p_mount;lockmode:Integer;vpp:pp_vnode):Integer;
function  devfs_access(ap:p_vop_access_args):Integer;
function  devfs_close(ap:p_vop_close_args):Integer;
function  devfs_close_f(fp:p_file):Integer;
function  devfs_fsync(ap:p_vop_fsync_args):Integer;
function  devfs_getattr(ap:p_vop_getattr_args):Integer;
function  devfs_ioctl_f(fp:p_file;com:QWORD;data:Pointer):Integer;
function  devfs_kqfilter_f(fp:p_file;kn:p_knote):Integer;
function  devfs_prison_check(de:p_devfs_dirent):Integer;
function  devfs_lookup(ap:p_vop_lookup_args):Integer;
function  devfs_mknod(ap:p_vop_mknod_args):Integer;
function  devfs_open(ap:p_vop_open_args):Integer;
function  devfs_pathconf(ap:p_vop_pathconf_args):Integer;
function  devfs_poll_f(fp:p_file;events:Integer):Integer;
function  devfs_print(ap:p_vop_print_args):Integer;
function  devfs_read_f(fp:p_file;uio:p_uio;flags:Integer):Integer;
function  devfs_readdir(ap:p_vop_readdir_args):Integer;
function  devfs_readlink(ap:p_vop_readlink_args):Integer;
function  devfs_reclaim(ap:p_vop_reclaim_args):Integer;
function  devfs_remove(ap:p_vop_remove_args):Integer;
function  devfs_revoke(ap:p_vop_revoke_args):Integer;
function  devfs_rioctl(ap:p_vop_ioctl_args):Integer;
function  devfs_rread(ap:p_vop_read_args):Integer;
function  devfs_setattr(ap:p_vop_setattr_args):Integer;
function  devfs_stat_f(fp:p_file;sb:p_stat):Integer;
function  devfs_symlink(ap:p_vop_symlink_args):Integer;
function  devfs_truncate_f(fp:p_file;length:Int64):Integer;
function  devfs_write_f(fp:p_file;uio:p_uio;flags:Integer):Integer;
function  dev2udev(x:p_cdev):Integer;

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

  vop_islocked      :nil;
  vop_lookup        :@devfs_lookup;
  vop_create        :nil;
  vop_whiteout      :nil;
  vop_mknod         :@devfs_mknod;
  vop_open          :nil;
  vop_close         :nil;
  vop_access        :@devfs_access;
  vop_accessx       :nil;
  vop_getattr       :@devfs_getattr;
  vop_setattr       :@devfs_setattr;
  vop_markatime     :nil;
  vop_read          :@devfs_rread;
  vop_write         :nil;
  vop_ioctl         :@devfs_rioctl;
  vop_poll          :nil;
  vop_kqfilter      :nil;
  vop_revoke        :@devfs_revoke;
  vop_fsync         :nil;
  vop_remove        :@devfs_remove;
  vop_link          :nil;
  vop_rename        :nil;
  vop_mkdir         :nil;
  vop_rmdir         :nil;
  vop_symlink       :@devfs_symlink;
  vop_readdir       :@devfs_readdir;
  vop_readlink      :@devfs_readlink;
  vop_inactive      :nil;
  vop_reclaim       :@devfs_reclaim;
  vop_lock1         :nil;
  vop_unlock        :nil;
  vop_bmap          :nil;
  vop_strategy      :nil;
  vop_getwritemount :nil;
  vop_print         :nil;
  vop_pathconf      :@devfs_pathconf;
  vop_advlock       :nil;
  vop_advlockasync  :nil;
  vop_advlockpurge  :nil;
  vop_reallocblks   :nil;
  vop_getpages      :nil;
  vop_putpages      :nil;
  vop_vptofh        :nil;
  vop_vptocnp       :@devfs_vptocnp;
  vop_allocate      :nil;
  vop_unp_bind      :nil;
  vop_unp_connect   :nil;
  vop_unp_detach    :nil;
 );

 devfs_specops:vop_vector=(
  vop_default       :@default_vnodeops;
  vop_bypass        :nil;

  vop_islocked      :nil;
  vop_lookup        :nil;
  vop_create        :@VOP_PANIC;
  vop_whiteout      :nil;
  vop_mknod         :@VOP_PANIC;
  vop_open          :@devfs_open;
  vop_close         :@devfs_close;
  vop_access        :@devfs_access;
  vop_accessx       :nil;
  vop_getattr       :@devfs_getattr;
  vop_setattr       :@devfs_setattr;
  vop_markatime     :nil;
  vop_read          :@VOP_PANIC;
  vop_write         :@VOP_PANIC;
  vop_ioctl         :nil;
  vop_poll          :nil;
  vop_kqfilter      :nil;
  vop_revoke        :@devfs_revoke;
  vop_fsync         :@devfs_fsync;
  vop_remove        :@devfs_remove;
  vop_link          :@VOP_PANIC;
  vop_rename        :@VOP_PANIC;
  vop_mkdir         :@VOP_PANIC;
  vop_rmdir         :@VOP_PANIC;
  vop_symlink       :@VOP_PANIC;
  vop_readdir       :@VOP_PANIC;
  vop_readlink      :@VOP_PANIC;
  vop_inactive      :nil;
  vop_reclaim       :@devfs_reclaim;
  vop_lock1         :nil;
  vop_unlock        :nil;
  vop_bmap          :@VOP_PANIC;
  vop_strategy      :@VOP_PANIC;
  vop_getwritemount :nil;
  vop_print         :@devfs_print;
  vop_pathconf      :@devfs_pathconf;
  vop_advlock       :nil;
  vop_advlockasync  :nil;
  vop_advlockpurge  :nil;
  vop_reallocblks   :@VOP_PANIC;
  vop_getpages      :nil;
  vop_putpages      :nil;
  vop_vptofh        :nil;
  vop_vptocnp       :@devfs_vptocnp;
  vop_allocate      :nil;
  vop_unp_bind      :nil;
  vop_unp_connect   :nil;
  vop_unp_detach    :nil;
 );

 devfs_ops_f:fileops=(
  fo_read    :@devfs_read_f;
  fo_write   :@devfs_write_f;
  fo_truncate:@devfs_truncate_f;
  fo_ioctl   :@devfs_ioctl_f;
  fo_poll    :@devfs_poll_f;
  fo_kqfilter:@devfs_kqfilter_f;
  fo_stat    :@devfs_stat_f;
  fo_close   :@devfs_close_f;
  fo_chmod   :@vn_chmod;
  fo_chown   :@vn_chown;
  fo_flags   :DFLAG_PASSABLE or DFLAG_SEEKABLE
 );

implementation

uses
 sysutils,
 errno,
 systm,
 kern_thr,
 vfs_subr,
 vsys_generic,
 //devfs_vfsops,
 kern_proc,
 kern_descrip,
 kern_mtxpool,
 subr_uio,
 vnode_pager;

function VFSTODEVFS(mp:p_mount):p_devfs_mount; inline;
begin
 Result:=mp^.mnt_data;
end;

function cdev2priv(c:Pointer):p_cdev_priv; inline;
begin
 Result:=c-ptruint(@p_cdev_priv(nil)^.cdp_c);
end;

function devfs_fp_check(fp:p_file;devp:pp_cdev;dswp:pp_cdevsw;ref:PInteger):Integer;
begin
 dswp^:=devvn_refthread(fp^.f_vnode, devp, ref);
 if (devp^<>fp^.f_data) then
 begin
  if (dswp^<>nil) then
  begin
   dev_relthread(devp^, ref^);
  end;

  Exit(ENXIO);
 end;
 Assert(devp^^.si_refcount > 0,'devfs: un-referenced struct cdev');
 if (dswp^=nil) then Exit(ENXIO);
 curkthread^.td_fpop:=fp;
 Exit(0);
end;

function devfs_get_cdevpriv(datap:PPointer):Integer; public;
var
 fp:p_file;
 p:p_cdev_privdata;
 error:Integer;
begin
 fp:=curkthread^.td_fpop;
 if (fp=nil) then Exit(EBADF);

 p:=fp^.f_cdevpriv;
 if (p<>nil) then
 begin
  error:=0;
  datap^:=p^.cdpd_data;
 end else
 begin
  error:=ENOENT;
 end;

 Exit(error);
end;

function devfs_set_cdevpriv(priv:Pointer;priv_dtr:cdevpriv_dtr_t):Integer; public;
var
 fp:p_file;
 cdp:p_cdev_priv;
 p:p_cdev_privdata;
 error:Integer;
begin
 fp:=curkthread^.td_fpop;
 if (fp=nil) then Exit(ENOENT);

 cdp:=cdev2priv(fp^.f_data);
 p:=AllocMem(sizeof(t_cdev_privdata));
 p^.cdpd_data:=priv;
 p^.cdpd_dtr :=priv_dtr;
 p^.cdpd_fp  :=fp;
 mtx_lock(cdevpriv_mtx);
 if (fp^.f_cdevpriv=nil) then
 begin
  LIST_INSERT_HEAD(@cdp^.cdp_fdpriv,p,@p^.cdpd_list);
  fp^.f_cdevpriv:=p;
  mtx_unlock(cdevpriv_mtx);
  error:=0;
 end else
 begin
  mtx_unlock(cdevpriv_mtx);
  FreeMem(p);
  error:=EBUSY;
 end;
 Exit(error);
end;

procedure devfs_destroy_cdevpriv(p:p_cdev_privdata); public;
begin
 mtx_assert(cdevpriv_mtx);
 p^.cdpd_fp^.f_cdevpriv:=nil;
 LIST_REMOVE(p,@p^.cdpd_list);
 mtx_unlock(cdevpriv_mtx);
 p^.cdpd_dtr(p^.cdpd_data);
 FreeMem(p);
end;

procedure devfs_fpdrop(fp:p_file); public;
var
 p:p_cdev_privdata;
begin
 mtx_lock(cdevpriv_mtx);
 p:=fp^.f_cdevpriv;
 if (p=nil) then
 begin
  mtx_unlock(cdevpriv_mtx);
  Exit;
 end;
 devfs_destroy_cdevpriv(p);
end;

procedure devfs_clear_cdevpriv(); public;
var
 fp:p_file;
begin
 fp:=curkthread^.td_fpop;
 if (fp=nil) then Exit;

 devfs_fpdrop(fp);
end;

{
 * On success devfs_populate_vp() Exits with dmp^.dm_lock held.
 }
function devfs_populate_vp(vp:p_vnode):Integer;
var
 de:p_devfs_dirent;
 dmp:p_devfs_mount;
 locked:Integer;
begin
 ASSERT_VOP_LOCKED(vp, 'devfs_populate_vp');

 dmp:=VFSTODEVFS(vp^.v_mount);
 locked:=VOP_ISLOCKED(vp);

 sx_xlock(@dmp^.dm_lock);
 DEVFS_DMP_HOLD(dmp);

 { Can't call devfs_populate() with the vnode lock held. }
 VOP_UNLOCK(vp, 0);
 devfs_populate(dmp);

 sx_xunlock(@dmp^.dm_lock);
 vn_lock(vp, locked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 sx_xlock(@dmp^.dm_lock);

 if DEVFS_DMP_DROP(dmp) then
 begin
  sx_xunlock(@dmp^.dm_lock);
  devfs_unmount_final(dmp);
  Exit(EBADF);
 end;
 if ((vp^.v_iflag and VI_DOOMED)<>0) then
 begin
  sx_xunlock(@dmp^.dm_lock);
  Exit(EBADF);
 end;
 de:=vp^.v_data;
 Assert(de<>nil,'devfs_populate_vp: vp^.v_data=nil but vnode not doomed');
 if ((de^.de_flags and DE_DOOMED)<>0) then
 begin
  sx_xunlock(@dmp^.dm_lock);
  Exit(EBADF);
 end;

 Exit(0);
end;

function devfs_vptocnp(ap:p_vop_vptocnp_args):Integer;
label
 finished;
var
 vp:p_vnode;
 dvp:pp_vnode;
 dmp:p_devfs_mount;
 buf:PChar;
 buflen:PInteger;
 dd,de:p_devfs_dirent;
 i,error:Integer;
begin
 vp    :=ap^.a_vp;
 dvp   :=ap^.a_vpp;
 buf   :=ap^.a_buf;
 buflen:=ap^.a_buflen;

 dmp:=VFSTODEVFS(vp^.v_mount);

 error:=devfs_populate_vp(vp);
 if (error<>0) then
 begin
  Exit(error);
 end;

 i:=buflen^;
 dd:=vp^.v_data;

 if (vp^.v_type=VCHR) then
 begin
  Dec(i,strlen(dd^.de_cdp^.cdp_c.si_name));
  if (i < 0) then
  begin
   error:=ENOMEM;
   goto finished;
  end;
  Move(dd^.de_cdp^.cdp_c.si_name^, (buf + i)^,strlen(dd^.de_cdp^.cdp_c.si_name));
  de:=dd^.de_dir;
 end else
 if (vp^.v_type=VDIR) then
 begin
  if (dd=dmp^.dm_rootdir) then
  begin
   dvp^:=vp;
   vref(dvp^);
   goto finished;
  end;
  Dec(i,dd^.de_dirent^.d_namlen);
  if (i < 0) then
  begin
   error:=ENOMEM;
   goto finished;
  end;
  Move(dd^.de_dirent^.d_name, (buf + i)^, dd^.de_dirent^.d_namlen);
  de:=dd;
 end else
 begin
  error:=ENOENT;
  goto finished;
 end;
 buflen^:=i;
 de:=devfs_parent_dirent(de);
 if (de=nil) then
 begin
  error:=ENOENT;
  goto finished;
 end;
 mtx_lock(devfs_de_interlock);
 dvp^:=de^.de_vnode;
 if (dvp^<>nil) then
 begin
  VI_LOCK(dvp^);
  mtx_unlock(devfs_de_interlock);
  vholdl(dvp^);
  VI_UNLOCK(dvp^);
  vref(dvp^);
  vdrop(dvp^);
 end else
 begin
  mtx_unlock(devfs_de_interlock);
  error:=ENOENT;
 end;
finished:
 sx_xunlock(@dmp^.dm_lock);
 Exit(error);
end;

{
 * Construct the fully qualified path name relative to the mountpoint.
 * If a nil cnp is provided, no '/' is appended to the resulting path.
 }
function devfs_fqpn(buf:PChar;dmp:p_devfs_mount;dd:p_devfs_dirent;cnp:p_componentname):PChar; public;
var
 i:Integer;
 de:p_devfs_dirent;
begin
 sx_assert(@dmp^.dm_lock);

 i:=SPECNAMELEN;
 buf[i]:=#0;
 if (cnp<>nil) then
  Dec(i,cnp^.cn_namelen);
 if (i < 0) then
   Exit(nil);
 if (cnp<>nil) then
  Move(cnp^.cn_nameptr^, (buf + i)^, cnp^.cn_namelen);
 de:=dd;
 while (de<>dmp^.dm_rootdir) do
 begin
  if (cnp<>nil) or (i < SPECNAMELEN) then
  begin
   Dec(i);
   if (i < 0) then
     Exit(nil);
   buf[i]:='/';
  end;
  Dec(i,de^.de_dirent^.d_namlen);
  if (i < 0) then
    Exit(nil);
  Move(de^.de_dirent^.d_name, (buf + i)^, de^.de_dirent^.d_namlen);
  de:=devfs_parent_dirent(de);
  if (de=nil) then
   Exit(nil);
 end;
 Exit(buf + i);
end;

function devfs_allocv_drop_refs(drop_dm_lock:Integer;dmp:p_devfs_mount;de:p_devfs_dirent):Integer;
var
 not_found:Integer;
begin
 not_found:=0;
 if ((de^.de_flags and DE_DOOMED)<>0) then
 begin
  not_found:=1;
 end;
 if DEVFS_DE_DROP(de) then
 begin
  Assert(not_found=1,'DEVFS de dropped but not doomed');
  devfs_dirent_free(de);
 end;
 if DEVFS_DMP_DROP(dmp) then
 begin
  Assert(not_found=1,'DEVFS mount struct freed before dirent');
  not_found:=2;
  sx_xunlock(@dmp^.dm_lock);
  devfs_unmount_final(dmp);
 end;
 if (not_found=1) or ((drop_dm_lock<>0) and (not_found<>2)) then
  sx_unlock(@dmp^.dm_lock);
 Exit(not_found);
end;

procedure devfs_insmntque_dtr(vp:p_vnode;arg:Pointer);
var
 de:p_devfs_dirent;
begin
 de:=p_devfs_dirent(arg);
 mtx_lock(devfs_de_interlock);
 vp^.v_data:=nil;
 de^.de_vnode:=nil;
 mtx_unlock(devfs_de_interlock);
 vgone(vp);
 vput(vp);
end;

{
 * devfs_allocv shall be entered with dmp^.dm_lock held, and it drops
 * it on Exit.
 }
function devfs_allocv(de:p_devfs_dirent;mp:p_mount;lockmode:Integer;vpp:pp_vnode):Integer; public;
label
 loop;
var
 error:Integer;
 vp:p_vnode;
 dev:p_cdev;
 dmp:p_devfs_mount;
 dsw:p_cdevsw;
begin
 dmp:=VFSTODEVFS(mp);
 if ((de^.de_flags and DE_DOOMED)<>0) then
 begin
  sx_xunlock(@dmp^.dm_lock);
  Exit(ENOENT);
 end;

loop:
 DEVFS_DE_HOLD(de);
 DEVFS_DMP_HOLD(dmp);
 mtx_lock(devfs_de_interlock);
 vp:=de^.de_vnode;
 if (vp<>nil) then
 begin
  VI_LOCK(vp);
  mtx_unlock(devfs_de_interlock);
  sx_xunlock(@dmp^.dm_lock);
  vget(vp, lockmode or LK_INTERLOCK or LK_RETRY);
  sx_xlock(@dmp^.dm_lock);
  if (devfs_allocv_drop_refs(0, dmp, de)<>0) then
  begin
   vput(vp);
   Exit(ENOENT);
  end else
  if ((vp^.v_iflag and VI_DOOMED)<>0) then
  begin
   mtx_lock(devfs_de_interlock);
   if (de^.de_vnode=vp) then
   begin
    de^.de_vnode:=nil;
    vp^.v_data:=nil;
   end;
   mtx_unlock(devfs_de_interlock);
   vput(vp);
   goto loop;
  end;
  sx_xunlock(@dmp^.dm_lock);
  vpp^:=vp;
  Exit(0);
 end;

 mtx_unlock(devfs_de_interlock);

 if (de^.de_dirent^.d_type=DT_CHR) then
 begin
  if ((de^.de_cdp^.cdp_flags and CDP_ACTIVE)=0) then
  begin
   devfs_allocv_drop_refs(1, dmp, de);
   Exit(ENOENT);
  end;
  dev:=@de^.de_cdp^.cdp_c;
 end else
 begin
  dev:=nil;
 end;

 error:=getnewvnode('devfs', mp, @devfs_vnodeops, @vp);
 if (error<>0) then
 begin
  devfs_allocv_drop_refs(1, dmp, de);
  Writeln('devfs_allocv: failed to allocate new vnode');
  Exit(error);
 end;

 if (de^.de_dirent^.d_type=DT_CHR) then
 begin
  vp^.v_type:=VCHR;
  VI_LOCK(vp);
  dev_lock();
  dev_refl(dev);
  { XXX: v_rdev should be protect by vnode lock }
  vp^.v_rdev:=dev;
  Assert(vp^.v_usecount=1);
  Inc(dev^.si_usecount,vp^.v_usecount);
  { Special casing of ttys for deadfs.  Probably redundant. }
  dsw:=dev^.si_devsw;

  if (dsw<>nil) then
  if ((dsw^.d_flags and D_TTY)<>0) then
  begin
   vp^.v_vflag:=vp^.v_vflag or VV_ISTTY;
  end;

  dev_unlock();
  VI_UNLOCK(vp);

  if ((dev^.si_flags and SI_ETERNAL)<>0) then
  begin
   vp^.v_vflag:=vp^.v_vflag or VV_ETERNALDEV;
  end;

  vp^.v_op:=@devfs_specops;
 end else
 if (de^.de_dirent^.d_type=DT_DIR) then
 begin
  vp^.v_type:=VDIR;
 end else
 if (de^.de_dirent^.d_type=DT_LNK) then
 begin
  vp^.v_type:=VLNK;
 end else
 begin
  vp^.v_type:=VBAD;
 end;

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

 mtx_lock(devfs_de_interlock);

 vp^.v_data:=de;
 de^.de_vnode:=vp;

 mtx_unlock(devfs_de_interlock);

 error:=insmntque1(vp, mp, @devfs_insmntque_dtr, de);

 if (error<>0) then
 begin
  devfs_allocv_drop_refs(1, dmp, de);
  Exit(error);
 end;

 if (devfs_allocv_drop_refs(0, dmp, de)<>0) then
 begin
  vput(vp);
  Exit(ENOENT);
 end;

 //mac_devfs_vnode_associate(mp, de, vp);

 sx_xunlock(@dmp^.dm_lock);
 vpp^:=vp;
 Exit(0);
end;

function devfs_access(ap:p_vop_access_args):Integer;
var
 vp:p_vnode;
 de:p_devfs_dirent;
 error:Integer;
begin
 vp:=ap^.a_vp;

 de:=vp^.v_data;
 if (vp^.v_type=VDIR) then
 begin
  de:=de^.de_dir;
 end;

 error:=vaccess(vp^.v_type, de^.de_mode, de^.de_uid, de^.de_gid, ap^.a_accmode, nil);
 if (error=0) then
 begin
  Exit(0);
 end;
 if (error<>EACCES) then
  Exit(error);
 { We do, however, allow access to the controlling terminal }
 if ((p_proc.p_flag and P_CONTROLT)=0) then
 begin
  Exit(error);
 end;
 //if (ap^.a_td^.td_proc^.p_session^.s_ttydp=de^.de_cdp) then
 // Exit(0);
 Exit(error);
end;

{ ARGSUSED }
function devfs_close(ap:p_vop_close_args):Integer;
var
 vp,oldvp:p_vnode;
 dev:p_cdev;
 dsw:p_cdevsw;
 vp_locked,error,ref:Integer;
begin
 vp:=ap^.a_vp;
 //struct thread *td:=ap^.a_td;
 dev:=vp^.v_rdev;

 {
  * XXX: Don't call d_close() if we were called because of
  * XXX: insmntque1() failure.
  }
 if (vp^.v_data=nil) then
 begin
  Exit(0);
 end;

 {
  * Hack: a tty device that is a controlling terminal
  * has a reference from the session structure.
  * We cannot easily tell that a character device is
  * a controlling terminal, unless it is the closing
  * process' controlling terminal.  In that case,
  * if the reference count is 2 (this last descriptor
  * plus the session), release the reference from the session.
  }
 oldvp:=nil;
 //sx_xlock(@proctree_lock);
 //if (td<>nil) then
 //if (vp=td^.td_proc^.p_session^.s_ttyvp) then
 //begin
 // SESS_LOCK(td^.td_proc^.p_session);
 // VI_LOCK(vp);
 // if (count_dev(dev)=2) and ((vp^.v_iflag and VI_DOOMED)=0) then
 // begin
 //  td^.td_proc^.p_session^.s_ttyvp:=nil;
 //  td^.td_proc^.p_session^.s_ttydp:=nil;
 //  oldvp:=vp;
 // end;
 // VI_UNLOCK(vp);
 // SESS_UNLOCK(td^.td_proc^.p_session);
 //end;
 //sx_xunlock(@proctree_lock);
 if (oldvp<>nil) then
 begin
  vrele(oldvp);
 end;
 {
  * We do not want to really close the device if it
  * is still in use unless we are trying to close it
  * forcibly. Since every use (buffer, vnode, swap, cmap)
  * holds a reference to the vnode, and because we mark
  * any other vnodes that alias this device, when the
  * sum of the reference counts on all the aliased
  * vnodes descends to one, we are on last close.
  }
 dsw:=dev_refthread(dev, @ref);
 if (dsw=nil) then Exit(ENXIO);

 VI_LOCK(vp);
 if ((vp^.v_iflag and VI_DOOMED)<>0) then
 begin
  { Forced close. }
 end else
 if ((dsw^.d_flags and D_TRACKCLOSE)<>0) then
 begin
  { Keep device updated on status. }
 end else
 if (count_dev(dev) > 1) then
 begin
  VI_UNLOCK(vp);
  dev_relthread(dev, ref);
  Exit(0);
 end;
 vholdl(vp);
 VI_UNLOCK(vp);
 vp_locked:=VOP_ISLOCKED(vp);
 VOP_UNLOCK(vp, 0);
 Assert(dev^.si_refcount > 0,'devfs_close() on un-referenced struct cdev');
 error:=dsw^.d_close(dev, ap^.a_fflag, S_IFCHR);
 dev_relthread(dev, ref);
 vn_lock(vp, vp_locked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 vdrop(vp);
 Exit(error);
end;

function devfs_close_f(fp:p_file):Integer;
var
 td:p_kthread;
 fpop:p_file;
begin
 td:=curkthread;
 {
  * NB: td may be nil if this descriptor is closed due to
  * garbage collection from a closed UNIX domain socket.
  }
 fpop:=td^.td_fpop;
 td^.td_fpop:=fp;
 Result:=vnops.fo_close(fp);
 td^.td_fpop:=fpop;

 {
  * The f_cdevpriv cannot be assigned non-nil value while we
  * are destroying the file.
  }
 if (fp^.f_cdevpriv<>nil) then
  devfs_fpdrop(fp);
end;

function devfs_fsync(ap:p_vop_fsync_args):Integer;
var
 error:Integer;
 //struct bufobj *bo;
 de:p_devfs_dirent;
begin
 if vn_isdisk(ap^.a_vp, @error) then
 begin
  //bo:=@ap^.a_vp^.v_bufobj;
  de:=ap^.a_vp^.v_data;
  if (error=ENXIO) {and (bo^.bo_dirty.bv_cnt > 0)} then
  begin
   Writeln('Device %s went missing before all of the data ',
           'could be written to it; expect data loss.',
           de^.de_dirent^.d_name);

   error:=vop_stdfsync(ap);
   if {(bo^.bo_dirty.bv_cnt<>0) or} (error<>0) then
    Assert(False,'devfs_fsync: vop_stdfsync failed.');
  end;

  Exit(0);
 end;

 Exit(vop_stdfsync(ap));
end;

function devfs_getattr(ap:p_vop_getattr_args):Integer;
var
 vp:p_vnode;
 vap:p_vattr;
 error:Integer;
 de:p_devfs_dirent;
 dmp:p_devfs_mount;
 dev:p_cdev;

procedure fix(var src,dst:timespec); inline;
begin
 if (src.tv_sec <= 3600) then
 begin
  src.tv_sec :=boottime.tv_sec;
  src.tv_nsec:=boottime.tv_usec * 1000;
 end;
 dst:=src;
end;

begin
 vp:=ap^.a_vp;
 vap:=ap^.a_vap;

 error:=devfs_populate_vp(vp);
 if (error<>0) then
 begin
  Exit(error);
 end;

 dmp:=VFSTODEVFS(vp^.v_mount);
 sx_xunlock(@dmp^.dm_lock);

 de:=vp^.v_data;
 Assert(de<>nil,'nil dirent in devfs_getattr vp=%p');
 if (vp^.v_type=VDIR) then
 begin
  de:=de^.de_dir;
  Assert(de<>nil,'nil dir dirent in devfs_getattr vp=%p');
 end;

 vap^.va_uid :=de^.de_uid;
 vap^.va_gid :=de^.de_gid;
 vap^.va_mode:=de^.de_mode;

 case vp^.v_type of
  VLNK:
   begin
    vap^.va_size :=strlen(de^.de_symlink);
    vap^.va_bytes:=0;
   end;
  VDIR:
   begin
    vap^.va_size :=DEV_BSIZE;
    vap^.va_bytes:=DEV_BSIZE;
   end;
  else
   begin
    vap^.va_size :=0;
    vap^.va_bytes:=0;
   end;
 end;

 vap^.va_blocksize:=DEV_BSIZE;
 vap^.va_type:=vp^.v_type;

 if (vp^.v_type<>VCHR) then
 begin
  fix(de^.de_atime,vap^.va_atime);
  fix(de^.de_mtime,vap^.va_mtime);
  fix(de^.de_ctime,vap^.va_ctime);
 end else
 begin
  dev:=vp^.v_rdev;

  fix(dev^.si_atime,vap^.va_atime);
  fix(dev^.si_mtime,vap^.va_mtime);
  fix(dev^.si_ctime,vap^.va_ctime);

  vap^.va_rdev:=cdev2priv(dev)^.cdp_inode;
 end;

 vap^.va_gen    :=0;
 vap^.va_flags  :=0;
 vap^.va_filerev:=0;
 vap^.va_nlink  :=de^.de_links;
 vap^.va_fileid :=de^.de_inode;

 Exit(error);
end;

{ ARGSUSED }
function devfs_ioctl_f(fp:p_file;com:QWORD;data:Pointer):Integer;
var
 td:p_kthread;
 dev:p_cdev;
 dsw:p_cdevsw;
 vp:p_vnode;
 vpold:p_vnode;
 error,i,ref:Integer;
 p:PChar;
 fgn:p_fiodgname_arg;
 fpop:p_file;
begin
 td:=curkthread;
 fpop:=td^.td_fpop;
 error:=devfs_fp_check(fp, @dev, @dsw, @ref);
 if (error<>0) then Exit(error);

 if (com=FIODTYPE) then
 begin
  PInteger(data)^:=dsw^.d_flags and D_TYPEMASK;
  td^.td_fpop:=fpop;
  dev_relthread(dev, ref);
  Exit(0);
 end else
 if (com=FIODGNAME) then
 begin
  fgn:=data;
  p:=devtoname(dev);
  i:=strlen(p) + 1;

  if (i > fgn^.len) then
   error:=EINVAL
  else
   error:=copyout(p, fgn^.buf, i);

  td^.td_fpop:=fpop;
  dev_relthread(dev, ref);
  Exit(error);
 end;

 error:=dsw^.d_ioctl(dev, com, data, fp^.f_flag);

 td^.td_fpop:=nil;
 dev_relthread(dev, ref);

 if (error=ENOIOCTL) then
 begin
  error:=ENOTTY;
 end;
 if (error=0) and (com=TIOCSCTTY) then
 begin
  vp:=fp^.f_vnode;

  vpold:=nil;
  { Do nothing if reassigning same control tty }
  //sx_slock(@proctree_lock);
  //if (td^.td_proc^.p_session^.s_ttyvp=vp) then
  //begin
  // sx_sunlock(@proctree_lock);
  // Exit(0);
  //end;
  //
  //vpold:=td^.td_proc^.p_session^.s_ttyvp;
  //VREF(vp);
  //SESS_LOCK(td^.td_proc^.p_session);
  //td^.td_proc^.p_session^.s_ttyvp:=vp;
  //td^.td_proc^.p_session^.s_ttydp:=cdev2priv(dev);
  //SESS_UNLOCK(td^.td_proc^.p_session);
  //
  //sx_sunlock(@proctree_lock);

  { Get rid of reference to old control tty }
  if (vpold<>nil) then
  begin
   vrele(vpold);
  end;
 end;

 Exit(error);
end;

{ ARGSUSED }
function devfs_kqfilter_f(fp:p_file;kn:p_knote):Integer;
var
 td:p_kthread;
 dev:p_cdev;
 dsw:p_cdevsw;
 error,ref:Integer;
 fpop:p_file;
begin
 td:=curkthread;
 fpop:=td^.td_fpop;
 error:=devfs_fp_check(fp, @dev, @dsw, @ref);
 if (error<>0) then Exit(error);

 error:=dsw^.d_kqfilter(dev, kn);
 td^.td_fpop:=fpop;
 dev_relthread(dev, ref);
 Exit(error);
end;

function devfs_prison_check(de:p_devfs_dirent):Integer;
var
 cdp:p_cdev_priv;
 error:Integer;
begin
 cdp:=de^.de_cdp;
 if (cdp=nil) then
  Exit(0);

 error:=0;
 //error:=prison_check(td^.td_ucred, dcr);
 if (error=0) then
 begin
  Exit(0);
 end;
 { We do, however, allow access to the controlling terminal }
 if ((p_proc.p_flag and P_CONTROLT)=0) then
 begin
  Exit(error);
 end;
 //if (td^.td_proc^.p_session^.s_ttydp=cdp) then
 // Exit(0);
 Exit(error);
end;

function devfs_lookupx(ap:p_vop_lookup_args;dm_unlock:PBoolean):Integer;
label
 _or;
var
 cnp:p_componentname;
 dvp:p_vnode;
 vpp:pp_vnode;
 de,dd:p_devfs_dirent;
 dde:pp_devfs_dirent;
 dmp:p_devfs_mount;
 cdev:p_cdev;
 error,flags,nameiop,dvplocked:Integer;
 specname:array[0..SPECNAMELEN] of Char;
 pname:PChar;
begin
 cnp:=ap^.a_cnp;
 vpp:=ap^.a_vpp;
 dvp:=ap^.a_dvp;
 pname:=cnp^.cn_nameptr;
 flags:=cnp^.cn_flags;
 nameiop:=cnp^.cn_nameiop;
 dmp:=VFSTODEVFS(dvp^.v_mount);
 dd:=dvp^.v_data;
 vpp^:=nil;

 if ((flags and ISLASTCN)<>0) and (nameiop=RENAME) then
 begin
  Exit(EOPNOTSUPP);
 end;

 if (dvp^.v_type<>VDIR) then
 begin
  Exit(ENOTDIR);
 end;

 if (((flags and ISDOTDOT)<>0) and ((dvp^.v_vflag and VV_ROOT)<>0)) then
 begin
  Exit(EIO);
 end;

 error:=VOP_ACCESS(dvp, VEXEC);
 if (error<>0) then
 begin
  Exit(error);
 end;

 if (cnp^.cn_namelen=1) and (pname^='.') then
 begin
  if ((flags and ISLASTCN)<>0) and (nameiop<>LOOKUP) then
  begin
   Exit(EINVAL);
  end;
  vpp^:=dvp;
  VREF(dvp);
  Exit(0);
 end;

 if ((flags and ISDOTDOT)<>0) then
 begin
  if ((flags and ISLASTCN)<>0) and (nameiop<>LOOKUP) then
  begin
   Exit(EINVAL);
  end;
  de:=devfs_parent_dirent(dd);
  if (de=nil) then Exit(ENOENT);

  dvplocked:=VOP_ISLOCKED(dvp);
  VOP_UNLOCK(dvp, 0);

  error:=devfs_allocv(de, dvp^.v_mount, cnp^.cn_lkflags and LK_TYPE_MASK, vpp);
  dm_unlock^:=False;

  vn_lock(dvp, dvplocked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  Exit(error);
 end;

 dd:=dvp^.v_data;
 de:=devfs_find(dd, cnp^.cn_nameptr, cnp^.cn_namelen, 0);
 while (de=nil) do
 begin { While(...) so we can use break }

  if (nameiop=DELETE) then Exit(ENOENT);

  {
   * OK, we didn't have an entry for the name we were asked for
   * so we try to see if anybody can create it on demand.
   }
  pname:=devfs_fqpn(specname, dmp, dd, cnp);
  if (pname=nil) then break;

  cdev:=nil;
  DEVFS_DMP_HOLD(dmp);
  sx_xunlock(@dmp^.dm_lock);

  //sx_slock(@clone_drain_lock);
  //EVENTHANDLER_INVOKE(dev_clone, td^.td_ucred, pname, strlen(pname), @cdev);
  //sx_sunlock(@clone_drain_lock);

  if (cdev=nil) then
   sx_xlock(@dmp^.dm_lock)
  else
  if (devfs_populate_vp(dvp)<>0) then
  begin
   dm_unlock^:=false;
   sx_xlock(@dmp^.dm_lock);

   if (DEVFS_DMP_DROP(dmp)) then
   begin
    sx_xunlock(@dmp^.dm_lock);
    devfs_unmount_final(dmp);
   end else
    sx_xunlock(@dmp^.dm_lock);

   dev_rel(cdev);
   Exit(ENOENT);
  end;
  if DEVFS_DMP_DROP(dmp) then
  begin
   dm_unlock^:=false;
   sx_xunlock(@dmp^.dm_lock);
   devfs_unmount_final(dmp);

   if (cdev<>nil) then
   begin
    dev_rel(cdev);
   end;

   Exit(ENOENT);
  end;

  if (cdev=nil) then
   break;

  dev_lock();
  dde:=@cdev2priv(cdev)^.cdp_dirents[dmp^.dm_idx];

  if (dde<>nil) then
  if (dde^<>nil) then
  begin
   de:=dde^;
  end;

  dev_unlock();
  dev_rel(cdev);
  break;
 end;

 if (de=nil) then goto _or;

 if ((de^.de_flags and DE_WHITEOUT)<>0) then
 begin
  _or:
  if ((nameiop=CREATE) or (nameiop=RENAME)) and
     ((flags and (LOCKPARENT or WANTPARENT))<>0) and
     ((flags and ISLASTCN)<>0) then
  begin
   cnp^.cn_flags:=cnp^.cn_flags or SAVENAME;
   Exit(EJUSTRETURN);
  end;
  Exit(ENOENT);
 end;

 if (devfs_prison_check(de)<>0) then
 begin
  Exit(ENOENT);
 end;

 if (cnp^.cn_nameiop=DELETE) and ((flags and ISLASTCN)<>0) then
 begin
  error:=VOP_ACCESS(dvp, VWRITE);
  if (error<>0) then
  begin
   Exit(error);
  end;
  if (vpp^=dvp) then
  begin
   VREF(dvp);
   vpp^:=dvp;
   Exit(0);
  end;
 end;

 error:=devfs_allocv(de, dvp^.v_mount, cnp^.cn_lkflags and LK_TYPE_MASK, vpp);
 dm_unlock^:=false;

 Exit(error);
end;

function devfs_lookup(ap:p_vop_lookup_args):Integer;
var
 dmp:p_devfs_mount;
 dm_unlock:Boolean;
begin
 if (devfs_populate_vp(ap^.a_dvp)<>0) then
 begin
  Exit(ENOTDIR);
 end;

 dmp:=VFSTODEVFS(ap^.a_dvp^.v_mount);
 dm_unlock:=True;

 Result:=devfs_lookupx(ap, @dm_unlock);

 if (dm_unlock) then
 begin
  sx_xunlock(@dmp^.dm_lock);
 end;
end;

function devfs_mknod(ap:p_vop_mknod_args):Integer;
label
 notfound;
var
 cnp:p_componentname;
 dvp:p_vnode;
 vpp:pp_vnode;
 dd,de:p_devfs_dirent;
 dmp:p_devfs_mount;
 error:Integer;
begin
 {
  * The only type of node we should be creating here is a
  * character device, for anything else return EOPNOTSUPP.
  }
 if (ap^.a_vap^.va_type<>VCHR) then
  Exit(EOPNOTSUPP);

 dvp:=ap^.a_dvp;
 dmp:=VFSTODEVFS(dvp^.v_mount);

 cnp:=ap^.a_cnp;
 vpp:=ap^.a_vpp;
 dd:=dvp^.v_data;

 error:=ENOENT;
 sx_xlock(@dmp^.dm_lock);

 de:=TAILQ_FIRST(@dd^.de_dlist);
 while (de<>nil) do
 begin
  if (cnp^.cn_namelen<>de^.de_dirent^.d_namlen) then
  begin
   de:=TAILQ_NEXT(de,@de^.de_list);
   continue;
  end;
  if (CompareByte(cnp^.cn_nameptr^, de^.de_dirent^.d_name, de^.de_dirent^.d_namlen)<>0) then
  begin
   de:=TAILQ_NEXT(de,@de^.de_list);
   continue;
  end;
  if ((de^.de_flags and DE_WHITEOUT)<>0) then
   break;
  goto notfound;
 end;
 if (de=nil) then
  goto notfound;
 de^.de_flags:=de^.de_flags and (not DE_WHITEOUT);
 error:=devfs_allocv(de, dvp^.v_mount, LK_EXCLUSIVE, vpp);
 Exit(error);
notfound:
 sx_xunlock(@dmp^.dm_lock);
 Exit(error);
end;

{ ARGSUSED }
function devfs_open(ap:p_vop_open_args):Integer;
var
 td:p_kthread;
 vp:p_vnode;
 dev:p_cdev;
 fp:p_file;
 error,ref,vlocked:Integer;
 dsw:p_cdevsw;
 fpop:p_file;
 mtxp:p_mtx;
begin
 td:=curkthread;
 vp:=ap^.a_vp;
 dev:=vp^.v_rdev;
 fp:=ap^.a_fp;

 if (vp^.v_type=VBLK) then Exit(ENXIO);
 if (dev=nil) then Exit(ENXIO);

 { Make this field valid before any I/O in d_open. }
 if (dev^.si_iosize_max=0) then
 begin
  dev^.si_iosize_max:=DFLTPHYS;
 end;

 dsw:=dev_refthread(dev, @ref);
 if (dsw=nil) then Exit(ENXIO);

 if (fp=nil) and (dsw^.d_fdopen<>nil) then
 begin
  dev_relthread(dev, ref);
  Exit(ENXIO);
 end;

 vlocked:=VOP_ISLOCKED(vp);
 VOP_UNLOCK(vp, 0);

 fpop:=td^.td_fpop;
 td^.td_fpop:=fp;
 if (fp<>nil) then
 begin
  fp^.f_data:=dev;
  fp^.f_vnode:=vp;
 end;

 if (dsw^.d_fdopen<>nil) then
  error:=dsw^.d_fdopen(dev, ap^.a_mode, fp)
 else
  error:=dsw^.d_open(dev, ap^.a_mode, S_IFCHR);

 { cleanup any cdevpriv upon error }
 if (error<>0) then
 begin
  devfs_clear_cdevpriv();
 end;

 td^.td_fpop:=fpop;

 vn_lock(vp, vlocked or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 dev_relthread(dev, ref);
 if (error<>0) then
 begin
  if (error=ERESTART) then error:=EINTR;
  Exit(error);
 end;

 if (fp=nil) then Exit(error);

 if (fp^.f_ops=@badfileops) then
 begin
  finit(fp, fp^.f_flag, DTYPE_VNODE, dev, @devfs_ops_f);
 end;

 mtxp:=mtx_pool_find(mtxpool_sleep, fp);

 {
  * Hint to the dofilewrite() to not force the buffer draining
  * on the writer to the file.  Most likely, the write would
  * not need normal buffers.
  }
 mtx_lock(mtxp^);
 fp^.f_vnread_flags:=fp^.f_vnread_flags or FDEVFS_VNODE;
 mtx_unlock(mtxp^);
 Exit(error);
end;

function devfs_pathconf(ap:p_vop_pathconf_args):Integer;
begin
 case ap^.a_name of
  _PC_MAC_PRESENT:
   begin
    {
     * If MAC is enabled, devfs automatically supports
     * trivial non-persistant label storage.
     }
    ap^.a_retval^:=1;
    Exit(0);
   end
  else
   Exit(vop_stdpathconf(ap));
 end;
 { NOTREACHED }
end;

{ ARGSUSED }
function devfs_poll_f(fp:p_file;events:Integer):Integer;
var
 td:p_kthread;
 dev:p_cdev;
 dsw:p_cdevsw;
 error,ref:Integer;
 fpop:p_file;
begin
 td:=curkthread;
 fpop:=td^.td_fpop;
 error:=devfs_fp_check(fp, @dev, @dsw, @ref);
 if (error<>0) then Exit(poll_no_poll(events));

 error:=dsw^.d_poll(dev, events);
 td^.td_fpop:=fpop;
 dev_relthread(dev, ref);
 Exit(error);
end;

{
 * Print out the contents of a special device vnode.
 }
function devfs_print(ap:p_vop_print_args):Integer;
begin
 Writeln(Format('dev %s',[devtoname(ap^.a_vp^.v_rdev)]));
 Exit(0);
end;

function devfs_read_f(fp:p_file;uio:p_uio;flags:Integer):Integer;
var
 td:p_kthread;
 dev:p_cdev;
 ioflag,error,ref:Integer;
 resid:Int64;
 dsw:p_cdevsw;
 fpop:p_file;
begin
 if (uio^.uio_resid > DEVFS_IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;

 td:=curkthread;
 fpop:=td^.td_fpop;
 error:=devfs_fp_check(fp, @dev, @dsw, @ref);
 if (error<>0) then Exit(error);

 resid:=uio^.uio_resid;
 ioflag:=fp^.f_flag and (O_NONBLOCK or O_DIRECT);

 if ((ioflag and O_DIRECT)<>0) then
 begin
  ioflag:=ioflag or IO_DIRECT;
 end;

 foffset_lock_uio(fp, uio, flags or FOF_NOLOCK);
 error:=dsw^.d_read(dev, uio, ioflag);
 if (uio^.uio_resid<>resid) or ((error=0) and (resid<>0)) then
 begin
  vfs_timestamp(@dev^.si_atime);
 end;
 td^.td_fpop:=fpop;
 dev_relthread(dev, ref);

 foffset_unlock_uio(fp, uio, flags or FOF_NOLOCK or FOF_NEXTOFF);
 Exit(error);
end;

function devfs_readdir(ap:p_vop_readdir_args):Integer;
var
 error:Integer;
 uio:p_uio;
 dp:p_dirent;
 dd:p_devfs_dirent;
 de:p_devfs_dirent;
 dmp:p_devfs_mount;
 off:Int64;
 tmp_ncookies:PInteger;
begin
 tmp_ncookies:=nil;

 if (ap^.a_vp^.v_type<>VDIR) then
 begin
  Exit(ENOTDIR);
 end;

 uio:=ap^.a_uio;
 if (uio^.uio_offset < 0) then
 begin
  Exit(EINVAL);
 end;

 {
  * XXX: This is a temporary hack to get around this filesystem not
  * supporting cookies. We store the location of the ncookies pointer
  * in a temporary variable before calling vfs_subr.c:vfs_read_dirent()
  * and set the number of cookies to 0. We then set the pointer to
  * nil so that vfs_read_dirent doesn't try to call realloc() on
  * ap^.a_cookies. Later in this function, we restore the ap^.a_ncookies
  * pointer to its original location before Exiting to the caller.
  }
 if (ap^.a_ncookies<>nil) then
 begin
  tmp_ncookies:=ap^.a_ncookies;
  ap^.a_ncookies^:=0;
  ap^.a_ncookies:=nil;
 end;

 dmp:=VFSTODEVFS(ap^.a_vp^.v_mount);
 if (devfs_populate_vp(ap^.a_vp)<>0) then
 begin
  if (tmp_ncookies<>nil) then
  begin
   ap^.a_ncookies:=tmp_ncookies;
  end;
  Exit(EIO);
 end;
 error:=0;
 de:=ap^.a_vp^.v_data;
 off:=0;

 dd:=TAILQ_FIRST(@de^.de_dlist);
 while (dd<>nil) do
 begin
  Assert(ptruint(dd^.de_cdp)<>$deadc0de);
  if ((dd^.de_flags and (DE_COVERED or DE_WHITEOUT))<>0) then
  begin
   dd:=TAILQ_NEXT(dd,@dd^.de_list);
   continue;
  end;

  if (devfs_prison_check(dd)<>0) then
  begin
   dd:=TAILQ_NEXT(dd,@dd^.de_list);
   continue;
  end;

  if (dd^.de_dirent^.d_type=DT_DIR) then
   de:=dd^.de_dir
  else
   de:=dd;

  dp:=dd^.de_dirent;
  if (dp^.d_reclen > uio^.uio_resid) then
  begin
   break;
  end;

  dp^.d_fileno:=de^.de_inode;
  if (off >= uio^.uio_offset) then
  begin
   error:=vfs_read_dirent(ap, dp, off);
   if (error<>0) then
   begin
    break;
   end;
  end;
  Inc(off,dp^.d_reclen);
  dd:=TAILQ_NEXT(dd,@dd^.de_list);
 end;
 sx_xunlock(@dmp^.dm_lock);
 uio^.uio_offset:=off;

 {
  * Restore ap^.a_ncookies if it wasn't originally nil in the first
  * place.
  }
 if (tmp_ncookies<>nil) then
 begin
  ap^.a_ncookies:=tmp_ncookies;
 end;

 Exit(error);
end;

function devfs_readlink(ap:p_vop_readlink_args):Integer;
var
 de:p_devfs_dirent;
begin
 de:=ap^.a_vp^.v_data;
 if (de^.de_dirent^.d_type<>DT_LNK) then Exit(EINVAL);

 Exit(uiomove(de^.de_symlink, strlen(de^.de_symlink), ap^.a_uio));
end;

function devfs_reclaim(ap:p_vop_reclaim_args):Integer;
var
 vp:p_vnode;
 de:p_devfs_dirent;
 dev:p_cdev;
begin
 vp:=ap^.a_vp;

 mtx_lock(devfs_de_interlock);
 de:=vp^.v_data;
 if (de<>nil) then
 begin
  de^.de_vnode:=nil;
  vp^.v_data:=nil;
 end;
 mtx_unlock(devfs_de_interlock);

 vnode_destroy_vobject(vp);

 VI_LOCK(vp);
 dev_lock();
 dev:=vp^.v_rdev;
 vp^.v_rdev:=nil;

 if (dev=nil) then
 begin
  dev_unlock();
  VI_UNLOCK(vp);
  Exit(0);
 end;

 Dec(dev^.si_usecount,vp^.v_usecount);
 dev_unlock();
 VI_UNLOCK(vp);
 dev_rel(dev);
 Exit(0);
end;

function devfs_remove(ap:p_vop_remove_args):Integer;
var
 dvp,vp:p_vnode;
 dd:p_devfs_dirent;
 de,de_cov:p_devfs_dirent;
 dmp:p_devfs_mount;
begin
 dvp:=ap^.a_dvp;
 vp:=ap^.a_vp;
 dmp:=VFSTODEVFS(vp^.v_mount);

 ASSERT_VOP_ELOCKED(dvp, 'devfs_remove');
 ASSERT_VOP_ELOCKED(vp, 'devfs_remove');

 sx_xlock(@dmp^.dm_lock);
 dd:=ap^.a_dvp^.v_data;
 de:=vp^.v_data;
 if (de^.de_cdp=nil) then
 begin
  TAILQ_REMOVE(@dd^.de_dlist,de,@de^.de_list);

  if (de^.de_dirent^.d_type=DT_LNK) then
  begin
   de_cov:=devfs_find(dd, de^.de_dirent^.d_name, de^.de_dirent^.d_namlen, 0);
   if (de_cov<>nil) then
   begin
    de_cov^.de_flags:=de_cov^.de_flags and (not DE_COVERED);
   end;
  end;

  { We need to unlock dvp because devfs_delete() may lock it. }
  VOP_UNLOCK(vp, 0);
  if (dvp<>vp) then
  begin
   VOP_UNLOCK(dvp, 0);
  end;

  devfs_delete(dmp, de, 0);
  sx_xunlock(@dmp^.dm_lock);
  if (dvp<>vp) then
  begin
   vn_lock(dvp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  end;

  vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 end else
 begin
  de^.de_flags:=de^.de_flags or DE_WHITEOUT;
  sx_xunlock(@dmp^.dm_lock);
 end;
 Exit(0);
end;

{
 * Revoke is called on a tty when a terminal session ends.  The vnode
 * is orphaned by setting v_op to deadfs so we need to let go of it
 * as well so that we create a new one next time around.
 *
 }
function devfs_revoke(ap:p_vop_revoke_args):Integer;
label
 loop;
var
 vp,vp2:p_vnode;
 dev:p_cdev;
 cdp:p_cdev_priv;
 de:p_devfs_dirent;
 i:DWORD;
begin
 vp:=ap^.a_vp;

 Assert((ap^.a_flags and REVOKEALL)<>0,'devfs_revoke !REVOKEALL');

 dev:=vp^.v_rdev;
 cdp:=cdev2priv(dev);

 dev_lock();
 Inc(cdp^.cdp_inuse);
 dev_unlock();

 vhold(vp);
 vgone(vp);
 vdrop(vp);

 VOP_UNLOCK(vp,0);
 loop:
 repeat
  mtx_lock(devfs_de_interlock);
  dev_lock();
  vp2:=nil;
  For i:=0 to cdp^.cdp_maxdirent do
  begin
   de:=cdp^.cdp_dirents[i];
   if (de=nil) then
   begin
    continue;
   end;

   vp2:=de^.de_vnode;
   if (vp2<>nil) then
   begin
    dev_unlock();
    VI_LOCK(vp2);
    mtx_unlock(devfs_de_interlock);
    if (vget(vp2, LK_EXCLUSIVE or LK_INTERLOCK)<>0) then
    begin
     goto loop;
    end;
    vhold(vp2);
    vgone(vp2);
    vdrop(vp2);
    vput(vp2);
    break;
   end;
  end;
  if (vp2<>nil) then
  begin
   continue;
  end;
  dev_unlock();
  mtx_unlock(devfs_de_interlock);
  break;
 until false;
 dev_lock();
 Dec(cdp^.cdp_inuse);
 if ((cdp^.cdp_flags and CDP_ACTIVE)=0) and (cdp^.cdp_inuse=0) then
 begin
  TAILQ_REMOVE(@cdevp_list,cdp,@cdp^.cdp_list);
  dev_unlock();
  dev_rel(@cdp^.cdp_c);
 end else
 begin
  dev_unlock();
 end;

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

function devfs_rioctl(ap:p_vop_ioctl_args):Integer;
var
 vp:p_vnode;
 dmp:p_devfs_mount;
 error:Integer;
begin
 vp:=ap^.a_vp;
 vn_lock(vp, LK_SHARED or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 if ((vp^.v_iflag and VI_DOOMED)<>0) then
 begin
  VOP_UNLOCK(vp, 0);
  Exit(EBADF);
 end;
 dmp:=VFSTODEVFS(vp^.v_mount);
 sx_xlock(@dmp^.dm_lock);
 VOP_UNLOCK(vp, 0);
 DEVFS_DMP_HOLD(dmp);
 devfs_populate(dmp);
 if DEVFS_DMP_DROP(dmp) then
 begin
  sx_xunlock(@dmp^.dm_lock);
  devfs_unmount_final(dmp);
  Exit(ENOENT);
 end;
 error:=devfs_rules_ioctl(dmp, ap^.a_command, ap^.a_data);
 sx_xunlock(@dmp^.dm_lock);
 Exit(error);
end;

function devfs_rread(ap:p_vop_read_args):Integer;
begin
 if (ap^.a_vp^.v_type<>VDIR) then
  Exit(EINVAL);
 Exit(VOP_READDIR(ap^.a_vp, ap^.a_uio, nil, nil, nil));
end;

function devfs_setattr(ap:p_vop_setattr_args):Integer;
var
 de:p_devfs_dirent;
 vap:p_vattr;
 vp:p_vnode;
 c,error:Integer;
 uid:uid_t;
 gid:gid_t;
begin
 vap:=ap^.a_vap;
 vp:=ap^.a_vp;

 if (vap^.va_type     <>VNON) or
    (vap^.va_nlink    <>VNOVAL) or
    (vap^.va_fsid     <>VNOVAL) or
    (vap^.va_fileid   <>VNOVAL) or
    (vap^.va_blocksize<>VNOVAL) or
    ((vap^.va_flags   <>VNOVAL) and (vap^.va_flags<>0)) or
    (vap^.va_rdev     <>VNOVAL) or
    (vap^.va_bytes    <>VNOVAL) or
    (vap^.va_gen      <>VNOVAL) then
 begin
  Exit(EINVAL);
 end;

 de:=vp^.v_data;
 if (vp^.v_type=VDIR) then
 begin
  de:=de^.de_dir;
 end;

 error:=0;
 c:=0;

 if (vap^.va_uid=VNOVAL) then
  uid:=de^.de_uid
 else
  uid:=vap^.va_uid;

 if (vap^.va_gid=VNOVAL) then
  gid:=de^.de_gid
 else
  gid:=vap^.va_gid;

 if (uid<>de^.de_uid) or (gid<>de^.de_gid) then
 begin
  //if ((ap^.a_cred^.cr_uid<>de^.de_uid) or uid<>de^.de_uid or
  //    (gid<>de^.de_gid and !groupmember(gid, ap^.a_cred))) then
  //begin
  // error:=priv_check(td, PRIV_VFS_CHOWN);
  // if (error<>) then
  //  Exit(error);
  //end;
  de^.de_uid:=uid;
  de^.de_gid:=gid;
  c:=1;
 end;

 if (vap^.va_mode<>VNOVAL) then
 begin
  //if (ap^.a_cred^.cr_uid<>de^.de_uid) then
  //begin
  // error:=priv_check(td, PRIV_VFS_ADMIN);
  // if (error<>0) then
  //  Exit(error);
  //end;
  de^.de_mode:=vap^.va_mode;
  c:=1;
 end;

 if (vap^.va_atime.tv_sec<>VNOVAL) or (vap^.va_mtime.tv_sec<>VNOVAL) then
 begin
  { See the comment in ufs_vnops::ufs_setattr(). }
  error:=VOP_ACCESS(vp, VADMIN);
  if (error<>0) then
  begin
   if ((vap^.va_vaflags and VA_UTIMES_NULL)=0) then Exit(error);
   error:=VOP_ACCESS(vp, VWRITE);
   if (error<>0) then Exit(error);
  end;
  if (vap^.va_atime.tv_sec<>VNOVAL) then
  begin
   if (vp^.v_type=VCHR) then
    p_cdev(vp^.v_rdev)^.si_atime:=vap^.va_atime
   else
    de^.de_atime:=vap^.va_atime;
  end;
  if (vap^.va_mtime.tv_sec<>VNOVAL) then
  begin
   if (vp^.v_type=VCHR) then
    p_cdev(vp^.v_rdev)^.si_mtime:=vap^.va_mtime
   else
    de^.de_mtime:=vap^.va_mtime;
  end;
  c:=1;
 end;

 if (c<>0) then
 begin
  if (vp^.v_type=VCHR) then
   vfs_timestamp(@p_cdev(vp^.v_rdev)^.si_ctime)
  else
   vfs_timestamp(@de^.de_mtime);
 end;
 Exit(0);
end;

function devfs_stat_f(fp:p_file;sb:p_stat):Integer;
begin
 Exit(vnops.fo_stat(fp, sb));
end;

function devfs_symlink(ap:p_vop_symlink_args):Integer;
var
 i, error:Integer;
 dd:p_devfs_dirent;
 de,de_cov,de_dotdot:p_devfs_dirent;
 dmp:p_devfs_mount;
begin
 error:=0;
 //error:=priv_check(curkthread, PRIV_DEVFS_SYMLINK);
 if (error<>0) then
 begin
  Exit(error);
 end;

 dmp:=VFSTODEVFS(ap^.a_dvp^.v_mount);

 if (devfs_populate_vp(ap^.a_dvp)<>0) then
 begin
  Exit(ENOENT);
 end;

 dd:=ap^.a_dvp^.v_data;
 de_cov:=devfs_find(dd, ap^.a_cnp^.cn_nameptr, ap^.a_cnp^.cn_namelen, 0);

 if (de_cov<>nil) then
 begin
  if ((de_cov^.de_flags and DE_USER)<>0) then
  begin
   sx_xunlock(@dmp^.dm_lock);
   Exit(EEXIST);
  end;

  Assert((de_cov^.de_flags and DE_COVERED)=0,'devfs_symlink: entry %p already covered');
  de_cov^.de_flags:=de_cov^.de_flags or DE_COVERED;
 end;

 de:=devfs_newdirent(ap^.a_cnp^.cn_nameptr, ap^.a_cnp^.cn_namelen);
 de^.de_flags:=DE_USER;
 de^.de_uid  :=0;
 de^.de_gid  :=0;
 de^.de_mode :=&0755;
 de^.de_inode:=devfs_alloc_cdp_inode;
 de^.de_dir  :=dd;
 de^.de_dirent^.d_type:=DT_LNK;

 i:=strlen(ap^.a_target) + 1;
 de^.de_symlink:=AllocMem(i);
 Move(ap^.a_target^, de^.de_symlink^, i);

 //mac_devfs_create_symlink(ap^.a_cnp^.cn_cred, dmp^.dm_mount, dd, de);

 de_dotdot:=TAILQ_FIRST(@dd^.de_dlist);  { '.' }
 de_dotdot:=TAILQ_NEXT(de_dotdot,@de_dotdot^.de_list); { '..' }
 TAILQ_INSERT_AFTER(@dd^.de_dlist,de_dotdot,de,@de^.de_list);
 devfs_dir_ref_de(dmp, dd);
 devfs_rules_apply(dmp, de);

 Exit(devfs_allocv(de, ap^.a_dvp^.v_mount, LK_EXCLUSIVE, ap^.a_vpp));
end;

function devfs_truncate_f(fp:p_file;length:Int64):Integer;
begin
 Exit(vnops.fo_truncate(fp, length));
end;

function devfs_write_f(fp:p_file;uio:p_uio;flags:Integer):Integer;
var
 td:p_kthread;
 dev:p_cdev;
 error,ioflag,ref:Integer;
 resid:Int64;
 dsw:p_cdevsw;
 fpop:p_file;
begin
 td:=curkthread;

 if (uio^.uio_resid > DEVFS_IOSIZE_MAX) then
 begin
  Exit(EINVAL);
 end;

 fpop:=td^.td_fpop;
 error:=devfs_fp_check(fp, @dev, @dsw, @ref);
 if (error<>0) then Exit(error);

 Assert(uio^.uio_td=td, 'uio_td %p is not td %p');
 ioflag:=fp^.f_flag and (O_NONBLOCK or O_DIRECT or O_FSYNC);

 if ((ioflag and O_DIRECT)<>0) then
 begin
  ioflag:=ioflag or IO_DIRECT;
 end;

 foffset_lock_uio(fp, uio, flags or FOF_NOLOCK);

 resid:=uio^.uio_resid;

 error:=dsw^.d_write(dev, uio, ioflag);
 if (uio^.uio_resid<>resid) or ((error=0) and (resid<>0)) then
 begin
  vfs_timestamp(@dev^.si_ctime);
  dev^.si_mtime:=dev^.si_ctime;
 end;
 td^.td_fpop:=fpop;
 dev_relthread(dev, ref);

 foffset_unlock_uio(fp, uio, flags or FOF_NOLOCK or FOF_NEXTOFF);
 Exit(error);
end;

function dev2udev(x:p_cdev):Integer;
begin
 if (x=nil) then Exit(NODEV);

 Exit(cdev2priv(x)^.cdp_inode);
end;


end.

