Exemple #1
0
/*
 * validate a vnode/inode
 * - there are several things we need to check
 *   - parent dir data changes (rm, rmdir, rename, mkdir, create, link,
 *     symlink)
 *   - parent dir metadata changed (security changes)
 *   - dentry data changed (write, truncate)
 *   - dentry metadata changed (security changes)
 */
int afs_validate(struct afs_vnode *vnode, struct key *key)
{
	int ret;

	_enter("{v={%x:%u} fl=%lx},%x",
	       vnode->fid.vid, vnode->fid.vnode, vnode->flags,
	       key_serial(key));

	if (vnode->cb_promised &&
	    !test_bit(AFS_VNODE_CB_BROKEN, &vnode->flags) &&
	    !test_bit(AFS_VNODE_MODIFIED, &vnode->flags) &&
	    !test_bit(AFS_VNODE_ZAP_DATA, &vnode->flags)) {
		if (vnode->cb_expires < get_seconds() + 10) {
			_debug("callback expired");
			set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags);
		} else {
			goto valid;
		}
	}

	if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
		goto valid;

	mutex_lock(&vnode->validate_lock);

	/* if the promise has expired, we need to check the server again to get
	 * a new promise - note that if the (parent) directory's metadata was
	 * changed then the security may be different and we may no longer have
	 * access */
	if (!vnode->cb_promised ||
	    test_bit(AFS_VNODE_CB_BROKEN, &vnode->flags)) {
		_debug("not promised");
		ret = afs_vnode_fetch_status(vnode, NULL, key);
		if (ret < 0)
			goto error_unlock;
		_debug("new promise [fl=%lx]", vnode->flags);
	}

	if (test_bit(AFS_VNODE_DELETED, &vnode->flags)) {
		_debug("file already deleted");
		ret = -ESTALE;
		goto error_unlock;
	}

	/* if the vnode's data version number changed then its contents are
	 * different */
	if (test_and_clear_bit(AFS_VNODE_ZAP_DATA, &vnode->flags))
		afs_zap_data(vnode);

	clear_bit(AFS_VNODE_MODIFIED, &vnode->flags);
	mutex_unlock(&vnode->validate_lock);
valid:
	_leave(" = 0");
	return 0;

error_unlock:
	mutex_unlock(&vnode->validate_lock);
	_leave(" = %d", ret);
	return ret;
}
/*
 * attempt to fetch the status of an inode, coelescing multiple simultaneous
 * fetches
 */
static int afs_inode_fetch_status(struct inode *inode)
{
	struct afs_vnode *vnode;
	int ret;

	vnode = AFS_FS_I(inode);

	ret = afs_vnode_fetch_status(vnode);

	if (ret == 0)
		ret = afs_inode_map_status(vnode);

	return ret;

} /* end afs_inode_fetch_status() */
Exemple #3
0
/*
 * handle the data invalidation side of a callback being broken
 */
void afs_broken_callback_work(struct work_struct *work)
{
	struct afs_vnode *vnode =
		container_of(work, struct afs_vnode, cb_broken_work);

	_enter("");

	if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
		return;

	/* we're only interested in dealing with a broken callback on *this*
	 * vnode and only if no-one else has dealt with it yet */
	if (!mutex_trylock(&vnode->validate_lock))
		return; /* someone else is dealing with it */

	if (test_bit(AFS_VNODE_CB_BROKEN, &vnode->flags)) {
		if (S_ISDIR(vnode->vfs_inode.i_mode))
			afs_clear_permits(vnode);

		if (afs_vnode_fetch_status(vnode, NULL, NULL) < 0)
			goto out;

		if (test_bit(AFS_VNODE_DELETED, &vnode->flags))
			goto out;

		/* if the vnode's data version number changed then its contents
		 * are different */
		if (test_and_clear_bit(AFS_VNODE_ZAP_DATA, &vnode->flags))
			afs_zap_data(vnode);
	}

out:
	mutex_unlock(&vnode->validate_lock);

	/* avoid the potential race whereby the mutex_trylock() in this
	 * function happens again between the clear_bit() and the
	 * mutex_unlock() */
	if (test_bit(AFS_VNODE_CB_BROKEN, &vnode->flags)) {
		_debug("requeue");
		queue_work(afs_callback_update_worker, &vnode->cb_broken_work);
	}
	_leave("");
}
int afs_permission(struct inode *inode, int mask)
{
	struct afs_vnode *vnode = AFS_FS_I(inode);
	afs_access_t uninitialized_var(access);
	struct key *key;
	int ret;

	if (mask & MAY_NOT_BLOCK)
		return -ECHILD;

	_enter("{{%x:%u},%lx},%x,",
	       vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);

	key = afs_request_key(vnode->volume->cell);
	if (IS_ERR(key)) {
		_leave(" = %ld [key]", PTR_ERR(key));
		return PTR_ERR(key);
	}

	
	if (!vnode->cb_promised) {
		_debug("not promised");
		ret = afs_vnode_fetch_status(vnode, NULL, key);
		if (ret < 0)
			goto error;
		_debug("new promise [fl=%lx]", vnode->flags);
	}

	
	ret = afs_check_permit(vnode, key, &access);
	if (ret < 0)
		goto error;

	
	_debug("REQ %x ACC %x on %s",
	       mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");

	if (S_ISDIR(inode->i_mode)) {
		if (mask & MAY_EXEC) {
			if (!(access & AFS_ACE_LOOKUP))
				goto permission_denied;
		} else if (mask & MAY_READ) {
			if (!(access & AFS_ACE_READ))
				goto permission_denied;
		} else if (mask & MAY_WRITE) {
			if (!(access & (AFS_ACE_DELETE | 
					AFS_ACE_INSERT | 
					AFS_ACE_WRITE))) 
				goto permission_denied;
		} else {
			BUG();
		}
	} else {
		if (!(access & AFS_ACE_LOOKUP))
			goto permission_denied;
		if (mask & (MAY_EXEC | MAY_READ)) {
			if (!(access & AFS_ACE_READ))
				goto permission_denied;
		} else if (mask & MAY_WRITE) {
			if (!(access & AFS_ACE_WRITE))
				goto permission_denied;
		}
	}

	key_put(key);
	ret = generic_permission(inode, mask);
	_leave(" = %d", ret);
	return ret;

permission_denied:
	ret = -EACCES;
error:
	key_put(key);
	_leave(" = %d", ret);
	return ret;
}
static int afs_check_permit(struct afs_vnode *vnode, struct key *key,
			    afs_access_t *_access)
{
	struct afs_permits *permits;
	struct afs_permit *permit;
	struct afs_vnode *auth_vnode;
	bool valid;
	int loop, ret;

	_enter("{%x:%u},%x",
	       vnode->fid.vid, vnode->fid.vnode, key_serial(key));

	auth_vnode = afs_get_auth_inode(vnode, key);
	if (IS_ERR(auth_vnode)) {
		*_access = 0;
		_leave(" = %ld", PTR_ERR(auth_vnode));
		return PTR_ERR(auth_vnode);
	}

	ASSERT(S_ISDIR(auth_vnode->vfs_inode.i_mode));

	
	if (key == auth_vnode->volume->cell->anonymous_key) {
		_debug("anon");
		*_access = auth_vnode->status.anon_access;
		valid = true;
	} else {
		valid = false;
		rcu_read_lock();
		permits = rcu_dereference(auth_vnode->permits);
		if (permits) {
			permit = permits->permits;
			for (loop = permits->count; loop > 0; loop--) {
				if (permit->key == key) {
					_debug("found in cache");
					*_access = permit->access_mask;
					valid = true;
					break;
				}
				permit++;
			}
		}
		rcu_read_unlock();
	}

	if (!valid) {
		_debug("no valid permit");

		set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags);
		ret = afs_vnode_fetch_status(vnode, auth_vnode, key);
		if (ret < 0) {
			iput(&auth_vnode->vfs_inode);
			*_access = 0;
			_leave(" = %d", ret);
			return ret;
		}
		*_access = vnode->status.caller_access;
	}

	iput(&auth_vnode->vfs_inode);
	_leave(" = 0 [access %x]", *_access);
	return 0;
}
/*
 * inode retrieval
 */
