unit vfs_cache;

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

interface

uses
 vnode;

function vn_fullpath1(vp,rdir:p_vnode;buf:PChar;retbuf:PPChar;buflen:DWORD):Integer;
function vn_fullpath(vn:p_vnode;retbuf,freebuf:PPChar):Integer;
function vn_fullpath_global(vn:p_vnode;retbuf,freebuf:PPChar):Integer;
function vn_path_to_global_path(vp:p_vnode;path:PChar;pathlen:DWORD):Integer;
function vn_vptocnp(vp:pp_vnode;buf:PChar;buflen:PDWORD):Integer;

function sys___getcwd(buf:PChar;buflen:DWORD):Integer;

implementation

uses
 sysutils,
 systm,
 errno,
 vuio,
 vmount,
 vnamei,
 vfiledesc,
 vfs_subr,
 vfs_vnops,
 vfs_lookup,
 vnode_if,
 kern_thr,
 kern_mtx;

{
 * Retrieve the full filesystem path that correspond to a vnode from the name
 * cache (if available)
 }
function vn_fullpath(vn:p_vnode;retbuf,freebuf:PPChar):Integer;
var
 buf:PChar;
 rdir:p_vnode;
 error,vfslocked:Integer;
begin
 //if (disablefullpath) then Exit(ENODEV);

 if (vn=nil) then
 begin
  Exit(EINVAL);
 end;

 buf:=AllocMem(MAXPATHLEN);

 FILEDESC_SLOCK(@fd_table);
 rdir:=fd_table.fd_rdir;
 VREF(rdir);
 FILEDESC_SUNLOCK(@fd_table);

 error:=vn_fullpath1(vn, rdir, buf, retbuf, MAXPATHLEN);
 vfslocked:=VFS_LOCK_GIANT(rdir^.v_mount);
 vrele(rdir);
 VFS_UNLOCK_GIANT(vfslocked);

 if (error=0) then
  freebuf^:=buf
 else
  FreeMem(buf);

 Exit(error);
end;

{
 * This function is similar to vn_fullpath, but it attempts to lookup the
 * pathname relative to the global root mount point.  This is required for the
 * auditing sub-system, as audited pathnames must be absolute, relative to the
 * global root mount point.
 }
function vn_fullpath_global(vn:p_vnode;retbuf,freebuf:PPChar):Integer;
var
 buf:PChar;
 error:Integer;
begin
 //if (disablefullpath) then Exit(ENODEV);

 if (vn=nil) then
 begin
  Exit(EINVAL);
 end;

 buf:=AllocMem(MAXPATHLEN);

 error:=vn_fullpath1(vn, rootvnode, buf, retbuf, MAXPATHLEN);

 if (error=0) then
  freebuf^:=buf
 else
  FreeMem(buf);

 Exit(error);
end;



{
 * This function updates path string to vnode's full global path
 * and checks the size of the new path string against the pathlen argument.
 *
 * Requires a locked, referenced vnode and GIANT lock held.
 * Vnode is re-locked on success or ENODEV, otherwise unlocked.
 *
 * If sysctl debug.disablefullpath is set, ENODEV is returned,
 * vnode is left locked and path remain untouched.
 *
 * If vp is a directory, the call to vn_fullpath_global() always succeeds
 * because it falls back to the ".." lookup if the namecache lookup fails.
 }
function vn_path_to_global_path(vp:p_vnode;path:PChar;pathlen:DWORD):Integer;
label
 _out;
var
 nd:t_nameidata;
 vp1:p_vnode;
 rpath,fbuf:PChar;
 error,vfslocked:Integer;
begin
 VFS_ASSERT_GIANT(vp^.v_mount);
 ASSERT_VOP_ELOCKED(vp, 'vn_path_to_global_path');

 { Return ENODEV if sysctl debug.disablefullpath==1 }
 //if (disablefullpath) then Exit(ENODEV);

 { Construct global filesystem path from vp. }
 VOP_UNLOCK(vp, 0);

 error:=vn_fullpath_global(vp, @rpath, @fbuf);
 if (error<>0) then
 begin
  vrele(vp);
  Exit(error);
 end;

 if (strlen(rpath) >= pathlen) then
 begin
  vrele(vp);
  error:=ENAMETOOLONG;
  goto _out;
 end;

 {
  * Re-lookup the vnode by path to detect a possible rename.
  * As a side effect, the vnode is relocked.
  * If vnode was renamed, return ENOENT.
  }
 NDINIT(@nd, LOOKUP, FOLLOW or LOCKLEAF or MPSAFE or AUDITVNODE1, UIO_SYSSPACE, path, curkthread);

 error:=nd_namei(@nd);
 if (error<>0) then
 begin
  vrele(vp);
  goto _out;
 end;

 vfslocked:=NDHASGIANT(@nd);
 NDFREE(@nd, NDF_ONLY_PNBUF);
 vp1:=nd.ni_vp;
 vrele(vp);
 if (vp1=vp) then
 begin
  strcopy(path, rpath);
 end else
 begin
  vput(vp1);
  error:=ENOENT;
 end;
 VFS_UNLOCK_GIANT(vfslocked);

_out:
 FreeMem(fbuf);
 Exit(error);
end;

function vn_vptocnp(vp:pp_vnode;buf:PChar;buflen:PDWORD):Integer;
var
 dvp:p_vnode;
 error,vfslocked:Integer;
