unit vfs_mount;

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

interface

uses
 sysutils,
 mqueue,
 kern_param,
 vmount,
 vuio,
 vnamei,
 kern_mtx,
 kern_thr,
 vnode,
 vfs_init,
 vnode_if;

const
 VFS_MOUNTARG_SIZE_MAX=(1024 * 64);

 global_opts:array[0..7] of PChar=(
  'errmsg',
  'fstype',
  'fspath',
  'ro',
  'rw',
  'nosuid',
  'noexec',
  nil
 );

type
 { A memory allocation which must be freed when we are done }
 p_mntaarg=^t_mntaarg;
 t_mntaarg=packed record
  next:SLIST_ENTRY; //mntaarg
 end;

 { The header for the mount arguments }
 p_mntarg=^t_mntarg;
 t_mntarg=packed record
      v:p_iovec;
    len:Integer;
  error:Integer;
   list:SLIST_HEAD; //mntaarg
 end;

procedure vfs_freeopt(opts:p_vfsoptlist;opt:p_vfsopt);
procedure vfs_freeopts(opts:p_vfsoptlist);
procedure vfs_deleteopt(opts:p_vfsoptlist;name:PChar);
function  vfs_isopt_ro(opt:PChar):Integer;
function  vfs_isopt_rw(opt:PChar):Integer;
function  vfs_equalopts(opt1,opt2:PChar):Integer;
procedure vfs_sanitizeopts(opts:p_vfsoptlist);
function  vfs_buildopts(auio:p_uio;options:pp_vfsoptlist):Integer;
procedure vfs_mergeopts(toopts,oldopts:p_vfsoptlist);

procedure vfs_mount_error(mp:p_mount;fmt:PChar;const Args:Array of const); register;
procedure vfs_opterror(opts:p_vfsoptlist;fmt:PChar;const Args:Array of const); register;
function  vfs_filteropt(opts:p_vfsoptlist;legal:ppchar):Integer;
function  vfs_getopt(opts:p_vfsoptlist;name:PChar;buf:PPointer;len:PInteger):Integer;
function  vfs_getopt_pos(opts:p_vfsoptlist;name:PChar):Integer;
function  vfs_getopts(opts:p_vfsoptlist;name:PChar;error:PInteger):PChar;
function  vfs_flagopt(opts:p_vfsoptlist;name:PChar;w:PQWORD;val:QWORD):Integer;
function  vfs_scanopt(opts:p_vfsoptlist;name,fmt:PChar;const Args:Array of const):Integer; register;
function  vfs_setopt(opts:p_vfsoptlist;name,value:PChar;len:Integer):Integer;
function  vfs_setopt_part(opts:p_vfsoptlist;name,value:PChar;len:Integer):Integer;
function  vfs_setopts(opts:p_vfsoptlist;name,value:PChar):Integer;
procedure vfs_mountedfrom(mp:p_mount;from:PChar);

procedure vfs_ref(mp:p_mount); inline;
procedure vfs_rel(mp:p_mount); inline;

procedure mount_init(mp:p_mount);
procedure mount_fini(mp:p_mount);

function  vfs_mount_alloc(vp    :p_vnode;
                          vfsp  :p_vfsconf;
                          fspath:PChar):p_mount;
procedure vfs_mount_destroy(mp:p_mount);

function  vfs_domount(fstype:PChar;         { Filesystem type. }
                      fspath:PChar;         { Mount path. }
                      fsflags:QWORD;        { Flags common to all filesystems. }
                      optlist:pp_vfsoptlist { Options local to the filesystem. }
                     ):Integer;

function  vfs_donmount(fsflags:QWORD;fsoptions:p_uio):Integer;

function  dounmount(mp:p_mount;flags:Integer):Integer;

function  mount_argb(ma:p_mntarg;flag:Integer;name:PChar):p_mntarg;
function  mount_argf(ma:p_mntarg;name,fmt:PChar;const Args:Array of const):p_mntarg; register;
function  mount_argsu(ma:p_mntarg;name:PChar;val:Pointer;len:Integer):p_mntarg;
function  mount_arg(ma:p_mntarg;name:PChar;val:Pointer;len:Integer):p_mntarg;
procedure free_mntarg(ma:p_mntarg);

function  kernel_nmount(ma:p_mntarg;flags:QWORD):Integer;
function  kern_unmount(path:PChar;flags:Integer):Integer;

function  sys_nmount(iovp:Pointer;iovcnt:DWORD;flags:QWORD):Integer;
function  sys_mount(ftype,fpath:PChar;flags:QWORD;data:Pointer):Integer;
function  sys_unmount(path:PChar;flags:Integer):Integer;

implementation

uses
 murmurhash,
 errno,
 systm,
 subr_uio,
 vfs_vnops,
 vfs_subr,
 vfs_cache;

{
 * ---------------------------------------------------------------------
 * Functions for building and sanitizing the mount options
 }

{ Remove one mount option. }
procedure vfs_freeopt(opts:p_vfsoptlist;opt:p_vfsopt);
begin
 TAILQ_REMOVE(opts,opt,@opt^.link);
 FreeMem(opt^.name);
 if (opt^.value<>nil) then
 begin
  FreeMem(opt^.value);
 end;
 FreeMem(opt);
end;

{ Release all resources related to the mount options. }
procedure vfs_freeopts(opts:p_vfsoptlist);
var
 opt:p_vfsopt;
begin
 while (not TAILQ_EMPTY(opts)) do
 begin
  opt:=TAILQ_FIRST(opts);
  vfs_freeopt(opts, opt);
 end;
 FreeMem(opts);
end;

procedure vfs_deleteopt(opts:p_vfsoptlist;name:PChar);
var
 opt,temp:p_vfsopt;
begin
 if (opts=nil) then Exit;
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  temp:=TAILQ_NEXT(opt,@opt^.link);
  //
  if (strcomp(opt^.name, name)=0) then
  begin
   vfs_freeopt(opts, opt);
  end;
  //
  opt:=temp;
 end;
end;

function vfs_isopt_ro(opt:PChar):Integer;
begin
 if (strcomp(opt, 'ro')=0) or
    (strcomp(opt, 'rdonly')=0) or
    (strcomp(opt, 'norw')=0) then
 begin
  Exit(1);
 end;
 Exit(0);
end;

function vfs_isopt_rw(opt:PChar):Integer;
begin
 if (strcomp(opt, 'rw')=0) or
    (strcomp(opt, 'noro')=0) then
 begin
  Exit(1);
 end;
 Exit(0);
end;

{
 * Check if options are equal (with or without the 'no' prefix).
 }
function vfs_equalopts(opt1,opt2:PChar):Integer;
var
 p:PChar;
begin
 { 'opt' vs. 'opt' or 'noopt' vs. 'noopt' }
 if (strcomp(opt1, opt2)=0) then
 begin
  Exit(1);
 end;
 { 'noopt' vs. 'opt' }
 if (strlcomp(opt1, 'no', 2)=0) and
    (strcomp(opt1 + 2, opt2)=0) then
 begin
  Exit(1);
 end;
 { 'opt' vs. 'noopt' }
 if (strlcomp(opt2, 'no', 2)=0) and
    (strcomp(opt1, opt2 + 2)=0) then
 begin
  Exit(1);
 end;

 p:=strscan(opt1, '.');
 while (p<>nil) and
       (strlcomp(opt1,opt2,(p+1)-opt1)=0) do
 begin
  Inc(p);
  Inc(opt2,p-opt1);
  opt1:=p;
  { 'foo.noopt' vs. 'foo.opt' }
  if (strlcomp(opt1, 'no', 2)=0) and
     (strcomp(opt1 + 2, opt2)=0) then
   Exit(1);
  { 'foo.opt' vs. 'foo.noopt' }
  if (strlcomp(opt2, 'no', 2)=0) and
     (strcomp(opt1, opt2 + 2)=0) then
   Exit(1);
  //
  p:=strscan(opt1, '.');
 end;
 { 'ro' / 'rdonly' / 'norw' / 'rw' / 'noro' }
 if ((vfs_isopt_ro(opt1)<>0) or
     (vfs_isopt_rw(opt1)<>0)) and
    ((vfs_isopt_ro(opt2)<>0) or
     (vfs_isopt_rw(opt2)<>0)) then
 begin
  Exit(1);
 end;
 Exit(0);
end;

{
 * If a mount option is specified several times,
 * (with or without the 'no' prefix) only keep
 * the last occurence of it.
 }
procedure vfs_sanitizeopts(opts:p_vfsoptlist);
var
 opt,opt2,tmp:p_vfsopt;
begin
 opt:=TAILQ_LAST(opts);
 while (opt<>nil) do
 begin
  opt2:=TAILQ_PREV(opt,@opt^.link);
  while (opt2<>nil) do
  begin
   if (vfs_equalopts(opt^.name, opt2^.name)<>0) then
   begin
    tmp:=TAILQ_PREV(opt2,@opt2^.link);
    vfs_freeopt(opts, opt2);
    opt2:=tmp;
   end else
   begin
    opt2:=TAILQ_PREV(opt2,@opt2^.link);
   end;
  end;
  //
  opt:=TAILQ_PREV(opt,@opt^.link);
 end;
end;

{
 * Build a linked list of mount options from a struct uio.
 }
function vfs_buildopts(auio:p_uio;options:pp_vfsoptlist):Integer;
label
 bad;
var
 opts:p_vfsoptlist;
 opt:p_vfsopt;
 memused,namelen,optlen:QWORD;
 i,iovcnt:DWORD;
 error:Integer;
begin
 opts:=AllocMem(sizeof(vfsoptlist));
 TAILQ_INIT(opts);
 memused:=0;
 iovcnt:=auio^.uio_iovcnt;
 i:=0;
 while (i < iovcnt) do
 begin
  namelen:=auio^.uio_iov[i].iov_len;
  optlen :=auio^.uio_iov[i + 1].iov_len;
  Inc(memused,sizeof(vfsopt)+optlen+namelen);
  {
   * Avoid consuming too much memory, and attempts to overflow
   * memused.
   }
  if (memused > VFS_MOUNTARG_SIZE_MAX) or
     (optlen  > VFS_MOUNTARG_SIZE_MAX) or
     (namelen > VFS_MOUNTARG_SIZE_MAX) then
  begin
   error:=EINVAL;
   goto bad;
  end;

  opt:=AllocMem(sizeof(vfsopt));
  opt^.name :=AllocMem(namelen);
  opt^.value:=nil;
  opt^.len  :=0;
  opt^.pos  :=i div 2;
  opt^.seen :=0;

  {
   * Do this early, so jumps to 'bad' will free the current
   * option.
   }
  TAILQ_INSERT_TAIL(opts,opt,@opt^.link);

  if (auio^.uio_segflg=UIO_SYSSPACE) then
  begin
   Move(auio^.uio_iov[i].iov_base^, opt^.name^, namelen);
  end else
  begin
   error:=copyin(auio^.uio_iov[i].iov_base, opt^.name, namelen);
   if (error<>0) then
   begin
    goto bad;
   end;
  end;
  { Ensure names are nil-terminated strings. }
  if (namelen=0) or (opt^.name[namelen - 1]<>#0) then
  begin
   error:=EINVAL;
   goto bad;
  end;
  if (optlen<>0)  then
  begin
   opt^.len:=optlen;
   opt^.value:=AllocMem(optlen);
   if (auio^.uio_segflg=UIO_SYSSPACE) then
   begin
    Move(auio^.uio_iov[i + 1].iov_base^, opt^.value^, optlen);
   end else
   begin
    error:=copyin(auio^.uio_iov[i + 1].iov_base, opt^.value, optlen);
    if (error<>0) then
    begin
     goto bad;
    end;
   end;
  end;
  //
  Inc(i,2);
 end;
 vfs_sanitizeopts(opts);
 options^:=opts;
 Exit(0);
bad:
 vfs_freeopts(opts);
 Exit(error);
end;

function strdup(src:PChar):PChar; inline;
var
 i:ptrint;
begin
 i:=strlen(src);
 Result:=AllocMem(i+1);
 Move(src^,Result^,i);
end;

{
 * Merge the old mount options with the new ones passed
 * in the MNT_UPDATE case.
 *
 * XXX: This function will keep a 'nofoo' option in the new
 * options.  E.g, if the option's canonical name is 'foo',
 * 'nofoo' ends up in the mount point's active options.
 }
procedure vfs_mergeopts(toopts,oldopts:p_vfsoptlist);
var
 opt,new:p_vfsopt;
begin
 opt:=TAILQ_FIRST(oldopts);
 while (opt<>nil) do
 begin
  new:=AllocMem(sizeof(vfsopt));
  new^.name:=strdup(opt^.name);
  if (opt^.len<>0) then
  begin
   new^.value:=AllocMem(opt^.len);
   Move(opt^.value^, new^.value^, opt^.len);
  end else
  begin
   new^.value:=nil;
  end;
  new^.len :=opt^.len;
  new^.seen:=opt^.seen;
  TAILQ_INSERT_HEAD(toopts,new,@new^.link);
  //
  opt:=TAILQ_NEXT(opt,@opt^.link);
 end;
 vfs_sanitizeopts(toopts);
end;

{
 * Report errors during filesystem mounting.
 }
procedure vfs_mount_error(mp:p_mount;fmt:PChar;const Args:Array of const); register;
var
 moptlist:p_vfsoptlist;
 error,len:Integer;
 errmsg:PChar;
 S:RawByteString;
begin
 moptlist:=mp^.mnt_optnew;

 error:=vfs_getopt(moptlist, 'errmsg', @errmsg, @len);
 if (error<>0) or
    (errmsg=nil) or
    (len<=0) then
 begin
  Exit;
 end;

 S:=Format(fmt,Args);
 if (len>(Length(S)+1)) then len:=Length(S)+1;
 Move(PChar(S)^,errmsg^,len);
end;

procedure vfs_opterror(opts:p_vfsoptlist;fmt:PChar;const Args:Array of const); register;
var
 error,len:Integer;
 errmsg:PChar;
 S:RawByteString;
begin
 error:=vfs_getopt(opts, 'errmsg', @errmsg, @len);
 if (error<>0) or
    (errmsg=nil) or
    (len<=0) then
 begin
  Exit;
 end;

 S:=Format(fmt,Args);
 if (len>(Length(S)+1)) then len:=Length(S)+1;
 Move(PChar(S)^,errmsg^,len);
end;

{
 * Check that no unknown options are given
 }
function vfs_filteropt(opts:p_vfsoptlist;legal:ppchar):Integer;
var
 opt:p_vfsopt;
 errmsg:array[0..254] of Char;
 t:ppchar;
 p,q:pchar;
begin
 Result:=0;

 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  p:=opt^.name;
  q:=nil;

  if (p[0]='n') and (p[1]='o') then
  begin
   q:=p + 2;
  end;

  t:=@global_opts;
  while (t^<>nil) do
  begin
   if (strcomp(t^, p)=0) then break;
   if (q<>nil) then
   begin
    if (strcomp(t^, q)=0) then break;
   end;
   Inc(t);
  end;
  if (t^<>nil) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   //
   continue;
  end;

  t:=legal;
  while (t^<>nil) do
  begin
   if (strcomp(t^, p)=0) then break;
   if (q<>nil) then
   begin
    if (strcomp(t^, q)=0) then break;
   end;
   Inc(t);
  end;
  if (t^<>nil) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   //
   continue;
  end;

  errmsg:='mount option is unknown';
  Result:=EINVAL;
  //
  opt:=TAILQ_NEXT(opt,@opt^.link);
 end;
 if (Result<>0) then
 begin
  opt:=TAILQ_FIRST(opts);
  while (opt<>nil) do
  begin
   if (strcomp(opt^.name, 'errmsg')=0) then
   begin
    strlcopy(opt^.value, errmsg, opt^.len);
    break;
   end;
   opt:=TAILQ_NEXT(opt,@opt^.link);
  end;
  if (opt=nil) then
  begin
   Writeln(errmsg);
  end;
 end;
end;

{
 * Get a mount option by its name.
 *
 * return 0 if the option was found, ENOENT otherwise.
 * If len is non-nil it will be filled with the length
 * of the option. If buf is non-nil, it will be filled
 * with the address of the option.
 }
function vfs_getopt(opts:p_vfsoptlist;name:PChar;buf:PPointer;len:PInteger):Integer;
var
 opt:p_vfsopt;
begin
 Assert(opts<>nil,'vfs_getopt: caller passed opts as nil');

 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)=0) then
  begin
   opt^.seen:=1;
   if (len<>nil) then
   begin
    len^:=opt^.len;
   end;
   if (buf<>nil) then
   begin
    buf^:=opt^.value;
   end;
   Exit(0);
  end;
  //
  opt:=TAILQ_NEXT(opt,@opt^.link);
 end;
 Exit(ENOENT);