inline int afs_iget(struct super_block *sb, struct afs_fid *fid,
		    struct inode **_inode)
{
	struct afs_iget_data data = { .fid = *fid };
	struct afs_super_info *as;
	struct afs_vnode *vnode;
	struct inode *inode;
	int ret;

	_enter(",{%u,%u,%u},,", fid->vid, fid->vnode, fid->unique);

	as = sb->s_fs_info;
	data.volume = as->volume;

	inode = iget5_locked(sb, fid->vnode, afs_iget5_test, afs_iget5_set,
			     &data);
	if (!inode) {
		_leave(" = -ENOMEM");
		return -ENOMEM;
	}

	vnode = AFS_FS_I(inode);

	/* deal with an existing inode */
	if (!(inode->i_state & I_NEW)) {
		ret = afs_vnode_fetch_status(vnode);
		if (ret==0)
			*_inode = inode;
		else
			iput(inode);
		_leave(" = %d", ret);
		return ret;
	}

#ifdef AFS_CACHING_SUPPORT
	/* set up caching before reading the status, as fetch-status reads the
	 * first page of symlinks to see if they're really mntpts */
	cachefs_acquire_cookie(vnode->volume->cache,
			       NULL,
			       vnode,
			       &vnode->cache);
#endif

	/* okay... it's a new inode */
	inode->i_flags |= S_NOATIME;
	vnode->flags |= AFS_VNODE_CHANGED;
	ret = afs_inode_fetch_status(inode);
	if (ret<0)
		goto bad_inode;

	/* success */
	unlock_new_inode(inode);

	*_inode = inode;
	_leave(" = 0 [CB { v=%u x=%lu t=%u }]",
	       vnode->cb_version,
	       vnode->cb_timeout.timo_jif,
	       vnode->cb_type);
	return 0;

	/* failure */
 bad_inode:
	make_bad_inode(inode);
	unlock_new_inode(inode);
	iput(inode);

	_leave(" = %d [bad]", ret);
	return ret;
} /* end afs_iget() */
Exemple #7
0
/*
 * inode retrieval
 */
struct inode *afs_iget(struct super_block *sb, struct key *key,
		       struct afs_fid *fid, struct afs_file_status *status,
		       struct afs_callback *cb)
{
	struct afs_iget_data data = { .fid = *fid };
	struct afs_super_info *as;
	struct afs_vnode *vnode;
	struct inode *inode;
	int ret;

	_enter(",{%x:%u.%u},,", fid->vid, fid->vnode, fid->unique);

	as = sb->s_fs_info;
	data.volume = as->volume;

	inode = iget5_locked(sb, fid->vnode, afs_iget5_test, afs_iget5_set,
			     &data);
	if (!inode) {
		_leave(" = -ENOMEM");
		return ERR_PTR(-ENOMEM);
	}

	_debug("GOT INODE %p { vl=%x vn=%x, u=%x }",
	       inode, fid->vid, fid->vnode, fid->unique);

	vnode = AFS_FS_I(inode);

	/* deal with an existing inode */
	if (!(inode->i_state & I_NEW)) {
		_leave(" = %p", inode);
		return inode;
	}

	if (!status) {
		/* it's a remotely extant inode */
		set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags);
		ret = afs_vnode_fetch_status(vnode, NULL, key);
		if (ret < 0)
			goto bad_inode;
	} else {
		/* it's an inode we just created */
		memcpy(&vnode->status, status, sizeof(vnode->status));

		if (!cb) {
			/* it's a symlink we just created (the fileserver
			 * didn't give us a callback) */
			vnode->cb_version = 0;
			vnode->cb_expiry = 0;
			vnode->cb_type = 0;
			vnode->cb_expires = get_seconds();
		} else {
			vnode->cb_version = cb->version;
			vnode->cb_expiry = cb->expiry;
			vnode->cb_type = cb->type;
			vnode->cb_expires = vnode->cb_expiry + get_seconds();
		}
	}

	/* set up caching before mapping the status, as map-status reads the
	 * first page of symlinks to see if they're really mountpoints */
	inode->i_size = vnode->status.size;
