mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-03 10:10:33 +02:00 
			
		
		
		
	afs: Fix whole-volume callback handling
It's possible for an AFS file server to issue a whole-volume notification
that callbacks on all the vnodes in the file have been broken.  This is
done for R/O and backup volumes (which don't have per-file callbacks) and
for things like a volume being taken offline.
Fix callback handling to detect whole-volume notifications, to track it
across operations and to check it during inode validation.
Fixes: c435ee3455 ("afs: Overhaul the callback handling")
Signed-off-by: David Howells <dhowells@redhat.com>
			
			
This commit is contained in:
		
							parent
							
								
									f9c1bba3d3
								
							
						
					
					
						commit
						68251f0a68
					
				
					 10 changed files with 63 additions and 32 deletions
				
			
		| 
						 | 
				
			
			@ -113,6 +113,7 @@ int afs_register_server_cb_interest(struct afs_vnode *vnode,
 | 
			
		|||
	old = vnode->cb_interest;
 | 
			
		||||
	vnode->cb_interest = cbi;
 | 
			
		||||
	vnode->cb_s_break = cbi->server->cb_s_break;
 | 
			
		||||
	vnode->cb_v_break = vnode->volume->cb_v_break;
 | 
			
		||||
	clear_bit(AFS_VNODE_CB_PROMISED, &vnode->flags);
 | 
			
		||||
 | 
			
		||||
	write_sequnlock(&vnode->cb_lock);
 | 
			
		||||
| 
						 | 
				
			
			@ -195,15 +196,26 @@ static void afs_break_one_callback(struct afs_server *server,
 | 
			
		|||
		if (cbi->vid != fid->vid)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		if (fid->vnode == 0 && fid->unique == 0) {
 | 
			
		||||
			/* The callback break applies to an entire volume. */
 | 
			
		||||
			struct afs_super_info *as = AFS_FS_S(cbi->sb);
 | 
			
		||||
			struct afs_volume *volume = as->volume;
 | 
			
		||||
 | 
			
		||||
			write_lock(&volume->cb_break_lock);
 | 
			
		||||
			volume->cb_v_break++;
 | 
			
		||||
			write_unlock(&volume->cb_break_lock);
 | 
			
		||||
		} else {
 | 
			
		||||
			data.volume = NULL;
 | 
			
		||||
			data.fid = *fid;
 | 
			
		||||
		inode = ilookup5_nowait(cbi->sb, fid->vnode, afs_iget5_test, &data);
 | 
			
		||||
			inode = ilookup5_nowait(cbi->sb, fid->vnode,
 | 
			
		||||
						afs_iget5_test, &data);
 | 
			
		||||
			if (inode) {
 | 
			
		||||
				vnode = AFS_FS_I(inode);
 | 
			
		||||
				afs_break_callback(vnode);
 | 
			
		||||
				iput(inode);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	read_unlock(&server->cb_break_lock);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -219,6 +231,8 @@ void afs_break_callbacks(struct afs_server *server, size_t count,
 | 
			
		|||
	ASSERT(server != NULL);
 | 
			
		||||
	ASSERTCMP(count, <=, AFSCBMAX);
 | 
			
		||||
 | 
			
		||||
	/* TODO: Sort the callback break list by volume ID */
 | 
			
		||||
 | 
			
		||||
	for (; count > 0; callbacks++, count--) {
 | 
			
		||||
		_debug("- Fid { vl=%08x n=%u u=%u }  CB { v=%u x=%u t=%u }",
 | 
			
		||||
		       callbacks->fid.vid,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										18
									
								
								fs/afs/dir.c
									
									
									
									
									
								
							
							
						
						
									
										18
									
								
								fs/afs/dir.c
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -1141,7 +1141,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, dvnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			afs_fs_create(&fc, dentry->d_name.name, mode, data_version,
 | 
			
		||||
				      &newfid, &newstatus, &newcb);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1211,7 +1211,7 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, dvnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			afs_fs_remove(&fc, dentry->d_name.name, true,
 | 
			
		||||
				      data_version);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1314,7 +1314,7 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, dvnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			afs_fs_remove(&fc, dentry->d_name.name, false,
 | 
			
		||||
				      data_version);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1371,7 +1371,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode,
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, dvnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			afs_fs_create(&fc, dentry->d_name.name, mode, data_version,
 | 
			
		||||
				      &newfid, &newstatus, &newcb);
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -1441,8 +1441,8 @@ static int afs_link(struct dentry *from, struct inode *dir,
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break_2 = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			fc.cb_break_2 = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_link(&fc, vnode, dentry->d_name.name, data_version);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1510,7 +1510,7 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry,
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, dvnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = dvnode->cb_break + dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(dvnode);
 | 
			
		||||
			afs_fs_symlink(&fc, dentry->d_name.name,
 | 
			
		||||
				       content, data_version,
 | 
			
		||||
				       &newfid, &newstatus);
 | 
			
		||||
| 
						 | 
				
			
			@ -1586,8 +1586,8 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry,
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = orig_dvnode->cb_break + orig_dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break_2 = new_dvnode->cb_break + new_dvnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(orig_dvnode);
 | 
			
		||||
			fc.cb_break_2 = afs_calc_vnode_cb_break(new_dvnode);
 | 
			
		||||
			afs_fs_rename(&fc, old_dentry->d_name.name,
 | 
			
		||||
				      new_dvnode, new_dentry->d_name.name,
 | 
			
		||||
				      orig_data_version, new_data_version);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -238,7 +238,7 @@ int afs_fetch_data(struct afs_vnode *vnode, struct key *key, struct afs_read *de
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_fetch_data(&fc, desc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ static int afs_set_lock(struct afs_vnode *vnode, struct key *key,
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_set_lock(&fc, type);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -117,7 +117,7 @@ static int afs_extend_lock(struct afs_vnode *vnode, struct key *key)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_current_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_extend_lock(&fc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -148,7 +148,7 @@ static int afs_release_lock(struct afs_vnode *vnode, struct key *key)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_current_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_release_lock(&fc);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -261,7 +261,7 @@ static void xdr_decode_AFSCallBack(struct afs_call *call,
 | 
			
		|||
 | 
			
		||||
	write_seqlock(&vnode->cb_lock);
 | 
			
		||||
 | 
			
		||||
	if (call->cb_break == (vnode->cb_break + cbi->server->cb_s_break)) {
 | 
			
		||||
	if (call->cb_break == afs_cb_break_sum(vnode, cbi)) {
 | 
			
		||||
		vnode->cb_version	= ntohl(*bp++);
 | 
			
		||||
		cb_expiry		= ntohl(*bp++);
 | 
			
		||||
		vnode->cb_type		= ntohl(*bp++);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -108,7 +108,7 @@ int afs_fetch_status(struct afs_vnode *vnode, struct key *key, bool new_inode)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_fetch_file_status(&fc, NULL, new_inode);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -393,8 +393,11 @@ int afs_validate(struct afs_vnode *vnode, struct key *key)
 | 
			
		|||
	read_seqlock_excl(&vnode->cb_lock);
 | 
			
		||||
 | 
			
		||||
	if (test_bit(AFS_VNODE_CB_PROMISED, &vnode->flags)) {
 | 
			
		||||
		if (vnode->cb_s_break != vnode->cb_interest->server->cb_s_break) {
 | 
			
		||||
		if (vnode->cb_s_break != vnode->cb_interest->server->cb_s_break ||
 | 
			
		||||
		    vnode->cb_v_break != vnode->volume->cb_v_break) {
 | 
			
		||||
			vnode->cb_s_break = vnode->cb_interest->server->cb_s_break;
 | 
			
		||||
			vnode->cb_v_break = vnode->volume->cb_v_break;
 | 
			
		||||
			valid = false;
 | 
			
		||||
		} else if (vnode->status.type == AFS_FTYPE_DIR &&
 | 
			
		||||
			   test_bit(AFS_VNODE_DIR_VALID, &vnode->flags) &&
 | 
			
		||||
			   vnode->cb_expires_at - 10 > now) {
 | 
			
		||||
| 
						 | 
				
			
			@ -574,7 +577,7 @@ int afs_setattr(struct dentry *dentry, struct iattr *attr)
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_setattr(&fc, attr);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -461,6 +461,9 @@ struct afs_volume {
 | 
			
		|||
	rwlock_t		servers_lock;	/* Lock for ->servers */
 | 
			
		||||
	unsigned int		servers_seq;	/* Incremented each time ->servers changes */
 | 
			
		||||
 | 
			
		||||
	unsigned		cb_v_break;	/* Break-everything counter. */
 | 
			
		||||
	rwlock_t		cb_break_lock;
 | 
			
		||||
 | 
			
		||||
	afs_voltype_t		type;		/* type of volume */
 | 
			
		||||
	short			error;
 | 
			
		||||
	char			type_force;	/* force volume type (suppress R/O -> R/W) */
 | 
			
		||||
| 
						 | 
				
			
			@ -521,6 +524,7 @@ struct afs_vnode {
 | 
			
		|||
	/* outstanding callback notification on this file */
 | 
			
		||||
	struct afs_cb_interest	*cb_interest;	/* Server on which this resides */
 | 
			
		||||
	unsigned int		cb_s_break;	/* Mass break counter on ->server */
 | 
			
		||||
	unsigned int		cb_v_break;	/* Mass break counter on ->volume */
 | 
			
		||||
	unsigned int		cb_break;	/* Break counter on vnode */
 | 
			
		||||
	seqlock_t		cb_lock;	/* Lock for ->cb_interest, ->status, ->cb_*break */
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -662,6 +666,17 @@ static inline struct afs_cb_interest *afs_get_cb_interest(struct afs_cb_interest
 | 
			
		|||
	return cbi;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline unsigned int afs_calc_vnode_cb_break(struct afs_vnode *vnode)
 | 
			
		||||
{
 | 
			
		||||
	return vnode->cb_break + vnode->cb_s_break + vnode->cb_v_break;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static inline unsigned int afs_cb_break_sum(struct afs_vnode *vnode,
 | 
			
		||||
					    struct afs_cb_interest *cbi)
 | 
			
		||||
{
 | 
			
		||||
	return vnode->cb_break + cbi->server->cb_s_break + vnode->volume->cb_v_break;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * cell.c
 | 
			
		||||
 */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -147,8 +147,7 @@ void afs_cache_permit(struct afs_vnode *vnode, struct key *key,
 | 
			
		|||
					break;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				if (cb_break != (vnode->cb_break +
 | 
			
		||||
						 vnode->cb_interest->server->cb_s_break)) {
 | 
			
		||||
				if (cb_break != afs_cb_break_sum(vnode, vnode->cb_interest)) {
 | 
			
		||||
					changed = true;
 | 
			
		||||
					break;
 | 
			
		||||
				}
 | 
			
		||||
| 
						 | 
				
			
			@ -178,7 +177,7 @@ void afs_cache_permit(struct afs_vnode *vnode, struct key *key,
 | 
			
		|||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (cb_break != (vnode->cb_break + vnode->cb_interest->server->cb_s_break))
 | 
			
		||||
	if (cb_break != afs_cb_break_sum(vnode, vnode->cb_interest))
 | 
			
		||||
		goto someone_else_changed_it;
 | 
			
		||||
 | 
			
		||||
	/* We need a ref on any permits list we want to copy as we'll have to
 | 
			
		||||
| 
						 | 
				
			
			@ -257,7 +256,7 @@ void afs_cache_permit(struct afs_vnode *vnode, struct key *key,
 | 
			
		|||
 | 
			
		||||
	spin_lock(&vnode->lock);
 | 
			
		||||
	zap = rcu_access_pointer(vnode->permit_cache);
 | 
			
		||||
	if (cb_break == (vnode->cb_break + vnode->cb_interest->server->cb_s_break) &&
 | 
			
		||||
	if (cb_break == afs_cb_break_sum(vnode, vnode->cb_interest) &&
 | 
			
		||||
	    zap == permits)
 | 
			
		||||
		rcu_assign_pointer(vnode->permit_cache, replacement);
 | 
			
		||||
	else
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -688,7 +688,7 @@ static int afs_statfs(struct dentry *dentry, struct kstatfs *buf)
 | 
			
		|||
	if (afs_begin_vnode_operation(&fc, vnode, key)) {
 | 
			
		||||
		fc.flags |= AFS_FS_CURSOR_NO_VSLEEP;
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_get_volume_status(&fc, &vs);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -351,7 +351,7 @@ static int afs_store_data(struct address_space *mapping,
 | 
			
		|||
	ret = -ERESTARTSYS;
 | 
			
		||||
	if (afs_begin_vnode_operation(&fc, vnode, wbk->key)) {
 | 
			
		||||
		while (afs_select_fileserver(&fc)) {
 | 
			
		||||
			fc.cb_break = vnode->cb_break + vnode->cb_s_break;
 | 
			
		||||
			fc.cb_break = afs_calc_vnode_cb_break(vnode);
 | 
			
		||||
			afs_fs_store_data(&fc, mapping, first, last, offset, to);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue