unit null_vfsops;

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

interface

uses
 mqueue,
 kern_param,
 vnode,
 vmount,
 nullfs,
 null_subr;

function  nullfs_mount(mp:p_mount):Integer;
function  nullfs_unmount(mp:p_mount;mntflags:Integer):Integer;
function  nullfs_root(mp:p_mount;flags:Integer;vpp:pp_vnode):Integer;
function  nullfs_quotactl(mp:p_mount;cmd,uid:Integer;arg:Pointer):Integer;
function  nullfs_statfs(mp:p_mount;sbp:p_statfs):Integer;
function  nullfs_sync(mp:p_mount;waitfor:Integer):Integer;
function  nullfs_vget(mp:p_mount;ino:DWORD;flags:Integer;vpp:pp_vnode):Integer;
function  nullfs_fhtovp(mp:p_mount;fidp:p_fid;flags:Integer;vpp:pp_vnode):Integer;
function  nullfs_extattrctl(mp:p_mount;cmd:Integer;filename_vp:p_vnode;namespace:Integer;attrname:PChar):Integer;

const
 _null_vfsops:vfsops=(
  vfs_mount          :@nullfs_mount;
  vfs_cmount         :nil;
  vfs_unmount        :@nullfs_unmount;
  vfs_root           :@nullfs_root;
  vfs_quotactl       :@nullfs_quotactl;
  vfs_statfs         :@nullfs_statfs;
  vfs_sync           :@nullfs_sync;
  vfs_vget           :@nullfs_vget;
  vfs_fhtovp         :@nullfs_fhtovp;
  vfs_checkexp       :nil;
  vfs_init           :@nullfs_init;
  vfs_uninit         :@nullfs_uninit;
  vfs_extattrctl     :@nullfs_extattrctl;
  vfs_sysctl         :nil;
  vfs_susp_clean     :nil;
 );

 //VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK or VFCF_JAIL);
 nullfs_vfsconf:vfsconf=(
  vfc_version :VFS_VERSION;
  vfc_name    :'nullfs';
  vfc_vfsops  :@_null_vfsops;
  vfc_typenum :-1;
  vfc_refcount:0;
  vfc_flags   :VFCF_LOOPBACK or VFCF_JAIL;
  vfc_opts    :nil;
  vfc_list    :(tqe_next:nil;tqe_prev:nil)
 );

implementation

uses
 errno,
 vuio,
 vnamei,
 vfs_mount,
 vfs_vnops,
 vfs_subr,
 vnode_if,
 null_vnops,
 kern_thr,
 kern_mtx,
 systm;

function MOUNTTONULLMOUNT(mp:p_mount):p_null_mount; inline;
begin
 Result:=mp^.mnt_data;
end;

function VTONULL(vp:p_vnode):p_null_node; inline;
begin
 Result:=vp^.v_data;
end;

function NULLTOV(xp:p_null_node):p_vnode; inline;
begin
 Result:=xp^.null_vnode;
end;

{
 * Mount null layer
 }
function nullfs_mount(mp:p_mount):Integer;
var
 error:Integer;
 lowerrootvp,vp,nullm_rootvp:p_vnode;
 xmp:p_null_mount;
 target:PChar;
 isvnunlocked,len:Integer;
 nd:t_nameidata;
 ndp:p_nameidata;
 lvp:p_vnode;
 lmp:p_mount;
begin
 error:=0;
 isvnunlocked:=0;
 ndp:=@nd;

 //if (prison_allow(td^.td_ucred, PR_ALLOW_MOUNT_NULLFS)=0) then
 // Exit(EPERM);

 //if ((mp^.mnt_flag and MNT_ROOTFS)<>0) then
 // Exit(EOPNOTSUPP);

 {
  * Update is a no-op
  }
 if ((mp^.mnt_flag and MNT_UPDATE)<>0) then
 begin
  {
   * Only support update mounts for NFS export.
   }
  if (vfs_flagopt(mp^.mnt_optnew, 'export', nil, 0)<>0) then
   Exit(0)
  else
   Exit(EOPNOTSUPP);
 end;

 {
  * Get argument
  }
 error:=vfs_getopt(mp^.mnt_optnew, {'target'} 'from', @target, @len);
 if (error<>0) or (target[len - 1]<>#0) then
  Exit(EINVAL);

 {
  * Unlock lower node to avoid possible deadlock.
  }
 if (mp^.mnt_vnodecovered^.v_op=@null_vnodeops) and
    (VOP_ISLOCKED(mp^.mnt_vnodecovered)=LK_EXCLUSIVE) then
 begin
  VOP_UNLOCK(mp^.mnt_vnodecovered, 0);
  isvnunlocked:=1;
 end;

 {
  * Find lower node
  }
 NDINIT(ndp, LOOKUP, FOLLOW or LOCKLEAF, UIO_SYSSPACE, target, curkthread);
 error:=nd_namei(ndp);

 {
  * Re-lock vnode.
  * XXXKIB This is deadlock-prone as well.
  }
 if (isvnunlocked<>0) then
 begin
  vn_lock(mp^.mnt_vnodecovered, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 end;

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

 NDFREE(ndp, NDF_ONLY_PNBUF);

 {
  * Sanity check on lower vnode
  }
 lowerrootvp:=ndp^.ni_vp;

 {
  * Check multi null mount to avoid `lock against myself' panic.
  }
 if (lowerrootvp=VTONULL(mp^.mnt_vnodecovered)^.null_lowervp) then
 begin
  vput(lowerrootvp);
  Exit(EDEADLK);
 end;

 xmp:=AllocMem(sizeof(t_null_mount));

 if (lowerrootvp=nil) then
 begin
  xmp^.nullm_vfs:=nil;

  vp:=nil;
  error:=0;
 end else
 begin
  {
   * Save reference to underlying FS
   }
  xmp^.nullm_vfs:=lowerrootvp^.v_mount;
 end;

 {
  * Save reference.  Each mount also holds
  * a reference on the root vnode.
  }
 error:=null_nodeget(mp, lowerrootvp, @vp);

 {
  * Make sure the node alias worked
  }
 if (error<>0) then
 begin
  FreeMem(xmp);
  Exit(error);
 end;

 {
  * Keep a held reference to the root vnode.
  * It is vrele'd in nullfs_unmount.
  }
 nullm_rootvp:=vp;
 nullm_rootvp^.v_vflag:=nullm_rootvp^.v_vflag or VV_ROOT;
 xmp^.nullm_rootvp:=nullm_rootvp;

 {
  * Unlock the node (either the lower or the alias)
  }
 VOP_UNLOCK(vp, 0);

 lvp:=NULLVPTOLOWERVP(nullm_rootvp);
 if (lvp<>nil) then
 begin
  lmp:=lvp^.v_mount;
  if ((lmp^.mnt_flag and MNT_LOCAL)<>0) then
  begin
   MNT_ILOCK(mp);
   mp^.mnt_flag:=mp^.mnt_flag or MNT_LOCAL;
   MNT_IUNLOCK(mp);
  end;
 end;

 xmp^.nullm_flags:=xmp^.nullm_flags or NULLM_CACHE;

 if (vfs_getopt(mp^.mnt_optnew, 'nocache', nil, nil)=0) then
  xmp^.nullm_flags:=xmp^.nullm_flags and (not NULLM_CACHE);

 MNT_ILOCK(mp);
 if ((xmp^.nullm_flags and NULLM_CACHE)<>0) then
 begin
  if (lowerrootvp<>nil) then
  begin
   lmp:=lowerrootvp^.v_mount;
   mp^.mnt_kern_flag:=mp^.mnt_kern_flag or (lmp^.mnt_kern_flag and (MNTK_MPSAFE or MNTK_SHARED_WRITES or MNTK_LOOKUP_SHARED or MNTK_EXTENDED_SHARED));
  end;
 end;
 mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_LOOKUP_EXCL_DOTDOT;
 MNT_IUNLOCK(mp);
 mp^.mnt_data:=xmp;
 vfs_getnewfsid(mp);
 if ((xmp^.nullm_flags and NULLM_CACHE)<>0) then
 if (xmp^.nullm_vfs<>nil) then
 begin
  MNT_ILOCK(xmp^.nullm_vfs);
  TAILQ_INSERT_TAIL(@xmp^.nullm_vfs^.mnt_uppers,mp,@mp^.mnt_upper_link);
  MNT_IUNLOCK(xmp^.nullm_vfs);
 end;

 vfs_mountedfrom(mp, target);

 Exit(0);
end;

{
 * Free reference to null layer
 }
function nullfs_unmount(mp:p_mount;mntflags:Integer):Integer;
var
 mntdata:p_null_mount;
 ump:p_mount;
 error,flags:Integer;
begin
 if ((mntflags and MNT_FORCE)<>0) then
  flags:=FORCECLOSE
 else
  flags:=0;

 { There is 1 extra root vnode reference (nullm_rootvp). }
 error:=vflush(mp, 1, flags);
 if (error<>0) then
  Exit(error);

 {
  * Finally, throw away the null_mount structure
  }
 mntdata:=mp^.mnt_data;
 ump:=mntdata^.nullm_vfs;

 if (ump<>nil) then
 if ((mntdata^.nullm_flags and NULLM_CACHE)<>0) then
 begin
  MNT_ILOCK(ump);
  while ((ump^.mnt_kern_flag and MNTK_VGONE_UPPER)<>0) do
  begin
   ump^.mnt_kern_flag:=ump^.mnt_kern_flag or MNTK_VGONE_WAITER;
   msleep(@ump^.mnt_uppers, @ump^.mnt_mtx, 0, 'vgnupw', 0);
  end;
  TAILQ_REMOVE(@ump^.mnt_uppers,mp,@mp^.mnt_upper_link);
  MNT_IUNLOCK(ump);
 end;

 mp^.mnt_data:=nil;

 FreeMem(mntdata);
 Exit(0);
end;

function nullfs_root(mp:p_mount;flags:Integer;vpp:pp_vnode):Integer;
var
 vp:p_vnode;
 xmp:p_null_mount;
begin
 {
  * Return locked reference to root.
  }
 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 vp:=xmp^.nullm_rootvp;
 VREF(vp);

 //ASSERT_VOP_UNLOCKED(vp, 'root vnode is locked');
 vn_lock(vp, flags or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 vpp^:=vp;
 Exit(0);
end;

function nullfs_quotactl(mp:p_mount;cmd,uid:Integer;arg:Pointer):Integer;
var
 xmp:p_null_mount;
begin
 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 Exit(VFS_QUOTACTL(xmp^.nullm_vfs, cmd, uid, arg));
end;

function nullfs_statfs(mp:p_mount;sbp:p_statfs):Integer;
var
 error:Integer;
 mstat:t_statfs;
 xmp:p_null_mount;
begin
 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 if (xmp^.nullm_vfs=nil) then
 begin
  sbp^.f_flags :=0;
  sbp^.f_bsize :=DEV_BSIZE;
  sbp^.f_iosize:=DEV_BSIZE;
  sbp^.f_blocks:=2;
  sbp^.f_bfree :=0;
  sbp^.f_bavail:=0;
  sbp^.f_files :=0;
  sbp^.f_ffree :=0;
  Exit(0);
 end;

 mstat:=Default(t_statfs);
 error:=VFS_STATFS(xmp^.nullm_vfs, @mstat);
 if (error<>0) then
  Exit(error);

 { now copy across the 'interesting' information and fake the rest }
 sbp^.f_type  :=mstat.f_type;
 sbp^.f_flags :=mstat.f_flags;
 sbp^.f_bsize :=mstat.f_bsize;
 sbp^.f_iosize:=mstat.f_iosize;
 sbp^.f_blocks:=mstat.f_blocks;
 sbp^.f_bfree :=mstat.f_bfree;
 sbp^.f_bavail:=mstat.f_bavail;
 sbp^.f_files :=mstat.f_files;
 sbp^.f_ffree :=mstat.f_ffree;

 Exit(0);
end;

function nullfs_sync(mp:p_mount;waitfor:Integer):Integer;
begin
 {
  * XXX - Assumes no data cached at null layer.
  }
 Exit(0);
end;

function nullfs_vget(mp:p_mount;ino:DWORD;flags:Integer;vpp:pp_vnode):Integer;
var
 xmp:p_null_mount;
 error:Integer;
begin
 Assert((flags and LK_TYPE_MASK)<>0,'nullfs_vget: no lock requested');

 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 if (xmp^.nullm_vfs=nil) then
 begin
  Exit(ENOENT);
 end;

 error:=VFS_VGET(xmp^.nullm_vfs, ino, flags, vpp);
 if (error<>0) then
  Exit(error);

 Exit(null_nodeget(mp, vpp^, vpp));
end;

function nullfs_fhtovp(mp:p_mount;fidp:p_fid;flags:Integer;vpp:pp_vnode):Integer;
var
 xmp:p_null_mount;
 error:Integer;
begin
 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 if (xmp^.nullm_vfs=nil) then
 begin
  Exit(ENOENT);
 end;

 error:=VFS_FHTOVP(xmp^.nullm_vfs, fidp, flags, vpp);
 if (error<>0) then
  Exit(error);
 Exit(null_nodeget(mp, vpp^, vpp));
end;

function nullfs_extattrctl(mp:p_mount;cmd:Integer;filename_vp:p_vnode;namespace:Integer;attrname:PChar):Integer;
var
 xmp:p_null_mount;
begin
 xmp:=MOUNTTONULLMOUNT(mp);
 if (xmp=nil) then Exit(EOPNOTSUPP);

 if (xmp^.nullm_vfs=nil) then
 begin
  Exit(ENOENT);
 end;

 Exit(VFS_EXTATTRCTL(xmp^.nullm_vfs, cmd, filename_vp, namespace, attrname));
end;

end.