#ifdef CONFIG_AFS_FSCACHE
	vnode->cache = fscache_acquire_cookie(vnode->volume->cache,
					      &afs_vnode_cache_index_def,
					      vnode, true);
#endif

	ret = afs_inode_map_status(vnode, key);
	if (ret < 0)
		goto bad_inode;

	/* success */
	clear_bit(AFS_VNODE_UNSET, &vnode->flags);
	inode->i_flags |= S_NOATIME;
	unlock_new_inode(inode);
	_leave(" = %p [CB { v=%u t=%u }]", inode, vnode->cb_version, vnode->cb_type);
	return inode;

	/* failure */
bad_inode:
#ifdef CONFIG_AFS_FSCACHE
	fscache_relinquish_cookie(vnode->cache, 0);
	vnode->cache = NULL;
#endif
	iget_failed(inode);
	_leave(" = %d [bad]", ret);
	return ERR_PTR(ret);
}
Exemple #8
0
/*
 * check that a dentry lookup hit has found a valid entry
 * - NOTE! the hit can be a negative hit too, so we can't assume we have an
 *   inode
 * (derived from nfs_lookup_revalidate)
 */
static int afs_d_revalidate(struct dentry *dentry, struct nameidata *nd)
{
	struct afs_dir_lookup_cookie cookie;
	struct dentry *parent;
	struct inode *inode, *dir;
	unsigned fpos;
	int ret;

	_enter("{sb=%p n=%s},", dentry->d_sb, dentry->d_name.name);

	/* lock down the parent dentry so we can peer at it */
	parent = dget_parent(dentry->d_parent);

	dir = parent->d_inode;
	inode = dentry->d_inode;

	/* handle a negative dentry */
	if (!inode)
		goto out_bad;

	/* handle a bad inode */
	if (is_bad_inode(inode)) {
		printk("kAFS: afs_d_revalidate: %s/%s has bad inode\n",
		       dentry->d_parent->d_name.name, dentry->d_name.name);
		goto out_bad;
	}

	/* force a full look up if the parent directory changed since last the
	 * server was consulted
	 * - otherwise this inode must still exist, even if the inode details
	 *   themselves have changed
	 */
	if (AFS_FS_I(dir)->flags & AFS_VNODE_CHANGED)
		afs_vnode_fetch_status(AFS_FS_I(dir));

	if (AFS_FS_I(dir)->flags & AFS_VNODE_DELETED) {
		_debug("%s: parent dir deleted", dentry->d_name.name);
		goto out_bad;
	}

	if (AFS_FS_I(inode)->flags & AFS_VNODE_DELETED) {
		_debug("%s: file already deleted", dentry->d_name.name);
		goto out_bad;
	}

	if ((unsigned long) dentry->d_fsdata !=
	    (unsigned long) AFS_FS_I(dir)->status.version) {
		_debug("%s: parent changed %lu -> %u",
		       dentry->d_name.name,
		       (unsigned long) dentry->d_fsdata,
		       (unsigned) AFS_FS_I(dir)->status.version);

		/* search the directory for this vnode */
		cookie.name	= dentry->d_name.name;
		cookie.nlen	= dentry->d_name.len;
		cookie.fid.vid	= AFS_FS_I(inode)->volume->vid;
		cookie.found	= 0;

		fpos = 0;
		ret = afs_dir_iterate(dir, &fpos, &cookie,
				      afs_dir_lookup_filldir);
		if (ret < 0) {
			_debug("failed to iterate dir %s: %d",
			       parent->d_name.name, ret);
			goto out_bad;
		}

		if (!cookie.found) {
			_debug("%s: dirent not found", dentry->d_name.name);
			goto not_found;
		}

		/* if the vnode ID has changed, then the dirent points to a
		 * different file */
		if (cookie.fid.vnode != AFS_FS_I(inode)->fid.vnode) {
			_debug("%s: dirent changed", dentry->d_name.name);
			goto not_found;
		}

		/* if the vnode ID uniqifier has changed, then the file has
		 * been deleted */
		if (cookie.fid.unique != AFS_FS_I(inode)->fid.unique) {
			_debug("%s: file deleted (uq %u -> %u I:%lu)",
			       dentry->d_name.name,
			       cookie.fid.unique,
			       AFS_FS_I(inode)->fid.unique,
			       inode->i_version);
			spin_lock(&AFS_FS_I(inode)->lock);
			AFS_FS_I(inode)->flags |= AFS_VNODE_DELETED;
			spin_unlock(&AFS_FS_I(inode)->lock);
			invalidate_remote_inode(inode);
			goto out_bad;
		}

		dentry->d_fsdata =
			(void *) (unsigned long) AFS_FS_I(dir)->status.version;
	}

 out_valid:
	dput(parent);
	_leave(" = 1 [valid]");
	return 1;

	/* the dirent, if it exists, now points to a different vnode */
 not_found:
	spin_lock(&dentry->d_lock);
	dentry->d_flags |= DCACHE_NFSFS_RENAMED;
	spin_unlock(&dentry->d_lock);

 out_bad:
	if (inode) {
		/* don't unhash if we have submounts */
		if (have_submounts(dentry))
			goto out_valid;
	}

	shrink_dcache_parent(dentry);

	_debug("dropping dentry %s/%s",
	       dentry->d_parent->d_name.name, dentry->d_name.name);
	d_drop(dentry);

	dput(parent);

	_leave(" = 0 [bad]");
	return 0;
} /* end afs_d_revalidate() */
Exemple #9
0
/*
 * check the permissions on an AFS file
 * - AFS ACLs are attached to directories only, and a file is controlled by its
 *   parent directory's ACL
 */