begin
 //no cache

 vfslocked:=VFS_LOCK_GIANT((vp^)^.v_mount);
 vn_lock(vp^, LK_SHARED or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
 error:=VOP_VPTOCNP(vp^, @dvp, buf, PInteger(buflen));
 vput(vp^);
 VFS_UNLOCK_GIANT(vfslocked);

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

 vp^:=dvp;

 if ((dvp^.v_iflag and VI_DOOMED)<>0) then
 begin
  { forced unmount }
  vfslocked:=VFS_LOCK_GIANT(dvp^.v_mount);
  vrele(dvp);
  VFS_UNLOCK_GIANT(vfslocked);
  error:=ENOENT;
  Exit(error);
 end;
 {
  * *vp has its use count incremented still.
  }

 Exit(0);
end;

{
 * The magic behind kern___getcwd() and vn_fullpath().
 }
function vn_fullpath1(vp,rdir:p_vnode;buf:PChar;retbuf:PPChar;buflen:DWORD):Integer;
var
 error,slash_prefixed,vfslocked:Integer;
 vp1:p_vnode;
begin
 Dec(buflen);
 buf[buflen]:=#0;
 error:=0;
 slash_prefixed:=0;

 vref(vp);

 if (vp^.v_type<>VDIR) then
 begin
  error:=vn_vptocnp(@vp, buf, @buflen);
  if (error<>0) then
  begin
   Exit(error);
  end;

  if (buflen=0) then
  begin
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   vrele(vp);
   VFS_UNLOCK_GIANT(vfslocked);
   Exit(ENOMEM);
  end;
  Dec(buflen);
  buf[buflen]:='/';
  slash_prefixed:=1;
 end;
 while (vp<>rdir) and (vp<>rootvnode) do
 begin
  if ((vp^.v_vflag and VV_ROOT)<>0) then
  begin
   if ((vp^.v_iflag and VI_DOOMED)<>0) then
   begin { forced unmount }
    vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
    vrele(vp);
    VFS_UNLOCK_GIANT(vfslocked);
    error:=ENOENT;
    break;
   end;
   vp1:=p_mount(vp^.v_mount)^.mnt_vnodecovered;
   vref(vp1);
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   vrele(vp);
   VFS_UNLOCK_GIANT(vfslocked);
   vp:=vp1;
   continue;
  end;
  if (vp^.v_type<>VDIR) then
  begin
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   vrele(vp);
   VFS_UNLOCK_GIANT(vfslocked);
   error:=ENOTDIR;
   break;
  end;
  error:=vn_vptocnp(@vp, buf, @buflen);
  if (error<>0) then
  begin
   break;
  end;

  if (buflen=0) then
  begin
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   vrele(vp);
   VFS_UNLOCK_GIANT(vfslocked);
   error:=ENOMEM;
   break;
  end;
  Dec(buflen);
  buf[buflen]:='/';
  slash_prefixed:=1;
 end;
 if (error<>0) then
 begin
  Exit(error);
 end;

 if (slash_prefixed=0) then
 begin
  if (buflen=0) then
  begin
   vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
   vrele(vp);
   VFS_UNLOCK_GIANT(vfslocked);
   Exit(ENOMEM);
  end;
  Dec(buflen);
  buf[buflen]:='/';
 end;
 vfslocked:=VFS_LOCK_GIANT(vp^.v_mount);
 vrele(vp);
 VFS_UNLOCK_GIANT(vfslocked);

 retbuf^:=buf + buflen;
 Exit(0);
end;

function kern___getcwd(buf:PChar;bufseg:uio_seg;buflen:DWORD):Integer;
var
 bp,tmpbuf:PChar;
 cdir,rdir:p_vnode;
 error,vfslocked:Integer;
begin
 //if (disablecwd) then Exit(ENODEV);

 if (buflen < 2) then Exit(EINVAL);

 if (buflen > MAXPATHLEN) then
 begin
  buflen:=MAXPATHLEN;
 end;

 tmpbuf:=AllocMem(buflen);

 FILEDESC_SLOCK(@fd_table);
 cdir:=fd_table.fd_cdir;
 VREF(cdir);
 rdir:=fd_table.fd_rdir;
 VREF(rdir);
 FILEDESC_SUNLOCK(@fd_table);

 error:=vn_fullpath1(cdir, rdir, tmpbuf, @bp, buflen);
 vfslocked:=VFS_LOCK_GIANT(rdir^.v_mount);
 vrele(rdir);
 VFS_UNLOCK_GIANT(vfslocked);
 vfslocked:=VFS_LOCK_GIANT(cdir^.v_mount);
 vrele(cdir);
 VFS_UNLOCK_GIANT(vfslocked);

 if (error=0) then
 begin
  if (bufseg=UIO_SYSSPACE) then
   Move(bp^, buf^, strlen(bp) + 1)
  else
   error:=copyout(bp, buf, strlen(bp) + 1);
 end;

 FreeMem(tmpbuf);

 Exit(error);
end;

{ Implementation of the getcwd syscall. }
function sys___getcwd(buf:PChar;buflen:DWORD):Integer;
begin
 //priv_check(td,683);
 Exit(EPERM);

 Exit(kern___getcwd(buf, UIO_USERSPACE, buflen));
end;

end.