end;

function vfs_getopt_pos(opts:p_vfsoptlist;name:PChar):Integer;
var
 opt:p_vfsopt;
begin
 if (opts=nil) then
 begin
  Exit(-1);
 end;

 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)=0) then
  begin
   opt^.seen:=1;
   Exit(opt^.pos);
  end;
  //
  opt:=TAILQ_NEXT(opt,@opt^.link);
 end;
 Exit(-1);
end;

function vfs_getopts(opts:p_vfsoptlist;name:PChar;error:PInteger):PChar;
var
 opt:p_vfsopt;
begin
 error^:=0;
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)<>0) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   continue;
  end;
  opt^.seen:=1;
  if (opt^.len=0) or
     (PChar(opt^.value)[opt^.len - 1]<>#0) then
  begin
   error^:=EINVAL;
   Exit(nil);
  end;
  Exit(opt^.value);
 end;
 error^:=ENOENT;
 Exit(nil);
end;

function vfs_flagopt(opts:p_vfsoptlist;name:PChar;w:PQWORD;val:QWORD):Integer;
var
 opt:p_vfsopt;
begin
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)=0) then
  begin
   opt^.seen:=1;
   if (w<>nil) then
   begin
    w^:=w^ or val;
   end;
   Exit(1);
  end;
  //
  opt:=TAILQ_NEXT(opt,@opt^.link);
 end;
 if (w<>nil) then
 begin
  w^:=w^ and (not val);
 end;
 Exit(0);
end;

function vfs_scanopt(opts:p_vfsoptlist;name,fmt:PChar;const Args:Array of const):Integer; register;
var
 opt:p_vfsopt;
 S:RawByteString;
begin
 Assert(opts<>nil, 'vfs_getopt: caller passed opts as nil');

 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)<>0) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   continue;
  end;
  opt^.seen:=1;
  if (opt^.len=0) or (opt^.value=nil) then
  begin
   Exit(0);
  end;
  if (PChar(opt^.value)[opt^.len - 1]<>#0) then
  begin
   Exit(0);
  end;

  S:=Format(fmt,Args);
  Move(PChar(S)^,opt^.value^,Length(S)+1);
  Exit(0);
 end;
 Exit(0);
end;

function vfs_setopt(opts:p_vfsoptlist;name,value:PChar;len:Integer):Integer;
var
 opt:p_vfsopt;
begin
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)<>0) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   continue;
  end;
  opt^.seen:=1;
  if (opt^.value=nil) then
  begin
   opt^.len:=len;
  end else
  begin
   if (opt^.len<>len) then
   begin
    Exit(EINVAL);
   end;
   Move(value^, opt^.value^, len);
  end;
  Exit(0);
 end;
 Exit(ENOENT);
end;

function vfs_setopt_part(opts:p_vfsoptlist;name,value:PChar;len:Integer):Integer;
var
 opt:p_vfsopt;
begin
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)<>0) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   continue;
  end;
  opt^.seen:=1;
  if (opt^.value=nil) then
  begin
   opt^.len:=len;
  end else
  begin
   if (opt^.len < len) then
   begin
    Exit(EINVAL);
   end;
   opt^.len:=len;
   Move(value^, opt^.value^, len);
  end;
  Exit(0);
 end;
 Exit(ENOENT);
end;

function strlcpy(dst,src:PChar;size:ptrint):ptrint; inline;
begin
 strlcopy(dst,src,size);
 Result:=strlen(dst);
end;

function vfs_setopts(opts:p_vfsoptlist;name,value:PChar):Integer;
var
 opt:p_vfsopt;
begin
 opt:=TAILQ_FIRST(opts);
 while (opt<>nil) do
 begin
  if (strcomp(name, opt^.name)<>0) then
  begin
   opt:=TAILQ_NEXT(opt,@opt^.link);
   continue;
  end;
  opt^.seen:=1;
  if (opt^.value=nil) then
  begin
   opt^.len:=strlen(value) + 1;
  end else
  if (strlcpy(opt^.value, value, opt^.len) >= opt^.len) then
  begin
   Exit(EINVAL);
  end;
  Exit(0);
 end;
 Exit(ENOENT);
end;

procedure vfs_mountedfrom(mp:p_mount;from:PChar);
begin
 mp^.mnt_stat.f_mntfromname:=Default(t_mname);
 strlcopy(@mp^.mnt_stat.f_mntfromname,from,sizeof(mp^.mnt_stat.f_mntfromname));
end;

///

procedure vfs_ref(mp:p_mount); inline;
begin
 MNT_REF(mp);
end;

procedure vfs_rel(mp:p_mount); inline;
begin
 MNT_REL(mp);
end;

procedure mount_init(mp:p_mount);
begin
 mtx_init(mp^.mnt_mtx    ,'struct mount mtx');
 mtx_init(mp^.mnt_explock,'explock');
end;

procedure mount_fini(mp:p_mount);
begin
 mtx_destroy(mp^.mnt_explock);
 mtx_destroy(mp^.mnt_mtx);
end;

var
 mnt_hashseed:QWORD=QWORD($FEEDBABEFEEDBABE);

function get_mnt_hashseed:DWORD;
var
 i:QWORD;
begin
 i:=MurmurHash64A(@mnt_hashseed,SizeOf(mnt_hashseed),mnt_hashseed);
 mnt_hashseed:=i;
 Result:=DWORD(i);
end;

function vfs_mount_alloc(vp    :p_vnode;
                         vfsp  :p_vfsconf;
                         fspath:PChar):p_mount;
var
 mp:p_mount;
begin
 mp:=AllocMem(SizeOf(t_mount));
 mount_init(mp);

 TAILQ_INIT(@mp^.mnt_nvnodelist);
 mp^.mnt_nvnodelistsize:=0;
 TAILQ_INIT(@mp^.mnt_activevnodelist);
 mp^.mnt_activevnodelistsize:=0;
 mp^.mnt_ref:=0;
 vfs_busy(mp, MBF_NOWAIT);
 mp^.mnt_op:=vfsp^.vfc_vfsops;
 mp^.mnt_vfc:=vfsp;
 Inc(vfsp^.vfc_refcount); // XXX Unlocked
 mp^.mnt_stat.f_type:=vfsp^.vfc_typenum;
 Inc(mp^.mnt_gen);

 strlcopy(@mp^.mnt_stat.f_fstypename, @vfsp^.vfc_name, MFSNAMELEN);

 mp^.mnt_vnodecovered:=vp;

 strlcopy(@mp^.mnt_stat.f_mntonname, fspath, MNAMELEN);

 mp^.mnt_iosize_max:=DFLTPHYS;

 //mac_mount_init(mp);
 //mac_mount_create(cred, mp);

 //arc4rand(&mp->mnt_hashseed, sizeof mp->mnt_hashseed, 0);
 mp^.mnt_hashseed:=get_mnt_hashseed;

 TAILQ_INIT(@mp^.mnt_uppers);
 Result:=mp;
