diff options
author | swilcox <none@none> | 2005-09-27 14:00:52 -0700 |
---|---|---|
committer | swilcox <none@none> | 2005-09-27 14:00:52 -0700 |
commit | baa4d0994b47d1bd4979964a1928d26ddea35c7a (patch) | |
tree | c66645cb3bfb9e4307f519a95de2b8bc418c4411 /usr/src | |
parent | f90bab20b71a9cc6ff5e29009567c8625c162843 (diff) | |
download | illumos-joyent-baa4d0994b47d1bd4979964a1928d26ddea35c7a.tar.gz |
6186851 A race in ufs_rename can result in incorrect hard link to a file/dir
Diffstat (limited to 'usr/src')
-rw-r--r-- | usr/src/uts/common/fs/ufs/ufs_dir.c | 49 | ||||
-rw-r--r-- | usr/src/uts/common/fs/ufs/ufs_vnops.c | 51 | ||||
-rw-r--r-- | usr/src/uts/common/sys/fs/ufs_inode.h | 48 |
3 files changed, 100 insertions, 48 deletions
diff --git a/usr/src/uts/common/fs/ufs/ufs_dir.c b/usr/src/uts/common/fs/ufs/ufs_dir.c index 4580ae87e0..53c580ee3d 100644 --- a/usr/src/uts/common/fs/ufs/ufs_dir.c +++ b/usr/src/uts/common/fs/ufs/ufs_dir.c @@ -136,7 +136,6 @@ int ufs_negative_cache = 1; uint64_t ufs_dirremove_retry_cnt; static void dirbad(); -static int ufs_dircheckforname(); static int ufs_dirrename(); static int ufs_diraddentry(); static int ufs_dirempty(); @@ -584,52 +583,6 @@ bad: } /* - * If ufs_dircheckforname() fails to find an entry with the given name, - * this "slot" structure holds state for ufs_direnter_*() as to where - * there is space to put an entry with that name. - * If ufs_dircheckforname() finds an entry with the given name, this structure - * holds state for ufs_dirrename() and ufs_dirremove() as to where the - * entry is. "status" indicates what ufs_dircheckforname() found: - * NONE name not found, large enough free slot not found, - * FOUND name not found, large enough free slot found - * EXIST name found - * If ufs_dircheckforname() fails due to an error, this structure is not - * filled in. - * - * After ufs_dircheckforname() succeeds the values are: - * status offset size fbp, ep - * ------ ------ ---- ------- - * NONE end of dir needed not valid - * FOUND start of entry of ent both valid if fbp != NULL - * EXIST start of entry of prev ent valid - * - * "endoff" is set to 0 if the an entry with the given name is found, or if no - * free slot could be found or made; this means that the directory should not - * be truncated. If the entry was found, the search terminates so - * ufs_dircheckforname() didn't find out where the last valid entry in the - * directory was, so it doesn't know where to cut the directory off; if no free - * slot could be found or made, the directory has to be extended to make room - * for the new entry, so there's nothing to cut off. - * Otherwise, "endoff" is set to the larger of the offset of the last - * non-empty entry in the directory, or the offset at which the new entry will - * be placed, whichever is larger. This is used by ufs_diraddentry(); if a new - * entry is to be added to the directory, any complete directory blocks at the - * end of the directory that contain no non-empty entries are lopped off the - * end, thus shrinking the directory dynamically. - */ -typedef enum {NONE, FOUND, EXIST} slotstat_t; -struct slot { - struct direct *ep; /* pointer to slot */ - struct fbuf *fbp; /* dir buf where slot is */ - off_t offset; /* offset of area with free space */ - off_t endoff; /* last useful location found in search */ - slotstat_t status; /* status of slot */ - int size; /* size of area at slotoffset */ - int cached; /* cached directory */ -}; - - -/* * Write a new directory entry for DE_CREATE or DE_MKDIR operations. */ int @@ -1008,7 +961,7 @@ out2: * * This may not be used on "." or "..", but aliases of "." are ok. */ -static int +int ufs_dircheckforname( struct inode *tdp, /* inode of directory being checked */ char *namep, /* name we're checking for */ diff --git a/usr/src/uts/common/fs/ufs/ufs_vnops.c b/usr/src/uts/common/fs/ufs/ufs_vnops.c index 611ef4ca4b..9cd6c30902 100644 --- a/usr/src/uts/common/fs/ufs/ufs_vnops.c +++ b/usr/src/uts/common/fs/ufs/ufs_vnops.c @@ -3208,6 +3208,7 @@ clock_t ufs_rename_backoff_delay = 1; * the inode on disk, which isn't feasible at this time. Best we * can do is always guarantee that the TARGET exists. */ + /*ARGSUSED*/ static int ufs_rename( @@ -3218,12 +3219,15 @@ ufs_rename( struct cred *cr) { struct inode *sip = NULL; /* source inode */ + struct inode *ip = NULL; /* check inode */ struct inode *sdp; /* old (source) parent inode */ struct inode *tdp; /* new (target) parent inode */ struct vnode *tvp = NULL; /* target vnode, if it exists */ struct vnode *realvp; struct ufsvfs *ufsvfsp; struct ulockfs *ulp; + struct slot slot; + timestruc_t now; int error; int issync; int trans_size; @@ -3233,6 +3237,7 @@ ufs_rename( sdp = VTOI(sdvp); + slot.fbp = NULL; ufsvfsp = sdp->i_ufsvfs; error = ufs_lockfs_begin(ufsvfsp, &ulp, ULOCKFS_RENAME_MASK); if (error) @@ -3247,6 +3252,7 @@ ufs_rename( tdp = VTOI(tdvp); + /* * We only allow renaming of attributes from ATTRDIR to ATTRDIR. */ @@ -3258,6 +3264,7 @@ ufs_rename( /* * Look up inode of file we're supposed to rename. */ + gethrestime(&now); if (error = ufs_dirlook(sdp, snm, &sip, cr, 0)) { goto unlock; } @@ -3413,6 +3420,47 @@ retry: goto retry; } } + + /* + * Now that all the locks are held check to make sure another thread + * didn't slip in and take out the sip. + */ + slot.status = NONE; + if ((sip->i_ctime.tv_usec * 1000) > now.tv_nsec || + sip->i_ctime.tv_sec > now.tv_sec) { + rw_enter(&sdp->i_ufsvfs->vfs_dqrwlock, RW_READER); + rw_enter(&sdp->i_contents, RW_WRITER); + error = ufs_dircheckforname(sdp, snm, strlen(snm), &slot, + &ip, cr, 0); + rw_exit(&sdp->i_contents); + rw_exit(&sdp->i_ufsvfs->vfs_dqrwlock); + if (error) { + goto errout; + } + if (ip == NULL) { + error = ENOENT; + goto errout; + } else { + /* + * If the inode was found need to drop the v_count + * so as not to keep the filesystem from being + * unmounted at a later time. + */ + VN_RELE(ITOV(ip)); + } + + /* + * Release the slot.fbp that has the page mapped and + * locked SE_SHARED, and could be used in in + * ufs_direnter_lr() which needs to get the SE_EXCL lock + * on said page. + */ + if (slot.fbp) { + fbrelse(slot.fbp, S_OTHER); + slot.fbp = NULL; + } + } + /* * Link source to the target. If a target exists, return its * vnode pointer in tvp. We'll release it after sending the @@ -3441,6 +3489,9 @@ retry: error = 0; errout: + if (slot.fbp) + fbrelse(slot.fbp, S_OTHER); + rw_exit(&tdp->i_rwlock); if (sdp != tdp) { rw_exit(&sdp->i_rwlock); diff --git a/usr/src/uts/common/sys/fs/ufs_inode.h b/usr/src/uts/common/sys/fs/ufs_inode.h index d0815f62de..650314dcef 100644 --- a/usr/src/uts/common/sys/fs/ufs_inode.h +++ b/usr/src/uts/common/sys/fs/ufs_inode.h @@ -393,6 +393,52 @@ struct dinode { #define I_QUOTA 0x00000020 /* quota file */ #define I_NOCANCEL 0x40 /* Don't cancel these fragments */ #define I_ACCT 0x00000080 /* Update ufsvfs' unreclaimed_blocks */ + +/* + * If ufs_dircheckforname() fails to find an entry with the given name, + * this "slot" structure holds state for ufs_direnter_*() as to where + * there is space to put an entry with that name. + * If ufs_dircheckforname() finds an entry with the given name, this structure + * holds state for ufs_dirrename() and ufs_dirremove() as to where the + * entry is. "status" indicates what ufs_dircheckforname() found: + * NONE name not found, large enough free slot not found, + * FOUND name not found, large enough free slot found + * EXIST name found + * If ufs_dircheckforname() fails due to an error, this structure is not + * filled in. + * + * After ufs_dircheckforname() succeeds the values are: + * status offset size fbp, ep + * ------ ------ ---- ------- + * NONE end of dir needed not valid + * FOUND start of entry of ent both valid if fbp != NULL + * EXIST start of entry of prev ent valid + * + * "endoff" is set to 0 if the an entry with the given name is found, or if no + * free slot could be found or made; this means that the directory should not + * be truncated. If the entry was found, the search terminates so + * ufs_dircheckforname() didn't find out where the last valid entry in the + * directory was, so it doesn't know where to cut the directory off; if no free + * slot could be found or made, the directory has to be extended to make room + * for the new entry, so there's nothing to cut off. + * Otherwise, "endoff" is set to the larger of the offset of the last + * non-empty entry in the directory, or the offset at which the new entry will + * be placed, whichever is larger. This is used by ufs_diraddentry(); if a new + * entry is to be added to the directory, any complete directory blocks at the + * end of the directory that contain no non-empty entries are lopped off the + * end, thus shrinking the directory dynamically. + */ +typedef enum {NONE, FOUND, EXIST} slotstat_t; +struct slot { + struct direct *ep; /* pointer to slot */ + struct fbuf *fbp; /* dir buf where slot is */ + off_t offset; /* offset of area with free space */ + off_t endoff; /* last useful location found in search */ + slotstat_t status; /* status of slot */ + int size; /* size of area at slotoffset */ + int cached; /* cached directory */ +}; + /* * Statistics on inodes * Not protected by locks @@ -786,6 +832,8 @@ extern int ufs_dirmakeinode(struct inode *, struct inode **, struct vattr *, enum de_op, cred_t *); extern int ufs_dirremove(struct inode *, char *, struct inode *, vnode_t *, enum dr_op, cred_t *, vnode_t **); +extern int ufs_dircheckforname(struct inode *, char *, int, struct slot *, + struct inode **, struct cred *, int); extern int ufs_xattrdirempty(struct inode *, ino_t, cred_t *); extern int blkatoff(struct inode *, off_t, char **, struct fbuf **); |