Skip to content
Snippets Groups Projects
inode.c 59.6 KiB
Newer Older
  • Learn to ignore specific revisions
  • Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	if (atomic_dec_and_test(&ctx->count)) {
    		if (!list_empty(&ctx->list)) {
    			struct inode *inode = ctx->dentry->d_inode;
    			spin_lock(&inode->i_lock);
    			list_del(&ctx->list);
    			spin_unlock(&inode->i_lock);
    		}
    		if (ctx->state != NULL)
    			nfs4_close_state(ctx->state, ctx->mode);
    		if (ctx->cred != NULL)
    			put_rpccred(ctx->cred);
    		dput(ctx->dentry);
    		kfree(ctx);
    	}
    }
    
    /*
     * Ensure that mmap has a recent RPC credential for use when writing out
     * shared pages
     */
    void nfs_file_set_open_context(struct file *filp, struct nfs_open_context *ctx)
    {
    	struct inode *inode = filp->f_dentry->d_inode;
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	filp->private_data = get_nfs_open_context(ctx);
    	spin_lock(&inode->i_lock);
    	list_add(&ctx->list, &nfsi->open_files);
    	spin_unlock(&inode->i_lock);
    }
    
    
    /*
     * Given an inode, search for an open context with the desired characteristics
     */
    struct nfs_open_context *nfs_find_open_context(struct inode *inode, struct rpc_cred *cred, int mode)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    	struct nfs_open_context *pos, *ctx = NULL;
    
    	spin_lock(&inode->i_lock);
    	list_for_each_entry(pos, &nfsi->open_files, list) {
    
    		if (cred != NULL && pos->cred != cred)
    			continue;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if ((pos->mode & mode) == mode) {
    			ctx = get_nfs_open_context(pos);
    			break;
    		}
    	}
    	spin_unlock(&inode->i_lock);
    	return ctx;
    }
    
    void nfs_file_clear_open_context(struct file *filp)
    {
    	struct inode *inode = filp->f_dentry->d_inode;
    	struct nfs_open_context *ctx = (struct nfs_open_context *)filp->private_data;
    
    	if (ctx) {
    		filp->private_data = NULL;
    		spin_lock(&inode->i_lock);
    		list_move_tail(&ctx->list, &NFS_I(inode)->open_files);
    		spin_unlock(&inode->i_lock);
    		put_nfs_open_context(ctx);
    	}
    }
    
    /*
     * These allocate and release file read/write context information.
     */
    int nfs_open(struct inode *inode, struct file *filp)
    {
    	struct nfs_open_context *ctx;
    	struct rpc_cred *cred;
    
    	cred = rpcauth_lookupcred(NFS_CLIENT(inode)->cl_auth, 0);
    	if (IS_ERR(cred))
    		return PTR_ERR(cred);
    	ctx = alloc_nfs_open_context(filp->f_dentry, cred);
    	put_rpccred(cred);
    	if (ctx == NULL)
    		return -ENOMEM;
    	ctx->mode = filp->f_mode;
    	nfs_file_set_open_context(filp, ctx);
    	put_nfs_open_context(ctx);
    	return 0;
    }
    
    int nfs_release(struct inode *inode, struct file *filp)
    {
    	nfs_file_clear_open_context(filp);
    	return 0;
    }
    
    /*
     * This function is called whenever some part of NFS notices that
     * the cached attributes have to be refreshed.
     */
    int
    __nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
    {
    	int		 status = -ESTALE;
    	struct nfs_fattr fattr;
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	dfprintk(PAGECACHE, "NFS: revalidating (%s/%Ld)\n",
    		inode->i_sb->s_id, (long long)NFS_FILEID(inode));
    
    	lock_kernel();
    	if (!inode || is_bad_inode(inode))
     		goto out_nowait;
    	if (NFS_STALE(inode))
     		goto out_nowait;
    
    
    	status = nfs_wait_on_inode(inode);
    	if (status < 0)
    		goto out;
    	if (NFS_STALE(inode)) {
    		status = -ESTALE;
    		/* Do we trust the cached ESTALE? */
    		if (NFS_ATTRTIMEO(inode) != 0) {
    			if (nfsi->cache_validity & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ATIME)) {
    				/* no */
    			} else
    				goto out;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	status = NFS_PROTO(inode)->getattr(server, NFS_FH(inode), &fattr);
    	if (status != 0) {
    		dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Ld) getattr failed, error=%d\n",
    			 inode->i_sb->s_id,
    			 (long long)NFS_FILEID(inode), status);
    		if (status == -ESTALE) {
    			nfs_zap_caches(inode);
    			if (!S_ISDIR(inode->i_mode))
    
    				set_bit(NFS_INO_STALE, &NFS_FLAGS(inode));
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    		goto out;
    	}
    
    
    	spin_lock(&inode->i_lock);
    
    	status = nfs_update_inode(inode, &fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (status) {
    
    		spin_unlock(&inode->i_lock);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		dfprintk(PAGECACHE, "nfs_revalidate_inode: (%s/%Ld) refresh failed, error=%d\n",
    			 inode->i_sb->s_id,
    			 (long long)NFS_FILEID(inode), status);
    		goto out;
    	}
    
    	nfs_revalidate_mapping(inode, inode->i_mapping);
    
    	if (nfsi->cache_validity & NFS_INO_INVALID_ACL)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	dfprintk(PAGECACHE, "NFS: (%s/%Ld) revalidation complete\n",
    		inode->i_sb->s_id,
    		(long long)NFS_FILEID(inode));
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     out_nowait:
    	unlock_kernel();
    	return status;
    }
    
    int nfs_attribute_timeout(struct inode *inode)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	if (nfs_have_delegation(inode, FMODE_READ))
    		return 0;
    	return time_after(jiffies, nfsi->read_cache_jiffies+nfsi->attrtimeo);
    }
    
    /**
     * nfs_revalidate_inode - Revalidate the inode attributes
     * @server - pointer to nfs_server struct
     * @inode - pointer to inode struct
     *
     * Updates inode attribute information by retrieving the data from the server.
     */
    int nfs_revalidate_inode(struct nfs_server *server, struct inode *inode)
    {
    
    	if (!(NFS_I(inode)->cache_validity & (NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			&& !nfs_attribute_timeout(inode))
    		return NFS_STALE(inode) ? -ESTALE : 0;
    	return __nfs_revalidate_inode(server, inode);
    }
    
    
    /**
     * nfs_revalidate_mapping - Revalidate the pagecache
     * @inode - pointer to host inode
     * @mapping - pointer to mapping
     */
    void nfs_revalidate_mapping(struct inode *inode, struct address_space *mapping)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    
    	if (nfsi->cache_validity & NFS_INO_INVALID_DATA) {
    
    		if (S_ISREG(inode->i_mode))
    			nfs_sync_mapping(mapping);
    
    		nfsi->cache_validity &= ~NFS_INO_INVALID_DATA;
    
    		if (S_ISDIR(inode->i_mode)) {
    			memset(nfsi->cookieverf, 0, sizeof(nfsi->cookieverf));
    			/* This ensures we revalidate child dentries */
    
    			nfsi->cache_change_attribute = jiffies;
    
    		dfprintk(PAGECACHE, "NFS: (%s/%Ld) data cache invalidated\n",
    				inode->i_sb->s_id,
    				(long long)NFS_FILEID(inode));
    	}
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
     * nfs_begin_data_update
     * @inode - pointer to inode
     * Declare that a set of operations will update file data on the server
     */
    void nfs_begin_data_update(struct inode *inode)
    {
    	atomic_inc(&NFS_I(inode)->data_updates);
    }
    
    /**
     * nfs_end_data_update
     * @inode - pointer to inode
     * Declare end of the operations that will update file data
     * This will mark the inode as immediately needing revalidation
     * of its attribute cache.
     */
    void nfs_end_data_update(struct inode *inode)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	if (!nfs_have_delegation(inode, FMODE_READ)) {
    
    		/* Directories and symlinks: invalidate page cache */
    		if (S_ISDIR(inode->i_mode) || S_ISLNK(inode->i_mode)) {
    			spin_lock(&inode->i_lock);
    
    			nfsi->cache_validity |= NFS_INO_INVALID_DATA;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	nfsi->cache_change_attribute = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	atomic_dec(&nfsi->data_updates);
    }
    
    
    static void nfs_wcc_update_inode(struct inode *inode, struct nfs_fattr *fattr)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	if ((fattr->valid & NFS_ATTR_PRE_CHANGE) != 0
    			&& nfsi->change_attr == fattr->pre_change_attr) {
    		nfsi->change_attr = fattr->change_attr;
    		nfsi->cache_change_attribute = jiffies;
    	}
    
    	/* If we have atomic WCC data, we may update some attributes */
    	if ((fattr->valid & NFS_ATTR_WCC) != 0) {
    		if (timespec_equal(&inode->i_ctime, &fattr->pre_ctime)) {
    			memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
    			nfsi->cache_change_attribute = jiffies;
    		}
    		if (timespec_equal(&inode->i_mtime, &fattr->pre_mtime)) {
    			memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
    			nfsi->cache_change_attribute = jiffies;
    		}
    		if (inode->i_size == fattr->pre_size && nfsi->npages == 0) {
    			inode->i_size = fattr->size;
    			nfsi->cache_change_attribute = jiffies;
    		}
    	}
    }
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /**
    
     * nfs_check_inode_attributes - verify consistency of the inode attribute cache
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     * @inode - pointer to inode
     * @fattr - updated attributes
     *
     * Verifies the attribute cache. If we have just changed the attributes,
     * so that fattr carries weak cache consistency data, then it may
     * also update the ctime/mtime/change_attribute.
     */
    
    static int nfs_check_inode_attributes(struct inode *inode, struct nfs_fattr *fattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    	loff_t cur_size, new_isize;
    	int data_unstable;
    
    
    	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
    		return 0;
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* Are we in the process of updating data on the server? */
    	data_unstable = nfs_caches_unstable(inode);
    
    
    	/* Do atomic weak cache consistency updates */
    	nfs_wcc_update_inode(inode, fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	if ((fattr->valid & NFS_ATTR_FATTR_V4) != 0 &&
    			nfsi->change_attr != fattr->change_attr) {
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
    		if (!data_unstable)
    			nfsi->cache_validity |= NFS_INO_REVAL_PAGECACHE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Has the inode gone and changed behind our back? */
    	if (nfsi->fileid != fattr->fileid
    
    			|| (inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT)) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EIO;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	cur_size = i_size_read(inode);
     	new_isize = nfs_size_to_loff_t(fattr->size);
    
    	/* Verify a few of the more important attributes */
    
    	if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {
    
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
    
    			nfsi->cache_validity |= NFS_INO_REVAL_PAGECACHE;
    
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
    
    			nfsi->cache_validity |= NFS_INO_REVAL_PAGECACHE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Have any file permissions changed? */
    	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO)
    			|| inode->i_uid != fattr->uid
    			|| inode->i_gid != fattr->gid)
    
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS | NFS_INO_INVALID_ACL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Has the link count changed? */
    	if (inode->i_nlink != fattr->nlink)
    
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!timespec_equal(&inode->i_atime, &fattr->atime))
    
    		nfsi->cache_validity |= NFS_INO_INVALID_ATIME;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	nfsi->read_cache_jiffies = fattr->time_start;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	return 0;
    }
    
    
    /**
     * nfs_refresh_inode - try to update the inode attribute cache
     * @inode - pointer to inode
     * @fattr - updated attributes
     *
     * Check that an RPC call that returned attributes has not overlapped with
     * other recent updates of the inode metadata, then decide whether it is
     * safe to do a full update of the inode attributes, or whether just to
     * call nfs_check_inode_attributes.
     */
    int nfs_refresh_inode(struct inode *inode, struct nfs_fattr *fattr)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    	int status;
    
    	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
    		return 0;
    	spin_lock(&inode->i_lock);
    	nfsi->cache_validity &= ~NFS_INO_REVAL_PAGECACHE;
    	if (time_after(fattr->time_start, nfsi->last_updated))
    
    		status = nfs_update_inode(inode, fattr);
    
    	else
    		status = nfs_check_inode_attributes(inode, fattr);
    
    	spin_unlock(&inode->i_lock);
    	return status;
    }
    
    
    /**
     * nfs_post_op_update_inode - try to update the inode attribute cache
     * @inode - pointer to inode
     * @fattr - updated attributes
     *
     * After an operation that has changed the inode metadata, mark the
     * attribute cache as being invalid, then try to update it.
     */
    int nfs_post_op_update_inode(struct inode *inode, struct nfs_fattr *fattr)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    	int status = 0;
    
    	spin_lock(&inode->i_lock);
    	if (unlikely((fattr->valid & NFS_ATTR_FATTR) == 0)) {
    		nfsi->cache_validity |= NFS_INO_INVALID_ATTR | NFS_INO_INVALID_ACCESS;
    		goto out;
    	}
    
    	status = nfs_update_inode(inode, fattr);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    /*
     * Many nfs protocol calls return the new file attributes after
     * an operation.  Here we update the inode to reflect the state
     * of the server's inode.
     *
     * This is a bit tricky because we have to make sure all dirty pages
     * have been sent off to the server before calling invalidate_inode_pages.
     * To make sure no other process adds more write requests while we try
     * our best to flush them, we make them sleep during the attribute refresh.
     *
     * A very similar scenario holds for the dir cache.
     */
    
    static int nfs_update_inode(struct inode *inode, struct nfs_fattr *fattr)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	loff_t cur_isize, new_isize;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	unsigned int	invalid = 0;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	dfprintk(VFS, "NFS: %s(%s/%ld ct=%d info=0x%x)\n",
    			__FUNCTION__, inode->i_sb->s_id, inode->i_ino,
    			atomic_read(&inode->i_count), fattr->valid);
    
    	if ((fattr->valid & NFS_ATTR_FATTR) == 0)
    		return 0;
    
    
    	if (nfsi->fileid != fattr->fileid)
    		goto out_fileid;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/*
    	 * Make sure the inode's type hasn't changed.
    	 */
    
    	if ((inode->i_mode & S_IFMT) != (fattr->mode & S_IFMT))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		goto out_changed;
    
    	/*
    	 * Update the read time so we don't revalidate too often.
    	 */
    
    	nfsi->read_cache_jiffies = fattr->time_start;
    	nfsi->last_updated = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	/* Are we racing with known updates of the metadata on the server? */
    
    	data_stable = nfs_verify_change_attribute(inode, fattr->time_start);
    	if (data_stable)
    		nfsi->cache_validity &= ~(NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	/* Do atomic weak cache consistency updates */
    	nfs_wcc_update_inode(inode, fattr);
    
    
    	/* Check if our cached file size is stale */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
     	new_isize = nfs_size_to_loff_t(fattr->size);
    	cur_isize = i_size_read(inode);
    
    	if (new_isize != cur_isize) {
    		/* Do we perhaps have any outstanding writes? */
    		if (nfsi->npages == 0) {
    			/* No, but did we race with nfs_end_data_update()? */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    				inode->i_size = new_isize;
    
    				invalid |= NFS_INO_INVALID_DATA;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			}
    
    			invalid |= NFS_INO_INVALID_ATTR;
    		} else if (new_isize > cur_isize) {
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			inode->i_size = new_isize;
    			invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
    		}
    
    		nfsi->cache_change_attribute = jiffies;
    
    		dprintk("NFS: isize change on server for file %s/%ld\n",
    				inode->i_sb->s_id, inode->i_ino);
    
    	/* Check if the mtime agrees */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!timespec_equal(&inode->i_mtime, &fattr->mtime)) {
    		memcpy(&inode->i_mtime, &fattr->mtime, sizeof(inode->i_mtime));
    
    		dprintk("NFS: mtime change on server for file %s/%ld\n",
    				inode->i_sb->s_id, inode->i_ino);
    
    		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA;
    		nfsi->cache_change_attribute = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	if ((fattr->valid & NFS_ATTR_FATTR_V4)
    	    && nfsi->change_attr != fattr->change_attr) {
    
    		dprintk("NFS: change_attr change on server for file %s/%ld\n",
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		       inode->i_sb->s_id, inode->i_ino);
    		nfsi->change_attr = fattr->change_attr;
    
    		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_DATA|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
    		nfsi->cache_change_attribute = jiffies;
    
    	/* If ctime has changed we should definitely clear access+acl caches */
    	if (!timespec_equal(&inode->i_ctime, &fattr->ctime)) {
    
    		invalid |= NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
    
    		memcpy(&inode->i_ctime, &fattr->ctime, sizeof(inode->i_ctime));
    
    		nfsi->cache_change_attribute = jiffies;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	memcpy(&inode->i_atime, &fattr->atime, sizeof(inode->i_atime));
    
    	if ((inode->i_mode & S_IALLUGO) != (fattr->mode & S_IALLUGO) ||
    	    inode->i_uid != fattr->uid ||
    	    inode->i_gid != fattr->gid)
    
    		invalid |= NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ACCESS|NFS_INO_INVALID_ACL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	inode->i_mode = fattr->mode;
    	inode->i_nlink = fattr->nlink;
    	inode->i_uid = fattr->uid;
    	inode->i_gid = fattr->gid;
    
    	if (fattr->valid & (NFS_ATTR_FATTR_V3 | NFS_ATTR_FATTR_V4)) {
    		/*
    		 * report the blocks in 512byte units
    		 */
    		inode->i_blocks = nfs_calc_block_size(fattr->du.nfs3.used);
    		inode->i_blksize = inode->i_sb->s_blocksize;
     	} else {
     		inode->i_blocks = fattr->du.nfs2.blocks;
     		inode->i_blksize = fattr->du.nfs2.blocksize;
     	}
    
    	/* Update attrtimeo value if we're out of the unstable period */
    	if (invalid & NFS_INO_INVALID_ATTR) {
    		nfsi->attrtimeo = NFS_MINATTRTIMEO(inode);
    		nfsi->attrtimeo_timestamp = jiffies;
    	} else if (time_after(jiffies, nfsi->attrtimeo_timestamp+nfsi->attrtimeo)) {
    		if ((nfsi->attrtimeo <<= 1) > NFS_MAXATTRTIMEO(inode))
    			nfsi->attrtimeo = NFS_MAXATTRTIMEO(inode);
    		nfsi->attrtimeo_timestamp = jiffies;
    	}
    	/* Don't invalidate the data if we were to blame */
    	if (!(S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode)
    				|| S_ISLNK(inode->i_mode)))
    		invalid &= ~NFS_INO_INVALID_DATA;
    
    	if (data_stable)
    		invalid &= ~(NFS_INO_INVALID_ATTR|NFS_INO_INVALID_ATIME|NFS_INO_REVAL_PAGECACHE);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (!nfs_have_delegation(inode, FMODE_READ))
    
    		nfsi->cache_validity |= invalid;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	return 0;
     out_changed:
    	/*
    	 * Big trouble! The inode has become a different object.
    	 */
    #ifdef NFS_PARANOIA
    	printk(KERN_DEBUG "%s: inode %ld mode changed, %07o to %07o\n",
    			__FUNCTION__, inode->i_ino, inode->i_mode, fattr->mode);
    #endif
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/*
    	 * No need to worry about unhashing the dentry, as the
    	 * lookup validation will know that the inode is bad.
    	 * (But we fall through to invalidate the caches.)
    	 */
    	nfs_invalidate_inode(inode);
    	return -ESTALE;
    
    
     out_fileid:
    	printk(KERN_ERR "NFS: server %s error: fileid changed\n"
    		"fsid %s: expected fileid 0x%Lx, got 0x%Lx\n",
    		NFS_SERVER(inode)->hostname, inode->i_sb->s_id,
    		(long long)nfsi->fileid, (long long)fattr->fileid);
    	goto out_err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    /*
     * File system information
     */
    
    static int nfs_set_super(struct super_block *s, void *data)
    {
    	s->s_fs_info = data;
    	return set_anon_super(s, data);
    }
     
    static int nfs_compare_super(struct super_block *sb, void *data)
    {
    	struct nfs_server *server = data;
    	struct nfs_server *old = NFS_SB(sb);
    
    	if (old->addr.sin_addr.s_addr != server->addr.sin_addr.s_addr)
    		return 0;
    	if (old->addr.sin_port != server->addr.sin_port)
    		return 0;
    	return !nfs_compare_fh(&old->fh, &server->fh);
    }
    
    static struct super_block *nfs_get_sb(struct file_system_type *fs_type,
    	int flags, const char *dev_name, void *raw_data)
    {
    	int error;
    
    	struct nfs_server *server = NULL;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	struct super_block *s;
    	struct nfs_fh *root;
    	struct nfs_mount_data *data = raw_data;
    
    
    	s = ERR_PTR(-EINVAL);
    	if (data == NULL) {
    		dprintk("%s: missing data argument\n", __FUNCTION__);
    		goto out_err;
    	}
    	if (data->version <= 0 || data->version > NFS_MOUNT_VERSION) {
    		dprintk("%s: bad mount version\n", __FUNCTION__);
    		goto out_err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	switch (data->version) {
    		case 1:
    			data->namlen = 0;
    		case 2:
    			data->bsize  = 0;
    		case 3:
    			if (data->flags & NFS_MOUNT_VER3) {
    				dprintk("%s: mount structure version %d does not support NFSv3\n",
    						__FUNCTION__,
    						data->version);
    				goto out_err;
    			}
    			data->root.size = NFS2_FHSIZE;
    			memcpy(data->root.data, data->old_root.data, NFS2_FHSIZE);
    		case 4:
    			if (data->flags & NFS_MOUNT_SECFLAVOUR) {
    				dprintk("%s: mount structure version %d does not support strong security\n",
    						__FUNCTION__,
    						data->version);
    				goto out_err;
    			}
    		case 5:
    			memset(data->context, 0, sizeof(data->context));
    	}
    #ifndef CONFIG_NFS_V3
    	/* If NFSv3 is not compiled in, return -EPROTONOSUPPORT */
    	s = ERR_PTR(-EPROTONOSUPPORT);
    	if (data->flags & NFS_MOUNT_VER3) {
    		dprintk("%s: NFSv3 not compiled into kernel\n", __FUNCTION__);
    		goto out_err;
    	}
    #endif /* CONFIG_NFS_V3 */
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	server = kmalloc(sizeof(struct nfs_server), GFP_KERNEL);
    	if (!server)
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	memset(server, 0, sizeof(struct nfs_server));
    	/* Zero out the NFS state stuff */
    	init_nfsv4_state(server);
    
    	server->client = server->client_sys = server->client_acl = ERR_PTR(-EINVAL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	root = &server->fh;
    	if (data->flags & NFS_MOUNT_VER3)
    		root->size = data->root.size;
    	else
    		root->size = NFS2_FHSIZE;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (root->size > sizeof(root->data)) {
    
    		dprintk("%s: invalid root filehandle\n", __FUNCTION__);
    		goto out_err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    	memcpy(root->data, data->root.data, root->size);
    
    	/* We now require that the mount process passes the remote address */
    	memcpy(&server->addr, &data->addr, sizeof(server->addr));
    	if (server->addr.sin_addr.s_addr == INADDR_ANY) {
    
    		dprintk("%s: mount program didn't pass remote address!\n",
    				__FUNCTION__);
    		goto out_err;
    
    	/* Fire up rpciod if not yet running */
    	s = ERR_PTR(rpciod_up());
    	if (IS_ERR(s)) {
    		dprintk("%s: couldn't start rpciod! Error = %ld\n",
    				__FUNCTION__, PTR_ERR(s));
    		goto out_err;
    
    	s = sget(fs_type, nfs_compare_super, nfs_set_super, server);
    	if (IS_ERR(s) || s->s_root)
    		goto out_rpciod_down;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    
    	error = nfs_fill_super(s, data, flags & MS_SILENT ? 1 : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (error) {
    		up_write(&s->s_umount);
    		deactivate_super(s);
    		return ERR_PTR(error);
    	}
    	s->s_flags |= MS_ACTIVE;
    	return s;
    
    out_rpciod_down:
    	rpciod_down();
    out_err:
    	kfree(server);
    	return s;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    }
    
    static void nfs_kill_super(struct super_block *s)
    {
    	struct nfs_server *server = NFS_SB(s);
    
    	kill_anon_super(s);
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		rpc_shutdown_client(server->client);
    
    	if (!IS_ERR(server->client_sys))
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		rpc_shutdown_client(server->client_sys);
    
    	if (!IS_ERR(server->client_acl))
    		rpc_shutdown_client(server->client_acl);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	if (!(server->flags & NFS_MOUNT_NONLM))
    		lockd_down();	/* release rpc.lockd */
    
    	rpciod_down();		/* release rpciod */
    
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    	kfree(server->hostname);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	kfree(server);
    }
    
    static struct file_system_type nfs_fs_type = {
    	.owner		= THIS_MODULE,
    	.name		= "nfs",
    	.get_sb		= nfs_get_sb,
    	.kill_sb	= nfs_kill_super,
    	.fs_flags	= FS_ODD_RENAME|FS_REVAL_DOT|FS_BINARY_MOUNTDATA,
    };
    
    #ifdef CONFIG_NFS_V4
    
    static void nfs4_clear_inode(struct inode *);
    
    
    static struct super_operations nfs4_sops = { 
    	.alloc_inode	= nfs_alloc_inode,
    	.destroy_inode	= nfs_destroy_inode,
    	.write_inode	= nfs_write_inode,
    	.delete_inode	= nfs_delete_inode,
    	.statfs		= nfs_statfs,
    	.clear_inode	= nfs4_clear_inode,
    	.umount_begin	= nfs_umount_begin,
    	.show_options	= nfs_show_options,
    };
    
    /*
     * Clean out any remaining NFSv4 state that might be left over due
     * to open() calls that passed nfs_atomic_lookup, but failed to call
     * nfs_open().
     */
    static void nfs4_clear_inode(struct inode *inode)
    {
    	struct nfs_inode *nfsi = NFS_I(inode);
    
    	/* If we are holding a delegation, return it! */
    
    	nfs_inode_return_delegation(inode);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	/* First call standard NFS clear_inode() code */
    	nfs_clear_inode(inode);
    	/* Now clear out any remaining state */
    	while (!list_empty(&nfsi->open_states)) {
    		struct nfs4_state *state;
    		
    		state = list_entry(nfsi->open_states.next,
    				struct nfs4_state,
    				inode_states);
    		dprintk("%s(%s/%Ld): found unclaimed NFSv4 state %p\n",
    				__FUNCTION__,
    				inode->i_sb->s_id,
    				(long long)NFS_FILEID(inode),
    				state);
    		BUG_ON(atomic_read(&state->count) != 1);
    		nfs4_close_state(state, state->state);
    	}
    }
    
    
    static int nfs4_fill_super(struct super_block *sb, struct nfs4_mount_data *data, int silent)
    {
    	struct nfs_server *server;
    	struct nfs4_client *clp = NULL;
    	struct rpc_xprt *xprt = NULL;
    	struct rpc_clnt *clnt = NULL;
    	struct rpc_timeout timeparms;
    	rpc_authflavor_t authflavour;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	sb->s_blocksize_bits = 0;
    	sb->s_blocksize = 0;
    	server = NFS_SB(sb);
    	if (data->rsize != 0)
    		server->rsize = nfs_block_size(data->rsize, NULL);
    	if (data->wsize != 0)
    		server->wsize = nfs_block_size(data->wsize, NULL);
    	server->flags = data->flags & NFS_MOUNT_FLAGMASK;
    	server->caps = NFS_CAP_ATOMIC_OPEN;
    
    	server->acregmin = data->acregmin*HZ;
    	server->acregmax = data->acregmax*HZ;
    	server->acdirmin = data->acdirmin*HZ;
    	server->acdirmax = data->acdirmax*HZ;
    
    	server->rpc_ops = &nfs_v4_clientops;
    
    
    	nfs_init_timeout_values(&timeparms, data->proto, data->timeo, data->retrans);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	clp = nfs4_get_client(&server->addr.sin_addr);
    	if (!clp) {
    
    		dprintk("%s: failed to create NFS4 client.\n", __FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -EIO;
    	}
    
    	/* Now create transport and client */
    	authflavour = RPC_AUTH_UNIX;
    	if (data->auth_flavourlen != 0) {
    
    		if (data->auth_flavourlen != 1) {
    			dprintk("%s: Invalid number of RPC auth flavours %d.\n",
    					__FUNCTION__, data->auth_flavourlen);
    			err = -EINVAL;
    			goto out_fail;
    		}
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (copy_from_user(&authflavour, data->auth_flavours, sizeof(authflavour))) {
    			err = -EFAULT;
    			goto out_fail;
    		}
    	}
    
    	down_write(&clp->cl_sem);
    
    	if (IS_ERR(clp->cl_rpcclient)) {
    
    		xprt = xprt_create_proto(data->proto, &server->addr, &timeparms);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		if (IS_ERR(xprt)) {
    			up_write(&clp->cl_sem);
    			err = PTR_ERR(xprt);
    
    			dprintk("%s: cannot create RPC transport. Error = %d\n",
    					__FUNCTION__, err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_fail;
    		}
    		clnt = rpc_create_client(xprt, server->hostname, &nfs_program,
    				server->rpc_ops->version, authflavour);
    		if (IS_ERR(clnt)) {
    			up_write(&clp->cl_sem);
    			err = PTR_ERR(clnt);
    
    			dprintk("%s: cannot create RPC client. Error = %d\n",
    					__FUNCTION__, err);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    			goto out_fail;
    		}
    		clnt->cl_intr     = 1;
    		clnt->cl_softrtry = 1;
    		clp->cl_rpcclient = clnt;
    		memcpy(clp->cl_ipaddr, server->ip_addr, sizeof(clp->cl_ipaddr));
    		nfs_idmap_new(clp);
    	}
    	list_add_tail(&server->nfs4_siblings, &clp->cl_superblocks);
    	clnt = rpc_clone_client(clp->cl_rpcclient);
    	if (!IS_ERR(clnt))
    			server->nfs4_state = clp;
    	up_write(&clp->cl_sem);
    	clp = NULL;
    
    	if (IS_ERR(clnt)) {
    
    		err = PTR_ERR(clnt);
    		dprintk("%s: cannot create RPC client. Error = %d\n",
    				__FUNCTION__, err);
    		return err;
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	}
    
    	server->client    = clnt;
    
    	if (server->nfs4_state->cl_idmap == NULL) {
    
    		dprintk("%s: failed to create idmapper.\n", __FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return -ENOMEM;
    	}
    
    	if (clnt->cl_auth->au_flavor != authflavour) {
    
    		struct rpc_auth *auth;
    
    		auth = rpcauth_create(authflavour, clnt);
    		if (IS_ERR(auth)) {
    
    			dprintk("%s: couldn't create credcache!\n", __FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		}
    	}
    
    	sb->s_time_gran = 1;
    
    	sb->s_op = &nfs4_sops;
    	err = nfs_sb_init(sb, authflavour);
    	if (err == 0)
    		return 0;
    out_fail:
    	if (clp)
    		nfs4_put_client(clp);
    	return err;
    }
    
    static int nfs4_compare_super(struct super_block *sb, void *data)
    {
    	struct nfs_server *server = data;
    	struct nfs_server *old = NFS_SB(sb);
    
    	if (strcmp(server->hostname, old->hostname) != 0)
    		return 0;
    	if (strcmp(server->mnt_path, old->mnt_path) != 0)
    		return 0;
    	return 1;
    }
    
    static void *
    nfs_copy_user_string(char *dst, struct nfs_string *src, int maxlen)
    {
    	void *p = NULL;
    
    	if (!src->len)
    		return ERR_PTR(-EINVAL);
    	if (src->len < maxlen)
    		maxlen = src->len;
    	if (dst == NULL) {
    		p = dst = kmalloc(maxlen + 1, GFP_KERNEL);
    		if (p == NULL)
    			return ERR_PTR(-ENOMEM);
    	}
    	if (copy_from_user(dst, src->data, maxlen)) {
    
    Jesper Juhl's avatar
    Jesper Juhl committed
    		kfree(p);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ERR_PTR(-EFAULT);
    	}
    	dst[maxlen] = '\0';
    	return dst;
    }
    
    static struct super_block *nfs4_get_sb(struct file_system_type *fs_type,
    	int flags, const char *dev_name, void *raw_data)
    {
    	int error;
    	struct nfs_server *server;
    	struct super_block *s;
    	struct nfs4_mount_data *data = raw_data;
    	void *p;
    
    
    	if (data == NULL) {
    		dprintk("%s: missing data argument\n", __FUNCTION__);
    		return ERR_PTR(-EINVAL);
    	}
    	if (data->version <= 0 || data->version > NFS4_MOUNT_VERSION) {
    		dprintk("%s: bad mount version\n", __FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		return ERR_PTR(-EINVAL);
    	}
    
    	server = kmalloc(sizeof(struct nfs_server), GFP_KERNEL);
    	if (!server)
    		return ERR_PTR(-ENOMEM);
    	memset(server, 0, sizeof(struct nfs_server));
    	/* Zero out the NFS state stuff */
    	init_nfsv4_state(server);
    
    	server->client = server->client_sys = server->client_acl = ERR_PTR(-EINVAL);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    
    	p = nfs_copy_user_string(NULL, &data->hostname, 256);
    	if (IS_ERR(p))
    		goto out_err;
    	server->hostname = p;
    
    	p = nfs_copy_user_string(NULL, &data->mnt_path, 1024);
    	if (IS_ERR(p))
    		goto out_err;
    	server->mnt_path = p;
    
    	p = nfs_copy_user_string(server->ip_addr, &data->client_addr,
    			sizeof(server->ip_addr) - 1);
    	if (IS_ERR(p))
    		goto out_err;
    
    	/* We now require that the mount process passes the remote address */
    	if (data->host_addrlen != sizeof(server->addr)) {
    		s = ERR_PTR(-EINVAL);
    		goto out_free;
    	}
    	if (copy_from_user(&server->addr, data->host_addr, sizeof(server->addr))) {
    		s = ERR_PTR(-EFAULT);
    		goto out_free;
    	}
    	if (server->addr.sin_family != AF_INET ||
    	    server->addr.sin_addr.s_addr == INADDR_ANY) {
    
    		dprintk("%s: mount program didn't pass remote IP address!\n",
    				__FUNCTION__);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    		s = ERR_PTR(-EINVAL);
    		goto out_free;
    	}
    
    
    	/* Fire up rpciod if not yet running */
    	s = ERR_PTR(rpciod_up());
    	if (IS_ERR(s)) {
    		dprintk("%s: couldn't start rpciod! Error = %ld\n",
    				__FUNCTION__, PTR_ERR(s));
    		goto out_free;
    	}
    
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	s = sget(fs_type, nfs4_compare_super, nfs_set_super, server);
    
    	if (IS_ERR(s) || s->s_root)
    		goto out_free;
    
    	s->s_flags = flags;
    
    
    	error = nfs4_fill_super(s, data, flags & MS_SILENT ? 1 : 0);
    
    Linus Torvalds's avatar
    Linus Torvalds committed
    	if (error) {