int afs_permission(struct inode *inode, int mask)
{
	struct afs_vnode *vnode = AFS_FS_I(inode);
	afs_access_t uninitialized_var(access);
	struct key *key;
	int ret;

	_enter("{{%x:%u},%lx},%x,",
	       vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);

	key = afs_request_key(vnode->volume->cell);
	if (IS_ERR(key)) {
		_leave(" = %ld [key]", PTR_ERR(key));
		return PTR_ERR(key);
	}

	/* if the promise has expired, we need to check the server again */
	if (!vnode->cb_promised) {
		_debug("not promised");
		ret = afs_vnode_fetch_status(vnode, NULL, key);
		if (ret < 0)
			goto error;
		_debug("new promise [fl=%lx]", vnode->flags);
	}

	/* check the permits to see if we've got one yet */
	ret = afs_check_permit(vnode, key, &access);
	if (ret < 0)
		goto error;

	/* interpret the access mask */
	_debug("REQ %x ACC %x on %s",
	       mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");

	if (S_ISDIR(inode->i_mode)) {
		if (mask & MAY_EXEC) {
			if (!(access & AFS_ACE_LOOKUP))
				goto permission_denied;
		} else if (mask & MAY_READ) {
			if (!(access & AFS_ACE_READ))
				goto permission_denied;
		} else if (mask & MAY_WRITE) {
			if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */
					AFS_ACE_INSERT | /* create, mkdir, symlink, rename to */
					AFS_ACE_WRITE))) /* chmod */
				goto permission_denied;
		} else {
			BUG();
		}
	} else {
		if (!(access & AFS_ACE_LOOKUP))
			goto permission_denied;
		if (mask & (MAY_EXEC | MAY_READ)) {
			if (!(access & AFS_ACE_READ))
				goto permission_denied;
		} else if (mask & MAY_WRITE) {
			if (!(access & AFS_ACE_WRITE))
				goto permission_denied;
		}
	}

	key_put(key);
	ret = generic_permission(inode, mask, NULL);
	_leave(" = %d", ret);
	return ret;

permission_denied:
	ret = -EACCES;
error:
	key_put(key);
	_leave(" = %d", ret);
	return ret;
}