]>
Commit | Line | Data |
---|---|---|
1c79356b A |
1 | /* |
2 | * Copyright (c) 1999 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * The contents of this file constitute Original Code as defined in and | |
7 | * are subject to the Apple Public Source License Version 1.1 (the | |
8 | * "License"). You may not use this file except in compliance with the | |
9 | * License. Please obtain a copy of the License at | |
10 | * http://www.apple.com/publicsource and read it before using this file. | |
11 | * | |
12 | * This Original Code and all software distributed under the License are | |
13 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
14 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
15 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the | |
17 | * License for the specific language governing rights and limitations | |
18 | * under the License. | |
19 | * | |
20 | * @APPLE_LICENSE_HEADER_END@ | |
21 | */ | |
22 | /* | |
23 | * Copyright (c) 1989, 1993 | |
24 | * The Regents of the University of California. All rights reserved. | |
25 | * (c) UNIX System Laboratories, Inc. | |
26 | * All or some portions of this file are derived from material licensed | |
27 | * to the University of California by American Telephone and Telegraph | |
28 | * Co. or Unix System Laboratories, Inc. and are reproduced herein with | |
29 | * the permission of UNIX System Laboratories, Inc. | |
30 | * | |
31 | * Redistribution and use in source and binary forms, with or without | |
32 | * modification, are permitted provided that the following conditions | |
33 | * are met: | |
34 | * 1. Redistributions of source code must retain the above copyright | |
35 | * notice, this list of conditions and the following disclaimer. | |
36 | * 2. Redistributions in binary form must reproduce the above copyright | |
37 | * notice, this list of conditions and the following disclaimer in the | |
38 | * documentation and/or other materials provided with the distribution. | |
39 | * 3. All advertising materials mentioning features or use of this software | |
40 | * must display the following acknowledgement: | |
41 | * This product includes software developed by the University of | |
42 | * California, Berkeley and its contributors. | |
43 | * 4. Neither the name of the University nor the names of its contributors | |
44 | * may be used to endorse or promote products derived from this software | |
45 | * without specific prior written permission. | |
46 | * | |
47 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
48 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
49 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
50 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
51 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
52 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
53 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
54 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
55 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
56 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
57 | * SUCH DAMAGE. | |
58 | * | |
59 | * @(#)hfs_lookup.c 1.0 | |
60 | * derived from @(#)ufs_lookup.c 8.15 (Berkeley) 6/16/95 | |
61 | * | |
62 | * (c) 1998-1999 Apple Computer, Inc. All Rights Reserved | |
63 | * (c) 1990, 1992 NeXT Computer, Inc. All Rights Reserved | |
64 | * | |
65 | * | |
66 | * hfs_lookup.c -- code to handle directory traversal on HFS/HFS+ volume | |
67 | * | |
68 | * MODIFICATION HISTORY: | |
69 | * 21-May-1999 Don Brady Add support for HFS rooting. | |
70 | * 25-Feb-1999 Clark Warner Fixed the error case of VFS_VGGET when | |
71 | * processing DotDot (..) to relock parent | |
72 | * 23-Feb-1999 Pat Dirks Finish cleanup around Don's last fix in "." and ".." handling. | |
73 | * 11-Nov-1998 Don Brady Take out VFS_VGET that got added as part of previous fix. | |
74 | * 14-Oct-1998 Don Brady Fix locking policy volation in hfs_lookup for ".." case | |
75 | * (radar #2279902). | |
76 | * 4-Jun-1998 Pat Dirks Split off from hfs_vnodeops.c | |
77 | */ | |
78 | ||
79 | #include <sys/param.h> | |
80 | #include <sys/namei.h> | |
81 | #include <sys/buf.h> | |
82 | #include <sys/file.h> | |
83 | #include <sys/mount.h> | |
84 | #include <sys/vnode.h> | |
85 | #include <sys/malloc.h> | |
86 | #include <sys/paths.h> | |
87 | ||
88 | #include "hfs.h" | |
89 | #include "hfs_dbg.h" | |
90 | #include "hfscommon/headers/FileMgrInternal.h" | |
91 | ||
92 | u_int16_t GetForkFromName(struct componentname *cnp); | |
93 | int hfs_vget_sibling(struct vnode *vdp, u_int16_t forkType, struct vnode **vpp); | |
94 | int hfs_vget_catinfo(struct vnode *parent_vp, struct hfsCatalogInfo *catInfo, u_int32_t forkType, struct vnode **target_vpp); | |
95 | ||
96 | /* | |
97 | * XXX SER fork strings. | |
98 | * Put these someplace better | |
99 | */ | |
100 | #define gHFSForkIdentStr "/" | |
101 | #define gDataForkNameStr "data" | |
102 | #define gRsrcForkNameStr "rsrc" | |
103 | ||
104 | ||
105 | #if DBG_VOP_TEST_LOCKS | |
106 | extern void DbgVopTest(int maxSlots, int retval, VopDbgStoreRec *VopDbgStore, char *funcname); | |
107 | #endif | |
108 | ||
109 | /***************************************************************************** | |
110 | * | |
111 | * Operations on vnodes | |
112 | * | |
113 | *****************************************************************************/ | |
114 | ||
115 | ||
116 | /* | |
117 | * FROM FREEBSD 3.1 | |
118 | * Convert a component of a pathname into a pointer to a locked hfsnode. | |
119 | * This is a very central and rather complicated routine. | |
120 | * If the file system is not maintained in a strict tree hierarchy, | |
121 | * this can result in a deadlock situation (see comments in code below). | |
122 | * | |
123 | * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending | |
124 | * on whether the name is to be looked up, created, renamed, or deleted. | |
125 | * When CREATE, RENAME, or DELETE is specified, information usable in | |
126 | * creating, renaming, or deleting a directory entry may be calculated. | |
127 | * Notice that these are the only operations that can affect the directory of the target. | |
128 | * | |
129 | * If flag has LOCKPARENT or'ed into it and the target of the pathname | |
130 | * exists, lookup returns both the target and its parent directory locked. | |
131 | * When creating or renaming and LOCKPARENT is specified, the target may | |
132 | * not be ".". When deleting and LOCKPARENT is specified, the target may | |
133 | * be "."., but the caller must check to ensure it does an vrele and vput | |
134 | * instead of two vputs. | |
135 | * | |
136 | * LOCKPARENT and WANTPARENT actually refer to the parent of the last item, | |
137 | * so if ISLASTCN is not set, they should be ignored. Also they are mutually exclusive, or | |
138 | * WANTPARENT really implies DONTLOCKPARENT. Either of them set means that the calling | |
139 | * routine wants to access the parent of the target, locked or unlocked. | |
140 | * | |
141 | * Keeping the parent locked as long as possible protects from other processes | |
142 | * looking up the same item, so it has to be locked until the hfsnode is totally finished | |
143 | * | |
144 | * This routine is actually used as VOP_CACHEDLOOKUP method, and the | |
145 | * filesystem employs the generic hfs_cache_lookup() as VOP_LOOKUP | |
146 | * method. | |
147 | * | |
148 | * hfs_cache_lookup() performs the following for us: | |
149 | * check that it is a directory | |
150 | * check accessibility of directory | |
151 | * check for modification attempts on read-only mounts | |
152 | * if name found in cache | |
153 | * if at end of path and deleting or creating | |
154 | * drop it | |
155 | * else | |
156 | * return name. | |
157 | * return VOP_CACHEDLOOKUP() | |
158 | * | |
159 | * Overall outline of hfs_lookup: | |
160 | * | |
161 | * handle simple cases of . and .. | |
162 | * search for name in directory, to found or notfound | |
163 | * notfound: | |
164 | * if creating, return locked directory, leaving info on available slots | |
165 | * else return error | |
166 | * found: | |
167 | * if at end of path and deleting, return information to allow delete | |
168 | * if at end of path and rewriting (RENAME and LOCKPARENT), lock target | |
169 | * inode and return info to allow rewrite | |
170 | * if not at end, add name to cache; if at end and neither creating | |
171 | * nor deleting, add name to cache | |
172 | */ | |
173 | ||
174 | /* | |
175 | * Lookup *nm in directory *pvp, return it in *a_vpp. | |
176 | * **a_vpp is held on exit. | |
177 | * We create a hfsnode for the file, but we do NOT open the file here. | |
178 | ||
179 | #% lookup dvp L ? ? | |
180 | #% lookup vpp - L - | |
181 | ||
182 | IN struct vnode *dvp - Parent node of file; | |
183 | INOUT struct vnode **vpp - node of target file, its a new node if the target vnode did not exist; | |
184 | IN struct componentname *cnp - Name of file; | |
185 | ||
186 | * When should we lock parent_hp in here ?? | |
187 | */ | |
188 | ||
189 | int | |
190 | hfs_lookup(ap) | |
191 | struct vop_cachedlookup_args /* { | |
192 | struct vnode *a_dvp; | |
193 | struct vnode **a_vpp; | |
194 | struct componentname *a_cnp; | |
195 | } */ *ap; | |
196 | { | |
197 | struct vnode *parent_vp; | |
198 | struct vnode *target_vp; | |
199 | struct vnode *tparent_vp; | |
200 | struct hfsnode *parent_hp; /* parent */ | |
201 | struct componentname *cnp; | |
202 | struct ucred *cred; | |
203 | struct proc *p; | |
204 | struct hfsCatalogInfo catInfo; | |
205 | u_int32_t parent_id; | |
206 | u_int32_t nodeID; | |
207 | u_int16_t targetLen; | |
208 | u_int16_t forkType; | |
209 | int flags; | |
210 | int lockparent; /* !0 => lockparent flag is set */ | |
211 | int wantparent; /* !0 => wantparent or lockparent flag */ | |
212 | int nameiop; | |
213 | int retval; | |
214 | u_char isDot, isDotDot, found; | |
215 | DBG_FUNC_NAME("lookup"); | |
216 | DBG_VOP_LOCKS_DECL(2); | |
217 | DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_POS); | |
218 | DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_POS); | |
219 | DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\n")); | |
220 | DBG_HFS_NODE_CHECK(ap->a_dvp); | |
221 | ||
222 | ||
223 | /* | |
224 | * Do initial setup | |
225 | */ | |
226 | INIT_CATALOGDATA(&catInfo.nodeData, 0); | |
227 | parent_vp = ap->a_dvp; | |
228 | cnp = ap->a_cnp; | |
229 | parent_hp = VTOH(parent_vp); /* parent */ | |
230 | target_vp = NULL; | |
231 | targetLen = cnp->cn_namelen; | |
232 | nameiop = cnp->cn_nameiop; | |
233 | cred = cnp->cn_cred; | |
234 | p = cnp->cn_proc; | |
235 | lockparent = cnp->cn_flags & LOCKPARENT; | |
236 | wantparent = cnp->cn_flags & (LOCKPARENT|WANTPARENT); | |
237 | flags = cnp->cn_flags; | |
238 | parent_id = H_FILEID(parent_hp); | |
239 | nodeID = kUnknownID; | |
240 | found = FALSE; | |
241 | isDot = FALSE; | |
242 | isDotDot = FALSE; | |
243 | retval = E_NONE; | |
244 | forkType = kUndefinedFork; | |
245 | ||
246 | ||
247 | /* | |
248 | * We now have a segment name to search for, and a directory to search. | |
249 | * | |
250 | */ | |
251 | ||
252 | /* | |
253 | * First check to see if it is a . or .., else look it up. | |
254 | */ | |
255 | ||
256 | if (flags & ISDOTDOT) { /* Wanting the parent */ | |
257 | isDotDot = TRUE; | |
258 | found = TRUE; /* .. is always defined */ | |
259 | nodeID = H_DIRID(parent_hp); | |
260 | } /* Wanting ourselves */ | |
261 | else if ((cnp->cn_nameptr[0] == '.') && (targetLen == 1)) { | |
262 | isDot = TRUE; | |
263 | found = TRUE; /* We always know who we are */ | |
264 | } | |
265 | else { /* Wanting something else */ | |
266 | catInfo.hint = kNoHint; | |
267 | ||
268 | /* lock catalog b-tree */ | |
269 | retval = hfs_metafilelocking(VTOHFS(parent_vp), kHFSCatalogFileID, LK_SHARED, p); | |
270 | if (retval) | |
271 | goto Err_Exit; | |
272 | ||
273 | retval = hfs_getcatalog (VTOVCB(parent_vp), parent_id, cnp->cn_nameptr, targetLen, &catInfo); | |
274 | ||
275 | /* unlock catalog b-tree */ | |
276 | (void) hfs_metafilelocking(VTOHFS(parent_vp), kHFSCatalogFileID, LK_RELEASE, p); | |
277 | ||
278 | if (retval == E_NONE) | |
279 | found = TRUE; | |
280 | }; | |
281 | ||
282 | ||
283 | /* | |
284 | * At this point we know IF we have a valid dir/name. | |
285 | */ | |
286 | ||
287 | ||
288 | retval = E_NONE; | |
289 | if (! found) { | |
290 | /* | |
291 | * This is a non-existing entry | |
292 | * | |
293 | * If creating, and at end of pathname and current | |
294 | * directory has not been removed, then can consider | |
295 | * allowing file to be created. | |
296 | */ | |
297 | if ((nameiop == CREATE || nameiop == RENAME || | |
298 | (nameiop == DELETE && | |
299 | (ap->a_cnp->cn_flags & DOWHITEOUT) && | |
300 | (ap->a_cnp->cn_flags & ISWHITEOUT))) && | |
301 | (flags & ISLASTCN)) { | |
302 | /* | |
303 | * Access for write is interpreted as allowing | |
304 | * creation of files in the directory. | |
305 | */ | |
306 | retval = VOP_ACCESS(parent_vp, VWRITE, cred, cnp->cn_proc); | |
307 | if (retval) | |
308 | return (retval); | |
309 | ||
310 | cnp->cn_flags |= SAVENAME; | |
311 | if (!lockparent) | |
312 | VOP_UNLOCK(parent_vp, 0, p); | |
313 | retval = EJUSTRETURN; | |
314 | goto Err_Exit; | |
315 | } | |
316 | ||
317 | /* | |
318 | * Insert name into cache (as non-existent) if appropriate. | |
319 | */ | |
320 | ||
321 | /* | |
322 | * XXX SER - Here we would store the name in cache as non-existant if not trying to create it, but, | |
323 | * the name cache IS case-sensitive, thus maybe showing a negative hit, when the name | |
324 | * is only different by case. So hfs does not support negative caching. Something to look at. | |
325 | * (See radar 2293594 for a failed example) | |
326 | if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE) | |
327 | cache_enter(parent_vp, *vpp, cnp); | |
328 | */ | |
329 | ||
330 | retval = ENOENT; | |
331 | } | |
332 | else { | |
333 | /* | |
334 | * We have found an entry | |
335 | * | |
336 | * Here we have to decide what type of vnode to create. | |
337 | * There are 3 type of objects that are given: | |
338 | * 1. '.': return the same dp | |
339 | * 2. '..' return the parent of dp, always a VDIR | |
340 | * 3. catinfo rec: return depending on type: | |
341 | * A. VDIR, nodeType is kCatalogFolderNode | |
342 | * B. VLINK nodeType is kCatalogFileNode, the mode is IFLNK (esp. if it is a link to a directory e.g. bar/link/foo) | |
343 | * C. VREG, nodeType is kCatalogFileNode, forkType at this point is unknown | |
344 | * To determine the forkType, we can use this algorithm (\0 in the strings mean the NULL character): | |
345 | * a. forkType is kDataType iff ISLASTCN is set (as in the case of the default fork e.g. data/foo). | |
346 | * b. forkType is kDataType iff ISLASTCN is not set and the namePtr is followed by "/?AppleHFSFork/data\0" | |
347 | * c. forkType is kRsrcType iff ISLASTCN is not set and the namePtr is followed by "/?AppleHFSFork/rsrc\0" | |
348 | * If the latter two are correct, then we 'consume' the remaining of the name buffer | |
349 | * and set the cnp as appropriate. | |
350 | * Anything else returns an retval | |
351 | */ | |
352 | ||
353 | ||
354 | /* | |
355 | * If deleting, and at end of pathname, return | |
356 | * parameters which can be used to remove file. | |
357 | * If the wantparent flag isn't set, we return only | |
358 | * the directory (in ndp->ndvp), otherwise we go | |
359 | * on and lock the hfsnode, being careful with ".". | |
360 | * | |
361 | * Forks cannot be deleted so scan-ahead is illegal, so just return the default fork | |
362 | */ | |
363 | if (nameiop == DELETE && (flags & ISLASTCN)) { | |
364 | /* | |
365 | * Write access to directory required to delete files. | |
366 | */ | |
367 | retval = VOP_ACCESS(parent_vp, VWRITE, cred, cnp->cn_proc); | |
368 | if (retval) | |
369 | goto Err_Exit; | |
370 | ||
371 | if (isDot) { /* Want to return ourselves */ | |
372 | VREF(parent_vp); | |
373 | target_vp = parent_vp; | |
374 | goto Err_Exit; | |
375 | } | |
376 | else if (isDotDot) { | |
377 | retval = VFS_VGET(parent_vp->v_mount, &nodeID, &target_vp); | |
378 | if (retval) | |
379 | goto Err_Exit; | |
380 | } | |
381 | else { | |
382 | retval = hfs_vget_catinfo(parent_vp, &catInfo, kAnyFork, &target_vp); | |
383 | if (retval) | |
384 | goto Err_Exit; | |
385 | CLEAN_CATALOGDATA(&catInfo.nodeData); | |
386 | }; | |
387 | ||
388 | ||
389 | /* | |
390 | * If directory is "sticky", then user must own | |
391 | * the directory, or the file in it, else she | |
392 | * may not delete it (unless she's root). This | |
393 | * implements append-only directories. | |
394 | */ | |
395 | if ((parent_hp->h_meta->h_mode & ISVTX) && | |
396 | (cred->cr_uid != 0) && | |
397 | (cred->cr_uid != parent_hp->h_meta->h_uid) && | |
398 | (target_vp->v_type != VLNK) && | |
399 | (hfs_owner_rights(target_vp, cred, p, false))) { | |
400 | vput(target_vp); | |
401 | retval = EPERM; | |
402 | goto Err_Exit; | |
403 | } | |
404 | #if HFS_HARDLINKS | |
405 | /* | |
406 | * If this is a link node then we need to save the name | |
407 | * (of the link) so we can delete it from the catalog b-tree. | |
408 | * In this case, hfs_remove will then free the component name. | |
409 | */ | |
410 | if (target_vp && (VTOH(target_vp)->h_meta->h_metaflags & IN_DATANODE)) | |
411 | cnp->cn_flags |= SAVENAME; | |
412 | #endif | |
413 | ||
414 | if (!lockparent) | |
415 | VOP_UNLOCK(parent_vp, 0, p); | |
416 | goto Err_Exit; | |
417 | }; | |
418 | ||
419 | /* | |
420 | * If rewriting 'RENAME', return the hfsnode and the | |
421 | * information required to rewrite the present directory | |
422 | */ | |
423 | if (nameiop == RENAME && wantparent && (cnp->cn_flags & ISLASTCN)) { | |
424 | ||
425 | if ((retval = VOP_ACCESS(parent_vp, VWRITE, cred, cnp->cn_proc)) != 0) | |
426 | goto Err_Exit; | |
427 | ||
428 | /* | |
429 | * Careful about locking second inode. | |
430 | * This can only occur if the target is ".". like 'mv foo/bar foo/.' | |
431 | */ | |
432 | if (isDot) { | |
433 | retval = EISDIR; | |
434 | goto Err_Exit; | |
435 | } | |
436 | else if (isDotDot) { | |
437 | retval = VFS_VGET(parent_vp->v_mount, &nodeID, &target_vp); | |
438 | if (retval) | |
439 | goto Err_Exit; | |
440 | } | |
441 | else { | |
442 | /* If then name differs in case, then act like it does not exist | |
443 | * This allows renaming foo->Foo | |
444 | * Exclude length difference due to compose/decompe issues. | |
445 | */ | |
446 | if ((cnp->cn_namelen == catInfo.nodeData.cnm_length) && | |
447 | strncmp(cnp->cn_nameptr, catInfo.nodeData.cnm_nameptr, targetLen)) { | |
448 | if (!lockparent) | |
449 | VOP_UNLOCK(parent_vp, 0, p); | |
450 | retval = EJUSTRETURN; | |
451 | goto Err_Exit; | |
452 | }; | |
453 | ||
454 | retval = hfs_vget_catinfo(parent_vp, &catInfo, kAnyFork, &target_vp); | |
455 | if (retval) | |
456 | goto Err_Exit; | |
457 | ||
458 | CLEAN_CATALOGDATA(&catInfo.nodeData); /* Should do nothing */ | |
459 | }; | |
460 | ||
461 | cnp->cn_flags |= SAVENAME; | |
462 | if (!lockparent) | |
463 | VOP_UNLOCK(parent_vp, 0, p); | |
464 | ||
465 | goto Err_Exit; | |
466 | /* Finished...all is well, goto the end */ | |
467 | }; | |
468 | ||
469 | /* | |
470 | * Step through the translation in the name. We do not `vput' the | |
471 | * directory because we may need it again if a symbolic link | |
472 | * is relative to the current directory. Instead we save it | |
473 | * unlocked as "tparent_vp". We must get the target hfsnode before unlocking | |
474 | * the directory to insure that the hfsnode will not be removed | |
475 | * before we get it. We prevent deadlock by always fetching | |
476 | * inodes from the root, moving down the directory tree. Thus | |
477 | * when following backward pointers ".." we must unlock the | |
478 | * parent directory before getting the requested directory. | |
479 | * There is a potential race condition here if both the current | |
480 | * and parent directories are removed before the VFS_VGET for the | |
481 | * hfsnode associated with ".." returns. We hope that this occurs | |
482 | * infrequently since we cannot avoid this race condition without | |
483 | * implementing a sophisticated deadlock detection algorithm. | |
484 | * Note also that this simple deadlock detection scheme will not | |
485 | * work if the file system has any hard links other than ".." | |
486 | * that point backwards in the directory structure. | |
487 | */ | |
488 | ||
489 | tparent_vp = parent_vp; | |
490 | if (isDotDot) { | |
491 | VOP_UNLOCK(tparent_vp, 0, p); /* race to get the inode */ | |
492 | if ((retval = VFS_VGET(parent_vp->v_mount, &nodeID, &target_vp))) { | |
493 | vn_lock(tparent_vp, LK_EXCLUSIVE | LK_RETRY, p); | |
494 | goto Err_Exit; | |
495 | } | |
496 | if (lockparent && (flags & ISLASTCN) && (tparent_vp != target_vp) && | |
497 | (retval = vn_lock(tparent_vp, LK_EXCLUSIVE, p))) { | |
498 | vput(target_vp); | |
499 | goto Err_Exit; | |
500 | } | |
501 | } | |
502 | else if (isDot) { | |
503 | VREF(parent_vp); /* we want ourself, ie "." */ | |
504 | target_vp = parent_vp; | |
505 | } | |
506 | else { | |
507 | mode_t mode; | |
508 | /* | |
509 | * Determine what fork to get, currenty 3 scenarios are supported: | |
510 | * 1. ./foo: if it is a dir, return a VDIR else return data fork | |
511 | * 2. ./foo/.__Fork/data: return data fork | |
512 | * 3. ./foo/.__Fork/rsrc: return resource fork | |
513 | * So the algorithm is: | |
514 | * If the object is a directory | |
515 | * then return a VDIR vnode | |
516 | * else if ISLASTCN is true | |
517 | * then get the vnode with forkType=kDataFork | |
518 | * else | |
519 | * compare with the remaining cnp buffer with "/.__Fork/" | |
520 | * if a match | |
521 | * then compare string after that with either 'data' or 'rsrc' | |
522 | * if match | |
523 | * then | |
524 | * 'consume' rest of cnp, setting appropriate values and flags | |
525 | * return vnode depending on match | |
526 | * else | |
527 | * bad fork name | |
528 | * else | |
529 | * illegal path after a file object | |
530 | */ | |
531 | ||
532 | mode = (mode_t)(catInfo.nodeData.cnd_mode); | |
533 | ||
534 | if (catInfo.nodeData.cnd_type == kCatalogFolderNode) { | |
535 | forkType = kDirectory; /* Really ignored */ | |
536 | } | |
537 | else if ((mode & IFMT) == IFLNK) { | |
538 | forkType = kDataFork; | |
539 | } /* After this point, nodeType should be a file */ | |
540 | else if (flags & ISLASTCN) { /* Create a default fork */ | |
541 | forkType = kDataFork; | |
542 | } | |
543 | else { /* determine what fork was specified */ | |
544 | forkType = GetForkFromName(cnp); | |
545 | flags |= ISLASTCN; /* To know to unlock the parent if needed */ | |
546 | }; /* else */ | |
547 | ||
548 | ||
549 | /* If couldn't determine what type of fork, leave */ | |
550 | if (forkType == kUndefinedFork) { | |
551 | retval = EISDIR; | |
552 | goto Err_Exit; | |
553 | }; | |
554 | ||
555 | /* Get the vnode now that what type of fork is known */ | |
556 | DBG_ASSERT((forkType==kDirectory) || (forkType==kDataFork) || (forkType==kRsrcFork)); | |
557 | retval = hfs_vget_catinfo(tparent_vp, &catInfo, forkType, &target_vp); | |
558 | if (retval != E_NONE) | |
559 | goto Err_Exit; | |
560 | ||
561 | if (!lockparent || !(flags & ISLASTCN)) | |
562 | VOP_UNLOCK(tparent_vp, 0, p); | |
563 | ||
564 | CLEAN_CATALOGDATA(&catInfo.nodeData); | |
565 | ||
566 | }; /* else found */ | |
567 | ||
568 | ||
569 | /* | |
570 | * Insert name in cache if wanted. | |
571 | * Names with composed chars are not put into the name cache. | |
150bd074 A |
572 | * Resource forks are not entered in the name cache. This |
573 | * avoids deadlocks. | |
1c79356b A |
574 | */ |
575 | if ((cnp->cn_flags & MAKEENTRY) | |
150bd074 A |
576 | && (cnp->cn_namelen == catInfo.nodeData.cnm_length) |
577 | && ((H_FORKTYPE(VTOH(target_vp))) != kRsrcFork)) { | |
1c79356b A |
578 | /* |
579 | * XXX SER - Might be good idea to bcopy(catInfo.nodeData.fsspec.name, cnp->cn_nameptr) | |
580 | * to "normalize" the name cache. This will avoid polluting the name cache with | |
581 | * names that are different in case, and allow negative caching | |
582 | */ | |
583 | cache_enter(parent_vp, target_vp, cnp); | |
584 | } | |
585 | ||
586 | ||
587 | ||
588 | }; /* else found == TRUE */ | |
589 | ||
590 | Err_Exit: | |
591 | ||
592 | CLEAN_CATALOGDATA(&catInfo.nodeData); /* Just to make sure */ | |
593 | *ap->a_vpp = target_vp; | |
594 | ||
595 | DBG_VOP_UPDATE_VP(1, *ap->a_vpp); | |
596 | //DBG_VOP_LOOKUP_TEST (funcname, cnp, parent_vp, target_vp); | |
597 | //DBG_VOP_LOCKS_TEST(E_NONE); | |
598 | ||
599 | return (retval); | |
600 | } | |
601 | ||
602 | ||
603 | ||
604 | /* | |
605 | * Based on vn_cache_lookup (which is vfs_cache_lookup in FreeBSD 3.1) | |
606 | * | |
607 | * Name caching works as follows: | |
608 | * | |
609 | * Names found by directory scans are retained in a cache | |
610 | * for future reference. It is managed LRU, so frequently | |
611 | * used names will hang around. Cache is indexed by hash value | |
612 | * obtained from (vp, name) where vp refers to the directory | |
613 | * containing name. | |
614 | * | |
615 | * If it is a "negative" entry, (i.e. for a name that is known NOT to | |
616 | * exist) the vnode pointer will be NULL. | |
617 | * | |
618 | * Upon reaching the last segment of a path, if the reference | |
619 | * is for DELETE, or NOCACHE is set (rewrite), and the | |
620 | * name is located in the cache, it will be dropped. | |
621 | * | |
622 | * In hfs, since a name can represent multiple forks, it cannot | |
623 | * be known what fork the name matches, so further checks have to be done. | |
624 | * Currently a policy of first requested, is the one stored, is followed. | |
625 | * | |
626 | * SER XXX If this proves inadequate maybe we can munge the name to contain a fork reference | |
627 | * like foo -> foo.d for the data fork. | |
628 | */ | |
629 | ||
630 | int | |
631 | hfs_cache_lookup(ap) | |
632 | struct vop_lookup_args /* { | |
633 | struct vnode *a_dvp; | |
634 | struct vnode **a_vpp; | |
635 | struct componentname *a_cnp; | |
636 | } */ *ap; | |
637 | { | |
638 | struct vnode *vdp; | |
639 | struct vnode *pdp; | |
640 | int lockparent; | |
641 | int error; | |
642 | struct vnode **vpp = ap->a_vpp; | |
643 | struct componentname *cnp = ap->a_cnp; | |
644 | struct ucred *cred = cnp->cn_cred; | |
645 | int flags = cnp->cn_flags; | |
646 | struct proc *p = cnp->cn_proc; | |
647 | struct hfsnode *hp; | |
648 | u_int32_t vpid; /* capability number of vnode */ | |
649 | DBG_FUNC_NAME("cache_lookup"); | |
650 | DBG_VOP_LOCKS_DECL(2); | |
651 | DBG_VOP_LOCKS_INIT(0,ap->a_dvp, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_IGNORE, VOPDBG_POS); | |
652 | DBG_VOP_LOCKS_INIT(1,*ap->a_vpp, VOPDBG_IGNORE, VOPDBG_LOCKED, VOPDBG_IGNORE, VOPDBG_POS); | |
653 | DBG_VOP_PRINT_FUNCNAME();DBG_VOP_CONT(("\n")); | |
654 | DBG_VOP_CONT(("\tTarget: "));DBG_VOP_PRINT_CPN_INFO(ap->a_cnp);DBG_VOP_CONT(("\n")); | |
655 | DBG_HFS_NODE_CHECK(ap->a_dvp); | |
656 | ||
657 | *vpp = NULL; | |
658 | vdp = ap->a_dvp; | |
659 | lockparent = flags & LOCKPARENT; | |
660 | ||
661 | if (vdp->v_type != VDIR) | |
662 | return (ENOTDIR); | |
663 | ||
664 | if ((flags & ISLASTCN) && (vdp->v_mount->mnt_flag & MNT_RDONLY) && | |
665 | (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) | |
666 | return (EROFS); | |
667 | ||
668 | error = VOP_ACCESS(vdp, VEXEC, cred, cnp->cn_proc); | |
669 | ||
670 | if (error) | |
671 | return (error); | |
672 | ||
673 | /* | |
674 | * Lookup an entry in the cache | |
675 | * If the lookup succeeds, the vnode is returned in *vpp, and a status of -1 is | |
676 | * returned. If the lookup determines that the name does not exist | |
677 | * (negative cacheing), a status of ENOENT is returned. If the lookup | |
678 | * fails, a status of zero is returned. | |
679 | */ | |
680 | error = cache_lookup(vdp, vpp, cnp); | |
681 | ||
682 | if (error == 0) { /* Unsuccessfull */ | |
683 | DBG_VOP(("\tWas not in name cache\n")); | |
684 | error = hfs_lookup(ap); | |
685 | #if HFS_HARDLINKS | |
686 | if (error) | |
687 | return (error); | |
688 | /* | |
689 | * If this is a hard-link vnode then we need to update | |
690 | * the name (of the link) and update the parent ID. This | |
691 | * enables getattrlist calls to return correct link info. | |
692 | */ | |
693 | hp = VTOH(*ap->a_vpp); | |
694 | if ((flags & ISLASTCN) && (hp->h_meta->h_metaflags & IN_DATANODE)) { | |
695 | H_DIRID(hp) = H_FILEID(VTOH(ap->a_dvp)); | |
696 | hfs_set_metaname(cnp->cn_nameptr, hp->h_meta, HTOHFS(hp)); | |
697 | } | |
698 | #endif | |
699 | return (error); | |
700 | }; | |
701 | ||
702 | DBG_VOP(("\tName was found in the name cache")); | |
703 | if (error == ENOENT) { | |
704 | DBG_VOP_CONT((" though it was a NEGATIVE HIT\n")); | |
705 | return (error); | |
706 | }; | |
707 | DBG_VOP_CONT(("\n")); | |
708 | ||
709 | #if HFS_HARDLINKS | |
710 | /* | |
711 | * If this is a hard-link vnode then we need to update | |
712 | * the name (of the link) and update the parent ID. This | |
713 | * enables getattrlist calls to return correct link info. | |
714 | */ | |
715 | hp = VTOH(*vpp); | |
716 | if ((flags & ISLASTCN) && (hp->h_meta->h_metaflags & IN_DATANODE)) { | |
717 | H_DIRID(hp) = H_FILEID(VTOH(vdp)); | |
718 | hfs_set_metaname(cnp->cn_nameptr, hp->h_meta, HTOHFS(hp)); | |
719 | } | |
720 | #endif | |
721 | ||
722 | /* We have a name that matched */ | |
723 | pdp = vdp; | |
724 | vdp = *vpp; | |
725 | vpid = vdp->v_id; | |
726 | if (pdp == vdp) { /* lookup on "." */ | |
727 | VREF(vdp); | |
728 | error = 0; | |
729 | } else if (flags & ISDOTDOT) { | |
730 | /* | |
731 | * Carefull on the locking policy, | |
732 | * remember we always lock from parent to child, so have | |
733 | * to release lock on child before trying to lock parent | |
734 | * then regain lock if needed | |
735 | */ | |
736 | VOP_UNLOCK(pdp, 0, p); | |
737 | error = vget(vdp, LK_EXCLUSIVE, p); | |
738 | if (!error && lockparent && (flags & ISLASTCN)) | |
739 | error = vn_lock(pdp, LK_EXCLUSIVE, p); | |
740 | } else { | |
741 | /* | |
742 | * Check to see if a specific fork is not being requested. | |
743 | * | |
744 | * If it is a file and not the last path item | |
745 | * then check if its a proper fork | |
746 | * If it is, check to see if the matched vnode is the same fork | |
747 | * else see if the proper fork exists. | |
748 | * If it does, return that one, else do VOP_CACHEDLOOKUP() | |
749 | * Notice that nothing is done if an undefined fork is named. Just leave and let lookup() | |
750 | * handle strange cases. | |
751 | * | |
752 | * XXX SER Notice that when the target is not what was in the name cache, | |
753 | * it is locked, before trying to get its sibling. Could this be a problem since both | |
754 | * siblings can be locked, but not in a determinalistic order???? | |
755 | */ | |
756 | u_int16_t forkType; | |
757 | ||
758 | error = vget(vdp, LK_EXCLUSIVE, p); | |
759 | if ((! error) && (vdp->v_type == VREG) && (vpid == vdp->v_id)) { | |
760 | if (!(flags & ISLASTCN)) { | |
761 | forkType = GetForkFromName(cnp); | |
762 | if (forkType != kUndefinedFork) { | |
763 | flags |= ISLASTCN; | |
764 | if (H_FORKTYPE(VTOH(vdp)) != forkType) { | |
765 | error = hfs_vget_sibling(vdp, forkType, vpp); | |
766 | vput(vdp); | |
767 | if (! error) { | |
768 | vdp = *vpp; | |
769 | vpid = vdp->v_id; | |
770 | } | |
771 | } | |
772 | } | |
773 | } | |
774 | else { | |
775 | /* Its the last item, so we want the data fork */ | |
776 | if (H_FORKTYPE(VTOH(vdp)) != kDataFork) { | |
777 | error = hfs_vget_sibling(vdp, kDataFork, vpp); | |
778 | vput(vdp); | |
779 | if (! error) { | |
780 | vdp = *vpp; | |
781 | vpid = vdp->v_id; | |
782 | } | |
783 | } | |
784 | }; | |
785 | }; | |
786 | if (!lockparent || error || !(flags & ISLASTCN)) | |
787 | VOP_UNLOCK(pdp, 0, p); | |
788 | }; | |
789 | /* | |
790 | * Check that the capability number did not change | |
791 | * while we were waiting for the lock. | |
792 | */ | |
793 | if (!error) { | |
794 | if (vpid == vdp->v_id) | |
795 | return (0); /* HERE IS THE NORMAL EXIT FOR CACHE LOOKUP!!!! */ | |
796 | /* | |
797 | * The above is the NORMAL exit, after this point is an error | |
798 | * condition. | |
799 | */ | |
800 | vput(vdp); | |
801 | if (lockparent && pdp != vdp && (flags & ISLASTCN)) | |
802 | VOP_UNLOCK(pdp, 0, p); | |
803 | } | |
804 | error = vn_lock(pdp, LK_EXCLUSIVE, p); | |
805 | if (error) | |
806 | return (error); | |
807 | return (hfs_lookup(ap)); | |
808 | } | |
809 | ||
810 | /* | |
811 | * Parses a componentname and sees if the remaining path | |
812 | * contains a hfs named fork specifier. If it does set the | |
813 | * componentname to consume the rest of the path, and | |
814 | * return the forkType | |
815 | */ | |
816 | ||
817 | u_int16_t GetForkFromName(struct componentname *cnp) | |
818 | { | |
819 | u_int16_t forkType = kUndefinedFork; | |
820 | char *tcp = cnp->cn_nameptr + cnp->cn_namelen; | |
821 | ||
822 | if (bcmp(tcp, _PATH_FORKSPECIFIER, sizeof(_PATH_FORKSPECIFIER) - 1) == 0) { | |
823 | /* Its a HFS fork, so far */ | |
824 | tcp += (sizeof(_PATH_FORKSPECIFIER) - 1); | |
825 | if (bcmp(tcp, _PATH_DATANAME, sizeof(_PATH_DATANAME)) == 0) { | |
826 | forkType = kDataFork; | |
827 | cnp->cn_consume = sizeof(_PATH_FORKSPECIFIER) + sizeof(_PATH_DATANAME) - 2; | |
828 | } | |
829 | else if (bcmp(tcp, _PATH_RSRCNAME, sizeof(_PATH_RSRCNAME)) == 0) { | |
830 | forkType = kRsrcFork; | |
831 | cnp->cn_consume = sizeof(_PATH_FORKSPECIFIER) + sizeof(_PATH_RSRCNAME) - 2; | |
832 | }; /* else if */ | |
833 | }; /* if bcmp */ | |
834 | ||
835 | ||
836 | /* XXX SER For backwards compatability...keep it */ | |
837 | if (forkType == kUndefinedFork) { | |
838 | tcp = cnp->cn_nameptr + cnp->cn_namelen; | |
839 | if (bcmp(tcp, gHFSForkIdentStr, sizeof(gHFSForkIdentStr) - 1) == 0) { | |
840 | /* Its a HFS fork, so far */ | |
841 | tcp += (sizeof(gHFSForkIdentStr) - 1); | |
842 | if (bcmp(tcp, gDataForkNameStr, sizeof(gDataForkNameStr)) == 0) { | |
843 | forkType = kDataFork; | |
844 | cnp->cn_consume = sizeof(gHFSForkIdentStr) + sizeof(gDataForkNameStr) - 2; | |
845 | } | |
846 | else if (bcmp(tcp, gRsrcForkNameStr, sizeof(gRsrcForkNameStr)) == 0) { | |
847 | forkType = kRsrcFork; | |
848 | cnp->cn_consume = sizeof(gHFSForkIdentStr) + sizeof(gRsrcForkNameStr) - 2; | |
849 | }; /* else if */ | |
850 | }; /* if bcmp */ | |
851 | }; | |
852 | ||
853 | return forkType; | |
854 | } | |
855 | ||
856 | #if DBG_VOP_TEST_LOCKS | |
857 | ||
858 | void DbgLookupTest( char *funcname, struct componentname *cnp, struct vnode *dvp, struct vnode *vp) | |
859 | { | |
860 | if (! (hfs_dbg_lookup || hfs_dbg_all)) | |
861 | return; | |
862 | ||
863 | ||
864 | if (dvp) { | |
865 | if (lockstatus(&VTOH(dvp)->h_lock)) { | |
866 | DBG_LOOKUP (("%s: Parent vnode exited LOCKED", funcname)); | |
867 | } | |
868 | else { | |
869 | DBG_LOOKUP (("%s: Parent vnode exited UNLOCKED", funcname)); | |
870 | } | |
871 | } | |
872 | ||
873 | if (vp) { | |
874 | if (vp==dvp) | |
875 | { | |
876 | DBG_LOOKUP (("%s: Target and Parent are the same", funcname)); | |
877 | } | |
878 | else { | |
879 | if (lockstatus(&VTOH(vp)->h_lock)) { | |
880 | DBG_LOOKUP (("%s: Found vnode exited LOCKED", funcname)); | |
881 | } | |
882 | else { | |
883 | DBG_LOOKUP (("%s: Found vnode exited LOCKED", funcname)); | |
884 | } | |
885 | } | |
886 | DBG_LOOKUP (("%s: Found vnode 0x%x has vtype of %d\n ", funcname, (u_int)vp, vp->v_type)); | |
887 | } | |
888 | else | |
889 | DBG_LOOKUP (("%s: Found vnode exited NULL\n", funcname)); | |
890 | ||
891 | ||
892 | } | |
893 | ||
894 | #endif /* DBG_VOP_TEST_LOCKS */ | |
895 |