end;

procedure vfs_mount_destroy(mp:p_mount);
begin
 MNT_ILOCK(mp);
 mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_REFEXPIRE;
 if ((mp^.mnt_kern_flag and MNTK_MWAIT)<>0) then
 begin
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_MWAIT);
  wakeup(mp);
 end;
 while (mp^.mnt_ref<>0) do
 begin
  msleep(mp, MNT_MTX(mp), PVFS, 'mntref', 0);
 end;
 Assert(mp^.mnt_ref=0,'invalid refcount in the drain path');
 Assert(mp^.mnt_writeopcount=0,'vfs_mount_destroy: nonzero writeopcount');
 Assert(mp^.mnt_secondary_writes=0,'vfs_mount_destroy: nonzero secondary_writes');
 Dec(mp^.mnt_vfc^.vfc_refcount);

 if (not TAILQ_EMPTY(@mp^.mnt_nvnodelist)) then
 begin
  Assert(false,'unmount: dangling vnode');
 end;
 Assert(TAILQ_EMPTY(@mp^.mnt_uppers),'mnt_uppers');
 Assert(mp^.mnt_nvnodelistsize=0,'vfs_mount_destroy: nonzero nvnodelistsize');
 Assert(mp^.mnt_activevnodelistsize=0,'vfs_mount_destroy: nonzero activevnodelistsize');
 Assert(mp^.mnt_lockref=0,'vfs_mount_destroy: nonzero lock refcount');
 MNT_IUNLOCK(mp);

 //mac_mount_destroy(mp);

 if (mp^.mnt_opt<>nil) then
 begin
  vfs_freeopts(mp^.mnt_opt);
 end;

 mount_fini(mp);
 FreeMem(mp);
end;

{
 * vfs_domount_first(): first file system mount (not update)
 }
function vfs_domount_first(vfsp:p_vfsconf;       { File system type. }
                           fspath:PChar;         { Mount path. }
                           vp:p_vnode;           { Vnode to be covered. }
                           fsflags:QWORD;        { Flags common to all filesystems. }
                           optlist:pp_vfsoptlist { Options local to the filesystem. }
                          ):Integer;
var
 //va:t_vattr;
 mp:p_mount;
 newdp:p_vnode;
 error:Integer;
begin
 mtx_assert(VFS_Giant);
 Assert((fsflags and MNT_UPDATE)=0,'MNT_UPDATE shouldnt be here');

 error:=0;
 //error:=vinvalbuf(vp, V_SAVE, 0, 0);
 //if (error=0) and (vp^.v_type<>VDIR) then
 // error:=ENOTDIR;

 if (error=0) then
 begin
  VI_LOCK(vp);

  if ((vp^.v_iflag and VI_MOUNT)=0) and (vp^.v_mountedhere=nil) then
   vp^.v_iflag:=vp^.v_iflag or VI_MOUNT
  else
   error:=EBUSY;

  VI_UNLOCK(vp);
 end;

 if (error<>0) then
 begin
  vput(vp);
  Exit(error);
 end;
 VOP_UNLOCK(vp, 0);

 { Allocate and initialize the filesystem. }
 mp:=vfs_mount_alloc(vp, vfsp, fspath);
 { XXXMAC: pass to vfs_mount_alloc? }
 mp^.mnt_optnew:=optlist^;
 { Set the mount level flags. }
 mp^.mnt_flag:=(fsflags and (MNT_UPDATEMASK or MNT_ROOTFS or MNT_RDONLY));

 {
  * Mount the filesystem.
  * XXX The final recipients of VFS_MOUNT just overwrite the ndp they
  * get.  No freeing of cn_pnbuf.
  }
 error:=vmount.VFS_MOUNT(mp);
 if (error<>0) then
 begin
  vfs_unbusy(mp);
  vfs_mount_destroy(mp);
  VI_LOCK(vp);
  vp^.v_iflag:=vp^.v_iflag and (not VI_MOUNT);
  VI_UNLOCK(vp);
  vrele(vp);
  Exit(error);
 end;

 if (mp^.mnt_opt<>nil) then
 begin
  vfs_freeopts(mp^.mnt_opt);
 end;

 mp^.mnt_opt:=mp^.mnt_optnew;

 optlist^:=nil;
 VFS_STATFS(mp,@mp^.mnt_stat);

 {
  * Prevent external consumers of mount options from reading mnt_optnew.
  }
 mp^.mnt_optnew:=nil;

 MNT_ILOCK(mp);

 if ((mp^.mnt_flag and MNT_ASYNC)<>0) and
    ((mp^.mnt_kern_flag and MNTK_NOASYNC)=0) then
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_ASYNC
 else
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_ASYNC);

 MNT_IUNLOCK(mp);

 vn_lock(vp, LK_EXCLUSIVE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 //cache_purge(vp);
 VI_LOCK(vp);
 vp^.v_iflag:=vp^.v_iflag and (not VI_MOUNT);
 VI_UNLOCK(vp);
 vp^.v_mountedhere:=mp;
 { Place the new filesystem at the end of the mount list. }
 mtx_lock(mountlist_mtx);
 TAILQ_INSERT_TAIL(@mountlist, mp,@mp^.mnt_list);
 mtx_unlock(mountlist_mtx);

 vfs_event_signal(nil, VQ_MOUNT, 0);

 if (VFS_ROOT(mp,LK_EXCLUSIVE,@newdp)<>0) then
 begin
  Assert(false,'mount: lost mount');
 end;

 VOP_UNLOCK(newdp, 0);
 VOP_UNLOCK(vp, 0);
 //mountcheckdirs(vp, newdp);
 vrele(newdp);
 //if ((mp^.mnt_flag and MNT_RDONLY)=0)
 // vfs_allocate_syncvnode(mp);
 vfs_unbusy(mp);
 Exit (0);
end;

{
 * vfs_domount_update(): update of mounted file system
 }
function vfs_domount_update(vp:p_vnode;           { Mount point vnode. }
                            fsflags:QWORD;        { Flags common to all filesystems. }
                            optlist:pp_vfsoptlist { Options local to the filesystem. }
                           ):Integer;
label
 _end;
var
 //struct oexport_args oexport;
 //struct export_args export;
 mp:p_mount;
 error,export_error:Integer;
 flag:QWORD;
begin
 mtx_assert(VFS_Giant);
 ASSERT_VOP_ELOCKED(vp, 'vfs_domount_update');
 Assert((fsflags and MNT_UPDATE)<>0, 'MNT_UPDATE should be here');

 if ((vp^.v_vflag and VV_ROOT)=0) then
 begin
  vput(vp);
  Exit(EINVAL);
 end;
 mp:=vp^.v_mount;
 {
  * We only allow the filesystem to be reloaded if it
  * is currently mounted read-only.
  }
 flag:=mp^.mnt_flag;
 if ((fsflags and MNT_RELOAD)<>0) and ((flag and MNT_RDONLY)=0) then
 begin
  vput(vp);
  Exit(EOPNOTSUPP); { Needs translation }
 end;
 {
  * Only privileged root, or (if MNT_USER is set) the user that
  * did the original mount is permitted to update it.
  }
 error:=0;
 //error:=vfs_suser(mp, td);
 if (error<>0) then
 begin
  vput(vp);
  Exit(error);
 end;
 if (vfs_busy(mp, MBF_NOWAIT)<>0) then
 begin
  vput(vp);
  Exit(EBUSY);
 end;
 VI_LOCK(vp);
 if ((vp^.v_iflag and VI_MOUNT)<>0) or (vp^.v_mountedhere<>nil) then
 begin
  VI_UNLOCK(vp);
  vfs_unbusy(mp);
  vput(vp);
  Exit(EBUSY);
 end;
 vp^.v_iflag:=vp^.v_iflag or VI_MOUNT;
 VI_UNLOCK(vp);
 VOP_UNLOCK(vp, 0);

 MNT_ILOCK(mp);
 mp^.mnt_flag:=mp^.mnt_flag and (not MNT_UPDATEMASK);
 mp^.mnt_flag:=(mp^.mnt_flag or fsflags) and (MNT_RELOAD or MNT_FORCE or MNT_UPDATE or
     MNT_SNAPSHOT or MNT_ROOTFS or MNT_UPDATEMASK or MNT_RDONLY);

 if ((mp^.mnt_flag and MNT_ASYNC)=0) then
 begin
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_ASYNC);
 end;
 MNT_IUNLOCK(mp);
 mp^.mnt_optnew:=optlist^;
 vfs_mergeopts(mp^.mnt_optnew, mp^.mnt_opt);

 {
  * Mount the filesystem.
  * XXX The final recipients of VFS_MOUNT just overwrite the ndp they
  * get.  No freeing of cn_pnbuf.
  }
 error:=vmount.VFS_MOUNT(mp);

 export_error:=0;
 if (error=0) then
 begin
  { Process the export option. }
  //if (vfs_copyopt(mp^.mnt_optnew, 'export', @export, sizeof(export))=0) then
  //begin
  // export_error:=vfs_export(mp, @export);
  //end else
  //if (vfs_copyopt(mp^.mnt_optnew, 'export', @oexport, sizeof(oexport))=0) then
  //begin
  // export.ex_flags        :=oexport.ex_flags;
  // export.ex_root         :=oexport.ex_root;
  // export.ex_anon         :=oexport.ex_anon;
  // export.ex_addr         :=oexport.ex_addr;
  // export.ex_addrlen      :=oexport.ex_addrlen;
  // export.ex_mask         :=oexport.ex_mask;
  // export.ex_masklen      :=oexport.ex_masklen;
  // export.ex_indexfile    :=oexport.ex_indexfile;
  // export.ex_numsecflavors:=0;
  // export_error:=vfs_export(mp, @export);
  //end;
 end;

 MNT_ILOCK(mp);
 if (error=0) then
 begin
  mp^.mnt_flag:=mp^.mnt_flag and (not (MNT_UPDATE or MNT_RELOAD or MNT_FORCE or MNT_SNAPSHOT));
 end else
 begin
  {
   * If we fail, restore old mount flags. MNT_QUOTA is special,
   * because it is not part of MNT_UPDATEMASK, but it could have
   * changed in the meantime if quotactl(2) was called.
   * All in all we want current value of MNT_QUOTA, not the old
   * one.
   }
  mp^.mnt_flag:=(mp^.mnt_flag and MNT_QUOTA) or (flag and (not MNT_QUOTA));
 end;

 if ((mp^.mnt_flag and MNT_ASYNC)<>0) and
    ((mp^.mnt_kern_flag and MNTK_NOASYNC)=0) then
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_ASYNC
 else
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_ASYNC);

 MNT_IUNLOCK(mp);

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

 if (mp^.mnt_opt<>nil) then
 begin
  vfs_freeopts(mp^.mnt_opt);
 end;
 mp^.mnt_opt:=mp^.mnt_optnew;
 optlist^:=nil;
 VFS_STATFS(mp, @mp^.mnt_stat);
 {
  * Prevent external consumers of mount options from reading
  * mnt_optnew.
  }
 mp^.mnt_optnew:=nil;

 //if ((mp^.mnt_flag and MNT_RDONLY)=0) then
 // vfs_allocate_syncvnode(mp)
 //else
 // vfs_deallocate_syncvnode(mp);
_end:
 vfs_unbusy(mp);
 VI_LOCK(vp);
 vp^.v_iflag:=vp^.v_iflag and (not VI_MOUNT);
 VI_UNLOCK(vp);
 vrele(vp);

 if (error<>0) then
  Exit(error)
 else
  Exit(export_error);
end;

{
 * vfs_domount(): actually attempt a filesystem mount.
 }
function vfs_domount(fstype:PChar;         { Filesystem type. }
                     fspath:PChar;         { Mount path. }
                     fsflags:QWORD;        { Flags common to all filesystems. }
                     optlist:pp_vfsoptlist { Options local to the filesystem. }
                    ):Integer;
var
 vfsp:p_vfsconf;
 nd:t_nameidata;
 vp:p_vnode;
 pathbuf:t_mname;
 error:Integer;
begin
 {
  * Be ultra-paranoid about making sure the type and fspath
  * variables will fit in our mp buffers, including the
  * terminating NUL.
  }
 if (strlen(fstype) >= MFSNAMELEN) or (strlen(fspath) >= MNAMELEN) then
 begin
  Exit(ENAMETOOLONG);
 end;

 { Load KLDs before we lock the covered vnode to avoid reversals. }
 vfsp:=nil;
 if ((fsflags and MNT_UPDATE)=0) then
 begin
  vfsp:=vfs_byname(fstype);

  if (vfsp=nil) then
  begin
   Exit(ENODEV);
  end;
 end;

 {
  * Get vnode to be covered or mount point's vnode in case of MNT_UPDATE.
  }
 NDINIT(@nd, LOOKUP, FOLLOW or LOCKLEAF or MPSAFE or AUDITVNODE1, UIO_SYSSPACE, fspath, curkthread);

 error:=nd_namei(@nd);
 if (error<>0) then
 begin
  Exit(error);
 end;

 if (NDHASGIANT(@nd)=0) then
 begin
  mtx_lock(VFS_Giant);
 end;

 NDFREE(@nd, NDF_ONLY_PNBUF);
 vp:=nd.ni_vp;
 if ((fsflags and MNT_UPDATE)=0) then
 begin
  pathbuf:=Default(t_mname);
  strcopy(@pathbuf, fspath);
  error:=vn_path_to_global_path(vp, @pathbuf, MNAMELEN);
  { debug.disablefullpath=1 results in ENODEV }
  if (error=0) or (error=ENODEV) then
  begin
   error:=vfs_domount_first(vfsp, @pathbuf, vp, fsflags, optlist);
  end;
 end else
 begin
  error:=vfs_domount_update(vp, fsflags, optlist);
 end;

 mtx_unlock(VFS_Giant);

 ASSERT_VI_UNLOCKED (vp, {$I %LINE%});
 ASSERT_VOP_UNLOCKED(vp, {$I %LINE%});

 Exit(error);
end;

function vfs_donmount(fsflags:QWORD;fsoptions:p_uio):Integer;
label
 bail;
var
 optlist:p_vfsoptlist;
 opt,tmp_opt:p_vfsopt;
 fstype,fspath,errmsg:PChar;
 error,fstypelen,fspathlen,errmsg_len,errmsg_pos:Integer;
begin
 errmsg:=nil;
 fspath:=nil;
 errmsg_len:=0;
 fspathlen :=0;
 errmsg_pos:=-1;

 error:=vfs_buildopts(fsoptions, @optlist);
 if (error<>0) then
 begin
  Exit(error);
 end;

 if (vfs_getopt(optlist, 'errmsg', @errmsg, @errmsg_len)=0) then
 begin
  errmsg_pos:=vfs_getopt_pos(optlist, 'errmsg');
 end;

 {
  * We need these two options before the others,
  * and they are mandatory for any filesystem.
  * Ensure they are NUL terminated as well.
  }
 fstypelen:=0;
 error:=vfs_getopt(optlist, 'fstype', @fstype, @fstypelen);
 if (error<>0) or (fstype[fstypelen - 1]<>#0) then
 begin
  error:=EINVAL;
  if (errmsg<>nil) then
  begin
   strlcopy(errmsg, 'Invalid fstype', errmsg_len);
  end;
  goto bail;
 end;
 fspathlen:=0;
 error:=vfs_getopt(optlist, 'fspath', @fspath, @fspathlen);
 if (error<>0) or (fspath[fspathlen - 1]<>#0) then
 begin
  error:=EINVAL;
  if (errmsg<>nil) then
  begin
   strlcopy(errmsg, 'Invalid fspath', errmsg_len);
  end;
  goto bail;
 end;

 {
  * We need to see if we have the 'update' option
  * before we call vfs_domount(), since vfs_domount() has special
  * logic based on MNT_UPDATE.  This is very important
  * when we want to update the root filesystem.
  }

 opt:=TAILQ_FIRST(optlist);
 while (opt<>nil) do
 begin
  tmp_opt:=TAILQ_NEXT(opt,@opt^.link);
  //
  case RawByteString(opt^.name) of
   'update':
     begin
      fsflags:=fsflags or MNT_UPDATE;
      vfs_freeopt(optlist, opt);
     end;
   'async':
     begin
      fsflags:=fsflags or MNT_ASYNC;
     end;
   'force':
     begin
      fsflags:=fsflags or MNT_FORCE;
      vfs_freeopt(optlist, opt);
     end;
   'reload':
     begin
      fsflags:=fsflags or MNT_RELOAD;
      vfs_freeopt(optlist, opt);
     end;
   'multilabel':
     begin
      fsflags:=fsflags or MNT_MULTILABEL;
     end;
   'noasync':
     begin
      fsflags:=fsflags and (not MNT_ASYNC)
     end;
   'noatime':
     begin
      fsflags:=fsflags or MNT_NOATIME;
     end;
   'atime':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonoatime');
     end;
   'noclusterr':
     begin
      fsflags:=fsflags or MNT_NOCLUSTERR;
     end;
   'clusterr':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonoclusterr');
     end;
   'noclusterw':
     begin
      fsflags:=fsflags or MNT_NOCLUSTERW;
     end;
   'clusterw':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonoclusterw');
     end;
   'noexec':
     begin
      fsflags:=fsflags or MNT_NOEXEC;
     end;
   'exec':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonoexec');
     end;
   'nosuid':
     begin
      fsflags:=fsflags or MNT_NOSUID;
     end;
   'suid':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonosuid');
     end;
   'nosymfollow':
     begin
      fsflags:=fsflags or MNT_NOSYMFOLLOW;
     end;
   'symfollow':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('nonosymfollow');
     end;
   'noro':
     begin
      fsflags:=fsflags and (not MNT_RDONLY);
     end;
   'rw':
     begin
      fsflags:=fsflags and (not MNT_RDONLY);
     end;
   'ro':
     begin
      fsflags:=fsflags or MNT_RDONLY;
     end;
   'rdonly':
     begin
      FreeMem(opt^.name);
      opt^.name:=strdup('ro');
      fsflags:=fsflags or MNT_RDONLY;
     end;
   'suiddir':
     begin
      fsflags:=fsflags or MNT_SUIDDIR;
     end;
   'sync':
     begin
      fsflags:=fsflags or MNT_SYNCHRONOUS;
     end;
   'union':
     begin
      fsflags:=fsflags or MNT_UNION;
     end;
   else;
  end;
  //
  opt:=tmp_opt;
 end;

 {
  * Be ultra-paranoid about making sure the type and fspath
  * variables will fit in our mp buffers, including the
  * terminating NUL.
  }
 if (fstypelen >= MFSNAMELEN - 1) or
    (fspathlen >= MNAMELEN - 1) then
 begin
  error:=ENAMETOOLONG;
  goto bail;
 end;

 error:=vfs_domount(fstype, fspath, fsflags, @optlist);
bail:
 { copyout the errmsg }
 if (errmsg_pos<>-1) and
    ((2 * errmsg_pos + 1) < fsoptions^.uio_iovcnt) and
    (errmsg_len > 0) and
    (errmsg<>nil) then
 begin
  if (fsoptions^.uio_segflg=UIO_SYSSPACE) then
  begin
   Move(errmsg^,
       fsoptions^.uio_iov[2 * errmsg_pos + 1].iov_base^,
       fsoptions^.uio_iov[2 * errmsg_pos + 1].iov_len);
  end else
  begin
   copyout(errmsg,
       fsoptions^.uio_iov[2 * errmsg_pos + 1].iov_base,
       fsoptions^.uio_iov[2 * errmsg_pos + 1].iov_len);
  end;
 end;

 if (optlist<>nil) then
 begin
  vfs_freeopts(optlist);
 end;
 Exit(error);
end;

{
 * Do the actual filesystem unmount.
 }
function dounmount(mp:p_mount;flags:Integer):Integer;
var
 coveredvp,fsrootvp:p_vnode;
 error:Integer;
 async_flag:QWORD;
 mnt_gen_r:Integer;
begin
 mtx_assert(VFS_Giant);

 coveredvp:=mp^.mnt_vnodecovered;
 if (coveredvp<>nil) then
 begin
  mnt_gen_r:=mp^.mnt_gen;
  VI_LOCK(coveredvp);
  vholdl(coveredvp);
  vn_lock(coveredvp, LK_EXCLUSIVE or LK_INTERLOCK or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
  vdrop(coveredvp);
  {
   * Check for mp being unmounted while waiting for the
   * covered vnode lock.
   }
  if (coveredvp^.v_mountedhere<>mp) or
     (p_mount(coveredvp^.v_mountedhere)^.mnt_gen<>mnt_gen_r) then
  begin
   VOP_UNLOCK(coveredvp, 0);
   Exit(EBUSY);
  end;
 end;
 {
  * Only privileged root, or (if MNT_USER is set) the user that did the
  * original mount is permitted to unmount this filesystem.
  }
 error:=0;
 //error:=vfs_suser(mp, td);
 if (error<>0) then
 begin
  if (coveredvp<>nil) then
  begin
   VOP_UNLOCK(coveredvp, 0);
  end;

  Exit(error);
 end;

 vn_start_write(nil, @mp, V_WAIT);
 MNT_ILOCK(mp);
 if ((mp^.mnt_kern_flag and MNTK_UNMOUNT)<>0) or
    (not TAILQ_EMPTY(@mp^.mnt_uppers)) then
 begin
  MNT_IUNLOCK(mp);
  if (coveredvp<>nil) then
  begin
   VOP_UNLOCK(coveredvp, 0);
  end;

  vn_finished_write(mp);
  Exit(EBUSY);
 end;
 mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_UNMOUNT or MNTK_NOINSMNTQ;

 { Allow filesystems to detect that a forced unmount is in progress. }
 if ((flags and MNT_FORCE)<>0) then
 begin
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_UNMOUNTF;
 end;

 error:=0;
 if (mp^.mnt_lockref<>0) then
 begin
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_DRAINING;
  error:=msleep(@mp^.mnt_lockref, MNT_MTX(mp), PVFS, 'mount drain', 0);
 end;
 MNT_IUNLOCK(mp);
 Assert(mp^.mnt_lockref=0,'%s: invalid lock refcount in the drain path @ %s:%d');
 Assert(error=0,'%s: invalid Exitvalue for msleep in the drain path @ %s:%d');

 //if (mp^.mnt_flag and MNT_EXPUBLIC) then
 // vfs_setpublicfs(nil, nil, nil);

 vfs_msync(mp, MNT_WAIT);

 MNT_ILOCK(mp);

 async_flag:=mp^.mnt_flag and MNT_ASYNC;

 mp^.mnt_flag     :=mp^.mnt_flag      and (not MNT_ASYNC);
 mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_ASYNC);

 MNT_IUNLOCK(mp);
 //cache_purgevfs(mp); { remove cache entries for this file sys }
 //vfs_deallocate_syncvnode(mp);
 {
  * For forced unmounts, move process cdir/rdir refs on the fs root
  * vnode to the covered vnode.  For non-forced unmounts we want
  * such references to cause an EBUSY error.
  }
 if ((flags and MNT_FORCE)<>0) and
    (VFS_ROOT(mp, LK_EXCLUSIVE, @fsrootvp)=0) then
 begin
  //if (mp^.mnt_vnodecovered<>nil) and
  //   ((mp^.mnt_flag and MNT_IGNORE)=0) then
  // mountcheckdirs(fsrootvp, mp^.mnt_vnodecovered);
  if (fsrootvp=rootvnode) then
  begin
   vrele(rootvnode);
   rootvnode:=nil;
  end;
  vput(fsrootvp);
 end;

 error:=VFS_SYNC(mp, MNT_WAIT);
 if ((mp^.mnt_flag and MNT_RDONLY)<>0) or
    (error=0) or
    ((flags and MNT_FORCE)<>0) then
 begin
  error:=VFS_UNMOUNT(mp, flags);
 end;
 vn_finished_write(mp);
 {
  * If we failed to flush the dirty blocks for this mount point,
  * undo all the cdir/rdir and rootvnode changes we made above.
  * Unless we failed to do so because the device is reporting that
  * it doesn't exist anymore.
  }
 if (error<>0) and (error<>ENXIO) then
 begin
  if ((flags and MNT_FORCE)<>0) and
     (VFS_ROOT(mp, LK_EXCLUSIVE, @fsrootvp)=0) then
  begin
   //if (mp^.mnt_vnodecovered<>nil) and
   //   ((mp^.mnt_flag and MNT_IGNORE)=0) then
   // mountcheckdirs(mp^.mnt_vnodecovered, fsrootvp);
   if (rootvnode=nil) then
   begin
    rootvnode:=fsrootvp;
    vref(rootvnode);
   end;
   vput(fsrootvp);
  end;
  MNT_ILOCK(mp);
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_NOINSMNTQ);
  if ((mp^.mnt_flag and MNT_RDONLY)=0) then
  begin
   //MNT_IUNLOCK(mp);
   //vfs_allocate_syncvnode(mp);
   //MNT_ILOCK(mp);
  end;
  mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not (MNTK_UNMOUNT or MNTK_UNMOUNTF));
  mp^.mnt_flag:=mp^.mnt_flag or async_flag;
  if ((mp^.mnt_flag and MNT_ASYNC)<>0) and
     ((mp^.mnt_kern_flag and MNTK_NOASYNC)=0) then
  begin
   mp^.mnt_kern_flag:=mp^.mnt_kern_flag or MNTK_ASYNC;
  end;
  if ((mp^.mnt_kern_flag and MNTK_MWAIT)<>0) then
  begin
   mp^.mnt_kern_flag:=mp^.mnt_kern_flag and (not MNTK_MWAIT);
   wakeup(mp);
  end;
  MNT_IUNLOCK(mp);
  if (coveredvp<>nil) then
  begin
   VOP_UNLOCK(coveredvp, 0);
  end;

  Exit(error);
 end;
 mtx_lock(mountlist_mtx);
 TAILQ_REMOVE(@mountlist,mp,@mp^.mnt_list);
 mtx_unlock(mountlist_mtx);
 if (coveredvp<>nil) then
 begin
  coveredvp^.v_mountedhere:=nil;
  vput(coveredvp);
 end;
 vfs_event_signal(nil, VQ_UNMOUNT, 0);
 vfs_mount_destroy(mp);
 Exit(0);
