mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-11-01 00:48:50 +00:00
Bug fixes for 6.11-rc6:
* Do not call out v1 inodes with non-zero di_nlink field as being corrupt. * Change xfs_finobt_count_blocks() to count "free inode btree" blocks rather than "inode btree" blocks. * Don't report the number of trimmed bytes via FITRIM because the underlying storage isn't required to do anything and failed discard IOs aren't reported to the caller anyway. * Fix incorrect setting of rm_owner field in an rmap query. * Report missing disk offset range in an fsmap query. * Obtain m_growlock when extending realtime section of the filesystem. * Reset rootdir extent size hint after extending realtime section of the filesystem. Signed-off-by: Chandan Babu R <chandanbabu@kernel.org> -----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQQjMC4mbgVeU7MxEIYH7y4RirJu9AUCZs3OYgAKCRAH7y4RirJu 9OF/AP9MXSSmBHmTfpqJZbKCI9j+EvAGyucbITi32ZBnbnNnKgEAr5FrueGcKS98 H/FxMeNbSWZp0s5hUYsXsACtdo75YgE= =prEp -----END PGP SIGNATURE----- Merge tag 'xfs-6.11-fixes-4' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux Pull xfs fixes from Chandan Babu: - Do not call out v1 inodes with non-zero di_nlink field as being corrupt - Change xfs_finobt_count_blocks() to count "free inode btree" blocks rather than "inode btree" blocks - Don't report the number of trimmed bytes via FITRIM because the underlying storage isn't required to do anything and failed discard IOs aren't reported to the caller anyway - Fix incorrect setting of rm_owner field in an rmap query - Report missing disk offset range in an fsmap query - Obtain m_growlock when extending realtime section of the filesystem - Reset rootdir extent size hint after extending realtime section of the filesystem * tag 'xfs-6.11-fixes-4' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux: xfs: reset rootdir extent size hint after growfsrt xfs: take m_growlock when running growfsrt xfs: Fix missing interval for missing_owner in xfs fsmap xfs: use XFS_BUF_DADDR_NULL for daddrs in getfsmap code xfs: Fix the owner setting issue for rmap query in xfs fsmap xfs: don't bother reporting blocks trimmed via FITRIM xfs: xfs_finobt_count_blocks() walks the wrong btree xfs: fix folio dirtying for XFILE_ALLOC callers xfs: fix di_onlink checking for V1/V2 inodes
This commit is contained in:
commit
0efdc09796
6 changed files with 114 additions and 48 deletions
|
@ -749,7 +749,7 @@ xfs_finobt_count_blocks(
|
|||
if (error)
|
||||
return error;
|
||||
|
||||
cur = xfs_inobt_init_cursor(pag, tp, agbp);
|
||||
cur = xfs_finobt_init_cursor(pag, tp, agbp);
|
||||
error = xfs_btree_count_blocks(cur, tree_blocks);
|
||||
xfs_btree_del_cursor(cur, error);
|
||||
xfs_trans_brelse(tp, agbp);
|
||||
|
|
|
@ -514,12 +514,18 @@ xfs_dinode_verify(
|
|||
return __this_address;
|
||||
}
|
||||
|
||||
if (dip->di_version > 1) {
|
||||
/*
|
||||
* Historical note: xfsprogs in the 3.2 era set up its incore inodes to
|
||||
* have di_nlink track the link count, even if the actual filesystem
|
||||
* only supported V1 inodes (i.e. di_onlink). When writing out the
|
||||
* ondisk inode, it would set both the ondisk di_nlink and di_onlink to
|
||||
* the the incore di_nlink value, which is why we cannot check for
|
||||
* di_nlink==0 on a V1 inode. V2/3 inodes would get written out with
|
||||
* di_onlink==0, so we can check that.
|
||||
*/
|
||||
if (dip->di_version >= 2) {
|
||||
if (dip->di_onlink)
|
||||
return __this_address;
|
||||
} else {
|
||||
if (dip->di_nlink)
|
||||
return __this_address;
|
||||
}
|
||||
|
||||
/* don't allow invalid i_size */
|
||||
|
|
|
@ -293,7 +293,7 @@ xfile_get_folio(
|
|||
* (potentially last) reference in xfile_put_folio.
|
||||
*/
|
||||
if (flags & XFILE_ALLOC)
|
||||
folio_set_dirty(folio);
|
||||
folio_mark_dirty(folio);
|
||||
return folio;
|
||||
}
|
||||
|
||||
|
|
|
@ -158,8 +158,7 @@ static int
|
|||
xfs_trim_gather_extents(
|
||||
struct xfs_perag *pag,
|
||||
struct xfs_trim_cur *tcur,
|
||||
struct xfs_busy_extents *extents,
|
||||
uint64_t *blocks_trimmed)
|
||||
struct xfs_busy_extents *extents)
|
||||
{
|
||||
struct xfs_mount *mp = pag->pag_mount;
|
||||
struct xfs_trans *tp;
|
||||
|
@ -280,7 +279,6 @@ xfs_trim_gather_extents(
|
|||
|
||||
xfs_extent_busy_insert_discard(pag, fbno, flen,
|
||||
&extents->extent_list);
|
||||
*blocks_trimmed += flen;
|
||||
next_extent:
|
||||
if (tcur->by_bno)
|
||||
error = xfs_btree_increment(cur, 0, &i);
|
||||
|
@ -327,8 +325,7 @@ xfs_trim_perag_extents(
|
|||
struct xfs_perag *pag,
|
||||
xfs_agblock_t start,
|
||||
xfs_agblock_t end,
|
||||
xfs_extlen_t minlen,
|
||||
uint64_t *blocks_trimmed)
|
||||
xfs_extlen_t minlen)
|
||||
{
|
||||
struct xfs_trim_cur tcur = {
|
||||
.start = start,
|
||||
|
@ -354,8 +351,7 @@ xfs_trim_perag_extents(
|
|||
extents->owner = extents;
|
||||
INIT_LIST_HEAD(&extents->extent_list);
|
||||
|
||||
error = xfs_trim_gather_extents(pag, &tcur, extents,
|
||||
blocks_trimmed);
|
||||
error = xfs_trim_gather_extents(pag, &tcur, extents);
|
||||
if (error) {
|
||||
kfree(extents);
|
||||
break;
|
||||
|
@ -389,8 +385,7 @@ xfs_trim_datadev_extents(
|
|||
struct xfs_mount *mp,
|
||||
xfs_daddr_t start,
|
||||
xfs_daddr_t end,
|
||||
xfs_extlen_t minlen,
|
||||
uint64_t *blocks_trimmed)
|
||||
xfs_extlen_t minlen)
|
||||
{
|
||||
xfs_agnumber_t start_agno, end_agno;
|
||||
xfs_agblock_t start_agbno, end_agbno;
|
||||
|
@ -411,8 +406,7 @@ xfs_trim_datadev_extents(
|
|||
|
||||
if (start_agno == end_agno)
|
||||
agend = end_agbno;
|
||||
error = xfs_trim_perag_extents(pag, start_agbno, agend, minlen,
|
||||
blocks_trimmed);
|
||||
error = xfs_trim_perag_extents(pag, start_agbno, agend, minlen);
|
||||
if (error)
|
||||
last_error = error;
|
||||
|
||||
|
@ -431,9 +425,6 @@ struct xfs_trim_rtdev {
|
|||
/* list of rt extents to free */
|
||||
struct list_head extent_list;
|
||||
|
||||
/* pointer to count of blocks trimmed */
|
||||
uint64_t *blocks_trimmed;
|
||||
|
||||
/* minimum length that caller allows us to trim */
|
||||
xfs_rtblock_t minlen_fsb;
|
||||
|
||||
|
@ -551,7 +542,6 @@ xfs_trim_gather_rtextent(
|
|||
busyp->length = rlen;
|
||||
INIT_LIST_HEAD(&busyp->list);
|
||||
list_add_tail(&busyp->list, &tr->extent_list);
|
||||
*tr->blocks_trimmed += rlen;
|
||||
|
||||
tr->restart_rtx = rec->ar_startext + rec->ar_extcount;
|
||||
return 0;
|
||||
|
@ -562,13 +552,11 @@ xfs_trim_rtdev_extents(
|
|||
struct xfs_mount *mp,
|
||||
xfs_daddr_t start,
|
||||
xfs_daddr_t end,
|
||||
xfs_daddr_t minlen,
|
||||
uint64_t *blocks_trimmed)
|
||||
xfs_daddr_t minlen)
|
||||
{
|
||||
struct xfs_rtalloc_rec low = { };
|
||||
struct xfs_rtalloc_rec high = { };
|
||||
struct xfs_trim_rtdev tr = {
|
||||
.blocks_trimmed = blocks_trimmed,
|
||||
.minlen_fsb = XFS_BB_TO_FSB(mp, minlen),
|
||||
};
|
||||
struct xfs_trans *tp;
|
||||
|
@ -634,7 +622,7 @@ xfs_trim_rtdev_extents(
|
|||
return error;
|
||||
}
|
||||
#else
|
||||
# define xfs_trim_rtdev_extents(m,s,e,n,b) (-EOPNOTSUPP)
|
||||
# define xfs_trim_rtdev_extents(...) (-EOPNOTSUPP)
|
||||
#endif /* CONFIG_XFS_RT */
|
||||
|
||||
/*
|
||||
|
@ -661,7 +649,6 @@ xfs_ioc_trim(
|
|||
xfs_daddr_t start, end;
|
||||
xfs_extlen_t minlen;
|
||||
xfs_rfsblock_t max_blocks;
|
||||
uint64_t blocks_trimmed = 0;
|
||||
int error, last_error = 0;
|
||||
|
||||
if (!capable(CAP_SYS_ADMIN))
|
||||
|
@ -706,15 +693,13 @@ xfs_ioc_trim(
|
|||
end = start + BTOBBT(range.len) - 1;
|
||||
|
||||
if (bdev_max_discard_sectors(mp->m_ddev_targp->bt_bdev)) {
|
||||
error = xfs_trim_datadev_extents(mp, start, end, minlen,
|
||||
&blocks_trimmed);
|
||||
error = xfs_trim_datadev_extents(mp, start, end, minlen);
|
||||
if (error)
|
||||
last_error = error;
|
||||
}
|
||||
|
||||
if (rt_bdev && !xfs_trim_should_stop()) {
|
||||
error = xfs_trim_rtdev_extents(mp, start, end, minlen,
|
||||
&blocks_trimmed);
|
||||
error = xfs_trim_rtdev_extents(mp, start, end, minlen);
|
||||
if (error)
|
||||
last_error = error;
|
||||
}
|
||||
|
@ -722,7 +707,8 @@ xfs_ioc_trim(
|
|||
if (last_error)
|
||||
return last_error;
|
||||
|
||||
range.len = XFS_FSB_TO_B(mp, blocks_trimmed);
|
||||
range.len = min_t(unsigned long long, range.len,
|
||||
XFS_FSB_TO_B(mp, max_blocks));
|
||||
if (copy_to_user(urange, &range, sizeof(range)))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
|
|
|
@ -71,7 +71,7 @@ xfs_fsmap_owner_to_rmap(
|
|||
switch (src->fmr_owner) {
|
||||
case 0: /* "lowest owner id possible" */
|
||||
case -1ULL: /* "highest owner id possible" */
|
||||
dest->rm_owner = 0;
|
||||
dest->rm_owner = src->fmr_owner;
|
||||
break;
|
||||
case XFS_FMR_OWN_FREE:
|
||||
dest->rm_owner = XFS_RMAP_OWN_NULL;
|
||||
|
@ -162,6 +162,7 @@ struct xfs_getfsmap_info {
|
|||
xfs_daddr_t next_daddr; /* next daddr we expect */
|
||||
/* daddr of low fsmap key when we're using the rtbitmap */
|
||||
xfs_daddr_t low_daddr;
|
||||
xfs_daddr_t end_daddr; /* daddr of high fsmap key */
|
||||
u64 missing_owner; /* owner of holes */
|
||||
u32 dev; /* device id */
|
||||
/*
|
||||
|
@ -182,6 +183,7 @@ struct xfs_getfsmap_dev {
|
|||
int (*fn)(struct xfs_trans *tp,
|
||||
const struct xfs_fsmap *keys,
|
||||
struct xfs_getfsmap_info *info);
|
||||
sector_t nr_sectors;
|
||||
};
|
||||
|
||||
/* Compare two getfsmap device handlers. */
|
||||
|
@ -252,7 +254,7 @@ xfs_getfsmap_rec_before_start(
|
|||
const struct xfs_rmap_irec *rec,
|
||||
xfs_daddr_t rec_daddr)
|
||||
{
|
||||
if (info->low_daddr != -1ULL)
|
||||
if (info->low_daddr != XFS_BUF_DADDR_NULL)
|
||||
return rec_daddr < info->low_daddr;
|
||||
if (info->low.rm_blockcount)
|
||||
return xfs_rmap_compare(rec, &info->low) < 0;
|
||||
|
@ -294,6 +296,18 @@ xfs_getfsmap_helper(
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* For an info->last query, we're looking for a gap between the last
|
||||
* mapping emitted and the high key specified by userspace. If the
|
||||
* user's query spans less than 1 fsblock, then info->high and
|
||||
* info->low will have the same rm_startblock, which causes rec_daddr
|
||||
* and next_daddr to be the same. Therefore, use the end_daddr that
|
||||
* we calculated from userspace's high key to synthesize the record.
|
||||
* Note that if the btree query found a mapping, there won't be a gap.
|
||||
*/
|
||||
if (info->last && info->end_daddr != XFS_BUF_DADDR_NULL)
|
||||
rec_daddr = info->end_daddr;
|
||||
|
||||
/* Are we just counting mappings? */
|
||||
if (info->head->fmh_count == 0) {
|
||||
if (info->head->fmh_entries == UINT_MAX)
|
||||
|
@ -904,17 +918,21 @@ xfs_getfsmap(
|
|||
|
||||
/* Set up our device handlers. */
|
||||
memset(handlers, 0, sizeof(handlers));
|
||||
handlers[0].nr_sectors = XFS_FSB_TO_BB(mp, mp->m_sb.sb_dblocks);
|
||||
handlers[0].dev = new_encode_dev(mp->m_ddev_targp->bt_dev);
|
||||
if (use_rmap)
|
||||
handlers[0].fn = xfs_getfsmap_datadev_rmapbt;
|
||||
else
|
||||
handlers[0].fn = xfs_getfsmap_datadev_bnobt;
|
||||
if (mp->m_logdev_targp != mp->m_ddev_targp) {
|
||||
handlers[1].nr_sectors = XFS_FSB_TO_BB(mp,
|
||||
mp->m_sb.sb_logblocks);
|
||||
handlers[1].dev = new_encode_dev(mp->m_logdev_targp->bt_dev);
|
||||
handlers[1].fn = xfs_getfsmap_logdev;
|
||||
}
|
||||
#ifdef CONFIG_XFS_RT
|
||||
if (mp->m_rtdev_targp) {
|
||||
handlers[2].nr_sectors = XFS_FSB_TO_BB(mp, mp->m_sb.sb_rblocks);
|
||||
handlers[2].dev = new_encode_dev(mp->m_rtdev_targp->bt_dev);
|
||||
handlers[2].fn = xfs_getfsmap_rtdev_rtbitmap;
|
||||
}
|
||||
|
@ -946,6 +964,7 @@ xfs_getfsmap(
|
|||
|
||||
info.next_daddr = head->fmh_keys[0].fmr_physical +
|
||||
head->fmh_keys[0].fmr_length;
|
||||
info.end_daddr = XFS_BUF_DADDR_NULL;
|
||||
info.fsmap_recs = fsmap_recs;
|
||||
info.head = head;
|
||||
|
||||
|
@ -966,8 +985,11 @@ xfs_getfsmap(
|
|||
* low key, zero out the low key so that we get
|
||||
* everything from the beginning.
|
||||
*/
|
||||
if (handlers[i].dev == head->fmh_keys[1].fmr_device)
|
||||
if (handlers[i].dev == head->fmh_keys[1].fmr_device) {
|
||||
dkeys[1] = head->fmh_keys[1];
|
||||
info.end_daddr = min(handlers[i].nr_sectors - 1,
|
||||
dkeys[1].fmr_physical);
|
||||
}
|
||||
if (handlers[i].dev > head->fmh_keys[0].fmr_device)
|
||||
memset(&dkeys[0], 0, sizeof(struct xfs_fsmap));
|
||||
|
||||
|
@ -983,7 +1005,7 @@ xfs_getfsmap(
|
|||
info.dev = handlers[i].dev;
|
||||
info.last = false;
|
||||
info.pag = NULL;
|
||||
info.low_daddr = -1ULL;
|
||||
info.low_daddr = XFS_BUF_DADDR_NULL;
|
||||
info.low.rm_blockcount = 0;
|
||||
error = handlers[i].fn(tp, dkeys, &info);
|
||||
if (error)
|
||||
|
|
|
@ -784,6 +784,39 @@ xfs_alloc_rsum_cache(
|
|||
xfs_warn(mp, "could not allocate realtime summary cache");
|
||||
}
|
||||
|
||||
/*
|
||||
* If we changed the rt extent size (meaning there was no rt volume previously)
|
||||
* and the root directory had EXTSZINHERIT and RTINHERIT set, it's possible
|
||||
* that the extent size hint on the root directory is no longer congruent with
|
||||
* the new rt extent size. Log the rootdir inode to fix this.
|
||||
*/
|
||||
static int
|
||||
xfs_growfs_rt_fixup_extsize(
|
||||
struct xfs_mount *mp)
|
||||
{
|
||||
struct xfs_inode *ip = mp->m_rootip;
|
||||
struct xfs_trans *tp;
|
||||
int error = 0;
|
||||
|
||||
xfs_ilock(ip, XFS_IOLOCK_EXCL);
|
||||
if (!(ip->i_diflags & XFS_DIFLAG_RTINHERIT) ||
|
||||
!(ip->i_diflags & XFS_DIFLAG_EXTSZINHERIT))
|
||||
goto out_iolock;
|
||||
|
||||
error = xfs_trans_alloc_inode(ip, &M_RES(mp)->tr_ichange, 0, 0, false,
|
||||
&tp);
|
||||
if (error)
|
||||
goto out_iolock;
|
||||
|
||||
xfs_trans_log_inode(tp, ip, XFS_ILOG_CORE);
|
||||
error = xfs_trans_commit(tp);
|
||||
xfs_iunlock(ip, XFS_ILOCK_EXCL);
|
||||
|
||||
out_iolock:
|
||||
xfs_iunlock(ip, XFS_IOLOCK_EXCL);
|
||||
return error;
|
||||
}
|
||||
|
||||
/*
|
||||
* Visible (exported) functions.
|
||||
*/
|
||||
|
@ -812,6 +845,7 @@ xfs_growfs_rt(
|
|||
xfs_extlen_t rsumblocks; /* current number of rt summary blks */
|
||||
xfs_sb_t *sbp; /* old superblock */
|
||||
uint8_t *rsum_cache; /* old summary cache */
|
||||
xfs_agblock_t old_rextsize = mp->m_sb.sb_rextsize;
|
||||
|
||||
sbp = &mp->m_sb;
|
||||
|
||||
|
@ -821,34 +855,39 @@ xfs_growfs_rt(
|
|||
/* Needs to have been mounted with an rt device. */
|
||||
if (!XFS_IS_REALTIME_MOUNT(mp))
|
||||
return -EINVAL;
|
||||
|
||||
if (!mutex_trylock(&mp->m_growlock))
|
||||
return -EWOULDBLOCK;
|
||||
/*
|
||||
* Mount should fail if the rt bitmap/summary files don't load, but
|
||||
* we'll check anyway.
|
||||
*/
|
||||
error = -EINVAL;
|
||||
if (!mp->m_rbmip || !mp->m_rsumip)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
/* Shrink not supported. */
|
||||
if (in->newblocks <= sbp->sb_rblocks)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
/* Can only change rt extent size when adding rt volume. */
|
||||
if (sbp->sb_rblocks > 0 && in->extsize != sbp->sb_rextsize)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
/* Range check the extent size. */
|
||||
if (XFS_FSB_TO_B(mp, in->extsize) > XFS_MAX_RTEXTSIZE ||
|
||||
XFS_FSB_TO_B(mp, in->extsize) < XFS_MIN_RTEXTSIZE)
|
||||
return -EINVAL;
|
||||
goto out_unlock;
|
||||
|
||||
/* Unsupported realtime features. */
|
||||
error = -EOPNOTSUPP;
|
||||
if (xfs_has_rmapbt(mp) || xfs_has_reflink(mp) || xfs_has_quota(mp))
|
||||
return -EOPNOTSUPP;
|
||||
goto out_unlock;
|
||||
|
||||
nrblocks = in->newblocks;
|
||||
error = xfs_sb_validate_fsb_count(sbp, nrblocks);
|
||||
if (error)
|
||||
return error;
|
||||
goto out_unlock;
|
||||
/*
|
||||
* Read in the last block of the device, make sure it exists.
|
||||
*/
|
||||
|
@ -856,7 +895,7 @@ xfs_growfs_rt(
|
|||
XFS_FSB_TO_BB(mp, nrblocks - 1),
|
||||
XFS_FSB_TO_BB(mp, 1), 0, &bp, NULL);
|
||||
if (error)
|
||||
return error;
|
||||
goto out_unlock;
|
||||
xfs_buf_relse(bp);
|
||||
|
||||
/*
|
||||
|
@ -864,8 +903,10 @@ xfs_growfs_rt(
|
|||
*/
|
||||
nrextents = nrblocks;
|
||||
do_div(nrextents, in->extsize);
|
||||
if (!xfs_validate_rtextents(nrextents))
|
||||
return -EINVAL;
|
||||
if (!xfs_validate_rtextents(nrextents)) {
|
||||
error = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
nrbmblocks = xfs_rtbitmap_blockcount(mp, nrextents);
|
||||
nrextslog = xfs_compute_rextslog(nrextents);
|
||||
nrsumlevels = nrextslog + 1;
|
||||
|
@ -876,8 +917,11 @@ xfs_growfs_rt(
|
|||
* the log. This prevents us from getting a log overflow,
|
||||
* since we'll log basically the whole summary file at once.
|
||||
*/
|
||||
if (nrsumblocks > (mp->m_sb.sb_logblocks >> 1))
|
||||
return -EINVAL;
|
||||
if (nrsumblocks > (mp->m_sb.sb_logblocks >> 1)) {
|
||||
error = -EINVAL;
|
||||
goto out_unlock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the old block counts for bitmap and summary inodes.
|
||||
* These can't change since other growfs callers are locked out.
|
||||
|
@ -889,10 +933,10 @@ xfs_growfs_rt(
|
|||
*/
|
||||
error = xfs_growfs_rt_alloc(mp, rbmblocks, nrbmblocks, mp->m_rbmip);
|
||||
if (error)
|
||||
return error;
|
||||
goto out_unlock;
|
||||
error = xfs_growfs_rt_alloc(mp, rsumblocks, nrsumblocks, mp->m_rsumip);
|
||||
if (error)
|
||||
return error;
|
||||
goto out_unlock;
|
||||
|
||||
rsum_cache = mp->m_rsum_cache;
|
||||
if (nrbmblocks != sbp->sb_rbmblocks)
|
||||
|
@ -1036,6 +1080,12 @@ xfs_growfs_rt(
|
|||
if (error)
|
||||
goto out_free;
|
||||
|
||||
if (old_rextsize != in->extsize) {
|
||||
error = xfs_growfs_rt_fixup_extsize(mp);
|
||||
if (error)
|
||||
goto out_free;
|
||||
}
|
||||
|
||||
/* Update secondary superblocks now the physical grow has completed */
|
||||
error = xfs_update_secondary_sbs(mp);
|
||||
|
||||
|
@ -1059,6 +1109,8 @@ xfs_growfs_rt(
|
|||
}
|
||||
}
|
||||
|
||||
out_unlock:
|
||||
mutex_unlock(&mp->m_growlock);
|
||||
return error;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue