mirror of https://github.com/red-prig/fpPS4.git
1230 lines
28 KiB
Plaintext
1230 lines
28 KiB
Plaintext
unit vfs_lookup;
|
|
|
|
{$mode ObjFPC}{$H+}
|
|
{$CALLING SysV_ABI_CDecl}
|
|
|
|
interface
|
|
|
|
uses
|
|
sysutils,
|
|
kern_param,
|
|
systm,
|
|
vcapability,
|
|
vuio,
|
|
vfiledesc,
|
|
vfcntl,
|
|
vnode,
|
|
vnode_if,
|
|
vnamei,
|
|
vmount,
|
|
kern_mtx,
|
|
kern_thr,
|
|
kern_descrip;
|
|
|
|
function nd_namei(ndp:p_nameidata):Integer;
|
|
function nd_lookup(ndp:p_nameidata):Integer;
|
|
procedure NDFREE(ndp:p_nameidata;flags:Integer);
|
|
|
|
procedure nameiinit; //SYSINIT(vfs, SI_SUB_VFS, SI_ORDER_SECOND, nameiinit, NULL);
|
|
|
|
implementation
|
|
|
|
uses
|
|
errno,
|
|
vfs_subr,
|
|
vfs_vnops;
|
|
|
|
//
|
|
|
|
var
|
|
dead_vnodeops:vop_vector; external;
|
|
|
|
//
|
|
|
|
var
|
|
vp_crossmp:p_vnode;
|
|
lookup_shared:Integer=1;
|
|
|
|
procedure nameiinit;
|
|
begin
|
|
getnewvnode('crossmp', nil, @dead_vnodeops, @vp_crossmp);
|
|
//vn_lock(vp_crossmp, LK_EXCLUSIVE);
|
|
//VN_LOCK_ASHARE(vp_crossmp);
|
|
//VOP_UNLOCK(vp_crossmp, 0);
|
|
end;
|
|
|
|
{
|
|
* Convert a pathname into a pointer to a locked vnode.
|
|
*
|
|
* The FOLLOW flag is set when symbolic links are to be followed
|
|
* when they occur at the end of the name translation process.
|
|
* Symbolic links are always followed for all other pathname
|
|
* components other than the last.
|
|
*
|
|
* The segflg defines whether the name is to be copied from user
|
|
* space or kernel space.
|
|
*
|
|
* Overall outline of namei:
|
|
*
|
|
* copy in name
|
|
* get starting directory
|
|
* while (!done and !error) begin
|
|
* call lookup to search path.
|
|
* if symbolic link, massage name in buffer and continue
|
|
* end;
|
|
}
|
|
procedure namei_cleanup_cnp(cnp:p_componentname); inline;
|
|
begin
|
|
FreeMem(cnp^.cn_pnbuf);
|
|
end;
|
|
|
|
function zalloc_namei:Pointer; inline;
|
|
begin
|
|
Result:=AllocMem(kern_param.MAXPATHLEN);
|
|
end;
|
|
|
|
function nd_namei(ndp:p_nameidata):Integer; public;
|
|
var
|
|
cp:PChar; { pointer into pathname argument }
|
|
dp:p_vnode; { the directory we are searching }
|
|
aiov:iovec; { uio for reading symbolic links }
|
|
auio:t_uio;
|
|
error, linklen:Integer;
|
|
cnp:p_componentname;
|
|
td:p_kthread;
|
|
vfslocked:Integer;
|
|
begin
|
|
cnp:=@ndp^.ni_cnd;
|
|
td:=cnp^.cn_thread;
|
|
|
|
Assert(((cnp^.cn_flags and MPSAFE)<>0) or mtx_owned(VFS_Giant),'NOT MPSAFE and Giant not held');
|
|
|
|
Assert((cnp^.cn_nameiop and (not OPMASK))=0,'namei: nameiop contaminated with flags');
|
|
Assert((cnp^.cn_flags and OPMASK)=0,'namei: flags contaminated with nameiops');
|
|
|
|
if (lookup_shared=0) then
|
|
begin
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not LOCKSHARED);
|
|
end;
|
|
|
|
{ We will set this ourselves if we need it. }
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not TRAILINGSLASH);
|
|
|
|
{
|
|
* Get a buffer for the name to be translated, and copy the
|
|
* name into the buffer.
|
|
}
|
|
if ((cnp^.cn_flags and HASBUF)=0) then
|
|
begin
|
|
cnp^.cn_pnbuf:=zalloc_namei;
|
|
end;
|
|
|
|
if (ndp^.ni_segflg=UIO_SYSSPACE) then
|
|
begin
|
|
error:=copystr (ndp^.ni_dirp, cnp^.cn_pnbuf, MAXPATHLEN, @ndp^.ni_pathlen);
|
|
end else
|
|
begin
|
|
error:=copyinstr(ndp^.ni_dirp, cnp^.cn_pnbuf, MAXPATHLEN, @ndp^.ni_pathlen);
|
|
end;
|
|
|
|
{
|
|
* Don't allow empty pathnames.
|
|
}
|
|
if (error=0) and (cnp^.cn_pnbuf^=#0) then
|
|
begin
|
|
error:=ENOENT;
|
|
end;
|
|
|
|
if (error<>0) then
|
|
begin
|
|
namei_cleanup_cnp(cnp);
|
|
ndp^.ni_vp:=nil;
|
|
Exit(error);
|
|
end;
|
|
ndp^.ni_loopcnt:=0;
|
|
|
|
{
|
|
* Get starting point for the translation.
|
|
}
|
|
FILEDESC_SLOCK(@fd_table);
|
|
ndp^.ni_rootdir:=fd_table.fd_rdir;
|
|
ndp^.ni_topdir :=fd_table.fd_jdir;
|
|
|
|
dp:=nil;
|
|
if (cnp^.cn_pnbuf[0]<>'/') then
|
|
begin
|
|
if (ndp^.ni_startdir<>nil) then
|
|
begin
|
|
dp:=ndp^.ni_startdir;
|
|
error:=0;
|
|
end else
|
|
if (ndp^.ni_dirfd<>AT_FDCWD) then
|
|
begin
|
|
error:=fgetvp_rights(ndp^.ni_dirfd,
|
|
ndp^.ni_rightsneeded or CAP_LOOKUP,
|
|
@ndp^.ni_baserights,
|
|
@dp);
|
|
|
|
end;
|
|
if (error<>0) or (dp<>nil) then
|
|
begin
|
|
FILEDESC_SUNLOCK(@fd_table);
|
|
if (error=0) and (dp^.v_type<>VDIR) then
|
|
begin
|
|
vfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
vrele(dp);
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
error:=ENOTDIR;
|
|
end;
|
|
end;
|
|
if (error<>0) then
|
|
begin
|
|
namei_cleanup_cnp(cnp);
|
|
Exit(error);
|
|
end;
|
|
end;
|
|
if (dp=nil) then
|
|
begin
|
|
dp:=fd_table.fd_cdir;
|
|
VREF(dp);
|
|
FILEDESC_SUNLOCK(@fd_table);
|
|
if (ndp^.ni_startdir<>nil) then
|
|
begin
|
|
vfslocked:=VFS_LOCK_GIANT(ndp^.ni_startdir^.v_mount);
|
|
vrele(ndp^.ni_startdir);
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
end;
|
|
end;
|
|
|
|
vfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
|
|
while (true) do
|
|
begin
|
|
{
|
|
* Check if root directory should replace current directory.
|
|
* Done at start of translation and after symbolic link.
|
|
}
|
|
cnp^.cn_nameptr:=cnp^.cn_pnbuf;
|
|
if (cnp^.cn_nameptr^='/') then
|
|
begin
|
|
vrele(dp);
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
if (ndp^.ni_strictrelative<>0) then
|
|
begin
|
|
namei_cleanup_cnp(cnp);
|
|
Exit(ENOTCAPABLE);
|
|
end;
|
|
while (cnp^.cn_nameptr^='/') do
|
|
begin
|
|
Inc(cnp^.cn_nameptr);
|
|
Dec(ndp^.ni_pathlen);
|
|
end;
|
|
dp:=ndp^.ni_rootdir;
|
|
vfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
VREF(dp);
|
|
end;
|
|
|
|
if (vfslocked<>0) then
|
|
begin
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags or GIANTHELD;
|
|
end;
|
|
|
|
ndp^.ni_startdir:=dp;
|
|
error:=nd_lookup(ndp);
|
|
|
|
if (error<>0) then
|
|
begin
|
|
namei_cleanup_cnp(cnp);
|
|
Exit(error);
|
|
end;
|
|
|
|
vfslocked:=ord((ndp^.ni_cnd.cn_flags and GIANTHELD)<>0);
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags and (not GIANTHELD);
|
|
{
|
|
* If not a symbolic link, we're done.
|
|
}
|
|
if ((cnp^.cn_flags and ISSYMLINK)=0) then
|
|
begin
|
|
if ((cnp^.cn_flags and (SAVENAME or SAVESTART))=0) then
|
|
begin
|
|
namei_cleanup_cnp(cnp);
|
|
end else
|
|
begin
|
|
cnp^.cn_flags:=cnp^.cn_flags or HASBUF;
|
|
end;
|
|
|
|
if ((cnp^.cn_flags and MPSAFE)=0) then
|
|
begin
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
end else
|
|
if (vfslocked<>0) then
|
|
begin
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags or GIANTHELD;
|
|
end;
|
|
|
|
Exit(0);
|
|
end;
|
|
|
|
Inc(ndp^.ni_loopcnt);
|
|
if ((ndp^.ni_loopcnt-1)>=MAXSYMLINKS) then
|
|
begin
|
|
error:=ELOOP;
|
|
break;
|
|
end;
|
|
|
|
//if ((cnp^.cn_flags and NOMACCHECK)=0) then
|
|
//begin
|
|
// error:=mac_vnode_check_readlink(td^.td_ucred, ndp^.ni_vp);
|
|
// if (error<>0) then
|
|
// break;
|
|
//end;
|
|
|
|
if (ndp^.ni_pathlen>1) then
|
|
cp:=zalloc_namei
|
|
else
|
|
cp:=cnp^.cn_pnbuf;
|
|
|
|
aiov.iov_base :=cp;
|
|
aiov.iov_len :=MAXPATHLEN;
|
|
auio.uio_iov :=@aiov;
|
|
auio.uio_iovcnt:=1;
|
|
auio.uio_offset:=0;
|
|
auio.uio_rw :=UIO_READ;
|
|
auio.uio_segflg:=UIO_SYSSPACE;
|
|
auio.uio_td :=td;
|
|
auio.uio_resid :=MAXPATHLEN;
|
|
|
|
error:=VOP_READLINK(ndp^.ni_vp, @auio);
|
|
if (error<>0) then
|
|
begin
|
|
if (ndp^.ni_pathlen > 1) then
|
|
begin
|
|
FreeMem(cp);
|
|
end;
|
|
break;
|
|
end;
|
|
|
|
linklen:=MAXPATHLEN - auio.uio_resid;
|
|
if (linklen=0) then
|
|
begin
|
|
if (ndp^.ni_pathlen>1) then
|
|
begin
|
|
FreeMem(cp);
|
|
end;
|
|
error:=ENOENT;
|
|
break;
|
|
end;
|
|
|
|
if (linklen + ndp^.ni_pathlen >= MAXPATHLEN) then
|
|
begin
|
|
if (ndp^.ni_pathlen > 1) then
|
|
begin
|
|
FreeMem(cp);
|
|
end;
|
|
error:=ENAMETOOLONG;
|
|
break;
|
|
end;
|
|
|
|
if (ndp^.ni_pathlen > 1) then
|
|
begin
|
|
Move(ndp^.ni_next^,(cp + linklen)^,ndp^.ni_pathlen);
|
|
FreeMem(cnp^.cn_pnbuf);
|
|
cnp^.cn_pnbuf:=cp;
|
|
end else
|
|
begin
|
|
cnp^.cn_pnbuf[linklen]:=#0;
|
|
end;
|
|
|
|
Inc(ndp^.ni_pathlen,linklen);
|
|
vput(ndp^.ni_vp);
|
|
dp:=ndp^.ni_dvp;
|
|
end;
|
|
|
|
namei_cleanup_cnp(cnp);
|
|
vput(ndp^.ni_vp);
|
|
ndp^.ni_vp:=nil;
|
|
vrele(ndp^.ni_dvp);
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
|
|
Exit(error);
|
|
end;
|
|
|
|
function compute_cn_lkflags(mp:p_mount;lkflags,cnflags:Integer):Integer;
|
|
begin
|
|
if (mp=nil) then
|
|
begin
|
|
lkflags:=lkflags and (not LK_SHARED);
|
|
lkflags:=lkflags or LK_EXCLUSIVE;
|
|
Exit(lkflags);
|
|
end;
|
|
|
|
if (((lkflags and LK_SHARED)<>0) and
|
|
(((mp^.mnt_kern_flag and MNTK_LOOKUP_SHARED)=0) or
|
|
(((cnflags and ISDOTDOT)<>0) and
|
|
((mp^.mnt_kern_flag and MNTK_LOOKUP_EXCL_DOTDOT)<>0)))) then
|
|
begin
|
|
lkflags:=lkflags and (not LK_SHARED);
|
|
lkflags:=lkflags or LK_EXCLUSIVE;
|
|
end;
|
|
Exit(lkflags);
|
|
end;
|
|
|
|
function needs_exclusive_leaf(mp:p_mount;flags:Integer):Integer;
|
|
begin
|
|
|
|
{
|
|
* Intermediate nodes can use shared locks, we only need to
|
|
* force an exclusive lock for leaf nodes.
|
|
}
|
|
if ((flags and (ISLASTCN or LOCKLEAF))<>(ISLASTCN or LOCKLEAF)) then
|
|
begin
|
|
Exit(0);
|
|
end;
|
|
|
|
{ Always use exclusive locks if LOCKSHARED isn't set. }
|
|
if ((flags and LOCKSHARED)=0) then
|
|
begin
|
|
Exit(1);
|
|
end;
|
|
|
|
{
|
|
* For lookups during open(), if the mount point supports
|
|
* extended shared operations, then use a shared lock for the
|
|
* leaf node, otherwise use an exclusive lock.
|
|
}
|
|
if ((flags and ISOPEN)<>0) then
|
|
begin
|
|
if (mp<>nil) then
|
|
if ((mp^.mnt_kern_flag and MNTK_EXTENDED_SHARED)<>0) then
|
|
begin
|
|
Exit(0)
|
|
end;
|
|
Exit(1);
|
|
end;
|
|
|
|
{
|
|
* Lookup requests outside of open() that specify LOCKSHARED
|
|
* only need a shared lock on the leaf vnode.
|
|
}
|
|
Exit(0);
|
|
end;
|
|
|
|
{
|
|
* Search a pathname.
|
|
* This is a very central and rather complicated routine.
|
|
*
|
|
* The pathname is pointed to by ni_ptr and is of length ni_pathlen.
|
|
* The starting directory is taken from ni_startdir. The pathname is
|
|
* descended until done, or a symbolic link is encountered. The variable
|
|
* ni_more is clear if the path is completed; it is set to one if a
|
|
* symbolic link needing interpretation is encountered.
|
|
*
|
|
* The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on
|
|
* whether the name is to be looked up, created, renamed, or deleted.
|
|
* When CREATE, RENAME, or DELETE is specified, information usable in
|
|
* creating, renaming, or deleting a directory entry may be calculated.
|
|
* If flag has LOCKPARENT or'ed into it, the parent directory is returned
|
|
* locked. If flag has WANTPARENT or'ed into it, the parent directory is
|
|
* returned unlocked. Otherwise the parent directory is not returned. If
|
|
* the target of the pathname exists and LOCKLEAF is or'ed into the flag
|
|
* the target is returned locked, otherwise it is returned unlocked.
|
|
* When creating or renaming and LOCKPARENT is specified, the target may not
|
|
* be '.'. When deleting and LOCKPARENT is specified, the target may be '.'.
|
|
*
|
|
* Overall outline of lookup:
|
|
*
|
|
* dirloop:
|
|
* identify next component of name at ndp^.ni_ptr
|
|
* handle degenerate case where name is nil string
|
|
* if .. and crossing mount points and on mounted filesys, find parent
|
|
* call VOP_LOOKUP routine for next component name
|
|
* directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set
|
|
* component vnode returned in ni_vp (if it exists), locked.
|
|
* if result vnode is mounted on and crossing mount points,
|
|
* find mounted on vnode
|
|
* if more components of name, do next level at dirloop
|
|
* Exitthe answer in ni_vp, locked if LOCKLEAF set
|
|
* if LOCKPARENT set, Exitlocked parent in ni_dvp
|
|
* if WANTPARENT set, Exitunlocked parent in ni_dvp
|
|
}
|
|
|
|
function nd_lookup(ndp:p_nameidata):Integer; public;
|
|
var
|
|
cp :PChar ; { pointer into pathname argument }
|
|
dp :p_vnode; { the directory we are searching }
|
|
tdp :p_vnode; { saved dp }
|
|
mp :p_mount; { mount table entry }
|
|
docache :Integer; {=0 do not cache last component }
|
|
_wantparent :Integer; { 1 => wantparent or lockparent flag }
|
|
_rdonly :Integer; { lookup read-only flag bit }
|
|
error :Integer;
|
|
dpunlocked :Integer; { dp has already been unlocked }
|
|
cnp :p_componentname;
|
|
vfslocked :Integer; { VFS Giant state for child }
|
|
dvfslocked :Integer; { VFS Giant state for parent }
|
|
tvfslocked :Integer;
|
|
lkflags_save :Integer;
|
|
ni_dvp_unlocked:Integer;
|
|
label
|
|
dirloop,
|
|
bad,
|
|
success,
|
|
nextname,
|
|
unionlookup,
|
|
bad2;
|
|
begin
|
|
dp:=nil;
|
|
error:=0;
|
|
dpunlocked:=0;
|
|
cnp:=@ndp^.ni_cnd;
|
|
|
|
{
|
|
* Setup: break out flag bits into variables.
|
|
}
|
|
dvfslocked:=ord((ndp^.ni_cnd.cn_flags and GIANTHELD)<>0);
|
|
vfslocked:=0;
|
|
ni_dvp_unlocked:=0;
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags and (not GIANTHELD);
|
|
_wantparent:=cnp^.cn_flags and (LOCKPARENT or WANTPARENT);
|
|
|
|
Assert((cnp^.cn_nameiop=LOOKUP) or (wantparent<>0),'CREATE, DELETE, RENAME require LOCKPARENT or WANTPARENT.');
|
|
|
|
docache:=(cnp^.cn_flags and NOCACHE) xor NOCACHE;
|
|
if (cnp^.cn_nameiop=DELETE) or
|
|
((_wantparent and cnp^.cn_nameiop<>CREATE) and
|
|
(cnp^.cn_nameiop<>LOOKUP)) then
|
|
begin
|
|
docache:=0;
|
|
end;
|
|
|
|
_rdonly:=cnp^.cn_flags and RDONLY;
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not ISSYMLINK);
|
|
ndp^.ni_dvp:=nil;
|
|
{
|
|
* We use shared locks until we hit the parent of the last cn then
|
|
* we adjust based on the requesting flags.
|
|
}
|
|
if (lookup_shared<>0) then
|
|
cnp^.cn_lkflags:=LK_SHARED
|
|
else
|
|
cnp^.cn_lkflags:=LK_EXCLUSIVE;
|
|
|
|
dp:=ndp^.ni_startdir;
|
|
ndp^.ni_startdir:=nil;
|
|
vn_lock(dp,compute_cn_lkflags(dp^.v_mount,cnp^.cn_lkflags or LK_RETRY,cnp^.cn_flags),{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
|
|
|
|
dirloop:
|
|
{
|
|
* Search a new directory.
|
|
*
|
|
* The last component of the filename is left accessible via
|
|
* cnp^.cn_nameptr for callers that need the name. Callers needing
|
|
* the name set the SAVENAME flag. When done, they assume
|
|
* responsibility for freeing the pathname buffer.
|
|
}
|
|
cnp^.cn_consume:=0;
|
|
|
|
cp:=cnp^.cn_nameptr;
|
|
while (cp^<>#0) and (cp^<>'/') do Inc(cp);
|
|
|
|
cnp^.cn_namelen:=cp - cnp^.cn_nameptr;
|
|
if (cnp^.cn_namelen > NAME_MAX) then
|
|
begin
|
|
error:=ENAMETOOLONG;
|
|
goto bad;
|
|
end;
|
|
|
|
Dec(ndp^.ni_pathlen,cnp^.cn_namelen);
|
|
ndp^.ni_next:=cp;
|
|
|
|
{
|
|
* Replace multiple slashes by a single slash and trailing slashes
|
|
* by a nil. This must be done before VOP_LOOKUP() because some
|
|
* fs's don't know about trailing slashes. Remember if there were
|
|
* trailing slashes to handle symlinks, existing non-directories
|
|
* and non-existing files that won't be directories specially later.
|
|
}
|
|
while ((cp^='/') and ((cp[1]='/') or (cp[1]=#0))) do
|
|
begin
|
|
Inc(cp);
|
|
Dec(ndp^.ni_pathlen);
|
|
if (cp^=#0) then
|
|
begin
|
|
ndp^.ni_next^:=#0;
|
|
cnp^.cn_flags:=cnp^.cn_flags or TRAILINGSLASH;
|
|
end;
|
|
end;
|
|
ndp^.ni_next:=cp;
|
|
|
|
cnp^.cn_flags:=cnp^.cn_flags or MAKEENTRY;
|
|
if (cp^=#0) and (docache=0) then
|
|
begin
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not MAKEENTRY);
|
|
end;
|
|
|
|
if (cnp^.cn_namelen=2) and
|
|
(cnp^.cn_nameptr[1]='.') and
|
|
(cnp^.cn_nameptr[0]='.') then
|
|
cnp^.cn_flags:=cnp^.cn_flags or ISDOTDOT
|
|
else
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not ISDOTDOT);
|
|
|
|
if (ndp^.ni_next^=#0) then
|
|
cnp^.cn_flags:=cnp^.cn_flags or ISLASTCN
|
|
else
|
|
cnp^.cn_flags:=cnp^.cn_flags and (not ISLASTCN);
|
|
|
|
if ((cnp^.cn_flags and ISLASTCN)<>0) and
|
|
(cnp^.cn_namelen=1) and
|
|
(cnp^.cn_nameptr[0]='.') and
|
|
((cnp^.cn_nameiop=DELETE) or (cnp^.cn_nameiop=RENAME)) then
|
|
begin
|
|
error:=EINVAL;
|
|
goto bad;
|
|
end;
|
|
|
|
{
|
|
* Check for degenerate name (e.g. / or '')
|
|
* which is a way of talking about a directory,
|
|
* e.g. like '/.' or '.'.
|
|
}
|
|
if (cnp^.cn_nameptr[0]=#0) then
|
|
begin
|
|
if (dp^.v_type<>VDIR) then
|
|
begin
|
|
error:=ENOTDIR;
|
|
goto bad;
|
|
end;
|
|
if (cnp^.cn_nameiop<>LOOKUP) then
|
|
begin
|
|
error:=EISDIR;
|
|
goto bad;
|
|
end;
|
|
if (_wantparent<>0) then
|
|
begin
|
|
ndp^.ni_dvp:=dp;
|
|
VREF(dp);
|
|
end;
|
|
ndp^.ni_vp:=dp;
|
|
|
|
if ((cnp^.cn_flags and (LOCKPARENT or LOCKLEAF))=0) then
|
|
begin
|
|
VOP_UNLOCK(dp, 0);
|
|
end;
|
|
|
|
{ XXX This should probably move to the top of function. }
|
|
if ((cnp^.cn_flags and SAVESTART)<>0) then
|
|
begin
|
|
Assert(false,'lookup: SAVESTART');
|
|
end;
|
|
goto success;
|
|
end;
|
|
|
|
{
|
|
* Handle '..': five special cases.
|
|
* 0. If doing a capability lookup, return ENOTCAPABLE (this is a
|
|
* fairly conservative design choice, but it's the only one that we
|
|
* are satisfied guarantees the property we're looking for).
|
|
* 1. Exitan error if this is the last component of
|
|
* the name and the operation is DELETE or RENAME.
|
|
* 2. If at root directory (e.g. after chroot)
|
|
* or at absolute root directory
|
|
* then ignore it so can't get out.
|
|
* 3. If this vnode is the root of a mounted
|
|
* filesystem, then replace it with the
|
|
* vnode which was mounted on so we take the
|
|
* .. in the other filesystem.
|
|
* 4. If the vnode is the top directory of
|
|
* the jail or chroot, don't let them out.
|
|
}
|
|
if ((cnp^.cn_flags and ISDOTDOT)<>0) then
|
|
begin
|
|
if (ndp^.ni_strictrelative<>0) then
|
|
begin
|
|
error:=ENOTCAPABLE;
|
|
goto bad;
|
|
end;
|
|
if ((cnp^.cn_flags and ISLASTCN)<>0) and
|
|
((cnp^.cn_nameiop=DELETE) or (cnp^.cn_nameiop=RENAME)) then
|
|
begin
|
|
error:=EINVAL;
|
|
goto bad;
|
|
end;
|
|
|
|
while (true) do
|
|
begin
|
|
|
|
if (dp=ndp^.ni_rootdir) or
|
|
(dp=ndp^.ni_topdir) or
|
|
(dp=rootvnode) or
|
|
(((dp^.v_vflag and VV_ROOT)<>0) and
|
|
((cnp^.cn_flags and NOCROSSMOUNT)<>0)) then
|
|
begin
|
|
ndp^.ni_dvp:=dp;
|
|
ndp^.ni_vp :=dp;
|
|
vfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
VREF(dp);
|
|
goto nextname;
|
|
end;
|
|
|
|
if ((dp^.v_vflag and VV_ROOT)=0) then
|
|
begin
|
|
break;
|
|
end;
|
|
|
|
if ((dp^.v_iflag and VI_DOOMED)<>0) then { forced unmount }
|
|
begin
|
|
error:=ENOENT;
|
|
goto bad;
|
|
end;
|
|
|
|
tdp:=dp;
|
|
dp:=p_mount(dp^.v_mount)^.mnt_vnodecovered;
|
|
tvfslocked:=dvfslocked;
|
|
dvfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
VREF(dp);
|
|
vput(tdp);
|
|
VFS_UNLOCK_GIANT(tvfslocked);
|
|
vn_lock(dp,compute_cn_lkflags(dp^.v_mount,cnp^.cn_lkflags or LK_RETRY,ISDOTDOT),{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
|
|
end;
|
|
end;
|
|
|
|
{
|
|
* We now have a segment name to search for, and a directory to search.
|
|
}
|
|
unionlookup:
|
|
|
|
//if ((cnp^.cn_flags and NOMACCHECK)=0) begin
|
|
// error:=mac_vnode_check_lookup(cnp^.cn_thread^.td_ucred, dp, cnp);
|
|
// if (error)
|
|
// goto bad;
|
|
//end;
|
|
|
|
ndp^.ni_dvp:=dp;
|
|
ndp^.ni_vp :=nil;
|
|
ASSERT_VOP_LOCKED(dp, 'lookup');
|
|
Assert(vfslocked=0,'lookup: vfslocked %d');
|
|
{
|
|
* If we have a shared lock we may need to upgrade the lock for the
|
|
* last operation.
|
|
}
|
|
if (dp<>vp_crossmp) and
|
|
(VOP_ISLOCKED(dp)=LK_SHARED) and
|
|
((cnp^.cn_flags and ISLASTCN)<>0) and ((cnp^.cn_flags and LOCKPARENT)<>0) then
|
|
begin
|
|
vn_lock(dp, LK_UPGRADE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
|
|
end;
|
|
|
|
if ((dp^.v_iflag and VI_DOOMED)<>0) then
|
|
begin
|
|
error:=ENOENT;
|
|
goto bad;
|
|
end;
|
|
{
|
|
* If we're looking up the last component and we need an exclusive
|
|
* lock, adjust our lkflags.
|
|
}
|
|
if (needs_exclusive_leaf(dp^.v_mount, cnp^.cn_flags)<>0) then
|
|
begin
|
|
cnp^.cn_lkflags:=LK_EXCLUSIVE;
|
|
end;
|
|
|
|
lkflags_save:=cnp^.cn_lkflags;
|
|
cnp^.cn_lkflags:=compute_cn_lkflags(dp^.v_mount, cnp^.cn_lkflags, cnp^.cn_flags);
|
|
|
|
error:=VOP_LOOKUP(dp, @ndp^.ni_vp, cnp);
|
|
if (error<>0) then
|
|
begin
|
|
cnp^.cn_lkflags:=lkflags_save;
|
|
Assert(ndp^.ni_vp=nil, 'leaf should be empty');
|
|
|
|
if (error=ENOENT) and
|
|
((dp^.v_vflag and VV_ROOT)<>0) and
|
|
(dp^.v_mount<>nil) then
|
|
if ((p_mount(dp^.v_mount)^.mnt_flag and MNT_UNION)<>0) then
|
|
begin
|
|
tdp:=dp;
|
|
dp:=p_mount(dp^.v_mount)^.mnt_vnodecovered;
|
|
tvfslocked:=dvfslocked;
|
|
dvfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
VREF(dp);
|
|
vput(tdp);
|
|
VFS_UNLOCK_GIANT(tvfslocked);
|
|
vn_lock(dp,compute_cn_lkflags(dp^.v_mount, cnp^.cn_lkflags or LK_RETRY, cnp^.cn_flags),{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
|
|
goto unionlookup;
|
|
end;
|
|
|
|
if (error<>EJUSTRETURN) then
|
|
begin
|
|
goto bad;
|
|
end;
|
|
{
|
|
* At this point, we know we're at the end of the
|
|
* pathname. If creating / renaming, we can consider
|
|
* allowing the file or directory to be created / renamed,
|
|
* provided we're not on a read-only filesystem.
|
|
}
|
|
if (_rdonly<>0) then
|
|
begin
|
|
error:=EROFS;
|
|
goto bad;
|
|
end;
|
|
{ trailing slash only allowed for directories }
|
|
if ((cnp^.cn_flags and TRAILINGSLASH)<>0) and
|
|
((cnp^.cn_flags and WILLBEDIR)=0) then
|
|
begin
|
|
error:=ENOENT;
|
|
goto bad;
|
|
end;
|
|
if ((cnp^.cn_flags and LOCKPARENT)=0) then
|
|
begin
|
|
VOP_UNLOCK(dp, 0);
|
|
end;
|
|
{
|
|
* We Exit with ni_vp nil to indicate that the entry
|
|
* doesn't currently exist, leaving a pointer to the
|
|
* (possibly locked) directory vnode in ndp^.ni_dvp.
|
|
}
|
|
if ((cnp^.cn_flags and SAVESTART)<>0) then
|
|
begin
|
|
ndp^.ni_startdir:=ndp^.ni_dvp;
|
|
VREF(ndp^.ni_startdir);
|
|
end;
|
|
goto success;
|
|
end else
|
|
begin
|
|
cnp^.cn_lkflags:=lkflags_save;
|
|
end;
|
|
|
|
{
|
|
* Take into account any additional components consumed by
|
|
* the underlying filesystem.
|
|
}
|
|
if (cnp^.cn_consume > 0) then
|
|
begin
|
|
Inc(cnp^.cn_nameptr,cnp^.cn_consume);
|
|
Inc(ndp^.ni_next ,cnp^.cn_consume);
|
|
Dec(ndp^.ni_pathlen,cnp^.cn_consume);
|
|
cnp^.cn_consume:=0;
|
|
end;
|
|
|
|
dp:=ndp^.ni_vp;
|
|
vfslocked:=VFS_LOCK_GIANT(dp^.v_mount);
|
|
|
|
{
|
|
* Check to see if the vnode has been mounted on;
|
|
* if so find the root of the mounted filesystem.
|
|
}
|
|
|
|
mp:=dp^.v_mountedhere;
|
|
while (dp^.v_type=VDIR) and
|
|
(mp<>nil) and
|
|
((cnp^.cn_flags and NOCROSSMOUNT)=0) do
|
|
begin
|
|
|
|
if (vfs_busy(mp, 0)<>0) then
|
|
begin
|
|
continue;
|
|
end;
|
|
|
|
vput(dp);
|
|
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
|
|
vfslocked:=VFS_LOCK_GIANT(mp);
|
|
|
|
if (dp<>ndp^.ni_dvp) then
|
|
vput(ndp^.ni_dvp)
|
|
else
|
|
vrele(ndp^.ni_dvp);
|
|
|
|
VFS_UNLOCK_GIANT(dvfslocked);
|
|
|
|
dvfslocked:=0;
|
|
vref(vp_crossmp);
|
|
ndp^.ni_dvp:=vp_crossmp;
|
|
error:=VFS_ROOT(mp, compute_cn_lkflags(mp, cnp^.cn_lkflags, cnp^.cn_flags), @tdp);
|
|
|
|
vfs_unbusy(mp);
|
|
|
|
if (vn_lock(vp_crossmp, LK_SHARED or LK_NOWAIT,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%})<>0) then
|
|
begin
|
|
Assert(False,'vp_crossmp exclusively locked or reclaimed');
|
|
end;
|
|
|
|
if (error<>0) then
|
|
begin
|
|
dpunlocked:=1;
|
|
goto bad2;
|
|
end;
|
|
dp:=tdp;
|
|
ndp^.ni_vp:=tdp;
|
|
|
|
mp:=dp^.v_mountedhere;
|
|
end;
|
|
|
|
{
|
|
* Check for symbolic link
|
|
}
|
|
if (dp^.v_type=VLNK) and
|
|
(((cnp^.cn_flags and FOLLOW)<>0) or
|
|
((cnp^.cn_flags and TRAILINGSLASH)<>0) or
|
|
(ndp^.ni_next^='/')) then
|
|
begin
|
|
cnp^.cn_flags:=cnp^.cn_flags or ISSYMLINK;
|
|
if ((dp^.v_iflag and VI_DOOMED)<>0) then
|
|
begin
|
|
{
|
|
* We can't know whether the directory was mounted with
|
|
* NOSYMFOLLOW, so we can't follow safely.
|
|
}
|
|
error:=ENOENT;
|
|
goto bad2;
|
|
end;
|
|
if ((p_mount(dp^.v_mount)^.mnt_flag and MNT_NOSYMFOLLOW)<>0) then
|
|
begin
|
|
error:=EACCES;
|
|
goto bad2;
|
|
end;
|
|
{
|
|
* Symlink code always expects an unlocked dvp.
|
|
}
|
|
if (ndp^.ni_dvp<>ndp^.ni_vp) then
|
|
begin
|
|
VOP_UNLOCK(ndp^.ni_dvp, 0);
|
|
ni_dvp_unlocked:=1;
|
|
end;
|
|
goto success;
|
|
end;
|
|
|
|
nextname:
|
|
{
|
|
* Not a symbolic link that we will follow. Continue with the
|
|
* next component if there is any; otherwise, we're done.
|
|
}
|
|
Assert(((cnp^.cn_flags and ISLASTCN)<>0) or (ndp^.ni_next^='/'),'lookup: invalid path state.');
|
|
if (ndp^.ni_next^='/') then
|
|
begin
|
|
cnp^.cn_nameptr:=ndp^.ni_next;
|
|
while (cnp^.cn_nameptr^='/') do
|
|
begin
|
|
Inc(cnp^.cn_nameptr);
|
|
Dec(ndp^.ni_pathlen);
|
|
end;
|
|
|
|
if (ndp^.ni_dvp<>dp) then
|
|
vput(ndp^.ni_dvp)
|
|
else
|
|
vrele(ndp^.ni_dvp);
|
|
|
|
VFS_UNLOCK_GIANT(dvfslocked);
|
|
dvfslocked:=vfslocked; { dp becomes dvp in dirloop }
|
|
vfslocked:=0;
|
|
goto dirloop;
|
|
end;
|
|
{
|
|
* If we're processing a path with a trailing slash,
|
|
* check that the end result is a directory.
|
|
}
|
|
if ((cnp^.cn_flags and TRAILINGSLASH)<>0) and (dp^.v_type<>VDIR) then
|
|
begin
|
|
error:=ENOTDIR;
|
|
goto bad2;
|
|
end;
|
|
{
|
|
* Disallow directory write attempts on read-only filesystems.
|
|
}
|
|
if (_rdonly<>0) and
|
|
((cnp^.cn_nameiop=DELETE) or (cnp^.cn_nameiop=RENAME)) then
|
|
begin
|
|
error:=EROFS;
|
|
goto bad2;
|
|
end;
|
|
if ((cnp^.cn_flags and SAVESTART)<>0) then
|
|
begin
|
|
ndp^.ni_startdir:=ndp^.ni_dvp;
|
|
VREF(ndp^.ni_startdir);
|
|
end;
|
|
if (_wantparent=0) then
|
|
begin
|
|
ni_dvp_unlocked:=2;
|
|
|
|
if (ndp^.ni_dvp<>dp) then
|
|
vput(ndp^.ni_dvp)
|
|
else
|
|
vrele(ndp^.ni_dvp);
|
|
|
|
VFS_UNLOCK_GIANT(dvfslocked);
|
|
dvfslocked:=0;
|
|
end else
|
|
if ((cnp^.cn_flags and LOCKPARENT)=0) and (ndp^.ni_dvp<>dp) then
|
|
begin
|
|
VOP_UNLOCK(ndp^.ni_dvp, 0);
|
|
ni_dvp_unlocked:=1;
|
|
end;
|
|
|
|
if ((cnp^.cn_flags and LOCKLEAF)=0) then
|
|
begin
|
|
VOP_UNLOCK(dp, 0);
|
|
end;
|
|
|
|
success:
|
|
{
|
|
* Because of lookup_shared we may have the vnode shared locked, but
|
|
* the caller may want it to be exclusively locked.
|
|
}
|
|
if (needs_exclusive_leaf(dp^.v_mount, cnp^.cn_flags)<>0) and
|
|
(VOP_ISLOCKED(dp)<>LK_EXCLUSIVE) then
|
|
begin
|
|
vn_lock(dp, LK_UPGRADE or LK_RETRY,{$INCLUDE %FILE%},{$INCLUDE %LINENUM%});
|
|
if ((dp^.v_iflag and VI_DOOMED)<>0) then
|
|
begin
|
|
error:=ENOENT;
|
|
goto bad2;
|
|
end;
|
|
end;
|
|
if (vfslocked<>0) and (dvfslocked<>0) then
|
|
begin
|
|
VFS_UNLOCK_GIANT(dvfslocked); { Only need one }
|
|
end;
|
|
|
|
if (vfslocked<>0) or (dvfslocked<>0) then
|
|
begin
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags or GIANTHELD;
|
|
end;
|
|
|
|
Exit(0);
|
|
|
|
bad2:
|
|
if (ni_dvp_unlocked<>2) then
|
|
begin
|
|
if (dp<>ndp^.ni_dvp) and (ni_dvp_unlocked=0) then
|
|
vput(ndp^.ni_dvp)
|
|
else
|
|
vrele(ndp^.ni_dvp);
|
|
end;
|
|
bad:
|
|
if (dpunlocked=0) then
|
|
begin
|
|
vput(dp);
|
|
end;
|
|
|
|
VFS_UNLOCK_GIANT(vfslocked);
|
|
VFS_UNLOCK_GIANT(dvfslocked);
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags and (not GIANTHELD);
|
|
ndp^.ni_vp:=nil;
|
|
Exit(error);
|
|
end;
|
|
|
|
{
|
|
* relookup - lookup a path name component
|
|
* Used by lookup to re-acquire things.
|
|
}
|
|
{
|
|
int
|
|
relookup(struct vnode *dvp, struct vnode **vpp, struct componentname *cnp)
|
|
begin
|
|
struct vnode *dp:=0; { the directory we are searching }
|
|
int _wantparent; { 1 => wantparent or lockparent flag }
|
|
int _rdonly; { lookup read-only flag bit }
|
|
int error:=0;
|
|
|
|
Assert(cnp^.cn_flags and ISLASTCN,
|
|
('relookup: Not given last component.'));
|
|
{
|
|
* Setup: break out flag bits into variables.
|
|
}
|
|
_wantparent:=cnp^.cn_flags and (LOCKPARENT or WANTPARENT);
|
|
Assert(_wantparent, ('relookup: parent not wanted.'));
|
|
_rdonly:=cnp^.cn_flags and RDONLY;
|
|
cnp^.cn_flags:= and ~ISSYMLINK;
|
|
dp:=dvp;
|
|
cnp^.cn_lkflags:=LK_EXCLUSIVE;
|
|
vn_lock(dp, LK_EXCLUSIVE or LK_RETRY);
|
|
|
|
{
|
|
* Search a new directory.
|
|
*
|
|
* The last component of the filename is left accessible via
|
|
* cnp^.cn_nameptr for callers that need the name. Callers needing
|
|
* the name set the SAVENAME flag. When done, they assume
|
|
* responsibility for freeing the pathname buffer.
|
|
}
|
|
|
|
{
|
|
* Check for '' which represents the root directory after slash
|
|
* removal.
|
|
}
|
|
if (cnp^.cn_nameptr[0]='\0') begin
|
|
{
|
|
* Support only LOOKUP for '/' because lookup()
|
|
* can't succeed for CREATE, DELETE and RENAME.
|
|
}
|
|
Assert(cnp^.cn_nameiop=LOOKUP, ('nameiop must be LOOKUP'));
|
|
Assert(dp^.v_type=VDIR, ('dp is not a directory'));
|
|
|
|
if (!(cnp^.cn_flags and LOCKLEAF))
|
|
VOP_UNLOCK(dp, 0);
|
|
*vpp:=dp;
|
|
{ XXX This should probably move to the top of function. }
|
|
if (cnp^.cn_flags and SAVESTART)
|
|
panic('lookup: SAVESTART');
|
|
Exit(0);
|
|
end;
|
|
|
|
if (cnp^.cn_flags and ISDOTDOT)
|
|
panic ('relookup: lookup on dot-dot');
|
|
|
|
{
|
|
* We now have a segment name to search for, and a directory to search.
|
|
}
|
|
if ((error:=VOP_LOOKUP(dp, vpp, cnp))<>0) begin
|
|
Assert(*vpp=nil, ('leaf should be empty'));
|
|
if (error<>EJUSTRETURN)
|
|
goto bad;
|
|
{
|
|
* If creating and at end of pathname, then can consider
|
|
* allowing file to be created.
|
|
}
|
|
if (_rdonly) begin
|
|
error:=EROFS;
|
|
goto bad;
|
|
end;
|
|
{ ASSERT(dvp=ndp^.ni_startdir) }
|
|
if (cnp^.cn_flags and SAVESTART)
|
|
VREF(dvp);
|
|
if ((cnp^.cn_flags and LOCKPARENT)=0)
|
|
VOP_UNLOCK(dp, 0);
|
|
{
|
|
* We Exitwith ni_vp nil to indicate that the entry
|
|
* doesn't currently exist, leaving a pointer to the
|
|
* (possibly locked) directory vnode in ndp^.ni_dvp.
|
|
}
|
|
Exit(0);
|
|
end;
|
|
|
|
dp:=*vpp;
|
|
|
|
{
|
|
* Disallow directory write attempts on read-only filesystems.
|
|
}
|
|
if (_rdonly and
|
|
(cnp^.cn_nameiop=DELETE or cnp^.cn_nameiop=RENAME)) begin
|
|
if (dvp=dp)
|
|
vrele(dvp);
|
|
else
|
|
vput(dvp);
|
|
error:=EROFS;
|
|
goto bad;
|
|
end;
|
|
{
|
|
* Set the parent lock/ref state to the requested state.
|
|
}
|
|
if ((cnp^.cn_flags and LOCKPARENT)=0 and dvp<>dp) begin
|
|
if (_wantparent)
|
|
VOP_UNLOCK(dvp, 0);
|
|
else
|
|
vput(dvp);
|
|
end; else if (!_wantparent)
|
|
vrele(dvp);
|
|
{
|
|
* Check for symbolic link
|
|
}
|
|
Assert(dp^.v_type<>VLNK or !(cnp^.cn_flags and FOLLOW),
|
|
('relookup: symlink found.\n'));
|
|
|
|
{ ASSERT(dvp=ndp^.ni_startdir) }
|
|
if (cnp^.cn_flags and SAVESTART)
|
|
VREF(dvp);
|
|
|
|
if ((cnp^.cn_flags and LOCKLEAF)=0)
|
|
VOP_UNLOCK(dp, 0);
|
|
Exit(0);
|
|
bad:
|
|
vput(dp);
|
|
*vpp:=nil;
|
|
Exit(error);
|
|
end;
|
|
}
|
|
|
|
{
|
|
* Free data allocated by namei(); see namei(9) for details.
|
|
}
|
|
procedure NDFREE(ndp:p_nameidata;flags:Integer); public;
|
|
var
|
|
unlock_dvp:Integer;
|
|
unlock_vp :Integer;
|
|
begin
|
|
unlock_dvp:=0;
|
|
unlock_vp:=0;
|
|
|
|
if ((flags and NDF_NO_FREE_PNBUF)=0) and
|
|
((ndp^.ni_cnd.cn_flags and HASBUF<>0)) then
|
|
begin
|
|
FreeMem(ndp^.ni_cnd.cn_pnbuf);
|
|
ndp^.ni_cnd.cn_flags:=ndp^.ni_cnd.cn_flags and (not HASBUF);
|
|
end;
|
|
|
|
if ((flags and NDF_NO_VP_UNLOCK)=0) and
|
|
((ndp^.ni_cnd.cn_flags and LOCKLEAF)<>0) and
|
|
(ndp^.ni_vp<>nil) then
|
|
begin
|
|
unlock_vp:=1;
|
|
end;
|
|
|
|
if (((flags and NDF_NO_VP_RELE)=0) and (ndp^.ni_vp<>nil)) then
|
|
begin
|
|
if (unlock_vp<>0) then
|
|
begin
|
|
vput(ndp^.ni_vp);
|
|
unlock_vp:=0;
|
|
end else
|
|
begin
|
|
vrele(ndp^.ni_vp);
|
|
end;
|
|
ndp^.ni_vp:=nil;
|
|
end;
|
|
|
|
if (unlock_vp<>0) then
|
|
begin
|
|
VOP_UNLOCK(ndp^.ni_vp, 0);
|
|
end;
|
|
|
|
if ((flags and NDF_NO_DVP_UNLOCK)=0) and
|
|
((ndp^.ni_cnd.cn_flags and LOCKPARENT)<>0) and
|
|
(ndp^.ni_dvp<>ndp^.ni_vp) then
|
|
begin
|
|
unlock_dvp:=1;
|
|
end;
|
|
|
|
if ((flags and NDF_NO_DVP_RELE)=0) and
|
|
((ndp^.ni_cnd.cn_flags and (LOCKPARENT or WANTPARENT))<>0) then
|
|
begin
|
|
if (unlock_dvp<>0) then
|
|
begin
|
|
vput(ndp^.ni_dvp);
|
|
unlock_dvp:=0;
|
|
end else
|
|
begin
|
|
vrele(ndp^.ni_dvp);
|
|
end;
|
|
ndp^.ni_dvp:=nil;
|
|
end;
|
|
|
|
if (unlock_dvp<>0) then
|
|
begin
|
|
VOP_UNLOCK(ndp^.ni_dvp, 0);
|
|
end;
|
|
|
|
if ((flags and NDF_NO_STARTDIR_RELE)=0) and
|
|
((ndp^.ni_cnd.cn_flags and SAVESTART)<>0) then
|
|
begin
|
|
vrele(ndp^.ni_startdir);
|
|
ndp^.ni_startdir:=nil;
|
|
end;
|
|
end;
|
|
|
|
|
|
end.
|
|
|