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
					
				| @ -113,6 +113,7 @@ again: | ||||
| 	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,13 +196,24 @@ static void afs_break_one_callback(struct afs_server *server, | ||||
| 		if (cbi->vid != fid->vid) | ||||
| 			continue; | ||||
| 
 | ||||
| 		data.volume = NULL; | ||||
| 		data.fid = *fid; | ||||
| 		inode = ilookup5_nowait(cbi->sb, fid->vnode, afs_iget5_test, &data); | ||||
| 		if (inode) { | ||||
| 			vnode = AFS_FS_I(inode); | ||||
| 			afs_break_callback(vnode); | ||||
| 			iput(inode); | ||||
| 		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); | ||||
| 			if (inode) { | ||||
| 				vnode = AFS_FS_I(inode); | ||||
| 				afs_break_callback(vnode); | ||||
| 				iput(inode); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| @ -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,15 +393,18 @@ 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) { | ||||
| 				valid = true; | ||||
| 			valid = true; | ||||
| 		} else if (!test_bit(AFS_VNODE_ZAP_DATA, &vnode->flags) && | ||||
| 			   vnode->cb_expires_at - 10 > now) { | ||||
| 				valid = true; | ||||
| 			valid = true; | ||||
| 		} | ||||
| 	} else if (test_bit(AFS_VNODE_DELETED, &vnode->flags)) { | ||||
| 		valid = true; | ||||
| @ -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 @@ found: | ||||
| 
 | ||||
| 	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 @@ found_key: | ||||
| 	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 New Issue
	
	Block a user