1*4882a593Smuzhiyun // SPDX-License-Identifier: GPL-2.0
2*4882a593Smuzhiyun /*
3*4882a593Smuzhiyun * Copyright (c) 2000-2001,2005 Silicon Graphics, Inc.
4*4882a593Smuzhiyun * All Rights Reserved.
5*4882a593Smuzhiyun */
6*4882a593Smuzhiyun #include "xfs.h"
7*4882a593Smuzhiyun #include "xfs_fs.h"
8*4882a593Smuzhiyun #include "xfs_shared.h"
9*4882a593Smuzhiyun #include "xfs_format.h"
10*4882a593Smuzhiyun #include "xfs_log_format.h"
11*4882a593Smuzhiyun #include "xfs_trans_resv.h"
12*4882a593Smuzhiyun #include "xfs_mount.h"
13*4882a593Smuzhiyun #include "xfs_inode.h"
14*4882a593Smuzhiyun #include "xfs_trans.h"
15*4882a593Smuzhiyun #include "xfs_bmap.h"
16*4882a593Smuzhiyun #include "xfs_dir2.h"
17*4882a593Smuzhiyun #include "xfs_dir2_priv.h"
18*4882a593Smuzhiyun #include "xfs_errortag.h"
19*4882a593Smuzhiyun #include "xfs_error.h"
20*4882a593Smuzhiyun #include "xfs_trace.h"
21*4882a593Smuzhiyun
22*4882a593Smuzhiyun struct xfs_name xfs_name_dotdot = { (unsigned char *)"..", 2, XFS_DIR3_FT_DIR };
23*4882a593Smuzhiyun
24*4882a593Smuzhiyun /*
25*4882a593Smuzhiyun * Convert inode mode to directory entry filetype
26*4882a593Smuzhiyun */
27*4882a593Smuzhiyun unsigned char
xfs_mode_to_ftype(int mode)28*4882a593Smuzhiyun xfs_mode_to_ftype(
29*4882a593Smuzhiyun int mode)
30*4882a593Smuzhiyun {
31*4882a593Smuzhiyun switch (mode & S_IFMT) {
32*4882a593Smuzhiyun case S_IFREG:
33*4882a593Smuzhiyun return XFS_DIR3_FT_REG_FILE;
34*4882a593Smuzhiyun case S_IFDIR:
35*4882a593Smuzhiyun return XFS_DIR3_FT_DIR;
36*4882a593Smuzhiyun case S_IFCHR:
37*4882a593Smuzhiyun return XFS_DIR3_FT_CHRDEV;
38*4882a593Smuzhiyun case S_IFBLK:
39*4882a593Smuzhiyun return XFS_DIR3_FT_BLKDEV;
40*4882a593Smuzhiyun case S_IFIFO:
41*4882a593Smuzhiyun return XFS_DIR3_FT_FIFO;
42*4882a593Smuzhiyun case S_IFSOCK:
43*4882a593Smuzhiyun return XFS_DIR3_FT_SOCK;
44*4882a593Smuzhiyun case S_IFLNK:
45*4882a593Smuzhiyun return XFS_DIR3_FT_SYMLINK;
46*4882a593Smuzhiyun default:
47*4882a593Smuzhiyun return XFS_DIR3_FT_UNKNOWN;
48*4882a593Smuzhiyun }
49*4882a593Smuzhiyun }
50*4882a593Smuzhiyun
51*4882a593Smuzhiyun /*
52*4882a593Smuzhiyun * ASCII case-insensitive (ie. A-Z) support for directories that was
53*4882a593Smuzhiyun * used in IRIX.
54*4882a593Smuzhiyun */
55*4882a593Smuzhiyun xfs_dahash_t
xfs_ascii_ci_hashname(struct xfs_name * name)56*4882a593Smuzhiyun xfs_ascii_ci_hashname(
57*4882a593Smuzhiyun struct xfs_name *name)
58*4882a593Smuzhiyun {
59*4882a593Smuzhiyun xfs_dahash_t hash;
60*4882a593Smuzhiyun int i;
61*4882a593Smuzhiyun
62*4882a593Smuzhiyun for (i = 0, hash = 0; i < name->len; i++)
63*4882a593Smuzhiyun hash = tolower(name->name[i]) ^ rol32(hash, 7);
64*4882a593Smuzhiyun
65*4882a593Smuzhiyun return hash;
66*4882a593Smuzhiyun }
67*4882a593Smuzhiyun
68*4882a593Smuzhiyun enum xfs_dacmp
xfs_ascii_ci_compname(struct xfs_da_args * args,const unsigned char * name,int len)69*4882a593Smuzhiyun xfs_ascii_ci_compname(
70*4882a593Smuzhiyun struct xfs_da_args *args,
71*4882a593Smuzhiyun const unsigned char *name,
72*4882a593Smuzhiyun int len)
73*4882a593Smuzhiyun {
74*4882a593Smuzhiyun enum xfs_dacmp result;
75*4882a593Smuzhiyun int i;
76*4882a593Smuzhiyun
77*4882a593Smuzhiyun if (args->namelen != len)
78*4882a593Smuzhiyun return XFS_CMP_DIFFERENT;
79*4882a593Smuzhiyun
80*4882a593Smuzhiyun result = XFS_CMP_EXACT;
81*4882a593Smuzhiyun for (i = 0; i < len; i++) {
82*4882a593Smuzhiyun if (args->name[i] == name[i])
83*4882a593Smuzhiyun continue;
84*4882a593Smuzhiyun if (tolower(args->name[i]) != tolower(name[i]))
85*4882a593Smuzhiyun return XFS_CMP_DIFFERENT;
86*4882a593Smuzhiyun result = XFS_CMP_CASE;
87*4882a593Smuzhiyun }
88*4882a593Smuzhiyun
89*4882a593Smuzhiyun return result;
90*4882a593Smuzhiyun }
91*4882a593Smuzhiyun
92*4882a593Smuzhiyun int
xfs_da_mount(struct xfs_mount * mp)93*4882a593Smuzhiyun xfs_da_mount(
94*4882a593Smuzhiyun struct xfs_mount *mp)
95*4882a593Smuzhiyun {
96*4882a593Smuzhiyun struct xfs_da_geometry *dageo;
97*4882a593Smuzhiyun
98*4882a593Smuzhiyun
99*4882a593Smuzhiyun ASSERT(mp->m_sb.sb_versionnum & XFS_SB_VERSION_DIRV2BIT);
100*4882a593Smuzhiyun ASSERT(xfs_dir2_dirblock_bytes(&mp->m_sb) <= XFS_MAX_BLOCKSIZE);
101*4882a593Smuzhiyun
102*4882a593Smuzhiyun mp->m_dir_geo = kmem_zalloc(sizeof(struct xfs_da_geometry),
103*4882a593Smuzhiyun KM_MAYFAIL);
104*4882a593Smuzhiyun mp->m_attr_geo = kmem_zalloc(sizeof(struct xfs_da_geometry),
105*4882a593Smuzhiyun KM_MAYFAIL);
106*4882a593Smuzhiyun if (!mp->m_dir_geo || !mp->m_attr_geo) {
107*4882a593Smuzhiyun kmem_free(mp->m_dir_geo);
108*4882a593Smuzhiyun kmem_free(mp->m_attr_geo);
109*4882a593Smuzhiyun return -ENOMEM;
110*4882a593Smuzhiyun }
111*4882a593Smuzhiyun
112*4882a593Smuzhiyun /* set up directory geometry */
113*4882a593Smuzhiyun dageo = mp->m_dir_geo;
114*4882a593Smuzhiyun dageo->blklog = mp->m_sb.sb_blocklog + mp->m_sb.sb_dirblklog;
115*4882a593Smuzhiyun dageo->fsblog = mp->m_sb.sb_blocklog;
116*4882a593Smuzhiyun dageo->blksize = xfs_dir2_dirblock_bytes(&mp->m_sb);
117*4882a593Smuzhiyun dageo->fsbcount = 1 << mp->m_sb.sb_dirblklog;
118*4882a593Smuzhiyun if (xfs_sb_version_hascrc(&mp->m_sb)) {
119*4882a593Smuzhiyun dageo->node_hdr_size = sizeof(struct xfs_da3_node_hdr);
120*4882a593Smuzhiyun dageo->leaf_hdr_size = sizeof(struct xfs_dir3_leaf_hdr);
121*4882a593Smuzhiyun dageo->free_hdr_size = sizeof(struct xfs_dir3_free_hdr);
122*4882a593Smuzhiyun dageo->data_entry_offset =
123*4882a593Smuzhiyun sizeof(struct xfs_dir3_data_hdr);
124*4882a593Smuzhiyun } else {
125*4882a593Smuzhiyun dageo->node_hdr_size = sizeof(struct xfs_da_node_hdr);
126*4882a593Smuzhiyun dageo->leaf_hdr_size = sizeof(struct xfs_dir2_leaf_hdr);
127*4882a593Smuzhiyun dageo->free_hdr_size = sizeof(struct xfs_dir2_free_hdr);
128*4882a593Smuzhiyun dageo->data_entry_offset =
129*4882a593Smuzhiyun sizeof(struct xfs_dir2_data_hdr);
130*4882a593Smuzhiyun }
131*4882a593Smuzhiyun dageo->leaf_max_ents = (dageo->blksize - dageo->leaf_hdr_size) /
132*4882a593Smuzhiyun sizeof(struct xfs_dir2_leaf_entry);
133*4882a593Smuzhiyun dageo->free_max_bests = (dageo->blksize - dageo->free_hdr_size) /
134*4882a593Smuzhiyun sizeof(xfs_dir2_data_off_t);
135*4882a593Smuzhiyun
136*4882a593Smuzhiyun dageo->data_first_offset = dageo->data_entry_offset +
137*4882a593Smuzhiyun xfs_dir2_data_entsize(mp, 1) +
138*4882a593Smuzhiyun xfs_dir2_data_entsize(mp, 2);
139*4882a593Smuzhiyun
140*4882a593Smuzhiyun /*
141*4882a593Smuzhiyun * Now we've set up the block conversion variables, we can calculate the
142*4882a593Smuzhiyun * segment block constants using the geometry structure.
143*4882a593Smuzhiyun */
144*4882a593Smuzhiyun dageo->datablk = xfs_dir2_byte_to_da(dageo, XFS_DIR2_DATA_OFFSET);
145*4882a593Smuzhiyun dageo->leafblk = xfs_dir2_byte_to_da(dageo, XFS_DIR2_LEAF_OFFSET);
146*4882a593Smuzhiyun dageo->freeblk = xfs_dir2_byte_to_da(dageo, XFS_DIR2_FREE_OFFSET);
147*4882a593Smuzhiyun dageo->node_ents = (dageo->blksize - dageo->node_hdr_size) /
148*4882a593Smuzhiyun (uint)sizeof(xfs_da_node_entry_t);
149*4882a593Smuzhiyun dageo->magicpct = (dageo->blksize * 37) / 100;
150*4882a593Smuzhiyun
151*4882a593Smuzhiyun /* set up attribute geometry - single fsb only */
152*4882a593Smuzhiyun dageo = mp->m_attr_geo;
153*4882a593Smuzhiyun dageo->blklog = mp->m_sb.sb_blocklog;
154*4882a593Smuzhiyun dageo->fsblog = mp->m_sb.sb_blocklog;
155*4882a593Smuzhiyun dageo->blksize = 1 << dageo->blklog;
156*4882a593Smuzhiyun dageo->fsbcount = 1;
157*4882a593Smuzhiyun dageo->node_hdr_size = mp->m_dir_geo->node_hdr_size;
158*4882a593Smuzhiyun dageo->node_ents = (dageo->blksize - dageo->node_hdr_size) /
159*4882a593Smuzhiyun (uint)sizeof(xfs_da_node_entry_t);
160*4882a593Smuzhiyun dageo->magicpct = (dageo->blksize * 37) / 100;
161*4882a593Smuzhiyun return 0;
162*4882a593Smuzhiyun }
163*4882a593Smuzhiyun
164*4882a593Smuzhiyun void
xfs_da_unmount(struct xfs_mount * mp)165*4882a593Smuzhiyun xfs_da_unmount(
166*4882a593Smuzhiyun struct xfs_mount *mp)
167*4882a593Smuzhiyun {
168*4882a593Smuzhiyun kmem_free(mp->m_dir_geo);
169*4882a593Smuzhiyun kmem_free(mp->m_attr_geo);
170*4882a593Smuzhiyun }
171*4882a593Smuzhiyun
172*4882a593Smuzhiyun /*
173*4882a593Smuzhiyun * Return 1 if directory contains only "." and "..".
174*4882a593Smuzhiyun */
175*4882a593Smuzhiyun int
xfs_dir_isempty(xfs_inode_t * dp)176*4882a593Smuzhiyun xfs_dir_isempty(
177*4882a593Smuzhiyun xfs_inode_t *dp)
178*4882a593Smuzhiyun {
179*4882a593Smuzhiyun xfs_dir2_sf_hdr_t *sfp;
180*4882a593Smuzhiyun
181*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
182*4882a593Smuzhiyun if (dp->i_d.di_size == 0) /* might happen during shutdown. */
183*4882a593Smuzhiyun return 1;
184*4882a593Smuzhiyun if (dp->i_d.di_size > XFS_IFORK_DSIZE(dp))
185*4882a593Smuzhiyun return 0;
186*4882a593Smuzhiyun sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
187*4882a593Smuzhiyun return !sfp->count;
188*4882a593Smuzhiyun }
189*4882a593Smuzhiyun
190*4882a593Smuzhiyun /*
191*4882a593Smuzhiyun * Validate a given inode number.
192*4882a593Smuzhiyun */
193*4882a593Smuzhiyun int
xfs_dir_ino_validate(xfs_mount_t * mp,xfs_ino_t ino)194*4882a593Smuzhiyun xfs_dir_ino_validate(
195*4882a593Smuzhiyun xfs_mount_t *mp,
196*4882a593Smuzhiyun xfs_ino_t ino)
197*4882a593Smuzhiyun {
198*4882a593Smuzhiyun bool ino_ok = xfs_verify_dir_ino(mp, ino);
199*4882a593Smuzhiyun
200*4882a593Smuzhiyun if (XFS_IS_CORRUPT(mp, !ino_ok) ||
201*4882a593Smuzhiyun XFS_TEST_ERROR(false, mp, XFS_ERRTAG_DIR_INO_VALIDATE)) {
202*4882a593Smuzhiyun xfs_warn(mp, "Invalid inode number 0x%Lx",
203*4882a593Smuzhiyun (unsigned long long) ino);
204*4882a593Smuzhiyun return -EFSCORRUPTED;
205*4882a593Smuzhiyun }
206*4882a593Smuzhiyun return 0;
207*4882a593Smuzhiyun }
208*4882a593Smuzhiyun
209*4882a593Smuzhiyun /*
210*4882a593Smuzhiyun * Initialize a directory with its "." and ".." entries.
211*4882a593Smuzhiyun */
212*4882a593Smuzhiyun int
xfs_dir_init(xfs_trans_t * tp,xfs_inode_t * dp,xfs_inode_t * pdp)213*4882a593Smuzhiyun xfs_dir_init(
214*4882a593Smuzhiyun xfs_trans_t *tp,
215*4882a593Smuzhiyun xfs_inode_t *dp,
216*4882a593Smuzhiyun xfs_inode_t *pdp)
217*4882a593Smuzhiyun {
218*4882a593Smuzhiyun struct xfs_da_args *args;
219*4882a593Smuzhiyun int error;
220*4882a593Smuzhiyun
221*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
222*4882a593Smuzhiyun error = xfs_dir_ino_validate(tp->t_mountp, pdp->i_ino);
223*4882a593Smuzhiyun if (error)
224*4882a593Smuzhiyun return error;
225*4882a593Smuzhiyun
226*4882a593Smuzhiyun args = kmem_zalloc(sizeof(*args), KM_NOFS);
227*4882a593Smuzhiyun if (!args)
228*4882a593Smuzhiyun return -ENOMEM;
229*4882a593Smuzhiyun
230*4882a593Smuzhiyun args->geo = dp->i_mount->m_dir_geo;
231*4882a593Smuzhiyun args->dp = dp;
232*4882a593Smuzhiyun args->trans = tp;
233*4882a593Smuzhiyun error = xfs_dir2_sf_create(args, pdp->i_ino);
234*4882a593Smuzhiyun kmem_free(args);
235*4882a593Smuzhiyun return error;
236*4882a593Smuzhiyun }
237*4882a593Smuzhiyun
238*4882a593Smuzhiyun /*
239*4882a593Smuzhiyun * Enter a name in a directory, or check for available space.
240*4882a593Smuzhiyun * If inum is 0, only the available space test is performed.
241*4882a593Smuzhiyun */
242*4882a593Smuzhiyun int
xfs_dir_createname(struct xfs_trans * tp,struct xfs_inode * dp,struct xfs_name * name,xfs_ino_t inum,xfs_extlen_t total)243*4882a593Smuzhiyun xfs_dir_createname(
244*4882a593Smuzhiyun struct xfs_trans *tp,
245*4882a593Smuzhiyun struct xfs_inode *dp,
246*4882a593Smuzhiyun struct xfs_name *name,
247*4882a593Smuzhiyun xfs_ino_t inum, /* new entry inode number */
248*4882a593Smuzhiyun xfs_extlen_t total) /* bmap's total block count */
249*4882a593Smuzhiyun {
250*4882a593Smuzhiyun struct xfs_da_args *args;
251*4882a593Smuzhiyun int rval;
252*4882a593Smuzhiyun int v; /* type-checking value */
253*4882a593Smuzhiyun
254*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
255*4882a593Smuzhiyun
256*4882a593Smuzhiyun if (inum) {
257*4882a593Smuzhiyun rval = xfs_dir_ino_validate(tp->t_mountp, inum);
258*4882a593Smuzhiyun if (rval)
259*4882a593Smuzhiyun return rval;
260*4882a593Smuzhiyun XFS_STATS_INC(dp->i_mount, xs_dir_create);
261*4882a593Smuzhiyun }
262*4882a593Smuzhiyun
263*4882a593Smuzhiyun args = kmem_zalloc(sizeof(*args), KM_NOFS);
264*4882a593Smuzhiyun if (!args)
265*4882a593Smuzhiyun return -ENOMEM;
266*4882a593Smuzhiyun
267*4882a593Smuzhiyun args->geo = dp->i_mount->m_dir_geo;
268*4882a593Smuzhiyun args->name = name->name;
269*4882a593Smuzhiyun args->namelen = name->len;
270*4882a593Smuzhiyun args->filetype = name->type;
271*4882a593Smuzhiyun args->hashval = xfs_dir2_hashname(dp->i_mount, name);
272*4882a593Smuzhiyun args->inumber = inum;
273*4882a593Smuzhiyun args->dp = dp;
274*4882a593Smuzhiyun args->total = total;
275*4882a593Smuzhiyun args->whichfork = XFS_DATA_FORK;
276*4882a593Smuzhiyun args->trans = tp;
277*4882a593Smuzhiyun args->op_flags = XFS_DA_OP_ADDNAME | XFS_DA_OP_OKNOENT;
278*4882a593Smuzhiyun if (!inum)
279*4882a593Smuzhiyun args->op_flags |= XFS_DA_OP_JUSTCHECK;
280*4882a593Smuzhiyun
281*4882a593Smuzhiyun if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
282*4882a593Smuzhiyun rval = xfs_dir2_sf_addname(args);
283*4882a593Smuzhiyun goto out_free;
284*4882a593Smuzhiyun }
285*4882a593Smuzhiyun
286*4882a593Smuzhiyun rval = xfs_dir2_isblock(args, &v);
287*4882a593Smuzhiyun if (rval)
288*4882a593Smuzhiyun goto out_free;
289*4882a593Smuzhiyun if (v) {
290*4882a593Smuzhiyun rval = xfs_dir2_block_addname(args);
291*4882a593Smuzhiyun goto out_free;
292*4882a593Smuzhiyun }
293*4882a593Smuzhiyun
294*4882a593Smuzhiyun rval = xfs_dir2_isleaf(args, &v);
295*4882a593Smuzhiyun if (rval)
296*4882a593Smuzhiyun goto out_free;
297*4882a593Smuzhiyun if (v)
298*4882a593Smuzhiyun rval = xfs_dir2_leaf_addname(args);
299*4882a593Smuzhiyun else
300*4882a593Smuzhiyun rval = xfs_dir2_node_addname(args);
301*4882a593Smuzhiyun
302*4882a593Smuzhiyun out_free:
303*4882a593Smuzhiyun kmem_free(args);
304*4882a593Smuzhiyun return rval;
305*4882a593Smuzhiyun }
306*4882a593Smuzhiyun
307*4882a593Smuzhiyun /*
308*4882a593Smuzhiyun * If doing a CI lookup and case-insensitive match, dup actual name into
309*4882a593Smuzhiyun * args.value. Return EEXIST for success (ie. name found) or an error.
310*4882a593Smuzhiyun */
311*4882a593Smuzhiyun int
xfs_dir_cilookup_result(struct xfs_da_args * args,const unsigned char * name,int len)312*4882a593Smuzhiyun xfs_dir_cilookup_result(
313*4882a593Smuzhiyun struct xfs_da_args *args,
314*4882a593Smuzhiyun const unsigned char *name,
315*4882a593Smuzhiyun int len)
316*4882a593Smuzhiyun {
317*4882a593Smuzhiyun if (args->cmpresult == XFS_CMP_DIFFERENT)
318*4882a593Smuzhiyun return -ENOENT;
319*4882a593Smuzhiyun if (args->cmpresult != XFS_CMP_CASE ||
320*4882a593Smuzhiyun !(args->op_flags & XFS_DA_OP_CILOOKUP))
321*4882a593Smuzhiyun return -EEXIST;
322*4882a593Smuzhiyun
323*4882a593Smuzhiyun args->value = kmem_alloc(len, KM_NOFS | KM_MAYFAIL);
324*4882a593Smuzhiyun if (!args->value)
325*4882a593Smuzhiyun return -ENOMEM;
326*4882a593Smuzhiyun
327*4882a593Smuzhiyun memcpy(args->value, name, len);
328*4882a593Smuzhiyun args->valuelen = len;
329*4882a593Smuzhiyun return -EEXIST;
330*4882a593Smuzhiyun }
331*4882a593Smuzhiyun
332*4882a593Smuzhiyun /*
333*4882a593Smuzhiyun * Lookup a name in a directory, give back the inode number.
334*4882a593Smuzhiyun * If ci_name is not NULL, returns the actual name in ci_name if it differs
335*4882a593Smuzhiyun * to name, or ci_name->name is set to NULL for an exact match.
336*4882a593Smuzhiyun */
337*4882a593Smuzhiyun
338*4882a593Smuzhiyun int
xfs_dir_lookup(xfs_trans_t * tp,xfs_inode_t * dp,struct xfs_name * name,xfs_ino_t * inum,struct xfs_name * ci_name)339*4882a593Smuzhiyun xfs_dir_lookup(
340*4882a593Smuzhiyun xfs_trans_t *tp,
341*4882a593Smuzhiyun xfs_inode_t *dp,
342*4882a593Smuzhiyun struct xfs_name *name,
343*4882a593Smuzhiyun xfs_ino_t *inum, /* out: inode number */
344*4882a593Smuzhiyun struct xfs_name *ci_name) /* out: actual name if CI match */
345*4882a593Smuzhiyun {
346*4882a593Smuzhiyun struct xfs_da_args *args;
347*4882a593Smuzhiyun int rval;
348*4882a593Smuzhiyun int v; /* type-checking value */
349*4882a593Smuzhiyun int lock_mode;
350*4882a593Smuzhiyun
351*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
352*4882a593Smuzhiyun XFS_STATS_INC(dp->i_mount, xs_dir_lookup);
353*4882a593Smuzhiyun
354*4882a593Smuzhiyun /*
355*4882a593Smuzhiyun * We need to use KM_NOFS here so that lockdep will not throw false
356*4882a593Smuzhiyun * positive deadlock warnings on a non-transactional lookup path. It is
357*4882a593Smuzhiyun * safe to recurse into inode recalim in that case, but lockdep can't
358*4882a593Smuzhiyun * easily be taught about it. Hence KM_NOFS avoids having to add more
359*4882a593Smuzhiyun * lockdep Doing this avoids having to add a bunch of lockdep class
360*4882a593Smuzhiyun * annotations into the reclaim path for the ilock.
361*4882a593Smuzhiyun */
362*4882a593Smuzhiyun args = kmem_zalloc(sizeof(*args), KM_NOFS);
363*4882a593Smuzhiyun args->geo = dp->i_mount->m_dir_geo;
364*4882a593Smuzhiyun args->name = name->name;
365*4882a593Smuzhiyun args->namelen = name->len;
366*4882a593Smuzhiyun args->filetype = name->type;
367*4882a593Smuzhiyun args->hashval = xfs_dir2_hashname(dp->i_mount, name);
368*4882a593Smuzhiyun args->dp = dp;
369*4882a593Smuzhiyun args->whichfork = XFS_DATA_FORK;
370*4882a593Smuzhiyun args->trans = tp;
371*4882a593Smuzhiyun args->op_flags = XFS_DA_OP_OKNOENT;
372*4882a593Smuzhiyun if (ci_name)
373*4882a593Smuzhiyun args->op_flags |= XFS_DA_OP_CILOOKUP;
374*4882a593Smuzhiyun
375*4882a593Smuzhiyun lock_mode = xfs_ilock_data_map_shared(dp);
376*4882a593Smuzhiyun if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
377*4882a593Smuzhiyun rval = xfs_dir2_sf_lookup(args);
378*4882a593Smuzhiyun goto out_check_rval;
379*4882a593Smuzhiyun }
380*4882a593Smuzhiyun
381*4882a593Smuzhiyun rval = xfs_dir2_isblock(args, &v);
382*4882a593Smuzhiyun if (rval)
383*4882a593Smuzhiyun goto out_free;
384*4882a593Smuzhiyun if (v) {
385*4882a593Smuzhiyun rval = xfs_dir2_block_lookup(args);
386*4882a593Smuzhiyun goto out_check_rval;
387*4882a593Smuzhiyun }
388*4882a593Smuzhiyun
389*4882a593Smuzhiyun rval = xfs_dir2_isleaf(args, &v);
390*4882a593Smuzhiyun if (rval)
391*4882a593Smuzhiyun goto out_free;
392*4882a593Smuzhiyun if (v)
393*4882a593Smuzhiyun rval = xfs_dir2_leaf_lookup(args);
394*4882a593Smuzhiyun else
395*4882a593Smuzhiyun rval = xfs_dir2_node_lookup(args);
396*4882a593Smuzhiyun
397*4882a593Smuzhiyun out_check_rval:
398*4882a593Smuzhiyun if (rval == -EEXIST)
399*4882a593Smuzhiyun rval = 0;
400*4882a593Smuzhiyun if (!rval) {
401*4882a593Smuzhiyun *inum = args->inumber;
402*4882a593Smuzhiyun if (ci_name) {
403*4882a593Smuzhiyun ci_name->name = args->value;
404*4882a593Smuzhiyun ci_name->len = args->valuelen;
405*4882a593Smuzhiyun }
406*4882a593Smuzhiyun }
407*4882a593Smuzhiyun out_free:
408*4882a593Smuzhiyun xfs_iunlock(dp, lock_mode);
409*4882a593Smuzhiyun kmem_free(args);
410*4882a593Smuzhiyun return rval;
411*4882a593Smuzhiyun }
412*4882a593Smuzhiyun
413*4882a593Smuzhiyun /*
414*4882a593Smuzhiyun * Remove an entry from a directory.
415*4882a593Smuzhiyun */
416*4882a593Smuzhiyun int
xfs_dir_removename(struct xfs_trans * tp,struct xfs_inode * dp,struct xfs_name * name,xfs_ino_t ino,xfs_extlen_t total)417*4882a593Smuzhiyun xfs_dir_removename(
418*4882a593Smuzhiyun struct xfs_trans *tp,
419*4882a593Smuzhiyun struct xfs_inode *dp,
420*4882a593Smuzhiyun struct xfs_name *name,
421*4882a593Smuzhiyun xfs_ino_t ino,
422*4882a593Smuzhiyun xfs_extlen_t total) /* bmap's total block count */
423*4882a593Smuzhiyun {
424*4882a593Smuzhiyun struct xfs_da_args *args;
425*4882a593Smuzhiyun int rval;
426*4882a593Smuzhiyun int v; /* type-checking value */
427*4882a593Smuzhiyun
428*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
429*4882a593Smuzhiyun XFS_STATS_INC(dp->i_mount, xs_dir_remove);
430*4882a593Smuzhiyun
431*4882a593Smuzhiyun args = kmem_zalloc(sizeof(*args), KM_NOFS);
432*4882a593Smuzhiyun if (!args)
433*4882a593Smuzhiyun return -ENOMEM;
434*4882a593Smuzhiyun
435*4882a593Smuzhiyun args->geo = dp->i_mount->m_dir_geo;
436*4882a593Smuzhiyun args->name = name->name;
437*4882a593Smuzhiyun args->namelen = name->len;
438*4882a593Smuzhiyun args->filetype = name->type;
439*4882a593Smuzhiyun args->hashval = xfs_dir2_hashname(dp->i_mount, name);
440*4882a593Smuzhiyun args->inumber = ino;
441*4882a593Smuzhiyun args->dp = dp;
442*4882a593Smuzhiyun args->total = total;
443*4882a593Smuzhiyun args->whichfork = XFS_DATA_FORK;
444*4882a593Smuzhiyun args->trans = tp;
445*4882a593Smuzhiyun
446*4882a593Smuzhiyun if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
447*4882a593Smuzhiyun rval = xfs_dir2_sf_removename(args);
448*4882a593Smuzhiyun goto out_free;
449*4882a593Smuzhiyun }
450*4882a593Smuzhiyun
451*4882a593Smuzhiyun rval = xfs_dir2_isblock(args, &v);
452*4882a593Smuzhiyun if (rval)
453*4882a593Smuzhiyun goto out_free;
454*4882a593Smuzhiyun if (v) {
455*4882a593Smuzhiyun rval = xfs_dir2_block_removename(args);
456*4882a593Smuzhiyun goto out_free;
457*4882a593Smuzhiyun }
458*4882a593Smuzhiyun
459*4882a593Smuzhiyun rval = xfs_dir2_isleaf(args, &v);
460*4882a593Smuzhiyun if (rval)
461*4882a593Smuzhiyun goto out_free;
462*4882a593Smuzhiyun if (v)
463*4882a593Smuzhiyun rval = xfs_dir2_leaf_removename(args);
464*4882a593Smuzhiyun else
465*4882a593Smuzhiyun rval = xfs_dir2_node_removename(args);
466*4882a593Smuzhiyun out_free:
467*4882a593Smuzhiyun kmem_free(args);
468*4882a593Smuzhiyun return rval;
469*4882a593Smuzhiyun }
470*4882a593Smuzhiyun
471*4882a593Smuzhiyun /*
472*4882a593Smuzhiyun * Replace the inode number of a directory entry.
473*4882a593Smuzhiyun */
474*4882a593Smuzhiyun int
xfs_dir_replace(struct xfs_trans * tp,struct xfs_inode * dp,struct xfs_name * name,xfs_ino_t inum,xfs_extlen_t total)475*4882a593Smuzhiyun xfs_dir_replace(
476*4882a593Smuzhiyun struct xfs_trans *tp,
477*4882a593Smuzhiyun struct xfs_inode *dp,
478*4882a593Smuzhiyun struct xfs_name *name, /* name of entry to replace */
479*4882a593Smuzhiyun xfs_ino_t inum, /* new inode number */
480*4882a593Smuzhiyun xfs_extlen_t total) /* bmap's total block count */
481*4882a593Smuzhiyun {
482*4882a593Smuzhiyun struct xfs_da_args *args;
483*4882a593Smuzhiyun int rval;
484*4882a593Smuzhiyun int v; /* type-checking value */
485*4882a593Smuzhiyun
486*4882a593Smuzhiyun ASSERT(S_ISDIR(VFS_I(dp)->i_mode));
487*4882a593Smuzhiyun
488*4882a593Smuzhiyun rval = xfs_dir_ino_validate(tp->t_mountp, inum);
489*4882a593Smuzhiyun if (rval)
490*4882a593Smuzhiyun return rval;
491*4882a593Smuzhiyun
492*4882a593Smuzhiyun args = kmem_zalloc(sizeof(*args), KM_NOFS);
493*4882a593Smuzhiyun if (!args)
494*4882a593Smuzhiyun return -ENOMEM;
495*4882a593Smuzhiyun
496*4882a593Smuzhiyun args->geo = dp->i_mount->m_dir_geo;
497*4882a593Smuzhiyun args->name = name->name;
498*4882a593Smuzhiyun args->namelen = name->len;
499*4882a593Smuzhiyun args->filetype = name->type;
500*4882a593Smuzhiyun args->hashval = xfs_dir2_hashname(dp->i_mount, name);
501*4882a593Smuzhiyun args->inumber = inum;
502*4882a593Smuzhiyun args->dp = dp;
503*4882a593Smuzhiyun args->total = total;
504*4882a593Smuzhiyun args->whichfork = XFS_DATA_FORK;
505*4882a593Smuzhiyun args->trans = tp;
506*4882a593Smuzhiyun
507*4882a593Smuzhiyun if (dp->i_df.if_format == XFS_DINODE_FMT_LOCAL) {
508*4882a593Smuzhiyun rval = xfs_dir2_sf_replace(args);
509*4882a593Smuzhiyun goto out_free;
510*4882a593Smuzhiyun }
511*4882a593Smuzhiyun
512*4882a593Smuzhiyun rval = xfs_dir2_isblock(args, &v);
513*4882a593Smuzhiyun if (rval)
514*4882a593Smuzhiyun goto out_free;
515*4882a593Smuzhiyun if (v) {
516*4882a593Smuzhiyun rval = xfs_dir2_block_replace(args);
517*4882a593Smuzhiyun goto out_free;
518*4882a593Smuzhiyun }
519*4882a593Smuzhiyun
520*4882a593Smuzhiyun rval = xfs_dir2_isleaf(args, &v);
521*4882a593Smuzhiyun if (rval)
522*4882a593Smuzhiyun goto out_free;
523*4882a593Smuzhiyun if (v)
524*4882a593Smuzhiyun rval = xfs_dir2_leaf_replace(args);
525*4882a593Smuzhiyun else
526*4882a593Smuzhiyun rval = xfs_dir2_node_replace(args);
527*4882a593Smuzhiyun out_free:
528*4882a593Smuzhiyun kmem_free(args);
529*4882a593Smuzhiyun return rval;
530*4882a593Smuzhiyun }
531*4882a593Smuzhiyun
532*4882a593Smuzhiyun /*
533*4882a593Smuzhiyun * See if this entry can be added to the directory without allocating space.
534*4882a593Smuzhiyun */
535*4882a593Smuzhiyun int
xfs_dir_canenter(xfs_trans_t * tp,xfs_inode_t * dp,struct xfs_name * name)536*4882a593Smuzhiyun xfs_dir_canenter(
537*4882a593Smuzhiyun xfs_trans_t *tp,
538*4882a593Smuzhiyun xfs_inode_t *dp,
539*4882a593Smuzhiyun struct xfs_name *name) /* name of entry to add */
540*4882a593Smuzhiyun {
541*4882a593Smuzhiyun return xfs_dir_createname(tp, dp, name, 0, 0);
542*4882a593Smuzhiyun }
543*4882a593Smuzhiyun
544*4882a593Smuzhiyun /*
545*4882a593Smuzhiyun * Utility routines.
546*4882a593Smuzhiyun */
547*4882a593Smuzhiyun
548*4882a593Smuzhiyun /*
549*4882a593Smuzhiyun * Add a block to the directory.
550*4882a593Smuzhiyun *
551*4882a593Smuzhiyun * This routine is for data and free blocks, not leaf/node blocks which are
552*4882a593Smuzhiyun * handled by xfs_da_grow_inode.
553*4882a593Smuzhiyun */
554*4882a593Smuzhiyun int
xfs_dir2_grow_inode(struct xfs_da_args * args,int space,xfs_dir2_db_t * dbp)555*4882a593Smuzhiyun xfs_dir2_grow_inode(
556*4882a593Smuzhiyun struct xfs_da_args *args,
557*4882a593Smuzhiyun int space, /* v2 dir's space XFS_DIR2_xxx_SPACE */
558*4882a593Smuzhiyun xfs_dir2_db_t *dbp) /* out: block number added */
559*4882a593Smuzhiyun {
560*4882a593Smuzhiyun struct xfs_inode *dp = args->dp;
561*4882a593Smuzhiyun struct xfs_mount *mp = dp->i_mount;
562*4882a593Smuzhiyun xfs_fileoff_t bno; /* directory offset of new block */
563*4882a593Smuzhiyun int count; /* count of filesystem blocks */
564*4882a593Smuzhiyun int error;
565*4882a593Smuzhiyun
566*4882a593Smuzhiyun trace_xfs_dir2_grow_inode(args, space);
567*4882a593Smuzhiyun
568*4882a593Smuzhiyun /*
569*4882a593Smuzhiyun * Set lowest possible block in the space requested.
570*4882a593Smuzhiyun */
571*4882a593Smuzhiyun bno = XFS_B_TO_FSBT(mp, space * XFS_DIR2_SPACE_SIZE);
572*4882a593Smuzhiyun count = args->geo->fsbcount;
573*4882a593Smuzhiyun
574*4882a593Smuzhiyun error = xfs_da_grow_inode_int(args, &bno, count);
575*4882a593Smuzhiyun if (error)
576*4882a593Smuzhiyun return error;
577*4882a593Smuzhiyun
578*4882a593Smuzhiyun *dbp = xfs_dir2_da_to_db(args->geo, (xfs_dablk_t)bno);
579*4882a593Smuzhiyun
580*4882a593Smuzhiyun /*
581*4882a593Smuzhiyun * Update file's size if this is the data space and it grew.
582*4882a593Smuzhiyun */
583*4882a593Smuzhiyun if (space == XFS_DIR2_DATA_SPACE) {
584*4882a593Smuzhiyun xfs_fsize_t size; /* directory file (data) size */
585*4882a593Smuzhiyun
586*4882a593Smuzhiyun size = XFS_FSB_TO_B(mp, bno + count);
587*4882a593Smuzhiyun if (size > dp->i_d.di_size) {
588*4882a593Smuzhiyun dp->i_d.di_size = size;
589*4882a593Smuzhiyun xfs_trans_log_inode(args->trans, dp, XFS_ILOG_CORE);
590*4882a593Smuzhiyun }
591*4882a593Smuzhiyun }
592*4882a593Smuzhiyun return 0;
593*4882a593Smuzhiyun }
594*4882a593Smuzhiyun
595*4882a593Smuzhiyun /*
596*4882a593Smuzhiyun * See if the directory is a single-block form directory.
597*4882a593Smuzhiyun */
598*4882a593Smuzhiyun int
xfs_dir2_isblock(struct xfs_da_args * args,int * vp)599*4882a593Smuzhiyun xfs_dir2_isblock(
600*4882a593Smuzhiyun struct xfs_da_args *args,
601*4882a593Smuzhiyun int *vp) /* out: 1 is block, 0 is not block */
602*4882a593Smuzhiyun {
603*4882a593Smuzhiyun xfs_fileoff_t last; /* last file offset */
604*4882a593Smuzhiyun int rval;
605*4882a593Smuzhiyun
606*4882a593Smuzhiyun if ((rval = xfs_bmap_last_offset(args->dp, &last, XFS_DATA_FORK)))
607*4882a593Smuzhiyun return rval;
608*4882a593Smuzhiyun rval = XFS_FSB_TO_B(args->dp->i_mount, last) == args->geo->blksize;
609*4882a593Smuzhiyun if (XFS_IS_CORRUPT(args->dp->i_mount,
610*4882a593Smuzhiyun rval != 0 &&
611*4882a593Smuzhiyun args->dp->i_d.di_size != args->geo->blksize))
612*4882a593Smuzhiyun return -EFSCORRUPTED;
613*4882a593Smuzhiyun *vp = rval;
614*4882a593Smuzhiyun return 0;
615*4882a593Smuzhiyun }
616*4882a593Smuzhiyun
617*4882a593Smuzhiyun /*
618*4882a593Smuzhiyun * See if the directory is a single-leaf form directory.
619*4882a593Smuzhiyun */
620*4882a593Smuzhiyun int
xfs_dir2_isleaf(struct xfs_da_args * args,int * vp)621*4882a593Smuzhiyun xfs_dir2_isleaf(
622*4882a593Smuzhiyun struct xfs_da_args *args,
623*4882a593Smuzhiyun int *vp) /* out: 1 is block, 0 is not block */
624*4882a593Smuzhiyun {
625*4882a593Smuzhiyun xfs_fileoff_t last; /* last file offset */
626*4882a593Smuzhiyun int rval;
627*4882a593Smuzhiyun
628*4882a593Smuzhiyun if ((rval = xfs_bmap_last_offset(args->dp, &last, XFS_DATA_FORK)))
629*4882a593Smuzhiyun return rval;
630*4882a593Smuzhiyun *vp = last == args->geo->leafblk + args->geo->fsbcount;
631*4882a593Smuzhiyun return 0;
632*4882a593Smuzhiyun }
633*4882a593Smuzhiyun
634*4882a593Smuzhiyun /*
635*4882a593Smuzhiyun * Remove the given block from the directory.
636*4882a593Smuzhiyun * This routine is used for data and free blocks, leaf/node are done
637*4882a593Smuzhiyun * by xfs_da_shrink_inode.
638*4882a593Smuzhiyun */
639*4882a593Smuzhiyun int
xfs_dir2_shrink_inode(struct xfs_da_args * args,xfs_dir2_db_t db,struct xfs_buf * bp)640*4882a593Smuzhiyun xfs_dir2_shrink_inode(
641*4882a593Smuzhiyun struct xfs_da_args *args,
642*4882a593Smuzhiyun xfs_dir2_db_t db,
643*4882a593Smuzhiyun struct xfs_buf *bp)
644*4882a593Smuzhiyun {
645*4882a593Smuzhiyun xfs_fileoff_t bno; /* directory file offset */
646*4882a593Smuzhiyun xfs_dablk_t da; /* directory file offset */
647*4882a593Smuzhiyun int done; /* bunmap is finished */
648*4882a593Smuzhiyun struct xfs_inode *dp;
649*4882a593Smuzhiyun int error;
650*4882a593Smuzhiyun struct xfs_mount *mp;
651*4882a593Smuzhiyun struct xfs_trans *tp;
652*4882a593Smuzhiyun
653*4882a593Smuzhiyun trace_xfs_dir2_shrink_inode(args, db);
654*4882a593Smuzhiyun
655*4882a593Smuzhiyun dp = args->dp;
656*4882a593Smuzhiyun mp = dp->i_mount;
657*4882a593Smuzhiyun tp = args->trans;
658*4882a593Smuzhiyun da = xfs_dir2_db_to_da(args->geo, db);
659*4882a593Smuzhiyun
660*4882a593Smuzhiyun /* Unmap the fsblock(s). */
661*4882a593Smuzhiyun error = xfs_bunmapi(tp, dp, da, args->geo->fsbcount, 0, 0, &done);
662*4882a593Smuzhiyun if (error) {
663*4882a593Smuzhiyun /*
664*4882a593Smuzhiyun * ENOSPC actually can happen if we're in a removename with no
665*4882a593Smuzhiyun * space reservation, and the resulting block removal would
666*4882a593Smuzhiyun * cause a bmap btree split or conversion from extents to btree.
667*4882a593Smuzhiyun * This can only happen for un-fragmented directory blocks,
668*4882a593Smuzhiyun * since you need to be punching out the middle of an extent.
669*4882a593Smuzhiyun * In this case we need to leave the block in the file, and not
670*4882a593Smuzhiyun * binval it. So the block has to be in a consistent empty
671*4882a593Smuzhiyun * state and appropriately logged. We don't free up the buffer,
672*4882a593Smuzhiyun * the caller can tell it hasn't happened since it got an error
673*4882a593Smuzhiyun * back.
674*4882a593Smuzhiyun */
675*4882a593Smuzhiyun return error;
676*4882a593Smuzhiyun }
677*4882a593Smuzhiyun ASSERT(done);
678*4882a593Smuzhiyun /*
679*4882a593Smuzhiyun * Invalidate the buffer from the transaction.
680*4882a593Smuzhiyun */
681*4882a593Smuzhiyun xfs_trans_binval(tp, bp);
682*4882a593Smuzhiyun /*
683*4882a593Smuzhiyun * If it's not a data block, we're done.
684*4882a593Smuzhiyun */
685*4882a593Smuzhiyun if (db >= xfs_dir2_byte_to_db(args->geo, XFS_DIR2_LEAF_OFFSET))
686*4882a593Smuzhiyun return 0;
687*4882a593Smuzhiyun /*
688*4882a593Smuzhiyun * If the block isn't the last one in the directory, we're done.
689*4882a593Smuzhiyun */
690*4882a593Smuzhiyun if (dp->i_d.di_size > xfs_dir2_db_off_to_byte(args->geo, db + 1, 0))
691*4882a593Smuzhiyun return 0;
692*4882a593Smuzhiyun bno = da;
693*4882a593Smuzhiyun if ((error = xfs_bmap_last_before(tp, dp, &bno, XFS_DATA_FORK))) {
694*4882a593Smuzhiyun /*
695*4882a593Smuzhiyun * This can't really happen unless there's kernel corruption.
696*4882a593Smuzhiyun */
697*4882a593Smuzhiyun return error;
698*4882a593Smuzhiyun }
699*4882a593Smuzhiyun if (db == args->geo->datablk)
700*4882a593Smuzhiyun ASSERT(bno == 0);
701*4882a593Smuzhiyun else
702*4882a593Smuzhiyun ASSERT(bno > 0);
703*4882a593Smuzhiyun /*
704*4882a593Smuzhiyun * Set the size to the new last block.
705*4882a593Smuzhiyun */
706*4882a593Smuzhiyun dp->i_d.di_size = XFS_FSB_TO_B(mp, bno);
707*4882a593Smuzhiyun xfs_trans_log_inode(tp, dp, XFS_ILOG_CORE);
708*4882a593Smuzhiyun return 0;
709*4882a593Smuzhiyun }
710*4882a593Smuzhiyun
711*4882a593Smuzhiyun /* Returns true if the directory entry name is valid. */
712*4882a593Smuzhiyun bool
xfs_dir2_namecheck(const void * name,size_t length)713*4882a593Smuzhiyun xfs_dir2_namecheck(
714*4882a593Smuzhiyun const void *name,
715*4882a593Smuzhiyun size_t length)
716*4882a593Smuzhiyun {
717*4882a593Smuzhiyun /*
718*4882a593Smuzhiyun * MAXNAMELEN includes the trailing null, but (name/length) leave it
719*4882a593Smuzhiyun * out, so use >= for the length check.
720*4882a593Smuzhiyun */
721*4882a593Smuzhiyun if (length >= MAXNAMELEN)
722*4882a593Smuzhiyun return false;
723*4882a593Smuzhiyun
724*4882a593Smuzhiyun /* There shouldn't be any slashes or nulls here */
725*4882a593Smuzhiyun return !memchr(name, '/', length) && !memchr(name, 0, length);
726*4882a593Smuzhiyun }
727*4882a593Smuzhiyun
728*4882a593Smuzhiyun xfs_dahash_t
xfs_dir2_hashname(struct xfs_mount * mp,struct xfs_name * name)729*4882a593Smuzhiyun xfs_dir2_hashname(
730*4882a593Smuzhiyun struct xfs_mount *mp,
731*4882a593Smuzhiyun struct xfs_name *name)
732*4882a593Smuzhiyun {
733*4882a593Smuzhiyun if (unlikely(xfs_sb_version_hasasciici(&mp->m_sb)))
734*4882a593Smuzhiyun return xfs_ascii_ci_hashname(name);
735*4882a593Smuzhiyun return xfs_da_hashname(name->name, name->len);
736*4882a593Smuzhiyun }
737*4882a593Smuzhiyun
738*4882a593Smuzhiyun enum xfs_dacmp
xfs_dir2_compname(struct xfs_da_args * args,const unsigned char * name,int len)739*4882a593Smuzhiyun xfs_dir2_compname(
740*4882a593Smuzhiyun struct xfs_da_args *args,
741*4882a593Smuzhiyun const unsigned char *name,
742*4882a593Smuzhiyun int len)
743*4882a593Smuzhiyun {
744*4882a593Smuzhiyun if (unlikely(xfs_sb_version_hasasciici(&args->dp->i_mount->m_sb)))
745*4882a593Smuzhiyun return xfs_ascii_ci_compname(args, name, len);
746*4882a593Smuzhiyun return xfs_da_compname(args, name, len);
747*4882a593Smuzhiyun }
748