/*-1* SPDX-License-Identifier: BSD-3-Clause2*3* Copyright (c) 1989, 1993, 19944* The Regents of the University of California. All rights reserved.5*6* This code is derived from software contributed to Berkeley7* by Pace Willisson ([email protected]). The Rock Ridge Extension8* Support code is derived from software contributed to Berkeley9* by Atsushi Murai ([email protected]).10*11* Redistribution and use in source and binary forms, with or without12* modification, are permitted provided that the following conditions13* are met:14* 1. Redistributions of source code must retain the above copyright15* notice, this list of conditions and the following disclaimer.16* 2. Redistributions in binary form must reproduce the above copyright17* notice, this list of conditions and the following disclaimer in the18* documentation and/or other materials provided with the distribution.19* 3. Neither the name of the University nor the names of its contributors20* may be used to endorse or promote products derived from this software21* without specific prior written permission.22*23* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND24* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE25* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE26* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE27* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL28* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS29* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)30* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT31* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY32* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF33* SUCH DAMAGE.34*/3536#include <sys/param.h>37#include <sys/systm.h>38#include <sys/namei.h>39#include <sys/bio.h>40#include <sys/buf.h>41#include <sys/vnode.h>42#include <sys/mount.h>4344#include <fs/cd9660/iso.h>45#include <fs/cd9660/cd9660_node.h>46#include <fs/cd9660/iso_rrip.h>4748struct cd9660_ino_alloc_arg {49ino_t ino;50ino_t i_ino;51struct iso_directory_record *ep;52};5354static int55cd9660_ino_alloc(struct mount *mp, void *arg, int lkflags,56struct vnode **vpp)57{58struct cd9660_ino_alloc_arg *dd_arg;5960dd_arg = arg;61return (cd9660_vget_internal(mp, dd_arg->i_ino, lkflags, vpp,62dd_arg->i_ino != dd_arg->ino, dd_arg->ep));63}6465/*66* Convert a component of a pathname into a pointer to a locked inode.67* This is a very central and rather complicated routine.68* If the filesystem is not maintained in a strict tree hierarchy,69* this can result in a deadlock situation (see comments in code below).70*71* The flag argument is LOOKUP, CREATE, RENAME, or DELETE depending on72* whether the name is to be looked up, created, renamed, or deleted.73* When CREATE, RENAME, or DELETE is specified, information usable in74* creating, renaming, or deleting a directory entry may be calculated.75* If flag has LOCKPARENT or'ed into it and the target of the pathname76* exists, lookup returns both the target and its parent directory locked.77* When creating or renaming and LOCKPARENT is specified, the target may78* not be ".". When deleting and LOCKPARENT is specified, the target may79* be "."., but the caller must check to ensure it does an vrele and iput80* instead of two iputs.81*82* Overall outline of ufs_lookup:83*84* search for name in directory, to found or notfound85* notfound:86* if creating, return locked directory, leaving info on available slots87* else return error88* found:89* if at end of path and deleting, return information to allow delete90* if at end of path and rewriting (RENAME and LOCKPARENT), lock target91* inode and return info to allow rewrite92* if not at end, add name to cache; if at end and neither creating93* nor deleting, add name to cache94*95* NOTE: (LOOKUP | LOCKPARENT) currently returns the parent inode unlocked.96*/97int98cd9660_lookup(struct vop_cachedlookup_args *ap)99{100struct vnode *vdp; /* vnode for directory being searched */101struct iso_node *dp; /* inode for directory being searched */102struct iso_mnt *imp; /* filesystem that directory is in */103struct buf *bp; /* a buffer of directory entries */104struct iso_directory_record *ep;/* the current directory entry */105struct iso_directory_record *ep2;/* copy of current directory entry */106int entryoffsetinblock; /* offset of ep in bp's buffer */107int saveoffset = 0; /* offset of last directory entry in dir */108doff_t i_diroff; /* cached i_diroff value. */109doff_t i_offset; /* cached i_offset value. */110int numdirpasses; /* strategy for directory search */111doff_t endsearch; /* offset to end directory search */112struct vnode *pdp; /* saved dp during symlink work */113struct vnode *tdp; /* returned by cd9660_vget_internal */114struct cd9660_ino_alloc_arg dd_arg;115u_long bmask; /* block offset mask */116int error;117ino_t ino, i_ino;118int ltype, reclen;119u_short namelen;120int isoflags;121char altname[NAME_MAX];122int res;123int assoc, len;124char *name;125struct vnode **vpp = ap->a_vpp;126struct componentname *cnp = ap->a_cnp;127uint64_t flags = cnp->cn_flags;128int nameiop = cnp->cn_nameiop;129130ep2 = ep = NULL;131bp = NULL;132*vpp = NULL;133vdp = ap->a_dvp;134dp = VTOI(vdp);135imp = dp->i_mnt;136137/*138* We now have a segment name to search for, and a directory to search.139*/140ino = reclen = 0;141i_diroff = dp->i_diroff;142len = cnp->cn_namelen;143name = cnp->cn_nameptr;144145/*146* A leading `=' means, we are looking for an associated file147*/148if ((assoc = (imp->iso_ftype != ISO_FTYPE_RRIP && *name == ASSOCCHAR)))149{150len--;151name++;152}153154/*155* If there is cached information on a previous search of156* this directory, pick up where we last left off.157* We cache only lookups as these are the most common158* and have the greatest payoff. Caching CREATE has little159* benefit as it usually must search the entire directory160* to determine that the entry does not exist. Caching the161* location of the last DELETE or RENAME has not reduced162* profiling time and hence has been removed in the interest163* of simplicity.164*/165bmask = imp->im_bmask;166if (nameiop != LOOKUP || i_diroff == 0 || i_diroff > dp->i_size) {167entryoffsetinblock = 0;168i_offset = 0;169numdirpasses = 1;170} else {171i_offset = i_diroff;172if ((entryoffsetinblock = i_offset & bmask) &&173(error = cd9660_blkatoff(vdp, (off_t)i_offset, NULL, &bp)))174return (error);175numdirpasses = 2;176nchstats.ncs_2passes++;177}178endsearch = dp->i_size;179180searchloop:181while (i_offset < endsearch) {182/*183* If offset is on a block boundary,184* read the next directory block.185* Release previous if it exists.186*/187if ((i_offset & bmask) == 0) {188if (bp != NULL)189brelse(bp);190if ((error =191cd9660_blkatoff(vdp, (off_t)i_offset, NULL, &bp)) != 0)192return (error);193entryoffsetinblock = 0;194}195/*196* Get pointer to next entry.197*/198ep = (struct iso_directory_record *)199((char *)bp->b_data + entryoffsetinblock);200201reclen = isonum_711(ep->length);202if (reclen == 0) {203/* skip to next block, if any */204i_offset =205(i_offset & ~bmask) + imp->logical_block_size;206continue;207}208209if (reclen < ISO_DIRECTORY_RECORD_SIZE)210/* illegal entry, stop */211break;212213if (entryoffsetinblock + reclen > imp->logical_block_size)214/* entries are not allowed to cross boundaries */215break;216217namelen = isonum_711(ep->name_len);218isoflags = isonum_711(imp->iso_ftype == ISO_FTYPE_HIGH_SIERRA?219&ep->date[6]: ep->flags);220221if (reclen < ISO_DIRECTORY_RECORD_SIZE + namelen)222/* illegal entry, stop */223break;224225/*226* Check for a name match.227*/228switch (imp->iso_ftype) {229default:230if (!(isoflags & 4) == !assoc) {231if ((len == 1232&& *name == '.')233|| (flags & ISDOTDOT)) {234if (namelen == 1235&& ep->name[0] == ((flags & ISDOTDOT) ? 1 : 0)) {236/*237* Save directory entry's inode number and238* release directory buffer.239*/240i_ino = isodirino(ep, imp);241goto found;242}243if (namelen != 1244|| ep->name[0] != 0)245goto notfound;246} else if (!(res = isofncmp(name, len,247ep->name, namelen,248imp->joliet_level,249imp->im_flags,250imp->im_d2l,251imp->im_l2d))) {252if (isoflags & 2)253ino = isodirino(ep, imp);254else255ino = dbtob(bp->b_blkno)256+ entryoffsetinblock;257saveoffset = i_offset;258} else if (ino)259goto foundino;260#ifdef NOSORTBUG /* On some CDs directory entries are not sorted correctly */261else if (res < 0)262goto notfound;263else if (res > 0 && numdirpasses == 2)264numdirpasses++;265#endif266}267break;268case ISO_FTYPE_RRIP:269if (isonum_711(ep->flags)&2)270ino = isodirino(ep, imp);271else272ino = dbtob(bp->b_blkno) + entryoffsetinblock;273i_ino = ino;274cd9660_rrip_getname(ep, altname, &namelen, &i_ino, imp);275if (namelen == cnp->cn_namelen276&& !bcmp(name,altname,namelen))277goto found;278ino = 0;279break;280}281i_offset += reclen;282entryoffsetinblock += reclen;283}284if (ino) {285foundino:286i_ino = ino;287if (saveoffset != i_offset) {288if (lblkno(imp, i_offset) !=289lblkno(imp, saveoffset)) {290if (bp != NULL)291brelse(bp);292if ((error = cd9660_blkatoff(vdp,293(off_t)saveoffset, NULL, &bp)) != 0)294return (error);295}296entryoffsetinblock = saveoffset & bmask;297ep = (struct iso_directory_record *)298((char *)bp->b_data + entryoffsetinblock);299reclen = isonum_711(ep->length);300i_offset = saveoffset;301}302goto found;303}304notfound:305/*306* If we started in the middle of the directory and failed307* to find our target, we must check the beginning as well.308*/309if (numdirpasses == 2) {310numdirpasses--;311i_offset = 0;312endsearch = i_diroff;313goto searchloop;314}315if (bp != NULL)316brelse(bp);317318/*319* Insert name into cache (as non-existent) if appropriate.320*/321if (cnp->cn_flags & MAKEENTRY)322cache_enter(vdp, *vpp, cnp);323if (nameiop == CREATE || nameiop == RENAME)324return (EROFS);325return (ENOENT);326327found:328if (numdirpasses == 2)329nchstats.ncs_pass2++;330331/*332* Found component in pathname.333* If the final component of path name, save information334* in the cache as to where the entry was found.335*/336if ((flags & ISLASTCN) && nameiop == LOOKUP)337dp->i_diroff = i_offset;338339/*340* Step through the translation in the name. We do not `vput' the341* directory because we may need it again if a symbolic link342* is relative to the current directory. Instead we save it343* unlocked as "pdp". We must get the target inode before unlocking344* the directory to insure that the inode will not be removed345* before we get it. We prevent deadlock by always fetching346* inodes from the root, moving down the directory tree. Thus347* when following backward pointers ".." we must unlock the348* parent directory before getting the requested directory.349* There is a potential race condition here if both the current350* and parent directories are removed before the `vget' for the351* inode associated with ".." returns. We hope that this occurs352* infrequently since we cannot avoid this race condition without353* implementing a sophisticated deadlock detection algorithm.354* Note also that this simple deadlock detection scheme will not355* work if the filesystem has any hard links other than ".."356* that point backwards in the directory structure.357*/358pdp = vdp;359360/*361* Make a copy of the directory entry for non "." lookups so362* we can drop the buffer before calling vget() to avoid a363* lock order reversal between the vnode lock and the buffer364* lock.365*/366if (dp->i_number != i_ino) {367ep2 = malloc(reclen, M_TEMP, M_WAITOK);368memcpy(ep2, ep, reclen);369ep = ep2;370}371brelse(bp);372373/*374* If ino is different from i_ino,375* it's a relocated directory.376*/377if (flags & ISDOTDOT) {378dd_arg.ino = ino;379dd_arg.i_ino = i_ino;380dd_arg.ep = ep;381error = vn_vget_ino_gen(pdp, cd9660_ino_alloc, &dd_arg,382cnp->cn_lkflags, &tdp);383free(ep2, M_TEMP);384if (error != 0)385return (error);386*vpp = tdp;387} else if (dp->i_number == i_ino) {388vref(vdp); /* we want ourself, ie "." */389/*390* When we lookup "." we still can be asked to lock it391* differently.392*/393ltype = cnp->cn_lkflags & LK_TYPE_MASK;394if (ltype != VOP_ISLOCKED(vdp)) {395if (ltype == LK_EXCLUSIVE)396vn_lock(vdp, LK_UPGRADE | LK_RETRY);397else /* if (ltype == LK_SHARED) */398vn_lock(vdp, LK_DOWNGRADE | LK_RETRY);399}400*vpp = vdp;401} else {402error = cd9660_vget_internal(vdp->v_mount, i_ino,403cnp->cn_lkflags, &tdp,404i_ino != ino, ep);405free(ep2, M_TEMP);406if (error)407return (error);408*vpp = tdp;409}410411/*412* Insert name into cache if appropriate.413*/414if (cnp->cn_flags & MAKEENTRY)415cache_enter(vdp, *vpp, cnp);416return (0);417}418419/*420* Return buffer with the contents of block "offset" from the beginning of421* directory "ip". If "res" is non-zero, fill it in with a pointer to the422* remaining space in the directory.423*/424int425cd9660_blkatoff(struct vnode *vp, off_t offset, char **res, struct buf **bpp)426{427struct iso_node *ip;428struct iso_mnt *imp;429struct buf *bp;430daddr_t lbn;431int bsize, bshift, error;432433ip = VTOI(vp);434imp = ip->i_mnt;435lbn = lblkno(imp, offset);436bsize = blksize(imp, ip, lbn);437bshift = imp->im_bshift;438439if ((error = bread(vp, lbn, bsize, NOCRED, &bp)) != 0) {440brelse(bp);441*bpp = NULL;442return (error);443}444445/*446* We must BMAP the buffer because the directory code may use b_blkno447* to calculate the inode for certain types of directory entries.448* We could get away with not doing it before we VMIO-backed the449* directories because the buffers would get freed atomically with450* the invalidation of their data. But with VMIO-backed buffers451* the buffers may be freed and then later reconstituted - and the452* reconstituted buffer will have no knowledge of b_blkno.453*/454if (bp->b_blkno == bp->b_lblkno) {455bp->b_blkno = (ip->iso_start + bp->b_lblkno) << (bshift - DEV_BSHIFT);456}457458if (res)459*res = (char *)bp->b_data + blkoff(imp, offset);460*bpp = bp;461return (0);462}463464465