Unexport do_add_mount() and add in follow_automount(), not ->d_automount()
Unexport do_add_mount() and make ->d_automount() return the vfsmount to be added rather than calling do_add_mount() itself. follow_automount() will then do the addition. This slightly complicates things as ->d_automount() normally wants to add the new vfsmount to an expiration list and start an expiration timer. The problem with that is that the vfsmount will be deleted if it has a refcount of 1 and the timer will not repeat if the expiration list is empty. To this end, we require the vfsmount to be returned from d_automount() with a refcount of (at least) 2. One of these refs will be dropped unconditionally. In addition, follow_automount() must get a 3rd ref around the call to do_add_mount() lest it eat a ref and return an error, leaving the mount we have open to being expired as we would otherwise have only 1 ref on it. d_automount() should also add the the vfsmount to the expiration list (by calling mnt_set_expiry()) and start the expiration timer before returning, if this mechanism is to be used. The vfsmount will be unlinked from the expiration list by follow_automount() if do_add_mount() fails. This patch also fixes the call to do_add_mount() for AFS to propagate the mount flags from the parent vfsmount. Signed-off-by: David Howells <dhowells@redhat.com> Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
This commit is contained in:
		
					parent
					
						
							
								ab90911ff9
							
						
					
				
			
			
				commit
				
					
						ea5b778a8b
					
				
			
		
					 8 changed files with 101 additions and 89 deletions
				
			
		| 
						 | 
					@ -933,15 +933,20 @@ struct dentry_operations {
 | 
				
			||||||
	dynamic_dname() helper function is provided to take care of this.
 | 
						dynamic_dname() helper function is provided to take care of this.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  d_automount: called when an automount dentry is to be traversed (optional).
 | 
					  d_automount: called when an automount dentry is to be traversed (optional).
 | 
				
			||||||
	This should create a new VFS mount record, mount it on the directory
 | 
						This should create a new VFS mount record and return the record to the
 | 
				
			||||||
	and return the record to the caller.  The caller is supplied with a
 | 
						caller.  The caller is supplied with a path parameter giving the
 | 
				
			||||||
	path parameter giving the automount directory to describe the automount
 | 
						automount directory to describe the automount target and the parent
 | 
				
			||||||
	target and the parent VFS mount record to provide inheritable mount
 | 
						VFS mount record to provide inheritable mount parameters.  NULL should
 | 
				
			||||||
	parameters.  NULL should be returned if someone else managed to make
 | 
						be returned if someone else managed to make the automount first.  If
 | 
				
			||||||
	the automount first.  If the automount failed, then an error code
 | 
						the vfsmount creation failed, then an error code should be returned.
 | 
				
			||||||
	should be returned.  If -EISDIR is returned, then the directory will
 | 
						If -EISDIR is returned, then the directory will be treated as an
 | 
				
			||||||
	be treated as an ordinary directory and returned to pathwalk to
 | 
						ordinary directory and returned to pathwalk to continue walking.
 | 
				
			||||||
	continue walking.
 | 
					
 | 
				
			||||||
 | 
						If a vfsmount is returned, the caller will attempt to mount it on the
 | 
				
			||||||
 | 
						mountpoint and will remove the vfsmount from its expiration list in
 | 
				
			||||||
 | 
						the case of failure.  The vfsmount should be returned with 2 refs on
 | 
				
			||||||
 | 
						it to prevent automatic expiration - the caller will clean up the
 | 
				
			||||||
 | 
						additional ref.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	This function is only used if DCACHE_NEED_AUTOMOUNT is set on the
 | 
						This function is only used if DCACHE_NEED_AUTOMOUNT is set on the
 | 
				
			||||||
	dentry.  This is set by __d_instantiate() if S_AUTOMOUNT is set on the
 | 
						dentry.  This is set by __d_instantiate() if S_AUTOMOUNT is set on the
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -241,7 +241,6 @@ error_no_devname:
 | 
				
			||||||
struct vfsmount *afs_d_automount(struct path *path)
 | 
					struct vfsmount *afs_d_automount(struct path *path)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct vfsmount *newmnt;
 | 
						struct vfsmount *newmnt;
 | 
				
			||||||
	int err;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_enter("{%s,%s}", path->mnt->mnt_devname, path->dentry->d_name.name);
 | 
						_enter("{%s,%s}", path->mnt->mnt_devname, path->dentry->d_name.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -249,24 +248,12 @@ struct vfsmount *afs_d_automount(struct path *path)
 | 
				
			||||||
	if (IS_ERR(newmnt))
 | 
						if (IS_ERR(newmnt))
 | 
				
			||||||
		return newmnt;
 | 
							return newmnt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mntget(newmnt);
 | 
						mntget(newmnt); /* prevent immediate expiration */
 | 
				
			||||||
	err = do_add_mount(newmnt, path, MNT_SHRINKABLE, &afs_vfsmounts);
 | 
						mnt_set_expiry(newmnt, &afs_vfsmounts);
 | 
				
			||||||
	switch (err) {
 | 
					 | 
				
			||||||
	case 0:
 | 
					 | 
				
			||||||
	queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer,
 | 
						queue_delayed_work(afs_wq, &afs_mntpt_expiry_timer,
 | 
				
			||||||
			   afs_mntpt_expiry_timeout * HZ);
 | 
								   afs_mntpt_expiry_timeout * HZ);
 | 
				
			||||||
	_leave(" = %p {%s}", newmnt, newmnt->mnt_devname);
 | 
						_leave(" = %p {%s}", newmnt, newmnt->mnt_devname);
 | 
				
			||||||
	return newmnt;
 | 
						return newmnt;
 | 
				
			||||||
	case -EBUSY:
 | 
					 | 
				
			||||||
		/* someone else made a mount here whilst we were busy */
 | 
					 | 
				
			||||||
		mntput(newmnt);
 | 
					 | 
				
			||||||
		_leave(" = NULL [EBUSY]");
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		mntput(newmnt);
 | 
					 | 
				
			||||||
		_leave(" = %d", err);
 | 
					 | 
				
			||||||
		return ERR_PTR(err);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -351,7 +351,6 @@ free_xid:
 | 
				
			||||||
struct vfsmount *cifs_dfs_d_automount(struct path *path)
 | 
					struct vfsmount *cifs_dfs_d_automount(struct path *path)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct vfsmount *newmnt;
 | 
						struct vfsmount *newmnt;
 | 
				
			||||||
	int err;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cFYI(1, "in %s", __func__);
 | 
						cFYI(1, "in %s", __func__);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -361,25 +360,12 @@ struct vfsmount *cifs_dfs_d_automount(struct path *path)
 | 
				
			||||||
		return newmnt;
 | 
							return newmnt;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mntget(newmnt);
 | 
						mntget(newmnt); /* prevent immediate expiration */
 | 
				
			||||||
	err = do_add_mount(newmnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE,
 | 
						mnt_set_expiry(newmnt, &cifs_dfs_automount_list);
 | 
				
			||||||
			   &cifs_dfs_automount_list);
 | 
					 | 
				
			||||||
	switch (err) {
 | 
					 | 
				
			||||||
	case 0:
 | 
					 | 
				
			||||||
	schedule_delayed_work(&cifs_dfs_automount_task,
 | 
						schedule_delayed_work(&cifs_dfs_automount_task,
 | 
				
			||||||
			      cifs_dfs_mountpoint_expiry_timeout);
 | 
								      cifs_dfs_mountpoint_expiry_timeout);
 | 
				
			||||||
	cFYI(1, "leaving %s [ok]" , __func__);
 | 
						cFYI(1, "leaving %s [ok]" , __func__);
 | 
				
			||||||
	return newmnt;
 | 
						return newmnt;
 | 
				
			||||||
	case -EBUSY:
 | 
					 | 
				
			||||||
		/* someone else made a mount here whilst we were busy */
 | 
					 | 
				
			||||||
		mntput(newmnt);
 | 
					 | 
				
			||||||
		cFYI(1, "leaving %s [EBUSY]" , __func__);
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		mntput(newmnt);
 | 
					 | 
				
			||||||
		cFYI(1, "leaving %s [error %d]" , __func__, err);
 | 
					 | 
				
			||||||
		return ERR_PTR(err);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const struct inode_operations cifs_dfs_referral_inode_operations = {
 | 
					const struct inode_operations cifs_dfs_referral_inode_operations = {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -70,6 +70,8 @@ extern void mnt_set_mountpoint(struct vfsmount *, struct dentry *,
 | 
				
			||||||
extern void release_mounts(struct list_head *);
 | 
					extern void release_mounts(struct list_head *);
 | 
				
			||||||
extern void umount_tree(struct vfsmount *, int, struct list_head *);
 | 
					extern void umount_tree(struct vfsmount *, int, struct list_head *);
 | 
				
			||||||
extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
 | 
					extern struct vfsmount *copy_tree(struct vfsmount *, struct dentry *, int);
 | 
				
			||||||
 | 
					extern int do_add_mount(struct vfsmount *, struct path *, int);
 | 
				
			||||||
 | 
					extern void mnt_clear_expiry(struct vfsmount *);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern void __init mnt_init(void);
 | 
					extern void __init mnt_init(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										28
									
								
								fs/namei.c
									
										
									
									
									
								
							
							
						
						
									
										28
									
								
								fs/namei.c
									
										
									
									
									
								
							| 
						 | 
					@ -900,6 +900,7 @@ static int follow_automount(struct path *path, unsigned flags,
 | 
				
			||||||
			    bool *need_mntput)
 | 
								    bool *need_mntput)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct vfsmount *mnt;
 | 
						struct vfsmount *mnt;
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!path->dentry->d_op || !path->dentry->d_op->d_automount)
 | 
						if (!path->dentry->d_op || !path->dentry->d_op->d_automount)
 | 
				
			||||||
		return -EREMOTE;
 | 
							return -EREMOTE;
 | 
				
			||||||
| 
						 | 
					@ -942,15 +943,41 @@ static int follow_automount(struct path *path, unsigned flags,
 | 
				
			||||||
			return -EREMOTE;
 | 
								return -EREMOTE;
 | 
				
			||||||
		return PTR_ERR(mnt);
 | 
							return PTR_ERR(mnt);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!mnt) /* mount collision */
 | 
						if (!mnt) /* mount collision */
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* The new mount record should have at least 2 refs to prevent it being
 | 
				
			||||||
 | 
						 * expired before we get a chance to add it
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						BUG_ON(mnt_get_count(mnt) < 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (mnt->mnt_sb == path->mnt->mnt_sb &&
 | 
						if (mnt->mnt_sb == path->mnt->mnt_sb &&
 | 
				
			||||||
	    mnt->mnt_root == path->dentry) {
 | 
						    mnt->mnt_root == path->dentry) {
 | 
				
			||||||
 | 
							mnt_clear_expiry(mnt);
 | 
				
			||||||
 | 
							mntput(mnt);
 | 
				
			||||||
		mntput(mnt);
 | 
							mntput(mnt);
 | 
				
			||||||
		return -ELOOP;
 | 
							return -ELOOP;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* We need to add the mountpoint to the parent.  The filesystem may
 | 
				
			||||||
 | 
						 * have placed it on an expiry list, and so we need to make sure it
 | 
				
			||||||
 | 
						 * won't be expired under us if do_add_mount() fails (do_add_mount()
 | 
				
			||||||
 | 
						 * will eat a reference unconditionally).
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						mntget(mnt);
 | 
				
			||||||
 | 
						err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE);
 | 
				
			||||||
 | 
						switch (err) {
 | 
				
			||||||
 | 
						case -EBUSY:
 | 
				
			||||||
 | 
							/* Someone else made a mount here whilst we were busy */
 | 
				
			||||||
 | 
							err = 0;
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							mnt_clear_expiry(mnt);
 | 
				
			||||||
 | 
							mntput(mnt);
 | 
				
			||||||
 | 
							mntput(mnt);
 | 
				
			||||||
 | 
							return err;
 | 
				
			||||||
 | 
						case 0:
 | 
				
			||||||
 | 
							mntput(mnt);
 | 
				
			||||||
		dput(path->dentry);
 | 
							dput(path->dentry);
 | 
				
			||||||
		if (*need_mntput)
 | 
							if (*need_mntput)
 | 
				
			||||||
			mntput(path->mnt);
 | 
								mntput(path->mnt);
 | 
				
			||||||
| 
						 | 
					@ -959,6 +986,7 @@ static int follow_automount(struct path *path, unsigned flags,
 | 
				
			||||||
		*need_mntput = true;
 | 
							*need_mntput = true;
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * Handle a dentry that is managed in some way.
 | 
					 * Handle a dentry that is managed in some way.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1925,15 +1925,14 @@ static int do_new_mount(struct path *path, char *type, int flags,
 | 
				
			||||||
	if (IS_ERR(mnt))
 | 
						if (IS_ERR(mnt))
 | 
				
			||||||
		return PTR_ERR(mnt);
 | 
							return PTR_ERR(mnt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return do_add_mount(mnt, path, mnt_flags, NULL);
 | 
						return do_add_mount(mnt, path, mnt_flags);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * add a mount into a namespace's mount tree
 | 
					 * add a mount into a namespace's mount tree
 | 
				
			||||||
 * - provide the option of adding the new mount to an expiration list
 | 
					 * - this unconditionally eats one of the caller's references to newmnt.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
int do_add_mount(struct vfsmount *newmnt, struct path *path,
 | 
					int do_add_mount(struct vfsmount *newmnt, struct path *path, int mnt_flags)
 | 
				
			||||||
		 int mnt_flags, struct list_head *fslist)
 | 
					 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	int err;
 | 
						int err;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1963,9 +1962,6 @@ int do_add_mount(struct vfsmount *newmnt, struct path *path,
 | 
				
			||||||
	if ((err = graft_tree(newmnt, path)))
 | 
						if ((err = graft_tree(newmnt, path)))
 | 
				
			||||||
		goto unlock;
 | 
							goto unlock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (fslist) /* add to the specified expiration list */
 | 
					 | 
				
			||||||
		list_add_tail(&newmnt->mnt_expire, fslist);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	up_write(&namespace_sem);
 | 
						up_write(&namespace_sem);
 | 
				
			||||||
	return 0;
 | 
						return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1975,7 +1971,36 @@ unlock:
 | 
				
			||||||
	return err;
 | 
						return err;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
EXPORT_SYMBOL_GPL(do_add_mount);
 | 
					/**
 | 
				
			||||||
 | 
					 * mnt_set_expiry - Put a mount on an expiration list
 | 
				
			||||||
 | 
					 * @mnt: The mount to list.
 | 
				
			||||||
 | 
					 * @expiry_list: The list to add the mount to.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						down_write(&namespace_sem);
 | 
				
			||||||
 | 
						br_write_lock(vfsmount_lock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						list_add_tail(&mnt->mnt_expire, expiry_list);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						br_write_unlock(vfsmount_lock);
 | 
				
			||||||
 | 
						up_write(&namespace_sem);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					EXPORT_SYMBOL(mnt_set_expiry);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Remove a vfsmount from any expiration list it may be on
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void mnt_clear_expiry(struct vfsmount *mnt)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (!list_empty(&mnt->mnt_expire)) {
 | 
				
			||||||
 | 
							down_write(&namespace_sem);
 | 
				
			||||||
 | 
							br_write_lock(vfsmount_lock);
 | 
				
			||||||
 | 
							list_del_init(&mnt->mnt_expire);
 | 
				
			||||||
 | 
							br_write_unlock(vfsmount_lock);
 | 
				
			||||||
 | 
							up_write(&namespace_sem);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/*
 | 
					/*
 | 
				
			||||||
 * process a list of expirable mountpoints with the intent of discarding any
 | 
					 * process a list of expirable mountpoints with the intent of discarding any
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -149,26 +149,10 @@ struct vfsmount *nfs_d_automount(struct path *path)
 | 
				
			||||||
	if (IS_ERR(mnt))
 | 
						if (IS_ERR(mnt))
 | 
				
			||||||
		goto out;
 | 
							goto out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	mntget(mnt);
 | 
					 | 
				
			||||||
	err = do_add_mount(mnt, path, path->mnt->mnt_flags | MNT_SHRINKABLE,
 | 
					 | 
				
			||||||
			   &nfs_automount_list);
 | 
					 | 
				
			||||||
	switch (err) {
 | 
					 | 
				
			||||||
	case 0:
 | 
					 | 
				
			||||||
	dprintk("%s: done, success\n", __func__);
 | 
						dprintk("%s: done, success\n", __func__);
 | 
				
			||||||
 | 
						mntget(mnt); /* prevent immediate expiration */
 | 
				
			||||||
 | 
						mnt_set_expiry(mnt, &nfs_automount_list);
 | 
				
			||||||
	schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
 | 
						schedule_delayed_work(&nfs_automount_task, nfs_mountpoint_expiry_timeout);
 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	case -EBUSY:
 | 
					 | 
				
			||||||
		/* someone else made a mount here whilst we were busy */
 | 
					 | 
				
			||||||
		mntput(mnt);
 | 
					 | 
				
			||||||
		dprintk("%s: done, collision\n", __func__);
 | 
					 | 
				
			||||||
		mnt = NULL;
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		mntput(mnt);
 | 
					 | 
				
			||||||
		dprintk("%s: done, error %d\n", __func__, err);
 | 
					 | 
				
			||||||
		mnt = ERR_PTR(err);
 | 
					 | 
				
			||||||
		break;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
out:
 | 
					out:
 | 
				
			||||||
	nfs_free_fattr(fattr);
 | 
						nfs_free_fattr(fattr);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -110,12 +110,7 @@ extern struct vfsmount *vfs_kern_mount(struct file_system_type *type,
 | 
				
			||||||
				      int flags, const char *name,
 | 
									      int flags, const char *name,
 | 
				
			||||||
				      void *data);
 | 
									      void *data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct nameidata;
 | 
					extern void mnt_set_expiry(struct vfsmount *mnt, struct list_head *expiry_list);
 | 
				
			||||||
 | 
					 | 
				
			||||||
struct path;
 | 
					 | 
				
			||||||
extern int do_add_mount(struct vfsmount *newmnt, struct path *path,
 | 
					 | 
				
			||||||
			int mnt_flags, struct list_head *fslist);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern void mark_mounts_for_expiry(struct list_head *mounts);
 | 
					extern void mark_mounts_for_expiry(struct list_head *mounts);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern dev_t name_to_dev_t(char *name);
 | 
					extern dev_t name_to_dev_t(char *name);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue