unit vfs_mountroot;

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

interface

uses
 mqueue,
 vmount;

const
 MNT_RDONLY=vmount.MNT_RDONLY;

procedure vfs_mountroot();
function  vfs_mount_path   (fstype,fspath,from,opts:PChar;flags:QWORD):Integer;
function  vfs_mount_mkdir  (fstype,fspath,from,opts:PChar;flags:QWORD):Integer;
function  vfs_unmount_rmdir(path:PChar;flags:Integer):Integer;

implementation

uses
 sysutils,
 errno,
 vuio,
 vnamei,
 vnode,
 vnode_if,
 vfiledesc,
 vfs_subr,
 vfs_init,
 vfs_mount,
 vfs_syscalls,
 kern_thr,
 kern_mtx;

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

function strsep(stringp:PPChar;delim:PChar):PChar;
var
 b,e:PChar;
begin
 b:=stringp^;
 if (b=nil) then Exit(nil);

 e:=strpos(b,delim)+strlen(delim);

 if (e^<>#0) then
 begin
  e^:=#0;
  Inc(e);
  stringp^:=e;
 end else
 begin
  stringp^:=nil;
 end;

 Result:=b;
end;

function parse_mountroot_options(ma:p_mntarg;options:PChar):p_mntarg;
var
 p:PChar;
 name,name_arg:PChar;
 val,val_arg:PChar;
 opts:PChar;
begin
 if (options=nil) then
  Exit(ma);

 if (options[0]=#0) then
  Exit(ma);

 p:=strdup(options);
 opts:=p;

 if (opts=nil) then
 begin
  Exit(ma);
 end;

 name:=strsep(@p, ',');
 while (name<>nil) do
 begin
  if (name[0]=#0) then
   break;

  val:=strscan(name, '=');
  if (val<>nil) then
  begin
   val^:=#0;
   Inc(val);
  end;

  name_arg:=strdup(name);
  val_arg:=nil;

  if (val<>nil) then
   val_arg:=strdup(val);

  ma:=mount_arg(ma, name_arg, val_arg, (ord(val_arg<>nil)*(-1)));

  name:=strsep(@p, ',');
 end;

 FreeMem(opts);
 Exit(ma);
end;

procedure set_rootvnode();
begin
 if (VFS_ROOT(TAILQ_FIRST(@mountlist), LK_EXCLUSIVE, @rootvnode)<>0) then
 begin
  Assert(False,'Cannot find root vnode');
 end;

 VOP_UNLOCK(rootvnode, 0);

 FILEDESC_XLOCK(@fd_table);

 if (fd_table.fd_cdir<>nil) then
 begin
  vrele(fd_table.fd_cdir);
 end;

 fd_table.fd_cdir:=rootvnode;
 VREF(rootvnode);

 if (fd_table.fd_rdir<>nil) then
 begin
  vrele(fd_table.fd_rdir);
 end;

 fd_table.fd_rdir:=rootvnode;
 VREF(rootvnode);

 FILEDESC_XUNLOCK(@fd_table);
end;

function vfs_mountroot_devfs(mpp:pp_mount):Integer;
var
 opts:p_vfsoptlist;
 vfsp:p_vfsconf;
 mp:p_mount;
 error:Integer;
begin
 mpp^:=nil;

 vfsp:=vfs_byname('devfs');
 Assert(vfsp<>nil,'Could not find devfs by name');

 if (vfsp=nil) then
  Exit(ENOENT);

 mp:=vfs_mount_alloc(nil, vfsp, '/dev');

 error:=vmount.VFS_MOUNT(mp);
 Assert(error=0,'VFS_MOUNT(devfs) failed');

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

 opts:=AllocMem(sizeof(vfsoptlist));
 TAILQ_INIT(opts);
 mp^.mnt_opt:=opts;

 mtx_lock(mountlist_mtx);
 TAILQ_INSERT_HEAD(@mountlist,mp,@mp^.mnt_list);
 mtx_unlock(mountlist_mtx);

 mpp^:=mp;
 set_rootvnode();

 //error:=kern_symlink('/', 'dev', UIO_SYSSPACE);
 //if (error<>0) then
 //begin
 // Writeln('kern_symlink /dev / returns ',error);
 //end;

 Exit(error);
end;

procedure mount_print;
var
 m:p_mount;

 procedure print_vp(vp:p_vnode;const prefix:RawByteString);
 var
  m:p_mount;
 begin
  Write(prefix,':');
  if (vp=nil) then
  begin
   Writeln(' nil');
   Exit;
  end;
  Write(' v_tag:',vp^.v_tag);
  m:=vp^.v_mount;
  Writeln(
   ' fstype:',m^.mnt_stat.f_fstypename,
   ' mntfrom:',m^.mnt_stat.f_mntfromname,
   ' mnton:',m^.mnt_stat.f_mntonname
  );
 end;

begin
 Writeln('[mount_print]->');

 mtx_lock(mountlist_mtx);

 print_vp(rootvnode,'root');

 print_vp(fd_table.fd_cdir,'cdir');
 print_vp(fd_table.fd_rdir,'rdir');
 print_vp(fd_table.fd_jdir,'jdir');

 m:=TAILQ_FIRST(@mountlist);
 while (m<>nil) do
 begin
  Writeln(
   ' fstype:',m^.mnt_stat.f_fstypename,
   ' mntfrom:',m^.mnt_stat.f_mntfromname,
   ' mnton:',m^.mnt_stat.f_mntonname
  );

  m:=TAILQ_NEXT(m,@m^.mnt_list);
 end;

 mtx_unlock(mountlist_mtx);

 Writeln('<-[mount_print]');
end;

function vfs_mountroot_shuffle(mpdevfs:p_mount):Integer;
var
 nd:t_nameidata;
 mporoot,mpnroot:p_mount;
 vp,vporoot,vpdevfs:p_vnode;
 //fspath:PChar;
 error:Integer;
begin
 mpnroot:=TAILQ_NEXT(mpdevfs,@mpdevfs^.mnt_list);

 { Shuffle the mountlist. }
 mtx_lock(mountlist_mtx);
 mporoot:=TAILQ_FIRST(@mountlist);
 TAILQ_REMOVE(@mountlist,mpdevfs,@mpdevfs^.mnt_list);
 if (mporoot<>mpdevfs) then
 begin
  TAILQ_REMOVE(@mountlist,mpnroot,@mpnroot^.mnt_list);
  TAILQ_INSERT_HEAD(@mountlist,mpnroot,@mpnroot^.mnt_list);
 end;
 TAILQ_INSERT_TAIL(@mountlist,mpdevfs,@mpdevfs^.mnt_list);
 mtx_unlock(mountlist_mtx);

 //cache_purgevfs(mporoot);
 //if (mporoot<>mpdevfs) then
 // cache_purgevfs(mpdevfs);

 VFS_ROOT(mporoot, LK_EXCLUSIVE, @vporoot);

 VI_LOCK(vporoot);
 vporoot^.v_iflag:=vporoot^.v_iflag and (not VI_MOUNT);
 VI_UNLOCK(vporoot);
 vporoot^.v_mountedhere:=nil;
 mporoot^.mnt_flag:=mporoot^.mnt_flag and (not MNT_ROOTFS);
 mporoot^.mnt_vnodecovered:=nil;
 vput(vporoot);

 { Set up the new rootvnode, and purge the cache }
 mpnroot^.mnt_vnodecovered:=nil;
 set_rootvnode();
 //cache_purgevfs(rootvnode^.v_mount);

 //mount_print;

 { Remount devfs under /dev }
 NDINIT(@nd, LOOKUP, FOLLOW or LOCKLEAF, UIO_SYSSPACE, '/dev', curkthread);

 error:=nd_namei(@nd);

 if (error=0) then
 begin
  vp:=nd.ni_vp;

  if (vp^.v_type=VDIR) then
   error:=0
  else
   error:=ENOTDIR;

  //if (error=0) then
  // error:=vinvalbuf(vp, V_SAVE, 0, 0);

  if (error=0) then
  begin
   vpdevfs:=mpdevfs^.mnt_vnodecovered;
   if (vpdevfs<>nil) then
   begin
    //cache_purge(vpdevfs);
    vpdevfs^.v_mountedhere:=nil;
    vrele(vpdevfs);
   end;
   mpdevfs^.mnt_vnodecovered:=vp;
   vp^.v_mountedhere:=mpdevfs;
   VOP_UNLOCK(vp, 0);
  end else
   vput(vp);
 end;
 if (error<>0) then
  Writeln('mountroot: unable to remount devfs under /dev ', error);

 NDFREE(@nd, NDF_ONLY_PNBUF);

 if (mporoot=mpdevfs) then
 begin
  vfs_unbusy(mpdevfs);
  { Unlink the no longer needed /dev/dev ^. / symlink }
  //error:=kern_unlink('/dev/dev', UIO_SYSSPACE);
  //if (error<>0) then
  // Writeln('mountroot: unable to unlink /dev/dev ', error);
 end;

 Exit(0);
end;

function vfs_mount_path(fstype,fspath,from,opts:PChar;flags:QWORD):Integer;
const
 ERRMSGL=255;
var
 ma:p_mntarg;
 errmsg:RawByteString;
begin
 errmsg:='';
 SetLength(errmsg,ERRMSGL);
 FillChar(PChar(errmsg)^,ERRMSGL,0);

 if (vfs_byname(fstype)=nil) then
 begin
  strlcopy(PChar(errmsg),'unknown file system',ERRMSGL);
  Exit(ENOENT);
 end;

 ma:=nil;
 ma:=mount_arg(ma, 'fstype', fstype, -1);
 ma:=mount_arg(ma, 'fspath', fspath, -1);
 ma:=mount_arg(ma, 'from'  , from  , -1);
 ma:=mount_arg(ma, 'errmsg', PChar(errmsg), ERRMSGL);

 ma:=parse_mountroot_options(ma, opts);
 Result:=kernel_nmount(ma,flags);
end;

function vfs_mount_mkdir(fstype,fspath,from,opts:PChar;flags:QWORD):Integer;
begin
 Result:=kern_mkdir(fspath,UIO_SYSSPACE,&777);
 if (Result=0) then
 begin
  Result:=vfs_mount_path(fstype,fspath,from,opts,flags);
 end;
end;

function vfs_unmount_rmdir(path:PChar;flags:Integer):Integer;
begin
 Result:=kern_unmount(path,flags);
 if (Result=0) then
 begin
  Result:=kern_rmdir(path,UIO_SYSSPACE);
 end;
end;

procedure vfs_mountroot();
label
 _end;
var
 mp:p_mount;
 error:Integer;
begin
 mtx_lock(VFS_Giant);

 error:=vfs_mountroot_devfs(@mp);
 if (error<>0) then goto _end;

 //mount_print;

 error:=vfs_mount_path('ufs','/','/',nil,MNT_ROOTFS);
 if (error<>0) then goto _end;

 //mount_print;

 error:=vfs_mountroot_shuffle(mp);

 {
 error:=kern_mkdir('null',UIO_SYSSPACE,&777);
 if (error=0) then
 begin
  error:=vfs_mountroot_simple('nullfs','/null','/dev',nil,0);
 end;
 }

 //error:=vfs_mount_mkdir('ufs','/app0','/',nil,0);
 //error:=vfs_mount_mkdir('ufs','/system','/system',nil,0);
 //
 //mount_print;

_end:
 mtx_unlock(VFS_Giant);
end;

end.

