Print this page
fixup .text where possible
7127 remove -Wno-missing-braces from Makefile.uts
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/fs/zut/zut.c
+++ new/usr/src/uts/common/fs/zut/zut.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21 /*
22 22 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
23 23 */
24 24
25 25 #include <sys/conf.h>
26 26 #include <sys/stat.h>
27 27 #include <sys/file.h>
28 28 #include <sys/types.h>
29 29 #include <sys/pathname.h>
30 30 #include <sys/proc.h>
31 31 #include <sys/mode.h>
32 32 #include <sys/vnode.h>
33 33 #include <sys/ddi.h>
34 34 #include <sys/sunddi.h>
35 35 #include <sys/sunldi.h>
36 36 #include <sys/uio.h>
37 37 #include <sys/attr.h>
38 38 #include <sys/acl.h>
39 39 #include <sys/fs/zut.h>
40 40
41 41 ldi_ident_t zut_li = NULL;
42 42 dev_info_t *zut_dip;
43 43
44 44 static int
45 45 zut_open_dir(char *path, vnode_t *startvp, cred_t *cr, int flags,
46 46 pathname_t *realpn, vnode_t **dvn)
47 47 {
48 48 pathname_t pn;
49 49 vnode_t *vp;
50 50 vnode_t *rootvp;
51 51 proc_t *p = curproc;
52 52 int error;
53 53
54 54 pn_alloc(&pn);
55 55 (void) strlcpy(pn.pn_buf, path, MAXPATHLEN);
56 56 pn.pn_pathlen = strlen(path);
57 57
58 58 mutex_enter(&p->p_lock); /* for u_rdir and u_cdir */
59 59 if ((rootvp = PTOU(p)->u_rdir) == NULL)
60 60 rootvp = rootdir;
61 61 else if (rootvp != rootdir) /* no need to VN_HOLD rootdir */
62 62 VN_HOLD(rootvp);
63 63
64 64 if (pn.pn_path[0] == '/') {
65 65 vp = rootvp;
66 66 } else {
67 67 vp = (startvp == NULL) ? PTOU(p)->u_cdir : startvp;
68 68 }
69 69 VN_HOLD(vp);
70 70 mutex_exit(&p->p_lock);
71 71
72 72 /*
73 73 * Skip over leading slashes
74 74 */
75 75 while (pn.pn_path[0] == '/') {
76 76 pn.pn_path++;
77 77 pn.pn_pathlen--;
78 78 }
79 79
80 80 error = lookuppnvp(&pn, realpn, flags | FOLLOW, NULL,
81 81 dvn, rootvp, vp, cr);
82 82
83 83 /*
84 84 * If we lack read access to the directory, we should error out.
85 85 */
86 86 if (!error) {
87 87 if (vfs_has_feature((*dvn)->v_vfsp, VFSFT_ACEMASKONACCESS)) {
88 88 error = VOP_ACCESS(*dvn, ACE_LIST_DIRECTORY,
89 89 V_ACE_MASK, cr, NULL);
90 90 } else {
91 91 error = VOP_ACCESS(*dvn, VREAD, 0, cr, NULL);
92 92 }
93 93 }
94 94
95 95 pn_free(&pn);
96 96
97 97 return (error);
98 98 }
99 99
100 100 static int
101 101 zut_readdir(intptr_t arg, cred_t *cr, int iflag, int *rvalp)
102 102 {
103 103 zut_readdir_t *zr;
104 104 struct iovec aiov;
105 105 struct uio auio;
106 106 vnode_t *dvn = NULL;
107 107 vnode_t *fvn = NULL;
108 108 char *kbuf;
109 109 int flags = 0;
110 110 int error, rc;
111 111
112 112 zr = kmem_zalloc(sizeof (zut_readdir_t), KM_SLEEP);
113 113 error = ddi_copyin((void *)arg, zr, sizeof (zut_readdir_t), iflag);
114 114 if (error)
115 115 goto zutr_bail;
116 116
117 117 kbuf = kmem_zalloc(zr->zr_buflen, KM_SLEEP);
118 118
119 119 zr->zr_retcode = zut_open_dir(zr->zr_dir, NULL, cr, flags, NULL, &dvn);
120 120 if (zr->zr_retcode)
121 121 goto zutr_done;
122 122
123 123 if (zr->zr_reqflags & ZUT_XATTR) {
124 124 vattr_t vattr;
125 125
126 126 zr->zr_retcode = VOP_LOOKUP(dvn, zr->zr_file, &fvn,
127 127 NULL, flags, NULL, cr, NULL, NULL, NULL);
128 128 VN_RELE(dvn);
129 129 dvn = NULL;
130 130 if (zr->zr_retcode)
131 131 goto zutr_done;
132 132
133 133 /*
134 134 * In order to access hidden attribute directory the
135 135 * user must have appropriate read access and be able
136 136 * to stat() the file
137 137 */
138 138 if (vfs_has_feature(fvn->v_vfsp, VFSFT_ACEMASKONACCESS)) {
139 139 zr->zr_retcode = VOP_ACCESS(fvn, ACE_READ_NAMED_ATTRS,
140 140 V_ACE_MASK, cr, NULL);
141 141 } else {
142 142 zr->zr_retcode = VOP_ACCESS(fvn, VREAD, 0, cr, NULL);
143 143 }
144 144 if (zr->zr_retcode)
145 145 goto zutr_done;
146 146
147 147 vattr.va_mask = AT_ALL;
148 148 zr->zr_retcode = VOP_GETATTR(fvn, &vattr, 0, cr, NULL);
149 149 if (zr->zr_retcode)
150 150 goto zutr_done;
151 151
152 152 zr->zr_retcode = VOP_LOOKUP(fvn, "", &dvn, NULL,
153 153 flags | LOOKUP_XATTR, NULL, cr, NULL, NULL, NULL);
154 154 VN_RELE(fvn);
155 155 if (zr->zr_retcode)
156 156 goto zutr_done;
157 157 }
158 158
159 159 aiov.iov_base = kbuf;
160 160 aiov.iov_len = zr->zr_buflen;
161 161 auio.uio_iov = &aiov;
162 162 auio.uio_iovcnt = 1;
163 163 auio.uio_loffset = zr->zr_loffset;
164 164 auio.uio_segflg = UIO_SYSSPACE;
165 165 auio.uio_resid = zr->zr_buflen;
166 166 auio.uio_fmode = 0;
167 167 auio.uio_extflg = UIO_COPY_CACHED;
168 168
169 169 if (zr->zr_reqflags & ZUT_EXTRDDIR)
170 170 flags |= V_RDDIR_ENTFLAGS;
171 171 if (zr->zr_reqflags & ZUT_ACCFILTER)
172 172 flags |= V_RDDIR_ACCFILTER;
173 173
174 174 (void) VOP_RWLOCK(dvn, V_WRITELOCK_FALSE, NULL);
175 175 zr->zr_retcode = VOP_READDIR(dvn, &auio, cr, &zr->zr_eof,
176 176 NULL, flags);
177 177 VOP_RWUNLOCK(dvn, V_WRITELOCK_FALSE, NULL);
178 178 VN_RELE(dvn);
179 179
180 180 zr->zr_bytes = aiov.iov_base - kbuf;
181 181 zr->zr_loffset = auio.uio_loffset;
182 182
183 183 error = ddi_copyout(kbuf, (void *)(uintptr_t)zr->zr_buf,
184 184 zr->zr_buflen, iflag);
185 185
186 186 zutr_done:
187 187 kmem_free(kbuf, zr->zr_buflen);
188 188 rc = ddi_copyout(zr, (void *)arg, sizeof (zut_readdir_t), iflag);
189 189 if (error == 0)
190 190 error = rc;
191 191
192 192 zutr_bail:
↓ open down ↓ |
192 lines elided |
↑ open up ↑ |
193 193 kmem_free(zr, sizeof (zut_readdir_t));
194 194 if (rvalp)
195 195 *rvalp = error;
196 196 return (error);
197 197 }
198 198
199 199 static int
200 200 zut_stat64(vnode_t *vp, struct stat64 *sb, uint64_t *xvs, int flag, cred_t *cr)
201 201 {
202 202 xoptattr_t *xoap = NULL;
203 - xvattr_t xv = { 0 };
203 + xvattr_t xv = {{ 0 }};
204 204 int error;
205 205
206 206 xva_init(&xv);
207 207
208 208 XVA_SET_REQ(&xv, XAT_ARCHIVE);
209 209 XVA_SET_REQ(&xv, XAT_SYSTEM);
210 210 XVA_SET_REQ(&xv, XAT_READONLY);
211 211 XVA_SET_REQ(&xv, XAT_HIDDEN);
212 212 XVA_SET_REQ(&xv, XAT_NOUNLINK);
213 213 XVA_SET_REQ(&xv, XAT_IMMUTABLE);
214 214 XVA_SET_REQ(&xv, XAT_APPENDONLY);
215 215 XVA_SET_REQ(&xv, XAT_NODUMP);
216 216 XVA_SET_REQ(&xv, XAT_OPAQUE);
217 217 XVA_SET_REQ(&xv, XAT_AV_QUARANTINED);
218 218 XVA_SET_REQ(&xv, XAT_AV_MODIFIED);
219 219 XVA_SET_REQ(&xv, XAT_REPARSE);
220 220 XVA_SET_REQ(&xv, XAT_OFFLINE);
221 221 XVA_SET_REQ(&xv, XAT_SPARSE);
222 222
223 223 xv.xva_vattr.va_mask |= AT_STAT | AT_NBLOCKS | AT_BLKSIZE | AT_SIZE;
224 224 if (error = VOP_GETATTR(vp, &xv.xva_vattr, flag, cr, NULL))
225 225 return (error);
226 226
227 227 bzero(sb, sizeof (sb));
228 228 sb->st_dev = xv.xva_vattr.va_fsid;
229 229 sb->st_ino = xv.xva_vattr.va_nodeid;
230 230 sb->st_mode = VTTOIF(xv.xva_vattr.va_type) | xv.xva_vattr.va_mode;
231 231 sb->st_nlink = xv.xva_vattr.va_nlink;
232 232 sb->st_uid = xv.xva_vattr.va_uid;
233 233 sb->st_gid = xv.xva_vattr.va_gid;
234 234 sb->st_rdev = xv.xva_vattr.va_rdev;
235 235 sb->st_size = xv.xva_vattr.va_size;
236 236 sb->st_atim = xv.xva_vattr.va_atime;
237 237 sb->st_mtim = xv.xva_vattr.va_mtime;
238 238 sb->st_ctim = xv.xva_vattr.va_ctime;
239 239 sb->st_blksize = xv.xva_vattr.va_blksize;
240 240 sb->st_blocks = xv.xva_vattr.va_nblocks;
241 241 sb->st_fstype[0] = 0;
242 242
243 243 if ((xoap = xva_getxoptattr(&xv)) == NULL)
244 244 return (0);
245 245
246 246 if (XVA_ISSET_RTN(&xv, XAT_ARCHIVE) && xoap->xoa_archive)
247 247 *xvs |= (1 << F_ARCHIVE);
248 248 if (XVA_ISSET_RTN(&xv, XAT_SYSTEM) && xoap->xoa_system)
249 249 *xvs |= (1 << F_SYSTEM);
250 250 if (XVA_ISSET_RTN(&xv, XAT_READONLY) && xoap->xoa_readonly)
251 251 *xvs |= (1 << F_READONLY);
252 252 if (XVA_ISSET_RTN(&xv, XAT_HIDDEN) && xoap->xoa_hidden)
253 253 *xvs |= (1 << F_HIDDEN);
254 254 if (XVA_ISSET_RTN(&xv, XAT_NOUNLINK) && xoap->xoa_nounlink)
255 255 *xvs |= (1 << F_NOUNLINK);
256 256 if (XVA_ISSET_RTN(&xv, XAT_IMMUTABLE) && xoap->xoa_immutable)
257 257 *xvs |= (1 << F_IMMUTABLE);
258 258 if (XVA_ISSET_RTN(&xv, XAT_APPENDONLY) && xoap->xoa_appendonly)
259 259 *xvs |= (1 << F_APPENDONLY);
260 260 if (XVA_ISSET_RTN(&xv, XAT_NODUMP) && xoap->xoa_nodump)
261 261 *xvs |= (1 << F_NODUMP);
262 262 if (XVA_ISSET_RTN(&xv, XAT_OPAQUE) && xoap->xoa_opaque)
263 263 *xvs |= (1 << F_OPAQUE);
264 264 if (XVA_ISSET_RTN(&xv, XAT_AV_QUARANTINED) && xoap->xoa_av_quarantined)
265 265 *xvs |= (1 << F_AV_QUARANTINED);
266 266 if (XVA_ISSET_RTN(&xv, XAT_AV_MODIFIED) && xoap->xoa_av_modified)
267 267 *xvs |= (1 << F_AV_MODIFIED);
268 268 if (XVA_ISSET_RTN(&xv, XAT_REPARSE) && xoap->xoa_reparse)
269 269 *xvs |= (1 << F_REPARSE);
270 270 if (XVA_ISSET_RTN(&xv, XAT_OFFLINE) && xoap->xoa_offline)
271 271 *xvs |= (1 << F_OFFLINE);
272 272 if (XVA_ISSET_RTN(&xv, XAT_SPARSE) && xoap->xoa_sparse)
273 273 *xvs |= (1 << F_SPARSE);
274 274
275 275 return (0);
276 276 }
277 277
278 278 /*ARGSUSED*/
279 279 static int
280 280 zut_lookup(intptr_t arg, cred_t *cr, int iflag, int *rvalp)
281 281 {
282 282 zut_lookup_t *zl;
283 283 pathname_t rpn;
284 284 vnode_t *dvn = NULL;
285 285 vnode_t *fvn = NULL;
286 286 vnode_t *xdvn = NULL;
287 287 vnode_t *xfvn = NULL;
288 288 vnode_t *release = NULL;
289 289 int flags = 0;
290 290 int error, rc;
291 291
292 292 zl = kmem_zalloc(sizeof (zut_lookup_t), KM_SLEEP);
293 293
294 294 error = ddi_copyin((void *)arg, zl, sizeof (zut_lookup_t), iflag);
295 295 if (error)
296 296 goto zutl_bail;
297 297
298 298 pn_alloc(&rpn);
299 299 bzero(rpn.pn_buf, MAXPATHLEN);
300 300
301 301 zl->zl_retcode = zut_open_dir(zl->zl_dir, NULL, cr, flags, &rpn, &dvn);
302 302 if (zl->zl_retcode)
303 303 goto zutl_done;
304 304
305 305 if (zl->zl_reqflags & ZUT_IGNORECASE)
306 306 flags |= FIGNORECASE;
307 307
308 308 zl->zl_retcode = VOP_LOOKUP(dvn, zl->zl_file, &fvn, NULL, flags, NULL,
309 309 cr, NULL, &zl->zl_deflags, &rpn);
310 310 if (zl->zl_retcode)
311 311 goto zutl_done;
312 312
313 313 release = fvn;
314 314
315 315 if (zl->zl_reqflags & ZUT_XATTR) {
316 316 vattr_t vattr;
317 317
318 318 /*
319 319 * In order to access hidden attribute directory the
320 320 * user must have appropriate read access and be able
321 321 * to stat() the file
322 322 */
323 323 if (vfs_has_feature(fvn->v_vfsp, VFSFT_ACEMASKONACCESS)) {
324 324 zl->zl_retcode = VOP_ACCESS(fvn, ACE_READ_NAMED_ATTRS,
325 325 V_ACE_MASK, cr, NULL);
326 326 } else {
327 327 zl->zl_retcode = VOP_ACCESS(fvn, VREAD, 0, cr, NULL);
328 328 }
329 329 if (zl->zl_retcode)
330 330 goto zutl_done;
331 331
332 332 vattr.va_mask = AT_ALL;
333 333 zl->zl_retcode = VOP_GETATTR(fvn, &vattr, 0, cr, NULL);
334 334 if (zl->zl_retcode)
335 335 goto zutl_done;
336 336
337 337 zl->zl_retcode = VOP_LOOKUP(fvn, "", &xdvn, NULL,
338 338 flags | LOOKUP_XATTR, NULL, cr, NULL, NULL, NULL);
339 339 if (zl->zl_retcode)
340 340 goto zutl_done;
341 341 VN_RELE(fvn);
342 342 release = xdvn;
343 343
344 344 zl->zl_retcode = VOP_LOOKUP(xdvn, zl->zl_xfile, &xfvn,
345 345 NULL, flags, NULL, cr, NULL, &zl->zl_deflags, &rpn);
346 346 if (zl->zl_retcode)
347 347 goto zutl_done;
348 348 VN_RELE(xdvn);
349 349 release = xfvn;
350 350 }
351 351
352 352 if (zl->zl_reqflags & ZUT_GETSTAT) {
353 353 zl->zl_retcode = zut_stat64(release,
354 354 &zl->zl_statbuf, &zl->zl_xvattrs, 0, cr);
355 355 }
356 356
357 357 zutl_done:
358 358 (void) strlcpy(zl->zl_real, rpn.pn_path, MAXPATHLEN);
359 359
360 360 rc = ddi_copyout(zl, (void *)arg, sizeof (zut_lookup_t), iflag);
361 361 if (error == 0)
362 362 error = rc;
363 363
364 364 if (release)
365 365 VN_RELE(release);
366 366 if (dvn)
367 367 VN_RELE(dvn);
368 368 pn_free(&rpn);
369 369
370 370 zutl_bail:
371 371 kmem_free(zl, sizeof (zut_lookup_t));
372 372 if (rvalp)
373 373 *rvalp = error;
374 374 return (error);
375 375 }
376 376
377 377 /*ARGSUSED*/
378 378 static int
379 379 zut_ioctl(dev_t dev, int cmd, intptr_t arg, int flag, cred_t *cr, int *rvalp)
380 380 {
381 381 int error;
382 382
383 383 if (getminor(dev) != 0)
384 384 return (ENXIO);
385 385
386 386 if (cmd <= ZUT_IOC_MIN_CMD || cmd >= ZUT_IOC_MAX_CMD)
387 387 return (EINVAL);
388 388
389 389 switch (cmd) {
390 390 case ZUT_IOC_LOOKUP:
391 391 error = zut_lookup(arg, cr, flag, rvalp);
392 392 break;
393 393 case ZUT_IOC_READDIR:
394 394 error = zut_readdir(arg, cr, flag, rvalp);
395 395 default:
396 396 break;
397 397 }
398 398
399 399 return (error);
400 400 }
401 401
402 402 static int
403 403 zut_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
404 404 {
405 405 if (cmd != DDI_ATTACH)
406 406 return (DDI_FAILURE);
407 407
408 408 if (ddi_create_minor_node(dip, "zut", S_IFCHR, 0,
409 409 DDI_PSEUDO, 0) == DDI_FAILURE)
410 410 return (DDI_FAILURE);
411 411
412 412 zut_dip = dip;
413 413
414 414 ddi_report_dev(dip);
415 415
416 416 return (DDI_SUCCESS);
417 417 }
418 418
419 419 static int
420 420 zut_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
421 421 {
422 422 if (cmd != DDI_DETACH)
423 423 return (DDI_FAILURE);
424 424
425 425 zut_dip = NULL;
426 426
427 427 ddi_prop_remove_all(dip);
428 428 ddi_remove_minor_node(dip, NULL);
429 429
430 430 return (DDI_SUCCESS);
431 431 }
432 432
433 433 /*ARGSUSED*/
434 434 static int
435 435 zut_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
436 436 {
437 437 switch (infocmd) {
438 438 case DDI_INFO_DEVT2DEVINFO:
439 439 *result = zut_dip;
440 440 return (DDI_SUCCESS);
441 441
442 442 case DDI_INFO_DEVT2INSTANCE:
443 443 *result = (void *)0;
444 444 return (DDI_SUCCESS);
445 445 }
446 446
447 447 return (DDI_FAILURE);
448 448 }
449 449
450 450 /*ARGSUSED*/
451 451 int
452 452 zut_open(dev_t *devp, int flag, int otyp, cred_t *cr)
453 453 {
454 454 minor_t minor = getminor(*devp);
455 455
456 456 if (minor == 0) /* This is the control device */
457 457 return (0);
458 458
459 459 return (ENXIO);
460 460 }
461 461
462 462 /*ARGSUSED*/
463 463 int
464 464 zut_close(dev_t dev, int flag, int otyp, cred_t *cr)
465 465 {
466 466 minor_t minor = getminor(dev);
467 467
468 468 if (minor == 0) /* This is the control device */
469 469 return (0);
470 470
471 471 return (ENXIO);
472 472 }
473 473
474 474 /*
475 475 * /dev/zut is the control node, i.e. minor 0.
476 476 *
477 477 * There are no other minor nodes, and /dev/zut basically does nothing
478 478 * other than serve up ioctls.
479 479 */
480 480 static struct cb_ops zut_cb_ops = {
481 481 zut_open, /* open */
482 482 zut_close, /* close */
483 483 nodev, /* strategy */
484 484 nodev, /* print */
485 485 nodev, /* dump */
486 486 nodev, /* read */
487 487 nodev, /* write */
488 488 zut_ioctl, /* ioctl */
489 489 nodev, /* devmap */
490 490 nodev, /* mmap */
491 491 nodev, /* segmap */
492 492 nochpoll, /* poll */
493 493 ddi_prop_op, /* prop_op */
494 494 NULL, /* streamtab */
495 495 D_NEW | D_MP | D_64BIT, /* Driver compatibility flag */
496 496 CB_REV, /* version */
497 497 nodev, /* async read */
498 498 nodev, /* async write */
499 499 };
500 500
501 501 static struct dev_ops zut_dev_ops = {
502 502 DEVO_REV, /* version */
503 503 0, /* refcnt */
504 504 zut_info, /* info */
505 505 nulldev, /* identify */
506 506 nulldev, /* probe */
507 507 zut_attach, /* attach */
508 508 zut_detach, /* detach */
509 509 nodev, /* reset */
510 510 &zut_cb_ops, /* driver operations */
↓ open down ↓ |
297 lines elided |
↑ open up ↑ |
511 511 NULL /* no bus operations */
512 512 };
513 513
514 514 static struct modldrv zut_modldrv = {
515 515 &mod_driverops, "ZFS unit test " ZUT_VERSION_STRING,
516 516 &zut_dev_ops
517 517 };
518 518
519 519 static struct modlinkage modlinkage = {
520 520 MODREV_1,
521 - (void *)&zut_modldrv,
522 - NULL
521 + { (void *)&zut_modldrv,
522 + NULL }
523 523 };
524 524
525 525 int
526 526 _init(void)
527 527 {
528 528 int error;
529 529
530 530 if ((error = mod_install(&modlinkage)) != 0) {
531 531 return (error);
532 532 }
533 533
534 534 error = ldi_ident_from_mod(&modlinkage, &zut_li);
535 535 ASSERT(error == 0);
536 536
537 537 return (0);
538 538 }
539 539
540 540 int
541 541 _fini(void)
542 542 {
543 543 int error;
544 544
545 545 if ((error = mod_remove(&modlinkage)) != 0)
546 546 return (error);
547 547
548 548 ldi_ident_release(zut_li);
549 549 zut_li = NULL;
550 550
551 551 return (error);
552 552 }
553 553
554 554 int
555 555 _info(struct modinfo *modinfop)
556 556 {
557 557 return (mod_info(&modlinkage, modinfop));
558 558 }
↓ open down ↓ |
26 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX