1 /*
2 * Copyright (c) 1989, 1993
3 * The Regents of the University of California. All rights reserved.
4 * (c) UNIX System Laboratories, Inc.
5 * All or some portions of this file are derived from material licensed
6 * to the University of California by American Telephone and Telegraph
7 * Co. or Unix System Laboratories, Inc. and are reproduced herein with
8 * the permission of UNIX System Laboratories, Inc.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 *
38 * @(#)ufs_lookup.c 8.15 (Berkeley) 6/16/95
39 */
40
41 #include <sys/cdefs.h>
42 __FBSDID("$FreeBSD: releng/5.2/sys/ufs/ufs/ufs_lookup.c 116192 2003-06-11 06:34:30Z obrien $");
43
44 #include "opt_ffs_broken_fixme.h"
45 #include "opt_ufs.h"
46
47 #include <sys/param.h>
48 #include <sys/systm.h>
49 #include <sys/kernel.h>
50 #include <sys/namei.h>
51 #include <sys/bio.h>
52 #include <sys/buf.h>
53 #include <sys/proc.h>
54 #include <sys/stat.h>
55 #include <sys/mount.h>
56 #include <sys/vnode.h>
57 #include <sys/sysctl.h>
58
59 #include <vm/vm.h>
60 #include <vm/vm_extern.h>
61
62 #include <ufs/ufs/extattr.h>
63 #include <ufs/ufs/quota.h>
64 #include <ufs/ufs/inode.h>
65 #include <ufs/ufs/dir.h>
66 #ifdef UFS_DIRHASH
67 #include <ufs/ufs/dirhash.h>
68 #endif
69 #include <ufs/ufs/ufsmount.h>
70 #include <ufs/ufs/ufs_extern.h>
71
72 #ifdef DIAGNOSTIC
73 static int dirchk = 1;
74 #else
75 static int dirchk = 0;
76 #endif
77
78 SYSCTL_INT(_debug, OID_AUTO, dircheck, CTLFLAG_RW, &dirchk, 0, "");
79
80 /* true if old FS format...*/
81 #define OFSFMT(vp) ((vp)->v_mount->mnt_maxsymlinklen <= 0)
82
83 /*
84 * Convert a component of a pathname into a pointer to a locked inode.
85 * This is a very central and rather complicated routine.
86 * If the filesystem is not maintained in a strict tree hierarchy,
87 * this can result in a deadlock situation (see comments in code below).
88 *
89 * The cnp->cn_nameiop argument is LOOKUP, CREATE, RENAME, or DELETE depending
90 * on whether the name is to be looked up, created, renamed, or deleted.
91 * When CREATE, RENAME, or DELETE is specified, information usable in
92 * creating, renaming, or deleting a directory entry may be calculated.
93 * If flag has LOCKPARENT or'ed into it and the target of the pathname
94 * exists, lookup returns both the target and its parent directory locked.
95 * When creating or renaming and LOCKPARENT is specified, the target may
96 * not be ".". When deleting and LOCKPARENT is specified, the target may
97 * be "."., but the caller must check to ensure it does an vrele and vput
98 * instead of two vputs.
99 *
100 * This routine is actually used as VOP_CACHEDLOOKUP method, and the
101 * filesystem employs the generic vfs_cache_lookup() as VOP_LOOKUP
102 * method.
103 *
104 * vfs_cache_lookup() performs the following for us:
105 * check that it is a directory
106 * check accessibility of directory
107 * check for modification attempts on read-only mounts
108 * if name found in cache
109 * if at end of path and deleting or creating
110 * drop it
111 * else
112 * return name.
113 * return VOP_CACHEDLOOKUP()
114 *
115 * Overall outline of ufs_lookup:
116 *
117 * search for name in directory, to found or notfound
118 * notfound:
119 * if creating, return locked directory, leaving info on available slots
120 * else return error
121 * found:
122 * if at end of path and deleting, return information to allow delete
123 * if at end of path and rewriting (RENAME and LOCKPARENT), lock target
124 * inode and return info to allow rewrite
125 * if not at end, add name to cache; if at end and neither creating
126 * nor deleting, add name to cache
127 */
128 int
129 ufs_lookup(ap)
130 struct vop_cachedlookup_args /* {
131 struct vnode *a_dvp;
132 struct vnode **a_vpp;
133 struct componentname *a_cnp;
134 } */ *ap;
135 {
136 struct vnode *vdp; /* vnode for directory being searched */
137 struct inode *dp; /* inode for directory being searched */
138 struct buf *bp; /* a buffer of directory entries */
139 struct direct *ep; /* the current directory entry */
140 int entryoffsetinblock; /* offset of ep in bp's buffer */
141 enum {NONE, COMPACT, FOUND} slotstatus;
142 doff_t slotoffset; /* offset of area with free space */
143 int slotsize; /* size of area at slotoffset */
144 int slotfreespace; /* amount of space free in slot */
145 int slotneeded; /* size of the entry we're seeking */
146 int numdirpasses; /* strategy for directory search */
147 doff_t endsearch; /* offset to end directory search */
148 doff_t prevoff; /* prev entry dp->i_offset */
149 struct vnode *pdp; /* saved dp during symlink work */
150 struct vnode *tdp; /* returned by VFS_VGET */
151 doff_t enduseful; /* pointer past last used dir slot */
152 u_long bmask; /* block offset mask */
153 int lockparent; /* 1 => lockparent flag is set */
154 int wantparent; /* 1 => wantparent or lockparent flag */
155 int namlen, error;
156 struct vnode **vpp = ap->a_vpp;
157 struct componentname *cnp = ap->a_cnp;
158 struct ucred *cred = cnp->cn_cred;
159 int flags = cnp->cn_flags;
160 int nameiop = cnp->cn_nameiop;
161 struct thread *td = cnp->cn_thread;
162
163 bp = NULL;
164 slotoffset = -1;
165 cnp->cn_flags &= ~PDIRUNLOCK;
166 /*
167 * XXX there was a soft-update diff about this I couldn't merge.
168 * I think this was the equiv.
169 */
170 *vpp = NULL;
171
172 vdp = ap->a_dvp;
173 dp = VTOI(vdp);
174 lockparent = flags & LOCKPARENT;
175 wantparent = flags & (LOCKPARENT|WANTPARENT);
176
177 /*
178 * We now have a segment name to search for, and a directory to search.
179 *
180 * Suppress search for slots unless creating
181 * file and at end of pathname, in which case
182 * we watch for a place to put the new file in
183 * case it doesn't already exist.
184 */
185 slotstatus = FOUND;
186 slotfreespace = slotsize = slotneeded = 0;
187 if ((nameiop == CREATE || nameiop == RENAME) &&
188 (flags & ISLASTCN)) {
189 slotstatus = NONE;
190 slotneeded = DIRECTSIZ(cnp->cn_namelen);
191 }
192 bmask = VFSTOUFS(vdp->v_mount)->um_mountp->mnt_stat.f_iosize - 1;
193
194 #ifdef UFS_DIRHASH
195 /*
196 * Use dirhash for fast operations on large directories. The logic
197 * to determine whether to hash the directory is contained within
198 * ufsdirhash_build(); a zero return means that it decided to hash
199 * this directory and it successfully built up the hash table.
200 */
201 if (ufsdirhash_build(dp) == 0) {
202 /* Look for a free slot if needed. */
203 enduseful = dp->i_size;
204 if (slotstatus != FOUND) {
205 slotoffset = ufsdirhash_findfree(dp, slotneeded,
206 &slotsize);
207 if (slotoffset >= 0) {
208 slotstatus = COMPACT;
209 enduseful = ufsdirhash_enduseful(dp);
210 if (enduseful < 0)
211 enduseful = dp->i_size;
212 }
213 }
214 /* Look up the component. */
215 numdirpasses = 1;
216 entryoffsetinblock = 0; /* silence compiler warning */
217 switch (ufsdirhash_lookup(dp, cnp->cn_nameptr, cnp->cn_namelen,
218 &dp->i_offset, &bp, nameiop == DELETE ? &prevoff : NULL)) {
219 case 0:
220 ep = (struct direct *)((char *)bp->b_data +
221 (dp->i_offset & bmask));
222 goto foundentry;
223 case ENOENT:
224 dp->i_offset = roundup2(dp->i_size, DIRBLKSIZ);
225 goto notfound;
226 default:
227 /* Something failed; just do a linear search. */
228 break;
229 }
230 }
231 #endif /* UFS_DIRHASH */
232 /*
233 * If there is cached information on a previous search of
234 * this directory, pick up where we last left off.
235 * We cache only lookups as these are the most common
236 * and have the greatest payoff. Caching CREATE has little
237 * benefit as it usually must search the entire directory
238 * to determine that the entry does not exist. Caching the
239 * location of the last DELETE or RENAME has not reduced
240 * profiling time and hence has been removed in the interest
241 * of simplicity.
242 */
243 if (nameiop != LOOKUP || dp->i_diroff == 0 ||
244 dp->i_diroff >= dp->i_size) {
245 entryoffsetinblock = 0;
246 dp->i_offset = 0;
247 numdirpasses = 1;
248 } else {
249 dp->i_offset = dp->i_diroff;
250 if ((entryoffsetinblock = dp->i_offset & bmask) &&
251 (error = UFS_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp)))
252 return (error);
253 numdirpasses = 2;
254 nchstats.ncs_2passes++;
255 }
256 prevoff = dp->i_offset;
257 endsearch = roundup2(dp->i_size, DIRBLKSIZ);
258 enduseful = 0;
259
260 searchloop:
261 while (dp->i_offset < endsearch) {
262 /*
263 * If necessary, get the next directory block.
264 */
265 if ((dp->i_offset & bmask) == 0) {
266 if (bp != NULL)
267 brelse(bp);
268 error =
269 UFS_BLKATOFF(vdp, (off_t)dp->i_offset, NULL, &bp);
270 if (error)
271 return (error);
272 entryoffsetinblock = 0;
273 }
274 /*
275 * If still looking for a slot, and at a DIRBLKSIZE
276 * boundary, have to start looking for free space again.
277 */
278 if (slotstatus == NONE &&
279 (entryoffsetinblock & (DIRBLKSIZ - 1)) == 0) {
280 slotoffset = -1;
281 slotfreespace = 0;
282 }
283 /*
284 * Get pointer to next entry.
285 * Full validation checks are slow, so we only check
286 * enough to insure forward progress through the
287 * directory. Complete checks can be run by patching
288 * "dirchk" to be true.
289 */
290 ep = (struct direct *)((char *)bp->b_data + entryoffsetinblock);
291 if (ep->d_reclen == 0 || ep->d_reclen >
292 DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
293 (dirchk && ufs_dirbadentry(vdp, ep, entryoffsetinblock))) {
294 int i;
295
296 ufs_dirbad(dp, dp->i_offset, "mangled entry");
297 i = DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1));
298 dp->i_offset += i;
299 entryoffsetinblock += i;
300 continue;
301 }
302
303 /*
304 * If an appropriate sized slot has not yet been found,
305 * check to see if one is available. Also accumulate space
306 * in the current block so that we can determine if
307 * compaction is viable.
308 */
309 if (slotstatus != FOUND) {
310 int size = ep->d_reclen;
311
312 if (ep->d_ino != 0)
313 size -= DIRSIZ(OFSFMT(vdp), ep);
314 if (size > 0) {
315 if (size >= slotneeded) {
316 slotstatus = FOUND;
317 slotoffset = dp->i_offset;
318 slotsize = ep->d_reclen;
319 } else if (slotstatus == NONE) {
320 slotfreespace += size;
321 if (slotoffset == -1)
322 slotoffset = dp->i_offset;
323 if (slotfreespace >= slotneeded) {
324 slotstatus = COMPACT;
325 slotsize = dp->i_offset +
326 ep->d_reclen - slotoffset;
327 }
328 }
329 }
330 }
331
332 /*
333 * Check for a name match.
334 */
335 if (ep->d_ino) {
336 # if (BYTE_ORDER == LITTLE_ENDIAN)
337 if (OFSFMT(vdp))
338 namlen = ep->d_type;
339 else
340 namlen = ep->d_namlen;
341 # else
342 namlen = ep->d_namlen;
343 # endif
344 if (namlen == cnp->cn_namelen &&
345 (cnp->cn_nameptr[0] == ep->d_name[0]) &&
346 !bcmp(cnp->cn_nameptr, ep->d_name,
347 (unsigned)namlen)) {
348 #ifdef UFS_DIRHASH
349 foundentry:
350 #endif
351 /*
352 * Save directory entry's inode number and
353 * reclen in ndp->ni_ufs area, and release
354 * directory buffer.
355 */
356 if (vdp->v_mount->mnt_maxsymlinklen > 0 &&
357 ep->d_type == DT_WHT) {
358 slotstatus = FOUND;
359 slotoffset = dp->i_offset;
360 slotsize = ep->d_reclen;
361 dp->i_reclen = slotsize;
362 enduseful = dp->i_size;
363 ap->a_cnp->cn_flags |= ISWHITEOUT;
364 numdirpasses--;
365 goto notfound;
366 }
367 dp->i_ino = ep->d_ino;
368 dp->i_reclen = ep->d_reclen;
369 goto found;
370 }
371 }
372 prevoff = dp->i_offset;
373 dp->i_offset += ep->d_reclen;
374 entryoffsetinblock += ep->d_reclen;
375 if (ep->d_ino)
376 enduseful = dp->i_offset;
377 }
378 notfound:
379 /*
380 * If we started in the middle of the directory and failed
381 * to find our target, we must check the beginning as well.
382 */
383 if (numdirpasses == 2) {
384 numdirpasses--;
385 dp->i_offset = 0;
386 endsearch = dp->i_diroff;
387 goto searchloop;
388 }
389 if (bp != NULL)
390 brelse(bp);
391 /*
392 * If creating, and at end of pathname and current
393 * directory has not been removed, then can consider
394 * allowing file to be created.
395 */
396 if ((nameiop == CREATE || nameiop == RENAME ||
397 (nameiop == DELETE &&
398 (ap->a_cnp->cn_flags & DOWHITEOUT) &&
399 (ap->a_cnp->cn_flags & ISWHITEOUT))) &&
400 (flags & ISLASTCN) && dp->i_effnlink != 0) {
401 /*
402 * Access for write is interpreted as allowing
403 * creation of files in the directory.
404 */
405 error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread);
406 if (error)
407 return (error);
408 /*
409 * Return an indication of where the new directory
410 * entry should be put. If we didn't find a slot,
411 * then set dp->i_count to 0 indicating
412 * that the new slot belongs at the end of the
413 * directory. If we found a slot, then the new entry
414 * can be put in the range from dp->i_offset to
415 * dp->i_offset + dp->i_count.
416 */
417 if (slotstatus == NONE) {
418 dp->i_offset = roundup2(dp->i_size, DIRBLKSIZ);
419 dp->i_count = 0;
420 enduseful = dp->i_offset;
421 } else if (nameiop == DELETE) {
422 dp->i_offset = slotoffset;
423 if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
424 dp->i_count = 0;
425 else
426 dp->i_count = dp->i_offset - prevoff;
427 } else {
428 dp->i_offset = slotoffset;
429 dp->i_count = slotsize;
430 if (enduseful < slotoffset + slotsize)
431 enduseful = slotoffset + slotsize;
432 }
433 dp->i_endoff = roundup2(enduseful, DIRBLKSIZ);
434 dp->i_flag |= IN_CHANGE | IN_UPDATE;
435 /*
436 * We return with the directory locked, so that
437 * the parameters we set up above will still be
438 * valid if we actually decide to do a direnter().
439 * We return ni_vp == NULL to indicate that the entry
440 * does not currently exist; we leave a pointer to
441 * the (locked) directory inode in ndp->ni_dvp.
442 * The pathname buffer is saved so that the name
443 * can be obtained later.
444 *
445 * NB - if the directory is unlocked, then this
446 * information cannot be used.
447 */
448 cnp->cn_flags |= SAVENAME;
449 if (!lockparent) {
450 VOP_UNLOCK(vdp, 0, td);
451 cnp->cn_flags |= PDIRUNLOCK;
452 }
453 return (EJUSTRETURN);
454 }
455 /*
456 * Insert name into cache (as non-existent) if appropriate.
457 */
458 if ((cnp->cn_flags & MAKEENTRY) && nameiop != CREATE)
459 cache_enter(vdp, *vpp, cnp);
460 return (ENOENT);
461
462 found:
463 if (numdirpasses == 2)
464 nchstats.ncs_pass2++;
465 /*
466 * Check that directory length properly reflects presence
467 * of this entry.
468 */
469 if (dp->i_offset + DIRSIZ(OFSFMT(vdp), ep) > dp->i_size) {
470 ufs_dirbad(dp, dp->i_offset, "i_size too small");
471 dp->i_size = dp->i_offset + DIRSIZ(OFSFMT(vdp), ep);
472 DIP(dp, i_size) = dp->i_size;
473 dp->i_flag |= IN_CHANGE | IN_UPDATE;
474 }
475 brelse(bp);
476
477 /*
478 * Found component in pathname.
479 * If the final component of path name, save information
480 * in the cache as to where the entry was found.
481 */
482 if ((flags & ISLASTCN) && nameiop == LOOKUP)
483 dp->i_diroff = dp->i_offset &~ (DIRBLKSIZ - 1);
484
485 /*
486 * If deleting, and at end of pathname, return
487 * parameters which can be used to remove file.
488 * If the wantparent flag isn't set, we return only
489 * the directory (in ndp->ni_dvp), otherwise we go
490 * on and lock the inode, being careful with ".".
491 */
492 if (nameiop == DELETE && (flags & ISLASTCN)) {
493 /*
494 * Write access to directory required to delete files.
495 */
496 error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread);
497 if (error)
498 return (error);
499 /*
500 * Return pointer to current entry in dp->i_offset,
501 * and distance past previous entry (if there
502 * is a previous entry in this block) in dp->i_count.
503 * Save directory inode pointer in ndp->ni_dvp for dirremove().
504 */
505 if ((dp->i_offset & (DIRBLKSIZ - 1)) == 0)
506 dp->i_count = 0;
507 else
508 dp->i_count = dp->i_offset - prevoff;
509 if (dp->i_number == dp->i_ino) {
510 VREF(vdp);
511 *vpp = vdp;
512 return (0);
513 }
514 if ((error = VFS_VGET(vdp->v_mount, dp->i_ino,
515 LK_EXCLUSIVE, &tdp)) != 0)
516 return (error);
517 /*
518 * If directory is "sticky", then user must own
519 * the directory, or the file in it, else she
520 * may not delete it (unless she's root). This
521 * implements append-only directories.
522 */
523 if ((dp->i_mode & ISVTX) &&
524 VOP_ACCESS(vdp, VADMIN, cred, cnp->cn_thread) &&
525 VOP_ACCESS(tdp, VADMIN, cred, cnp->cn_thread)) {
526 vput(tdp);
527 return (EPERM);
528 }
529 *vpp = tdp;
530 if (!lockparent) {
531 VOP_UNLOCK(vdp, 0, td);
532 cnp->cn_flags |= PDIRUNLOCK;
533 }
534 return (0);
535 }
536
537 /*
538 * If rewriting (RENAME), return the inode and the
539 * information required to rewrite the present directory
540 * Must get inode of directory entry to verify it's a
541 * regular file, or empty directory.
542 */
543 if (nameiop == RENAME && wantparent && (flags & ISLASTCN)) {
544 if ((error = VOP_ACCESS(vdp, VWRITE, cred, cnp->cn_thread)))
545 return (error);
546 /*
547 * Careful about locking second inode.
548 * This can only occur if the target is ".".
549 */
550 if (dp->i_number == dp->i_ino)
551 return (EISDIR);
552 if ((error = VFS_VGET(vdp->v_mount, dp->i_ino,
553 LK_EXCLUSIVE, &tdp)) != 0)
554 return (error);
555 *vpp = tdp;
556 cnp->cn_flags |= SAVENAME;
557 if (!lockparent) {
558 VOP_UNLOCK(vdp, 0, td);
559 cnp->cn_flags |= PDIRUNLOCK;
560 }
561 return (0);
562 }
563
564 /*
565 * Step through the translation in the name. We do not `vput' the
566 * directory because we may need it again if a symbolic link
567 * is relative to the current directory. Instead we save it
568 * unlocked as "pdp". We must get the target inode before unlocking
569 * the directory to insure that the inode will not be removed
570 * before we get it. We prevent deadlock by always fetching
571 * inodes from the root, moving down the directory tree. Thus
572 * when following backward pointers ".." we must unlock the
573 * parent directory before getting the requested directory.
574 * There is a potential race condition here if both the current
575 * and parent directories are removed before the VFS_VGET for the
576 * inode associated with ".." returns. We hope that this occurs
577 * infrequently since we cannot avoid this race condition without
578 * implementing a sophisticated deadlock detection algorithm.
579 * Note also that this simple deadlock detection scheme will not
580 * work if the filesystem has any hard links other than ".."
581 * that point backwards in the directory structure.
582 */
583 pdp = vdp;
584 if (flags & ISDOTDOT) {
585 if ((VFS_VGET(pdp->v_mount, dp->i_ino, LK_NOWAIT | LK_EXCLUSIVE,
586 &tdp)) != 0) {
587 VOP_UNLOCK(pdp, 0, td); /* race to get the inode */
588 error = VFS_VGET(pdp->v_mount, dp->i_ino,
589 LK_EXCLUSIVE, &tdp);
590 vn_lock(pdp, LK_EXCLUSIVE | LK_RETRY, td);
591 if (error)
592 return (error);
593 }
594 if (!lockparent || !(flags & ISLASTCN)) {
595 VOP_UNLOCK(pdp, 0, td);
596 cnp->cn_flags |= PDIRUNLOCK;
597 }
598 *vpp = tdp;
599 } else if (dp->i_number == dp->i_ino) {
600 VREF(vdp); /* we want ourself, ie "." */
601 *vpp = vdp;
602 } else {
603 error = VFS_VGET(pdp->v_mount, dp->i_ino, LK_EXCLUSIVE, &tdp);
604 if (error)
605 return (error);
606 if (!lockparent || !(flags & ISLASTCN)) {
607 VOP_UNLOCK(pdp, 0, td);
608 cnp->cn_flags |= PDIRUNLOCK;
609 }
610 *vpp = tdp;
611 }
612
613 /*
614 * Insert name into cache if appropriate.
615 */
616 if (cnp->cn_flags & MAKEENTRY)
617 cache_enter(vdp, *vpp, cnp);
618 return (0);
619 }
620
621 void
622 ufs_dirbad(ip, offset, how)
623 struct inode *ip;
624 doff_t offset;
625 char *how;
626 {
627 struct mount *mp;
628
629 mp = ITOV(ip)->v_mount;
630 (void)printf("%s: bad dir ino %lu at offset %ld: %s\n",
631 mp->mnt_stat.f_mntonname, (u_long)ip->i_number, (long)offset, how);
632 if ((mp->mnt_flag & MNT_RDONLY) == 0)
633 panic("ufs_dirbad: bad dir");
634 }
635
636 /*
637 * Do consistency checking on a directory entry:
638 * record length must be multiple of 4
639 * entry must fit in rest of its DIRBLKSIZ block
640 * record must be large enough to contain entry
641 * name is not longer than MAXNAMLEN
642 * name must be as long as advertised, and null terminated
643 */
644 int
645 ufs_dirbadentry(dp, ep, entryoffsetinblock)
646 struct vnode *dp;
647 struct direct *ep;
648 int entryoffsetinblock;
649 {
650 int i, namlen;
651
652 # if (BYTE_ORDER == LITTLE_ENDIAN)
653 if (OFSFMT(dp))
654 namlen = ep->d_type;
655 else
656 namlen = ep->d_namlen;
657 # else
658 namlen = ep->d_namlen;
659 # endif
660 if ((ep->d_reclen & 0x3) != 0 ||
661 ep->d_reclen > DIRBLKSIZ - (entryoffsetinblock & (DIRBLKSIZ - 1)) ||
662 ep->d_reclen < DIRSIZ(OFSFMT(dp), ep) || namlen > MAXNAMLEN) {
663 /*return (1); */
664 printf("First bad\n");
665 goto bad;
666 }
667 if (ep->d_ino == 0)
668 return (0);
669 for (i = 0; i < namlen; i++)
670 if (ep->d_name[i] == '\0') {
671 /*return (1); */
672 printf("Second bad\n");
673 goto bad;
674 }
675 if (ep->d_name[i])
676 goto bad;
677 return (0);
678 bad:
679 return (1);
680 }
681
682 /*
683 * Construct a new directory entry after a call to namei, using the
684 * parameters that it left in the componentname argument cnp. The
685 * argument ip is the inode to which the new directory entry will refer.
686 */
687 void
688 ufs_makedirentry(ip, cnp, newdirp)
689 struct inode *ip;
690 struct componentname *cnp;
691 struct direct *newdirp;
692 {
693
694 #ifdef DIAGNOSTIC
695 if ((cnp->cn_flags & SAVENAME) == 0)
696 panic("ufs_makedirentry: missing name");
697 #endif
698 newdirp->d_ino = ip->i_number;
699 newdirp->d_namlen = cnp->cn_namelen;
700 bcopy(cnp->cn_nameptr, newdirp->d_name, (unsigned)cnp->cn_namelen + 1);
701 if (ITOV(ip)->v_mount->mnt_maxsymlinklen > 0)
702 newdirp->d_type = IFTODT(ip->i_mode);
703 else {
704 newdirp->d_type = 0;
705 # if (BYTE_ORDER == LITTLE_ENDIAN)
706 { u_char tmp = newdirp->d_namlen;
707 newdirp->d_namlen = newdirp->d_type;
708 newdirp->d_type = tmp; }
709 # endif
710 }
711 }
712
713 /*
714 * Write a directory entry after a call to namei, using the parameters
715 * that it left in nameidata. The argument dirp is the new directory
716 * entry contents. Dvp is a pointer to the directory to be written,
717 * which was left locked by namei. Remaining parameters (dp->i_offset,
718 * dp->i_count) indicate how the space for the new entry is to be obtained.
719 * Non-null bp indicates that a directory is being created (for the
720 * soft dependency code).
721 */
722 int
723 ufs_direnter(dvp, tvp, dirp, cnp, newdirbp)
724 struct vnode *dvp;
725 struct vnode *tvp;
726 struct direct *dirp;
727 struct componentname *cnp;
728 struct buf *newdirbp;
729 {
730 struct ucred *cr;
731 struct thread *td;
732 int newentrysize;
733 struct inode *dp;
734 struct buf *bp;
735 u_int dsize;
736 struct direct *ep, *nep;
737 int error, ret, blkoff, loc, spacefree, flags;
738 char *dirbuf;
739
740 td = curthread; /* XXX */
741 cr = td->td_ucred;
742
743 dp = VTOI(dvp);
744 newentrysize = DIRSIZ(OFSFMT(dvp), dirp);
745
746 if (dp->i_count == 0) {
747 /*
748 * If dp->i_count is 0, then namei could find no
749 * space in the directory. Here, dp->i_offset will
750 * be on a directory block boundary and we will write the
751 * new entry into a fresh block.
752 */
753 if (dp->i_offset & (DIRBLKSIZ - 1))
754 panic("ufs_direnter: newblk");
755 flags = BA_CLRBUF;
756 if (!DOINGSOFTDEP(dvp) && !DOINGASYNC(dvp))
757 flags |= IO_SYNC;
758 if ((error = UFS_BALLOC(dvp, (off_t)dp->i_offset, DIRBLKSIZ,
759 cr, flags, &bp)) != 0) {
760 if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
761 bdwrite(newdirbp);
762 return (error);
763 }
764 dp->i_size = dp->i_offset + DIRBLKSIZ;
765 DIP(dp, i_size) = dp->i_size;
766 dp->i_flag |= IN_CHANGE | IN_UPDATE;
767 vnode_pager_setsize(dvp, (u_long)dp->i_size);
768 dirp->d_reclen = DIRBLKSIZ;
769 blkoff = dp->i_offset &
770 (VFSTOUFS(dvp->v_mount)->um_mountp->mnt_stat.f_iosize - 1);
771 bcopy((caddr_t)dirp, (caddr_t)bp->b_data + blkoff,newentrysize);
772 #ifdef UFS_DIRHASH
773 if (dp->i_dirhash != NULL) {
774 ufsdirhash_newblk(dp, dp->i_offset);
775 ufsdirhash_add(dp, dirp, dp->i_offset);
776 ufsdirhash_checkblock(dp, (char *)bp->b_data + blkoff,
777 dp->i_offset);
778 }
779 #endif
780 if (DOINGSOFTDEP(dvp)) {
781 /*
782 * Ensure that the entire newly allocated block is a
783 * valid directory so that future growth within the
784 * block does not have to ensure that the block is
785 * written before the inode.
786 */
787 blkoff += DIRBLKSIZ;
788 while (blkoff < bp->b_bcount) {
789 ((struct direct *)
790 (bp->b_data + blkoff))->d_reclen = DIRBLKSIZ;
791 blkoff += DIRBLKSIZ;
792 }
793 if (softdep_setup_directory_add(bp, dp, dp->i_offset,
794 dirp->d_ino, newdirbp, 1) == 0) {
795 bdwrite(bp);
796 return (UFS_UPDATE(dvp, 0));
797 }
798 /* We have just allocated a directory block in an
799 * indirect block. Rather than tracking when it gets
800 * claimed by the inode, we simply do a VOP_FSYNC
801 * now to ensure that it is there (in case the user
802 * does a future fsync). Note that we have to unlock
803 * the inode for the entry that we just entered, as
804 * the VOP_FSYNC may need to lock other inodes which
805 * can lead to deadlock if we also hold a lock on
806 * the newly entered node.
807 */
808 if ((error = BUF_WRITE(bp)))
809 return (error);
810 if (tvp != NULL)
811 VOP_UNLOCK(tvp, 0, td);
812 error = VOP_FSYNC(dvp, td->td_ucred, MNT_WAIT, td);
813 if (tvp != NULL)
814 vn_lock(tvp, LK_EXCLUSIVE | LK_RETRY, td);
815 return (error);
816 }
817 if (DOINGASYNC(dvp)) {
818 bdwrite(bp);
819 return (UFS_UPDATE(dvp, 0));
820 }
821 error = BUF_WRITE(bp);
822 ret = UFS_UPDATE(dvp, 1);
823 if (error == 0)
824 return (ret);
825 return (error);
826 }
827
828 /*
829 * If dp->i_count is non-zero, then namei found space for the new
830 * entry in the range dp->i_offset to dp->i_offset + dp->i_count
831 * in the directory. To use this space, we may have to compact
832 * the entries located there, by copying them together towards the
833 * beginning of the block, leaving the free space in one usable
834 * chunk at the end.
835 */
836
837 /*
838 * Increase size of directory if entry eats into new space.
839 * This should never push the size past a new multiple of
840 * DIRBLKSIZE.
841 *
842 * N.B. - THIS IS AN ARTIFACT OF 4.2 AND SHOULD NEVER HAPPEN.
843 */
844 if (dp->i_offset + dp->i_count > dp->i_size) {
845 dp->i_size = dp->i_offset + dp->i_count;
846 DIP(dp, i_size) = dp->i_size;
847 }
848 /*
849 * Get the block containing the space for the new directory entry.
850 */
851 error = UFS_BLKATOFF(dvp, (off_t)dp->i_offset, &dirbuf, &bp);
852 if (error) {
853 if (DOINGSOFTDEP(dvp) && newdirbp != NULL)
854 bdwrite(newdirbp);
855 return (error);
856 }
857 /*
858 * Find space for the new entry. In the simple case, the entry at
859 * offset base will have the space. If it does not, then namei
860 * arranged that compacting the region dp->i_offset to
861 * dp->i_offset + dp->i_count would yield the space.
862 */
863 ep = (struct direct *)dirbuf;
864 dsize = ep->d_ino ? DIRSIZ(OFSFMT(dvp), ep) : 0;
865 spacefree = ep->d_reclen - dsize;
866 for (loc = ep->d_reclen; loc < dp->i_count; ) {
867 nep = (struct direct *)(dirbuf + loc);
868
869 /* Trim the existing slot (NB: dsize may be zero). */
870 ep->d_reclen = dsize;
871 ep = (struct direct *)((char *)ep + dsize);
872
873 /* Read nep->d_reclen now as the bcopy() may clobber it. */
874 loc += nep->d_reclen;
875 if (nep->d_ino == 0) {
876 /*
877 * A mid-block unused entry. Such entries are
878 * never created by the kernel, but fsck_ffs
879 * can create them (and it doesn't fix them).
880 *
881 * Add up the free space, and initialise the
882 * relocated entry since we don't bcopy it.
883 */
884 spacefree += nep->d_reclen;
885 ep->d_ino = 0;
886 dsize = 0;
887 continue;
888 }
889 dsize = DIRSIZ(OFSFMT(dvp), nep);
890 spacefree += nep->d_reclen - dsize;
891 #ifdef UFS_DIRHASH
892 if (dp->i_dirhash != NULL)
893 ufsdirhash_move(dp, nep,
894 dp->i_offset + ((char *)nep - dirbuf),
895 dp->i_offset + ((char *)ep - dirbuf));
896 #endif
897 if (DOINGSOFTDEP(dvp))
898 softdep_change_directoryentry_offset(dp, dirbuf,
899 (caddr_t)nep, (caddr_t)ep, dsize);
900 else
901 bcopy((caddr_t)nep, (caddr_t)ep, dsize);
902 }
903 /*
904 * Here, `ep' points to a directory entry containing `dsize' in-use
905 * bytes followed by `spacefree' unused bytes. If ep->d_ino == 0,
906 * then the entry is completely unused (dsize == 0). The value
907 * of ep->d_reclen is always indeterminate.
908 *
909 * Update the pointer fields in the previous entry (if any),
910 * copy in the new entry, and write out the block.
911 */
912 if (ep->d_ino == 0 ||
913 (ep->d_ino == WINO &&
914 bcmp(ep->d_name, dirp->d_name, dirp->d_namlen) == 0)) {
915 if (spacefree + dsize < newentrysize)
916 panic("ufs_direnter: compact1");
917 dirp->d_reclen = spacefree + dsize;
918 } else {
919 if (spacefree < newentrysize)
920 panic("ufs_direnter: compact2");
921 dirp->d_reclen = spacefree;
922 ep->d_reclen = dsize;
923 ep = (struct direct *)((char *)ep + dsize);
924 }
925 #ifdef UFS_DIRHASH
926 if (dp->i_dirhash != NULL && (ep->d_ino == 0 ||
927 dirp->d_reclen == spacefree))
928 ufsdirhash_add(dp, dirp, dp->i_offset + ((char *)ep - dirbuf));
929 #endif
930 bcopy((caddr_t)dirp, (caddr_t)ep, (u_int)newentrysize);
931 #ifdef UFS_DIRHASH
932 if (dp->i_dirhash != NULL)
933 ufsdirhash_checkblock(dp, dirbuf -
934 (dp->i_offset & (DIRBLKSIZ - 1)),
935 dp->i_offset & ~(DIRBLKSIZ - 1));
936 #endif
937
938 if (DOINGSOFTDEP(dvp)) {
939 (void) softdep_setup_directory_add(bp, dp,
940 dp->i_offset + (caddr_t)ep - dirbuf,
941 dirp->d_ino, newdirbp, 0);
942 bdwrite(bp);
943 } else {
944 if (DOINGASYNC(dvp)) {
945 bdwrite(bp);
946 error = 0;
947 } else {
948 error = BUF_WRITE(bp);
949 }
950 }
951 dp->i_flag |= IN_CHANGE | IN_UPDATE;
952 /*
953 * If all went well, and the directory can be shortened, proceed
954 * with the truncation. Note that we have to unlock the inode for
955 * the entry that we just entered, as the truncation may need to
956 * lock other inodes which can lead to deadlock if we also hold a
957 * lock on the newly entered node.
958 */
959 if (error == 0 && dp->i_endoff && dp->i_endoff < dp->i_size) {
960 if (tvp != NULL)
961 VOP_UNLOCK(tvp, 0, td);
962 #ifdef UFS_DIRHASH
963 if (dp->i_dirhash != NULL)
964 ufsdirhash_dirtrunc(dp, dp->i_endoff);
965 #endif
966 (void) UFS_TRUNCATE(dvp, (off_t)dp->i_endoff,
967 IO_NORMAL | IO_SYNC, cr, td);
968 if (tvp != NULL)
969 vn_lock(tvp, LK_EXCLUSIVE | LK_RETRY, td);
970 }
971 return (error);
972 }
973
974 /*
975 * Remove a directory entry after a call to namei, using
976 * the parameters which it left in nameidata. The entry
977 * dp->i_offset contains the offset into the directory of the
978 * entry to be eliminated. The dp->i_count field contains the
979 * size of the previous record in the directory. If this
980 * is 0, the first entry is being deleted, so we need only
981 * zero the inode number to mark the entry as free. If the
982 * entry is not the first in the directory, we must reclaim
983 * the space of the now empty record by adding the record size
984 * to the size of the previous entry.
985 */
986 int
987 ufs_dirremove(dvp, ip, flags, isrmdir)
988 struct vnode *dvp;
989 struct inode *ip;
990 int flags;
991 int isrmdir;
992 {
993 struct inode *dp;
994 struct direct *ep;
995 struct buf *bp;
996 int error;
997
998 dp = VTOI(dvp);
999
1000 if (flags & DOWHITEOUT) {
1001 /*
1002 * Whiteout entry: set d_ino to WINO.
1003 */
1004 if ((error =
1005 UFS_BLKATOFF(dvp, (off_t)dp->i_offset, (char **)&ep, &bp)) != 0)
1006 return (error);
1007 ep->d_ino = WINO;
1008 ep->d_type = DT_WHT;
1009 goto out;
1010 }
1011
1012 if ((error = UFS_BLKATOFF(dvp,
1013 (off_t)(dp->i_offset - dp->i_count), (char **)&ep, &bp)) != 0)
1014 return (error);
1015 #ifdef UFS_DIRHASH
1016 /*
1017 * Remove the dirhash entry. This is complicated by the fact
1018 * that `ep' is the previous entry when dp->i_count != 0.
1019 */
1020 if (dp->i_dirhash != NULL)
1021 ufsdirhash_remove(dp, (dp->i_count == 0) ? ep :
1022 (struct direct *)((char *)ep + ep->d_reclen), dp->i_offset);
1023 #endif
1024 if (dp->i_count == 0) {
1025 /*
1026 * First entry in block: set d_ino to zero.
1027 */
1028 ep->d_ino = 0;
1029 } else {
1030 /*
1031 * Collapse new free space into previous entry.
1032 */
1033 ep->d_reclen += dp->i_reclen;
1034 }
1035 #ifdef UFS_DIRHASH
1036 if (dp->i_dirhash != NULL)
1037 ufsdirhash_checkblock(dp, (char *)ep -
1038 ((dp->i_offset - dp->i_count) & (DIRBLKSIZ - 1)),
1039 dp->i_offset & ~(DIRBLKSIZ - 1));
1040 #endif
1041 out:
1042 if (DOINGSOFTDEP(dvp)) {
1043 if (ip) {
1044 ip->i_effnlink--;
1045 softdep_change_linkcnt(ip);
1046 softdep_setup_remove(bp, dp, ip, isrmdir);
1047 }
1048 if (softdep_slowdown(dvp)) {
1049 error = BUF_WRITE(bp);
1050 } else {
1051 bdwrite(bp);
1052 error = 0;
1053 }
1054 } else {
1055 if (ip) {
1056 ip->i_effnlink--;
1057 ip->i_nlink--;
1058 DIP(ip, i_nlink) = ip->i_nlink;
1059 ip->i_flag |= IN_CHANGE;
1060 }
1061 if (flags & DOWHITEOUT)
1062 error = BUF_WRITE(bp);
1063 else if (DOINGASYNC(dvp) && dp->i_count != 0) {
1064 bdwrite(bp);
1065 error = 0;
1066 } else
1067 error = BUF_WRITE(bp);
1068 }
1069 dp->i_flag |= IN_CHANGE | IN_UPDATE;
1070 /*
1071 * If the last named reference to a snapshot goes away,
1072 * drop its snapshot reference so that it will be reclaimed
1073 * when last open reference goes away.
1074 */
1075 #if defined(FFS) || defined(IFS)
1076 if (ip != 0 && (ip->i_flags & SF_SNAPSHOT) != 0 && ip->i_effnlink == 0)
1077 ffs_snapgone(ip);
1078 #endif
1079 return (error);
1080 }
1081
1082 /*
1083 * Rewrite an existing directory entry to point at the inode
1084 * supplied. The parameters describing the directory entry are
1085 * set up by a call to namei.
1086 */
1087 int
1088 ufs_dirrewrite(dp, oip, newinum, newtype, isrmdir)
1089 struct inode *dp, *oip;
1090 ino_t newinum;
1091 int newtype;
1092 int isrmdir;
1093 {
1094 struct buf *bp;
1095 struct direct *ep;
1096 struct vnode *vdp = ITOV(dp);
1097 int error;
1098
1099 error = UFS_BLKATOFF(vdp, (off_t)dp->i_offset, (char **)&ep, &bp);
1100 if (error)
1101 return (error);
1102 ep->d_ino = newinum;
1103 if (!OFSFMT(vdp))
1104 ep->d_type = newtype;
1105 oip->i_effnlink--;
1106 if (DOINGSOFTDEP(vdp)) {
1107 softdep_change_linkcnt(oip);
1108 softdep_setup_directory_change(bp, dp, oip, newinum, isrmdir);
1109 bdwrite(bp);
1110 } else {
1111 oip->i_nlink--;
1112 DIP(oip, i_nlink) = oip->i_nlink;
1113 oip->i_flag |= IN_CHANGE;
1114 if (DOINGASYNC(vdp)) {
1115 bdwrite(bp);
1116 error = 0;
1117 } else {
1118 error = BUF_WRITE(bp);
1119 }
1120 }
1121 dp->i_flag |= IN_CHANGE | IN_UPDATE;
1122 /*
1123 * If the last named reference to a snapshot goes away,
1124 * drop its snapshot reference so that it will be reclaimed
1125 * when last open reference goes away.
1126 */
1127 #if defined(FFS) || defined(IFS)
1128 if ((oip->i_flags & SF_SNAPSHOT) != 0 && oip->i_effnlink == 0)
1129 ffs_snapgone(oip);
1130 #endif
1131 return (error);
1132 }
1133
1134 /*
1135 * Check if a directory is empty or not.
1136 * Inode supplied must be locked.
1137 *
1138 * Using a struct dirtemplate here is not precisely
1139 * what we want, but better than using a struct direct.
1140 *
1141 * NB: does not handle corrupted directories.
1142 */
1143 int
1144 ufs_dirempty(ip, parentino, cred)
1145 struct inode *ip;
1146 ino_t parentino;
1147 struct ucred *cred;
1148 {
1149 doff_t off;
1150 struct dirtemplate dbuf;
1151 struct direct *dp = (struct direct *)&dbuf;
1152 int error, count, namlen;
1153 #define MINDIRSIZ (sizeof (struct dirtemplate) / 2)
1154
1155 for (off = 0; off < ip->i_size; off += dp->d_reclen) {
1156 error = vn_rdwr(UIO_READ, ITOV(ip), (caddr_t)dp, MINDIRSIZ,
1157 off, UIO_SYSSPACE, IO_NODELOCKED | IO_NOMACCHECK, cred,
1158 NOCRED, &count, (struct thread *)0);
1159 /*
1160 * Since we read MINDIRSIZ, residual must
1161 * be 0 unless we're at end of file.
1162 */
1163 if (error || count != 0)
1164 return (0);
1165 /* avoid infinite loops */
1166 if (dp->d_reclen == 0)
1167 return (0);
1168 /* skip empty entries */
1169 if (dp->d_ino == 0 || dp->d_ino == WINO)
1170 continue;
1171 /* accept only "." and ".." */
1172 # if (BYTE_ORDER == LITTLE_ENDIAN)
1173 if (OFSFMT(ITOV(ip)))
1174 namlen = dp->d_type;
1175 else
1176 namlen = dp->d_namlen;
1177 # else
1178 namlen = dp->d_namlen;
1179 # endif
1180 if (namlen > 2)
1181 return (0);
1182 if (dp->d_name[0] != '.')
1183 return (0);
1184 /*
1185 * At this point namlen must be 1 or 2.
1186 * 1 implies ".", 2 implies ".." if second
1187 * char is also "."
1188 */
1189 if (namlen == 1 && dp->d_ino == ip->i_number)
1190 continue;
1191 if (dp->d_name[1] == '.' && dp->d_ino == parentino)
1192 continue;
1193 return (0);
1194 }
1195 return (1);
1196 }
1197
1198 /*
1199 * Check if source directory is in the path of the target directory.
1200 * Target is supplied locked, source is unlocked.
1201 * The target is always vput before returning.
1202 */
1203 int
1204 ufs_checkpath(source, target, cred)
1205 struct inode *source, *target;
1206 struct ucred *cred;
1207 {
1208 struct vnode *vp;
1209 int error, namlen;
1210 ino_t rootino;
1211 struct dirtemplate dirbuf;
1212
1213 vp = ITOV(target);
1214 if (target->i_number == source->i_number) {
1215 error = EEXIST;
1216 goto out;
1217 }
1218 rootino = ROOTINO;
1219 error = 0;
1220 if (target->i_number == rootino)
1221 goto out;
1222
1223 for (;;) {
1224 if (vp->v_type != VDIR) {
1225 error = ENOTDIR;
1226 break;
1227 }
1228 error = vn_rdwr(UIO_READ, vp, (caddr_t)&dirbuf,
1229 sizeof (struct dirtemplate), (off_t)0, UIO_SYSSPACE,
1230 IO_NODELOCKED | IO_NOMACCHECK, cred, NOCRED, (int *)0,
1231 (struct thread *)0);
1232 if (error != 0)
1233 break;
1234 # if (BYTE_ORDER == LITTLE_ENDIAN)
1235 if (OFSFMT(vp))
1236 namlen = dirbuf.dotdot_type;
1237 else
1238 namlen = dirbuf.dotdot_namlen;
1239 # else
1240 namlen = dirbuf.dotdot_namlen;
1241 # endif
1242 if (namlen != 2 ||
1243 dirbuf.dotdot_name[0] != '.' ||
1244 dirbuf.dotdot_name[1] != '.') {
1245 error = ENOTDIR;
1246 break;
1247 }
1248 if (dirbuf.dotdot_ino == source->i_number) {
1249 error = EINVAL;
1250 break;
1251 }
1252 if (dirbuf.dotdot_ino == rootino)
1253 break;
1254 vput(vp);
1255 error = VFS_VGET(vp->v_mount, dirbuf.dotdot_ino,
1256 LK_EXCLUSIVE, &vp);
1257 if (error) {
1258 vp = NULL;
1259 break;
1260 }
1261 }
1262
1263 out:
1264 if (error == ENOTDIR)
1265 printf("checkpath: .. not a directory\n");
1266 if (vp != NULL)
1267 vput(vp);
1268 return (error);
1269 }
Cache object: 569803639c50fe01f654df8d7ebc8ce9
|