end;

{
 * Add a boolean argument.
 *
 * flag is the boolean value.
 * name must start with 'no'.
 }
function mount_argb(ma:p_mntarg;flag:Integer;name:PChar):p_mntarg;
begin
 Assert((name[0]='n') and (name[1]='o'),'mount_argb(...,%s): name must start with no');

 Exit(mount_arg(ma, name + (ord(flag<>0)*2), nil, 0));
end;

{
 * Add an argument printf style
 }
function mount_argf(ma:p_mntarg;name,fmt:PChar;const Args:Array of const):p_mntarg; register;
var
 maa:p_mntaarg;
 len:Integer;
 sb:RawByteString;
begin
 if (ma=nil) then
 begin
  ma:=AllocMem(sizeof(t_mntarg));
  SLIST_INIT(@ma^.list);
 end;
 if (ma^.error<>0) then
 begin
  Exit(ma);
 end;

 ma^.v:=ReAllocMem(ma^.v, sizeof(iovec) * (ma^.len + 2));
 ma^.v[ma^.len].iov_base:=name;
 ma^.v[ma^.len].iov_len :=strlen(name) + 1;
 Inc(ma^.len);

 sb:=Format(fmt,Args);
 len:=Length(sb) + 1;
 maa:=AllocMem(sizeof(t_mntaarg) + len);
 SLIST_INSERT_HEAD(@ma^.list,maa,@maa^.next);
 Move(PChar(sb)^, (maa + 1)^, len);

 ma^.v[ma^.len].iov_base:=maa + 1;
 ma^.v[ma^.len].iov_len :=len;
 Inc(ma^.len);

 Exit(ma);
end;

{
 * Add an argument which is a userland string.
 }
function mount_argsu(ma:p_mntarg;name:PChar;val:Pointer;len:Integer):p_mntarg;
var
 maa:p_mntaarg;
 tbuf:Pointer;
begin
 if (val=nil) then
 begin
  Exit(ma);
 end;
 if (ma=nil) then
 begin
  ma:=AllocMem(sizeof(t_mntarg));
  SLIST_INIT(@ma^.list);
 end;
 if (ma^.error<>0) then
 begin
  Exit(ma);
 end;
 maa:=AllocMem(sizeof(t_mntaarg) + len);
 SLIST_INSERT_HEAD(@ma^.list,maa,@maa^.next);
 tbuf:=Pointer(maa + 1);
 ma^.error:=copyinstr(val, tbuf, len, nil);
 Exit(mount_arg(ma, name, tbuf, -1));
end;

{
 * Plain argument.
 *
 * If length is -1, treat value as a C string.
 }
function mount_arg(ma:p_mntarg;name:PChar;val:Pointer;len:Integer):p_mntarg;
begin
 if (ma=nil) then
 begin
  ma:=AllocMem(sizeof(t_mntarg));
  SLIST_INIT(@ma^.list);
 end;
 if (ma^.error<>0) then
 begin
  Exit(ma);
 end;

 if (val=nil) or (len=0) then Exit(ma);

 ma^.v:=ReAllocMem(ma^.v, sizeof(iovec) * (ma^.len + 2));
 ma^.v[ma^.len].iov_base:=name;
 ma^.v[ma^.len].iov_len :=strlen(name) + 1;
 Inc(ma^.len);

 ma^.v[ma^.len].iov_base:=val;

 if (len < 0) then
  ma^.v[ma^.len].iov_len:=strlen(val) + 1
 else
  ma^.v[ma^.len].iov_len:=len;

 Inc(ma^.len);

 Exit(ma);
end;

{
 * Free a mntarg structure
 }
procedure free_mntarg(ma:p_mntarg);
var
 maa:p_mntaarg;
begin
 while (not SLIST_EMPTY(@ma^.list)) do
 begin
  maa:=SLIST_FIRST(@ma^.list);
  SLIST_REMOVE_HEAD(@ma^.list,@p_mntaarg(nil)^.next);
  FreeMem(maa);
 end;
 FreeMem(ma^.v);
 FreeMem(ma);
end;

{
 * Mount a filesystem
 }
function kernel_nmount(ma:p_mntarg;flags:QWORD):Integer;
var
 auio:t_uio;
 error:Integer;
begin
 Assert(ma<>nil,           'kernel_mount nil ma');
 Assert(ma^.v<>nil,        'kernel_mount nil ma^.v');
 Assert((ma^.len and 1)=0, 'kernel_mount odd ma^.len (%d)');

 auio.uio_iov   :=ma^.v;
 auio.uio_iovcnt:=ma^.len;
 auio.uio_offset:=0;
 auio.uio_resid :=0;
 auio.uio_segflg:=UIO_SYSSPACE;
 auio.uio_rw    :=UIO_READ;
 auio.uio_td    :=nil;

 error:=ma^.error;
 if (error=0) then
 begin
  error:=vfs_donmount(flags, @auio);
 end;

 free_mntarg(ma);
 Exit(error);
end;

