summaryrefslogtreecommitdiff
path: root/usr/src
diff options
context:
space:
mode:
authorswilcox <none@none>2005-09-27 14:00:52 -0700
committerswilcox <none@none>2005-09-27 14:00:52 -0700
commitbaa4d0994b47d1bd4979964a1928d26ddea35c7a (patch)
treec66645cb3bfb9e4307f519a95de2b8bc418c4411 /usr/src
parentf90bab20b71a9cc6ff5e29009567c8625c162843 (diff)
downloadillumos-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.c49
-rw-r--r--usr/src/uts/common/fs/ufs/ufs_vnops.c51
-rw-r--r--usr/src/uts/common/sys/fs/ufs_inode.h48
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 **);