Commit | Line | Data |
---|---|---|
da7c5cc6 | 1 | /* |
6d0f0ece KM |
2 | * Copyright (c) 1982, 1986, 1989 Regents of the University of California. |
3 | * All rights reserved. | |
da7c5cc6 | 4 | * |
dbf0c423 | 5 | * %sccs.include.redist.c% |
6d0f0ece | 6 | * |
743467cb | 7 | * @(#)vfs_lookup.c 7.36 (Berkeley) %G% |
da7c5cc6 | 8 | */ |
10873320 | 9 | |
94368568 | 10 | #include "param.h" |
955e7a55 | 11 | #include "syslimits.h" |
6d0f0ece KM |
12 | #include "time.h" |
13 | #include "namei.h" | |
14 | #include "vnode.h" | |
94368568 | 15 | #include "mount.h" |
6d0f0ece | 16 | #include "errno.h" |
c3a74062 | 17 | #include "malloc.h" |
5e00df3b | 18 | #include "filedesc.h" |
658f5fdc | 19 | #include "proc.h" |
0caff0ad KM |
20 | |
21 | #ifdef KTRACE | |
658f5fdc MT |
22 | #include "ktrace.h" |
23 | #endif | |
10873320 BJ |
24 | |
25 | /* | |
7f69b0e6 | 26 | * Convert a pathname into a pointer to a locked inode. |
4f083fd7 | 27 | * |
d870be74 | 28 | * The FOLLOW flag is set when symbolic links are to be followed |
4f083fd7 | 29 | * when they occur at the end of the name translation process. |
7f69b0e6 KM |
30 | * Symbolic links are always followed for all other pathname |
31 | * components other than the last. | |
32 | * | |
33 | * The segflg defines whether the name is to be copied from user | |
34 | * space or kernel space. | |
10873320 | 35 | * |
f93197fc | 36 | * Overall outline of namei: |
6bd0bb92 BJ |
37 | * |
38 | * copy in name | |
39 | * get starting directory | |
955e7a55 KM |
40 | * while (!done && !error) { |
41 | * call lookup to search path. | |
42 | * if symbolic link, massage name in buffer and continue | |
43 | * } | |
10873320 | 44 | */ |
743467cb JH |
45 | int |
46 | namei(ndp) | |
d870be74 | 47 | register struct nameidata *ndp; |
10873320 | 48 | { |
5e00df3b | 49 | register struct filedesc *fdp; /* pointer to file descriptor state */ |
6bd0bb92 | 50 | register char *cp; /* pointer into pathname argument */ |
955e7a55 KM |
51 | register struct vnode *dp; /* the directory we are searching */ |
52 | struct iovec aiov; /* uio for reading symbolic links */ | |
53 | struct uio auio; | |
54 | int error, linklen; | |
743467cb | 55 | struct componentname *cnp = &ndp->ni_cnd; |
10873320 | 56 | |
743467cb JH |
57 | ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_proc->p_ucred; |
58 | #ifdef DIAGNOSTIC | |
59 | if (!cnp->cn_cred || !cnp->cn_proc) | |
60 | panic ("namei: bad cred/proc"); | |
61 | if (cnp->cn_nameiop & (~OPMASK)) | |
62 | panic ("namei: nameiop contaminated with flags"); | |
63 | if (cnp->cn_flags & OPMASK) | |
64 | panic ("namei: flags contaminated with nameiops"); | |
65 | #endif | |
66 | fdp = cnp->cn_proc->p_fd; | |
955e7a55 | 67 | |
f5039631 | 68 | /* |
6bd0bb92 BJ |
69 | * Get a buffer for the name to be translated, and copy the |
70 | * name into the buffer. | |
f5039631 | 71 | */ |
743467cb JH |
72 | if ((cnp->cn_flags & HASBUF) == 0) |
73 | MALLOC(cnp->cn_pnbuf, caddr_t, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
955e7a55 | 74 | if (ndp->ni_segflg == UIO_SYSSPACE) |
743467cb | 75 | error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, |
955e7a55 KM |
76 | MAXPATHLEN, &ndp->ni_pathlen); |
77 | else | |
743467cb | 78 | error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, |
955e7a55 KM |
79 | MAXPATHLEN, &ndp->ni_pathlen); |
80 | if (error) { | |
743467cb | 81 | free(cnp->cn_pnbuf, M_NAMEI); |
955e7a55 KM |
82 | ndp->ni_vp = NULL; |
83 | return (error); | |
d870be74 | 84 | } |
6d0f0ece | 85 | ndp->ni_loopcnt = 0; |
658f5fdc | 86 | #ifdef KTRACE |
743467cb JH |
87 | if (KTRPOINT(cnp->cn_proc, KTR_NAMEI)) |
88 | ktrnamei(cnp->cn_proc->p_tracep, cnp->cn_pnbuf); | |
658f5fdc | 89 | #endif |
6bd0bb92 | 90 | |
5f9e231a KM |
91 | /* |
92 | * Get starting point for the translation. | |
93 | */ | |
955e7a55 KM |
94 | if ((ndp->ni_rootdir = fdp->fd_rdir) == NULL) |
95 | ndp->ni_rootdir = rootdir; | |
96 | dp = fdp->fd_cdir; | |
5f9e231a | 97 | VREF(dp); |
955e7a55 KM |
98 | for (;;) { |
99 | /* | |
100 | * Check if root directory should replace current directory. | |
101 | * Done at start of translation and after symbolic link. | |
102 | */ | |
743467cb JH |
103 | cnp->cn_nameptr = cnp->cn_pnbuf; |
104 | if (*(cnp->cn_nameptr) == '/') { | |
955e7a55 | 105 | vrele(dp); |
743467cb JH |
106 | while (*(cnp->cn_nameptr) == '/') { |
107 | cnp->cn_nameptr++; | |
955e7a55 KM |
108 | ndp->ni_pathlen--; |
109 | } | |
110 | dp = ndp->ni_rootdir; | |
111 | VREF(dp); | |
112 | } | |
113 | ndp->ni_startdir = dp; | |
743467cb JH |
114 | if (error = lookup(ndp)) { |
115 | FREE(cnp->cn_pnbuf, M_NAMEI); | |
955e7a55 KM |
116 | return (error); |
117 | } | |
118 | /* | |
119 | * Check for symbolic link | |
120 | */ | |
743467cb JH |
121 | if ((cnp->cn_flags & ISSYMLINK) == 0) { |
122 | if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0) | |
123 | FREE(cnp->cn_pnbuf, M_NAMEI); | |
955e7a55 | 124 | else |
743467cb | 125 | cnp->cn_flags |= HASBUF; |
955e7a55 KM |
126 | return (0); |
127 | } | |
743467cb | 128 | if ((cnp->cn_flags & LOCKPARENT) && ndp->ni_pathlen == 1) |
955e7a55 KM |
129 | VOP_UNLOCK(ndp->ni_dvp); |
130 | if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { | |
131 | error = ELOOP; | |
132 | break; | |
133 | } | |
134 | if (ndp->ni_pathlen > 1) | |
135 | MALLOC(cp, char *, MAXPATHLEN, M_NAMEI, M_WAITOK); | |
136 | else | |
743467cb | 137 | cp = cnp->cn_pnbuf; |
955e7a55 KM |
138 | aiov.iov_base = cp; |
139 | aiov.iov_len = MAXPATHLEN; | |
140 | auio.uio_iov = &aiov; | |
141 | auio.uio_iovcnt = 1; | |
142 | auio.uio_offset = 0; | |
143 | auio.uio_rw = UIO_READ; | |
144 | auio.uio_segflg = UIO_SYSSPACE; | |
145 | auio.uio_procp = (struct proc *)0; | |
146 | auio.uio_resid = MAXPATHLEN; | |
743467cb | 147 | if (error = VOP_READLINK(ndp->ni_vp, &auio, cnp->cn_cred)) { |
955e7a55 KM |
148 | if (ndp->ni_pathlen > 1) |
149 | free(cp, M_NAMEI); | |
150 | break; | |
151 | } | |
152 | linklen = MAXPATHLEN - auio.uio_resid; | |
153 | if (linklen + ndp->ni_pathlen >= MAXPATHLEN) { | |
154 | if (ndp->ni_pathlen > 1) | |
155 | free(cp, M_NAMEI); | |
156 | error = ENAMETOOLONG; | |
157 | break; | |
6d0f0ece | 158 | } |
955e7a55 KM |
159 | if (ndp->ni_pathlen > 1) { |
160 | bcopy(ndp->ni_next, cp + linklen, ndp->ni_pathlen); | |
743467cb JH |
161 | FREE(cnp->cn_pnbuf, M_NAMEI); |
162 | cnp->cn_pnbuf = cp; | |
955e7a55 | 163 | } else |
743467cb | 164 | cnp->cn_pnbuf[linklen] = '\0'; |
955e7a55 KM |
165 | ndp->ni_pathlen += linklen; |
166 | vput(ndp->ni_vp); | |
167 | dp = ndp->ni_dvp; | |
6d0f0ece | 168 | } |
743467cb | 169 | FREE(cnp->cn_pnbuf, M_NAMEI); |
955e7a55 KM |
170 | vrele(ndp->ni_dvp); |
171 | vput(ndp->ni_vp); | |
172 | ndp->ni_vp = NULL; | |
173 | return (error); | |
174 | } | |
175 | ||
176 | /* | |
177 | * Search a pathname. | |
178 | * This is a very central and rather complicated routine. | |
179 | * | |
180 | * The pathname is pointed to by ni_ptr and is of length ni_pathlen. | |
181 | * The starting directory is taken from ni_startdir. The pathname is | |
182 | * descended until done, or a symbolic link is encountered. The variable | |
183 | * ni_more is clear if the path is completed; it is set to one if a | |
184 | * symbolic link needing interpretation is encountered. | |
185 | * | |
186 | * The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on | |
187 | * whether the name is to be looked up, created, renamed, or deleted. | |
188 | * When CREATE, RENAME, or DELETE is specified, information usable in | |
189 | * creating, renaming, or deleting a directory entry may be calculated. | |
190 | * If flag has LOCKPARENT or'ed into it, the parent directory is returned | |
191 | * locked. If flag has WANTPARENT or'ed into it, the parent directory is | |
192 | * returned unlocked. Otherwise the parent directory is not returned. If | |
193 | * the target of the pathname exists and LOCKLEAF is or'ed into the flag | |
194 | * the target is returned locked, otherwise it is returned unlocked. | |
195 | * When creating or renaming and LOCKPARENT is specified, the target may not | |
196 | * be ".". When deleting and LOCKPARENT is specified, the target may be ".". | |
197 | * NOTE: (LOOKUP | LOCKPARENT) currently returns the parent vnode unlocked. | |
198 | * | |
199 | * Overall outline of lookup: | |
200 | * | |
201 | * dirloop: | |
202 | * identify next component of name at ndp->ni_ptr | |
203 | * handle degenerate case where name is null string | |
204 | * if .. and crossing mount points and on mounted filesys, find parent | |
205 | * call VOP_LOOKUP routine for next component name | |
206 | * directory vnode returned in ni_dvp, unlocked unless LOCKPARENT set | |
207 | * component vnode returned in ni_vp (if it exists), locked. | |
208 | * if result vnode is mounted on and crossing mount points, | |
209 | * find mounted on vnode | |
210 | * if more components of name, do next level at dirloop | |
211 | * return the answer in ni_vp, locked if LOCKLEAF set | |
212 | * if LOCKPARENT set, return locked parent in ni_dvp | |
213 | * if WANTPARENT set, return unlocked parent in ni_dvp | |
214 | */ | |
743467cb JH |
215 | int |
216 | lookup(ndp) | |
955e7a55 | 217 | register struct nameidata *ndp; |
955e7a55 KM |
218 | { |
219 | register char *cp; /* pointer into pathname argument */ | |
220 | register struct vnode *dp = 0; /* the directory we are searching */ | |
221 | struct vnode *tdp; /* saved dp */ | |
222 | struct mount *mp; /* mount table entry */ | |
223 | int docache; /* == 0 do not cache last component */ | |
955e7a55 | 224 | int wantparent; /* 1 => wantparent or lockparent flag */ |
6c235bc4 | 225 | int rdonly; /* lookup read-only flag bit */ |
955e7a55 | 226 | int error = 0; |
cfef4373 | 227 | struct componentname *cnp = &ndp->ni_cnd; |
6bd0bb92 | 228 | |
10873320 | 229 | /* |
955e7a55 | 230 | * Setup: break out flag bits into variables. |
10873320 | 231 | */ |
743467cb | 232 | wantparent = cnp->cn_flags & (LOCKPARENT | WANTPARENT); |
cfef4373 | 233 | docache = (cnp->cn_flags & NOCACHE) ^ NOCACHE; |
743467cb JH |
234 | if (cnp->cn_nameiop == DELETE || |
235 | (wantparent && cnp->cn_nameiop != CREATE)) | |
955e7a55 | 236 | docache = 0; |
cfef4373 | 237 | rdonly = cnp->cn_flags & RDONLY; |
955e7a55 | 238 | ndp->ni_dvp = NULL; |
cfef4373 | 239 | cnp->cn_flags &= ~ISSYMLINK; |
955e7a55 KM |
240 | dp = ndp->ni_startdir; |
241 | ndp->ni_startdir = NULLVP; | |
242 | VOP_LOCK(dp); | |
243 | ||
6bd0bb92 | 244 | dirloop: |
6bd0bb92 | 245 | /* |
955e7a55 KM |
246 | * Search a new directory. |
247 | * | |
cfef4373 | 248 | * The cn_hash value is for use by vfs_cache. |
955e7a55 | 249 | * The last component of the filename is left accessible via |
cfef4373 | 250 | * cnp->cn_nameptr for callers that need the name. Callers needing |
955e7a55 KM |
251 | * the name set the SAVENAME flag. When done, they assume |
252 | * responsibility for freeing the pathname buffer. | |
6bd0bb92 | 253 | */ |
cfef4373 JH |
254 | cnp->cn_hash = 0; |
255 | for (cp = cnp->cn_nameptr; *cp != 0 && *cp != '/'; cp++) | |
256 | cnp->cn_hash += (unsigned char)*cp; | |
257 | cnp->cn_namelen = cp - cnp->cn_nameptr; | |
258 | if (cnp->cn_namelen >= NAME_MAX) { | |
955e7a55 KM |
259 | error = ENAMETOOLONG; |
260 | goto bad; | |
261 | } | |
6d0f0ece | 262 | #ifdef NAMEI_DIAGNOSTIC |
955e7a55 KM |
263 | { char c = *cp; |
264 | *cp = '\0'; | |
cfef4373 | 265 | printf("{%s}: ", cnp->cn_nameptr); |
955e7a55 | 266 | *cp = c; } |
6d0f0ece | 267 | #endif |
cfef4373 | 268 | ndp->ni_pathlen -= cnp->cn_namelen; |
955e7a55 | 269 | ndp->ni_next = cp; |
cfef4373 | 270 | cnp->cn_flags |= MAKEENTRY; |
87c05e6e | 271 | if (*cp == '\0' && docache == 0) |
cfef4373 JH |
272 | cnp->cn_flags &= ~MAKEENTRY; |
273 | if (cnp->cn_namelen == 2 && | |
274 | cnp->cn_nameptr[1] == '.' && cnp->cn_nameptr[0] == '.') | |
275 | cnp->cn_flags |= ISDOTDOT; | |
276 | else cnp->cn_flags &= ~ISDOTDOT; | |
277 | if (*ndp->ni_next == 0) | |
278 | cnp->cn_flags |= ISLASTCN; | |
279 | else cnp->cn_flags &= ~ISLASTCN; | |
280 | ||
6bd0bb92 BJ |
281 | |
282 | /* | |
283 | * Check for degenerate name (e.g. / or "") | |
284 | * which is a way of talking about a directory, | |
285 | * e.g. like "/." or ".". | |
286 | */ | |
cfef4373 JH |
287 | if (cnp->cn_nameptr[0] == '\0') { |
288 | if (cnp->cn_nameiop != LOOKUP || wantparent) { | |
6d0f0ece | 289 | error = EISDIR; |
6bd0bb92 | 290 | goto bad; |
f5039631 | 291 | } |
955e7a55 KM |
292 | if (dp->v_type != VDIR) { |
293 | error = ENOTDIR; | |
294 | goto bad; | |
295 | } | |
cfef4373 | 296 | if (!(cnp->cn_flags & LOCKLEAF)) |
6d0f0ece KM |
297 | VOP_UNLOCK(dp); |
298 | ndp->ni_vp = dp; | |
cfef4373 | 299 | if (cnp->cn_flags & SAVESTART) |
955e7a55 | 300 | panic("lookup: SAVESTART"); |
743467cb | 301 | return (0); |
f5039631 | 302 | } |
6bd0bb92 | 303 | |
e47da406 | 304 | /* |
6d0f0ece KM |
305 | * Handle "..": two special cases. |
306 | * 1. If at root directory (e.g. after chroot) | |
307 | * then ignore it so can't get out. | |
308 | * 2. If this vnode is the root of a mounted | |
955e7a55 | 309 | * filesystem, then replace it with the |
6d0f0ece KM |
310 | * vnode which was mounted on so we take the |
311 | * .. in the other file system. | |
e47da406 | 312 | */ |
cfef4373 | 313 | if (cnp->cn_flags & ISDOTDOT) { |
e47da406 | 314 | for (;;) { |
955e7a55 | 315 | if (dp == ndp->ni_rootdir) { |
6d0f0ece | 316 | ndp->ni_dvp = dp; |
7655c64a | 317 | ndp->ni_vp = dp; |
8fe1c702 | 318 | VREF(dp); |
6d0f0ece | 319 | goto nextname; |
e47da406 | 320 | } |
d5cea2aa | 321 | if ((dp->v_flag & VROOT) == 0 || |
cfef4373 | 322 | (cnp->cn_flags & NOCROSSMOUNT)) |
e47da406 | 323 | break; |
6d0f0ece | 324 | tdp = dp; |
54fb9dc2 | 325 | dp = dp->v_mount->mnt_vnodecovered; |
6d0f0ece | 326 | vput(tdp); |
8fe1c702 | 327 | VREF(dp); |
7655c64a | 328 | VOP_LOCK(dp); |
e47da406 KM |
329 | } |
330 | } | |
331 | ||
f93197fc KM |
332 | /* |
333 | * We now have a segment name to search for, and a directory to search. | |
6459ebe0 | 334 | */ |
cfef4373 JH |
335 | ndp->ni_dvp = dp; |
336 | if (error = VOP_LOOKUP(dp, &ndp->ni_vp, cnp)) { | |
955e7a55 | 337 | #ifdef DIAGNOSTIC |
6d0f0ece KM |
338 | if (ndp->ni_vp != NULL) |
339 | panic("leaf should be empty"); | |
955e7a55 | 340 | #endif |
658f5fdc | 341 | #ifdef NAMEI_DIAGNOSTIC |
6d0f0ece | 342 | printf("not found\n"); |
658f5fdc | 343 | #endif |
cfef4373 | 344 | if (cnp->cn_nameiop == LOOKUP || cnp->cn_nameiop == DELETE || |
1f6ef9f5 KM |
345 | error != ENOENT || *cp != 0) |
346 | goto bad; | |
f5039631 | 347 | /* |
6d0f0ece KM |
348 | * If creating and at end of pathname, then can consider |
349 | * allowing file to be created. | |
f5039631 | 350 | */ |
6c235bc4 | 351 | if (rdonly || (ndp->ni_dvp->v_mount->mnt_flag & MNT_RDONLY)) { |
6d0f0ece | 352 | error = EROFS; |
6bd0bb92 | 353 | goto bad; |
1f6ef9f5 | 354 | } |
f5039631 | 355 | /* |
6d0f0ece KM |
356 | * We return with ni_vp NULL to indicate that the entry |
357 | * doesn't currently exist, leaving a pointer to the | |
358 | * (possibly locked) directory inode in ndp->ni_dvp. | |
f5039631 | 359 | */ |
cfef4373 | 360 | if (cnp->cn_flags & SAVESTART) { |
955e7a55 KM |
361 | ndp->ni_startdir = ndp->ni_dvp; |
362 | VREF(ndp->ni_startdir); | |
b8c8091e | 363 | p->p_spare[1]++; |
955e7a55 | 364 | } |
743467cb | 365 | return (0); |
6bd0bb92 | 366 | } |
658f5fdc | 367 | #ifdef NAMEI_DIAGNOSTIC |
6d0f0ece | 368 | printf("found\n"); |
658f5fdc | 369 | #endif |
6bd0bb92 | 370 | |
955e7a55 | 371 | dp = ndp->ni_vp; |
6bd0bb92 | 372 | /* |
6d0f0ece | 373 | * Check for symbolic link |
4f083fd7 | 374 | */ |
6d0f0ece | 375 | if ((dp->v_type == VLNK) && |
cfef4373 JH |
376 | ((cnp->cn_flags & FOLLOW) || *ndp->ni_next == '/')) { |
377 | cnp->cn_flags |= ISSYMLINK; | |
743467cb | 378 | return (0); |
f93197fc KM |
379 | } |
380 | ||
6bd0bb92 | 381 | /* |
6d0f0ece KM |
382 | * Check to see if the vnode has been mounted on; |
383 | * if so find the root of the mounted file system. | |
6bd0bb92 | 384 | */ |
6d0f0ece | 385 | mntloop: |
d5cea2aa | 386 | while (dp->v_type == VDIR && (mp = dp->v_mountedhere) && |
cfef4373 | 387 | (cnp->cn_flags & NOCROSSMOUNT) == 0) { |
17c70d83 | 388 | if (mp->mnt_flag & MNT_MLOCK) { |
54fb9dc2 | 389 | mp->mnt_flag |= MNT_MWAIT; |
6d0f0ece KM |
390 | sleep((caddr_t)mp, PVFS); |
391 | goto mntloop; | |
6bd0bb92 | 392 | } |
955e7a55 | 393 | if (error = VFS_ROOT(dp->v_mountedhere, &tdp)) |
6bd0bb92 | 394 | goto bad2; |
6d0f0ece KM |
395 | vput(dp); |
396 | ndp->ni_vp = dp = tdp; | |
10873320 | 397 | } |
6bd0bb92 | 398 | |
6d0f0ece | 399 | nextname: |
10873320 | 400 | /* |
6bd0bb92 BJ |
401 | * Not a symbolic link. If more pathname, |
402 | * continue at next component, else return. | |
10873320 | 403 | */ |
955e7a55 | 404 | if (*ndp->ni_next == '/') { |
cfef4373 JH |
405 | cnp->cn_nameptr = ndp->ni_next; |
406 | while (*cnp->cn_nameptr == '/') { | |
407 | cnp->cn_nameptr++; | |
6d0f0ece | 408 | ndp->ni_pathlen--; |
6bd0bb92 | 409 | } |
6d0f0ece KM |
410 | vrele(ndp->ni_dvp); |
411 | goto dirloop; | |
6bd0bb92 BJ |
412 | } |
413 | /* | |
01633fea | 414 | * Check for read-only file systems. |
6bd0bb92 | 415 | */ |
cfef4373 | 416 | if (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME) { |
dfc0e8dd | 417 | /* |
01633fea KM |
418 | * Disallow directory write attempts on read-only |
419 | * file systems. | |
dfc0e8dd | 420 | */ |
6c235bc4 KM |
421 | if (rdonly || (dp->v_mount->mnt_flag & MNT_RDONLY) || |
422 | (wantparent && | |
423 | (ndp->ni_dvp->v_mount->mnt_flag & MNT_RDONLY))) { | |
dfc0e8dd KM |
424 | error = EROFS; |
425 | goto bad2; | |
426 | } | |
427 | } | |
cfef4373 | 428 | if (cnp->cn_flags & SAVESTART) { |
955e7a55 | 429 | ndp->ni_startdir = ndp->ni_dvp; |
b8c8091e | 430 | p->p_spare[1]++; |
955e7a55 KM |
431 | VREF(ndp->ni_startdir); |
432 | } | |
6d0f0ece KM |
433 | if (!wantparent) |
434 | vrele(ndp->ni_dvp); | |
cfef4373 | 435 | if ((cnp->cn_flags & LOCKLEAF) == 0) |
6d0f0ece | 436 | VOP_UNLOCK(dp); |
743467cb | 437 | return (0); |
4f083fd7 | 438 | |
6d0f0ece | 439 | bad2: |
cfef4373 | 440 | if ((cnp->cn_flags & LOCKPARENT) && *ndp->ni_next == '\0') |
206be3f9 | 441 | VOP_UNLOCK(ndp->ni_dvp); |
6d0f0ece KM |
442 | vrele(ndp->ni_dvp); |
443 | bad: | |
444 | vput(dp); | |
445 | ndp->ni_vp = NULL; | |
743467cb | 446 | return (error); |
b1aa93b9 | 447 | } |
cfef4373 JH |
448 | |
449 |