function kern_unmount(path:PChar;flags:Integer):Integer;
var
 nd:t_nameidata;
 mp:p_mount;

 pathbuf:t_mname;

 error,vfslocked:Integer;
begin
 pathbuf:=Default(t_mname);
 strlcopy(@pathbuf,path,MNAMELEN);

 mtx_lock(VFS_Giant);

 {
  * Try to find global path for path argument.
  }
 NDINIT(@nd, LOOKUP,
        FOLLOW or LOCKLEAF or MPSAFE or AUDITVNODE1,
        UIO_SYSSPACE, path, curkthread);

 if (nd_namei(@nd)=0) then
 begin
  vfslocked:=NDHASGIANT(@nd);
  NDFREE(@nd, NDF_ONLY_PNBUF);

  error:=vn_path_to_global_path(nd.ni_vp,@pathbuf,MNAMELEN);

  if (error=0) or (error=ENODEV) then
  begin
   vput(nd.ni_vp);
  end;

  VFS_UNLOCK_GIANT(vfslocked);
 end;

 mtx_lock(mountlist_mtx);
 mp:=TAILQ_LAST(@mountlist);
 while (mp<>nil) do
 begin
  if (strcomp(@mp^.mnt_stat.f_mntonname, @pathbuf)=0) then
  begin
   break;
  end;
  mp:=TAILQ_PREV(mp,@mp^.mnt_list);
 end;
 mtx_unlock(mountlist_mtx);

 if (mp=nil) then
 begin
  mtx_unlock(VFS_Giant);
  Exit(EINVAL);
 end;

 error:=dounmount(mp, flags);

 mtx_unlock(VFS_Giant);
 Exit(error);
end;

///

{
 * Mount a filesystem.
 }
function sys_nmount(iovp:Pointer;iovcnt:DWORD;flags:QWORD):Integer;
var
 auio:p_uio;
 error:Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 {
  * Filter out MNT_ROOTFS.  We do not want clients of nmount() in
  * userspace to set this flag, but we must filter it out if we want
  * MNT_UPDATE on the root file system to work.
  * MNT_ROOTFS should only be set by the kernel when mounting its
  * root file system.
  }
 flags:=flags and (not MNT_ROOTFS);

 {
  * Check that we have an even number of iovec's
  * and that we have at least two options.
  }
 if ((iovcnt and 1)<>0) or (iovcnt < 4) then
 begin
  Exit(EINVAL);
 end;

 error:=copyinuio(iovp, iovcnt, @auio);
 if (error<>0) then
 begin
  Exit(error);
 end;
 error:=vfs_donmount(flags, auio);

 FreeMem(auio);
 Exit(error);
end;


{
 * Old mount API.
 }
function sys_mount(ftype,fpath:PChar;flags:QWORD;data:Pointer):Integer;
var
 fstype:PChar;
 vfsp:p_vfsconf;
 ma:p_mntarg;
 error:Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 vfsp:=nil;
 ma:=nil;

 {
  * Filter out MNT_ROOTFS.  We do not want clients of mount() in
  * userspace to set this flag, but we must filter it out if we want
  * MNT_UPDATE on the root file system to work.
  * MNT_ROOTFS should only be set by the kernel when mounting its
  * root file system.
  }
 flags:=flags and (not MNT_ROOTFS);

 fstype:=AllocMem(MFSNAMELEN);

 error:=copyinstr(ftype, fstype, MFSNAMELEN, nil);
 if (error<>0) then
 begin
  FreeMem(fstype);
  Exit(error);
 end;

 mtx_lock(VFS_Giant);
 vfsp:=vfs_byname_kld(fstype, @error);
 FreeMem(fstype);

 if (vfsp=nil) then
 begin
  mtx_unlock(VFS_Giant);
  Exit(ENOENT);
 end;

 if (vfsp^.vfc_vfsops^.vfs_cmount=nil) then
 begin
  mtx_unlock(VFS_Giant);
  Exit(EOPNOTSUPP);
 end;

 ma:=mount_argsu(ma, 'fstype', ftype, MFSNAMELEN);
 ma:=mount_argsu(ma, 'fspath', fpath, MNAMELEN);
 ma:=mount_argb (ma, flags and MNT_RDONLY, 'noro');
 ma:=mount_argb (ma, ord((flags and MNT_NOSUID)=0), 'nosuid');
 ma:=mount_argb (ma, ord((flags and MNT_NOEXEC)=0), 'noexec');

 error:=vfsp^.vfc_vfsops^.vfs_cmount(ma, data, flags);

 mtx_unlock(VFS_Giant);
 Exit(error);
end;

{
 * Unmount a filesystem.
 *
 * Note: unmount takes a path to the vnode mounted on as argument, not
 * special file (as before).
 }
function sys_unmount(path:PChar;flags:Integer):Integer;
var
 nd:t_nameidata;
 mp:p_mount;
 pathbuf:t_mname;
 error,id0,id1,vfslocked:Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 //if (jailed(td^.td_ucred)) or (usermount=0) then
 //begin
 // error:=priv_check(td, PRIV_VFS_UNMOUNT);
 // if (error<>0) then
 //  Exit(error);
 //end;

 pathbuf:=Default(t_mname);
 error:=copyinstr(path, @pathbuf, MNAMELEN, nil);
 if (error<>0) then
 begin
  Exit(error);
 end;
 mtx_lock(VFS_Giant);
 if ((flags and MNT_BYFSID)<>0) then
 begin
  { Decode the filesystem ID. }
  if (sscanf(pathbuf, 'FSID:%d:%d', [@id0, @id1])<>2) then
  begin
   mtx_unlock(VFS_Giant);
   Exit(EINVAL);
  end;

  mtx_lock(mountlist_mtx);
  mp:=TAILQ_LAST(@mountlist);
  while (mp<>nil) do
  begin
   if (mp^.mnt_stat.f_fsid.val[0]=id0) and
      (mp^.mnt_stat.f_fsid.val[1]=id1) then
   begin
    break;
   end;
   mp:=TAILQ_PREV(mp,@mp^.mnt_list);
  end;
  mtx_unlock(mountlist_mtx);
 end else
 begin
  {
   * Try to find global path for path argument.
   }
  NDINIT(@nd, LOOKUP,
         FOLLOW or LOCKLEAF or MPSAFE or AUDITVNODE1,
         UIO_SYSSPACE, @pathbuf, curkthread);

  if (nd_namei(@nd)=0) then
  begin
   vfslocked:=NDHASGIANT(@nd);
   NDFREE(@nd, NDF_ONLY_PNBUF);

   error:=vn_path_to_global_path(nd.ni_vp,@pathbuf,MNAMELEN);

   if (error=0) or (error=ENODEV) then
   begin
    vput(nd.ni_vp);
   end;

   VFS_UNLOCK_GIANT(vfslocked);
  end;

  mtx_lock(mountlist_mtx);
  mp:=TAILQ_LAST(@mountlist);
  while (mp<>nil) do
  begin
   if (strcomp(@mp^.mnt_stat.f_mntonname, @pathbuf)=0) then
   begin
    break;
   end;
   mp:=TAILQ_PREV(mp,@mp^.mnt_list);
  end;
  mtx_unlock(mountlist_mtx);
 end;

 if (mp=nil) then
 begin
  {
   * Previously we returned ENOENT for a nonexistent path and
   * EINVAL for a non-mountpoint.  We cannot tell these apart
   * now, so in the !MNT_BYFSID case return the more likely
   * EINVAL for compatibility.
   }
  mtx_unlock(VFS_Giant);

  if ((flags and MNT_BYFSID)<>0) then
   Exit(ENOENT)
  else
   Exit(EINVAL);
 end;

 {
  * Don't allow unmounting the root filesystem.
  }
 if ((mp^.mnt_flag and MNT_ROOTFS)<>0) then
 begin
  mtx_unlock(VFS_Giant);
  Exit(EINVAL);
 end;

 error:=dounmount(mp, flags);

 mtx_unlock(VFS_Giant);
 Exit(error);
end;


end.

