Print this page
7127 remove -Wno-missing-braces from Makefile.uts
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/fs/smbclnt/netsmb/smb_dev.c
+++ new/usr/src/uts/common/fs/smbclnt/netsmb/smb_dev.c
1 1 /*
2 2 * Copyright (c) 2000-2001 Boris Popov
3 3 * All rights reserved.
4 4 *
5 5 * Redistribution and use in source and binary forms, with or without
6 6 * modification, are permitted provided that the following conditions
7 7 * are met:
8 8 * 1. Redistributions of source code must retain the above copyright
9 9 * notice, this list of conditions and the following disclaimer.
10 10 * 2. Redistributions in binary form must reproduce the above copyright
11 11 * notice, this list of conditions and the following disclaimer in the
12 12 * documentation and/or other materials provided with the distribution.
13 13 * 3. All advertising materials mentioning features or use of this software
14 14 * must display the following acknowledgement:
15 15 * This product includes software developed by Boris Popov.
16 16 * 4. Neither the name of the author nor the names of any co-contributors
17 17 * may be used to endorse or promote products derived from this software
18 18 * without specific prior written permission.
19 19 *
20 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21 21 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 22 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23 23 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24 24 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 25 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26 26 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 27 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28 28 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29 29 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30 30 * SUCH DAMAGE.
31 31 */
32 32
33 33 /*
34 34 * Copyright 2012 Nexenta Systems, Inc. All rights reserved.
35 35 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
36 36 * Use is subject to license terms.
37 37 */
38 38
39 39 #include <sys/types.h>
40 40 #include <sys/param.h>
41 41 #include <sys/errno.h>
42 42 #include <sys/sysmacros.h>
43 43 #include <sys/uio.h>
44 44 #include <sys/buf.h>
45 45 #include <sys/modctl.h>
46 46 #include <sys/open.h>
47 47 #include <sys/file.h>
48 48 #include <sys/kmem.h>
49 49 #include <sys/conf.h>
50 50 #include <sys/cmn_err.h>
51 51 #include <sys/stat.h>
52 52 #include <sys/ddi.h>
53 53 #include <sys/sunddi.h>
54 54 #include <sys/sunldi.h>
55 55 #include <sys/policy.h>
56 56 #include <sys/zone.h>
57 57 #include <sys/pathname.h>
58 58 #include <sys/mount.h>
59 59 #include <sys/sdt.h>
60 60 #include <fs/fs_subr.h>
61 61 #include <sys/modctl.h>
62 62 #include <sys/devops.h>
63 63 #include <sys/thread.h>
64 64 #include <sys/types.h>
65 65 #include <sys/zone.h>
66 66
67 67 #include <netsmb/smb_osdep.h>
68 68 #include <netsmb/mchain.h> /* for "htoles()" */
69 69
70 70 #include <netsmb/smb.h>
71 71 #include <netsmb/smb_conn.h>
72 72 #include <netsmb/smb_subr.h>
73 73 #include <netsmb/smb_dev.h>
74 74 #include <netsmb/smb_pass.h>
75 75
76 76 #define NSMB_MIN_MINOR 1
77 77 #define NSMB_MAX_MINOR L_MAXMIN32
78 78
79 79 /* for version checks */
80 80 const uint32_t nsmb_version = NSMB_VERSION;
81 81
82 82 static void *statep;
83 83 static major_t nsmb_major;
84 84 static minor_t last_minor = NSMB_MIN_MINOR;
85 85 static dev_info_t *nsmb_dip;
86 86 static kmutex_t dev_lck;
87 87
88 88 /* Zone support */
89 89 zone_key_t nsmb_zone_key;
90 90 extern void nsmb_zone_shutdown(zoneid_t zoneid, void *data);
91 91 extern void nsmb_zone_destroy(zoneid_t zoneid, void *data);
92 92
93 93 /*
94 94 * cb_ops device operations.
95 95 */
96 96 static int nsmb_open(dev_t *devp, int flag, int otyp, cred_t *credp);
97 97 static int nsmb_close(dev_t dev, int flag, int otyp, cred_t *credp);
98 98 static int nsmb_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
99 99 cred_t *credp, int *rvalp);
100 100 static int nsmb_close2(smb_dev_t *sdp, cred_t *cr);
101 101
102 102 /* smbfs cb_ops */
103 103 static struct cb_ops nsmb_cbops = {
104 104 nsmb_open, /* open */
105 105 nsmb_close, /* close */
106 106 nodev, /* strategy */
107 107 nodev, /* print */
108 108 nodev, /* dump */
109 109 nodev, /* read */
110 110 nodev, /* write */
111 111 nsmb_ioctl, /* ioctl */
112 112 nodev, /* devmap */
113 113 nodev, /* mmap */
114 114 nodev, /* segmap */
115 115 nochpoll, /* poll */
116 116 ddi_prop_op, /* prop_op */
117 117 NULL, /* stream */
118 118 D_MP, /* cb_flag */
119 119 CB_REV, /* rev */
120 120 nodev, /* int (*cb_aread)() */
121 121 nodev /* int (*cb_awrite)() */
122 122 };
123 123
124 124 /*
125 125 * Device options
126 126 */
127 127 static int nsmb_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
128 128 static int nsmb_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
129 129 static int nsmb_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd,
130 130 void *arg, void **result);
131 131
132 132 static struct dev_ops nsmb_ops = {
133 133 DEVO_REV, /* devo_rev, */
134 134 0, /* refcnt */
135 135 nsmb_getinfo, /* info */
136 136 nulldev, /* identify */
137 137 nulldev, /* probe */
138 138 nsmb_attach, /* attach */
139 139 nsmb_detach, /* detach */
140 140 nodev, /* reset */
141 141 &nsmb_cbops, /* driver ops - devctl interfaces */
142 142 NULL, /* bus operations */
143 143 NULL, /* power */
144 144 ddi_quiesce_not_needed, /* quiesce */
145 145 };
146 146
147 147 /*
148 148 * Module linkage information.
↓ open down ↓ |
148 lines elided |
↑ open up ↑ |
149 149 */
150 150
151 151 static struct modldrv nsmb_modldrv = {
152 152 &mod_driverops, /* Driver module */
153 153 "SMBFS network driver",
154 154 &nsmb_ops /* Driver ops */
155 155 };
156 156
157 157 static struct modlinkage nsmb_modlinkage = {
158 158 MODREV_1,
159 - (void *)&nsmb_modldrv,
160 - NULL
159 + { (void *)&nsmb_modldrv, NULL }
161 160 };
162 161
163 162 int
164 163 _init(void)
165 164 {
166 165 int error;
167 166
168 167 (void) ddi_soft_state_init(&statep, sizeof (smb_dev_t), 1);
169 168
170 169 /* Can initialize some mutexes also. */
171 170 mutex_init(&dev_lck, NULL, MUTEX_DRIVER, NULL);
172 171
173 172 /* Connection data structures. */
174 173 (void) smb_sm_init();
175 174
176 175 /* Initialize password Key chain DB. */
177 176 smb_pkey_init();
178 177
179 178 /* Time conversion stuff. */
180 179 smb_time_init();
181 180
182 181 /* Initialize crypto mechanisms. */
183 182 smb_crypto_mech_init();
184 183
185 184 zone_key_create(&nsmb_zone_key, NULL, nsmb_zone_shutdown,
186 185 nsmb_zone_destroy);
187 186
188 187 /*
189 188 * Install the module. Do this after other init,
190 189 * to prevent entrances before we're ready.
191 190 */
192 191 if ((error = mod_install((&nsmb_modlinkage))) != 0) {
193 192
194 193 /* Same as 2nd half of _fini */
195 194 (void) zone_key_delete(nsmb_zone_key);
196 195 smb_pkey_fini();
197 196 smb_sm_done();
198 197 mutex_destroy(&dev_lck);
199 198 ddi_soft_state_fini(&statep);
200 199
201 200 return (error);
202 201 }
203 202
204 203 return (0);
205 204 }
206 205
207 206 int
208 207 _fini(void)
209 208 {
210 209 int status;
211 210
212 211 /*
213 212 * Prevent unload if we have active VCs
214 213 * or stored passwords
215 214 */
216 215 if ((status = smb_sm_idle()) != 0)
217 216 return (status);
218 217 if ((status = smb_pkey_idle()) != 0)
219 218 return (status);
220 219
221 220 /*
222 221 * Remove the module. Do this before destroying things,
223 222 * to prevent new entrances while we're destorying.
224 223 */
225 224 if ((status = mod_remove(&nsmb_modlinkage)) != 0) {
226 225 return (status);
227 226 }
228 227
229 228 (void) zone_key_delete(nsmb_zone_key);
230 229
231 230 /* Time conversion stuff. */
232 231 smb_time_fini();
233 232
234 233 /* Destroy password Key chain DB. */
235 234 smb_pkey_fini();
236 235
237 236 smb_sm_done();
238 237
239 238 mutex_destroy(&dev_lck);
240 239 ddi_soft_state_fini(&statep);
241 240
242 241 return (status);
243 242 }
244 243
245 244 int
246 245 _info(struct modinfo *modinfop)
247 246 {
248 247 return (mod_info(&nsmb_modlinkage, modinfop));
249 248 }
250 249
251 250 /*ARGSUSED*/
252 251 static int
253 252 nsmb_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
254 253 {
255 254 int ret = DDI_SUCCESS;
256 255
257 256 switch (cmd) {
258 257 case DDI_INFO_DEVT2DEVINFO:
259 258 *result = nsmb_dip;
260 259 break;
261 260 case DDI_INFO_DEVT2INSTANCE:
262 261 *result = NULL;
263 262 break;
264 263 default:
265 264 ret = DDI_FAILURE;
266 265 }
267 266 return (ret);
268 267 }
269 268
270 269 static int
271 270 nsmb_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
272 271 {
273 272
274 273 if (cmd != DDI_ATTACH)
275 274 return (DDI_FAILURE);
276 275
277 276 /*
278 277 * We only support only one "instance". Note that
279 278 * "instances" are different from minor units.
280 279 * We get one (unique) minor unit per open.
281 280 */
282 281 if (ddi_get_instance(dip) > 0)
283 282 return (DDI_FAILURE);
284 283
285 284 if (ddi_create_minor_node(dip, "nsmb", S_IFCHR, 0, DDI_PSEUDO,
286 285 NULL) == DDI_FAILURE) {
287 286 cmn_err(CE_WARN, "nsmb_attach: create minor");
288 287 return (DDI_FAILURE);
289 288 }
290 289
291 290 /*
292 291 * We need the major number a couple places,
293 292 * i.e. in smb_dev2share()
294 293 */
295 294 nsmb_major = ddi_name_to_major(NSMB_NAME);
296 295
297 296 nsmb_dip = dip;
298 297 ddi_report_dev(dip);
299 298 return (DDI_SUCCESS);
300 299 }
301 300
302 301 /*ARGSUSED*/
303 302 static int
304 303 nsmb_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
305 304 {
306 305
307 306 if (cmd != DDI_DETACH)
308 307 return (DDI_FAILURE);
309 308 if (ddi_get_instance(dip) > 0)
310 309 return (DDI_FAILURE);
311 310
312 311 nsmb_dip = NULL;
313 312 ddi_remove_minor_node(dip, NULL);
314 313
315 314 return (DDI_SUCCESS);
316 315 }
317 316
318 317 /*ARGSUSED*/
319 318 static int
320 319 nsmb_ioctl(dev_t dev, int cmd, intptr_t arg, int flags, /* model.h */
321 320 cred_t *cr, int *rvalp)
322 321 {
323 322 smb_dev_t *sdp;
324 323 int err;
325 324
326 325 sdp = ddi_get_soft_state(statep, getminor(dev));
327 326 if (sdp == NULL) {
328 327 return (DDI_FAILURE);
329 328 }
330 329 if ((sdp->sd_flags & NSMBFL_OPEN) == 0) {
331 330 return (EBADF);
332 331 }
333 332
334 333 /*
335 334 * Dont give access if the zone id is not as the same as we
336 335 * set in the nsmb_open or dont belong to the global zone.
337 336 * Check if the user belongs to this zone..
338 337 */
339 338 if (sdp->zoneid != getzoneid())
340 339 return (EIO);
341 340
342 341 /*
343 342 * We have a zone_shutdown call back that kills all the VCs
344 343 * in a zone that's shutting down. That action will cause
345 344 * all of these ioctls to fail on such VCs, so no need to
346 345 * check the zone status here on every ioctl call.
347 346 */
348 347
349 348 err = 0;
350 349 switch (cmd) {
351 350 case SMBIOC_GETVERS:
352 351 (void) ddi_copyout(&nsmb_version, (void *)arg,
353 352 sizeof (nsmb_version), flags);
354 353 break;
355 354
356 355 case SMBIOC_FLAGS2:
357 356 err = smb_usr_get_flags2(sdp, arg, flags);
358 357 break;
359 358
360 359 case SMBIOC_GETSSNKEY:
361 360 err = smb_usr_get_ssnkey(sdp, arg, flags);
362 361 break;
363 362
364 363 case SMBIOC_DUP_DEV:
365 364 err = smb_usr_dup_dev(sdp, arg, flags);
366 365 break;
367 366
368 367 case SMBIOC_REQUEST:
369 368 err = smb_usr_simplerq(sdp, arg, flags, cr);
370 369 break;
371 370
372 371 case SMBIOC_T2RQ:
373 372 err = smb_usr_t2request(sdp, arg, flags, cr);
374 373 break;
375 374
376 375 case SMBIOC_READ:
377 376 case SMBIOC_WRITE:
378 377 err = smb_usr_rw(sdp, cmd, arg, flags, cr);
379 378 break;
380 379
381 380 case SMBIOC_NTCREATE:
382 381 err = smb_usr_ntcreate(sdp, arg, flags, cr);
383 382 break;
384 383
385 384 case SMBIOC_PRINTJOB:
386 385 err = smb_usr_printjob(sdp, arg, flags, cr);
387 386 break;
388 387
389 388 case SMBIOC_CLOSEFH:
390 389 err = smb_usr_closefh(sdp, cr);
391 390 break;
392 391
393 392 case SMBIOC_SSN_CREATE:
394 393 case SMBIOC_SSN_FIND:
395 394 err = smb_usr_get_ssn(sdp, cmd, arg, flags, cr);
396 395 break;
397 396
398 397 case SMBIOC_SSN_KILL:
399 398 case SMBIOC_SSN_RELE:
400 399 err = smb_usr_drop_ssn(sdp, cmd);
401 400 break;
402 401
403 402 case SMBIOC_TREE_CONNECT:
404 403 case SMBIOC_TREE_FIND:
405 404 err = smb_usr_get_tree(sdp, cmd, arg, flags, cr);
406 405 break;
407 406
408 407 case SMBIOC_TREE_KILL:
409 408 case SMBIOC_TREE_RELE:
410 409 err = smb_usr_drop_tree(sdp, cmd);
411 410 break;
412 411
413 412 case SMBIOC_IOD_WORK:
414 413 err = smb_usr_iod_work(sdp, arg, flags, cr);
415 414 break;
416 415
417 416 case SMBIOC_IOD_IDLE:
418 417 case SMBIOC_IOD_RCFAIL:
419 418 err = smb_usr_iod_ioctl(sdp, cmd, arg, flags);
420 419 break;
421 420
422 421 case SMBIOC_PK_ADD:
423 422 case SMBIOC_PK_DEL:
424 423 case SMBIOC_PK_CHK:
425 424 case SMBIOC_PK_DEL_OWNER:
426 425 case SMBIOC_PK_DEL_EVERYONE:
427 426 err = smb_pkey_ioctl(cmd, arg, flags, cr);
428 427 break;
429 428
430 429 default:
431 430 err = ENOTTY;
432 431 break;
433 432 }
434 433
435 434 return (err);
436 435 }
437 436
438 437 /*
439 438 * This does "clone" open, meaning it automatically
440 439 * assigns an available minor unit for each open.
441 440 */
442 441 /*ARGSUSED*/
443 442 static int
444 443 nsmb_open(dev_t *dev, int flags, int otyp, cred_t *cr)
445 444 {
446 445 smb_dev_t *sdp;
447 446 minor_t m;
448 447
449 448 mutex_enter(&dev_lck);
450 449
451 450 for (m = last_minor + 1; m != last_minor; m++) {
452 451 if (m > NSMB_MAX_MINOR)
453 452 m = NSMB_MIN_MINOR;
454 453
455 454 if (ddi_get_soft_state(statep, m) == NULL) {
456 455 last_minor = m;
457 456 goto found;
458 457 }
459 458 }
460 459
461 460 /* No available minor units. */
462 461 mutex_exit(&dev_lck);
463 462 return (ENXIO);
464 463
465 464 found:
466 465 /* NB: dev_lck still held */
467 466 if (ddi_soft_state_zalloc(statep, m) == DDI_FAILURE) {
468 467 mutex_exit(&dev_lck);
469 468 return (ENXIO);
470 469 }
471 470 if ((sdp = ddi_get_soft_state(statep, m)) == NULL) {
472 471 mutex_exit(&dev_lck);
473 472 return (ENXIO);
474 473 }
475 474 *dev = makedevice(nsmb_major, m);
476 475 mutex_exit(&dev_lck);
477 476
478 477 sdp->sd_cred = cr;
479 478 sdp->sd_smbfid = -1;
480 479 sdp->sd_flags |= NSMBFL_OPEN;
481 480 sdp->zoneid = crgetzoneid(cr);
482 481
483 482 return (0);
484 483 }
485 484
486 485 /*ARGSUSED*/
487 486 static int
488 487 nsmb_close(dev_t dev, int flags, int otyp, cred_t *cr)
489 488 {
490 489 minor_t inst = getminor(dev);
491 490 smb_dev_t *sdp;
492 491 int err;
493 492
494 493 /*
495 494 * 1. Check the validity of the minor number.
496 495 * 2. Release any shares/vc associated with the connection.
497 496 * 3. Can close the minor number.
498 497 * 4. Deallocate any resources allocated in open() call.
499 498 */
500 499
501 500 sdp = ddi_get_soft_state(statep, inst);
502 501 if (sdp != NULL)
503 502 err = nsmb_close2(sdp, cr);
504 503 else
505 504 err = ENXIO;
506 505
507 506 /*
508 507 * Free the instance
509 508 */
510 509 mutex_enter(&dev_lck);
511 510 ddi_soft_state_free(statep, inst);
512 511 mutex_exit(&dev_lck);
513 512 return (err);
514 513 }
515 514
516 515 static int
517 516 nsmb_close2(smb_dev_t *sdp, cred_t *cr)
518 517 {
519 518 struct smb_vc *vcp;
520 519 struct smb_share *ssp;
521 520
522 521 if (sdp->sd_smbfid != -1)
523 522 (void) smb_usr_closefh(sdp, cr);
524 523
525 524 ssp = sdp->sd_share;
526 525 if (ssp != NULL)
527 526 smb_share_rele(ssp);
528 527
529 528 vcp = sdp->sd_vc;
530 529 if (vcp != NULL) {
531 530 /*
532 531 * If this dev minor was opened by smbiod,
533 532 * mark this VC as "dead" because it now
534 533 * will have no IOD to service it.
535 534 */
536 535 if (sdp->sd_flags & NSMBFL_IOD)
537 536 smb_iod_disconnect(vcp);
538 537 smb_vc_rele(vcp);
539 538 }
540 539
541 540 return (0);
542 541 }
543 542
544 543 /*
545 544 * Helper for SMBIOC_DUP_DEV
546 545 * Duplicate state from the FD @arg ("from") onto
547 546 * the FD for this device instance.
548 547 */
549 548 int
550 549 smb_usr_dup_dev(smb_dev_t *sdp, intptr_t arg, int flags)
551 550 {
552 551 file_t *fp = NULL;
553 552 vnode_t *vp;
554 553 smb_dev_t *from_sdp;
555 554 dev_t dev;
556 555 int32_t ufd;
557 556 int err;
558 557
559 558 /* Should be no VC */
560 559 if (sdp->sd_vc != NULL)
561 560 return (EISCONN);
562 561
563 562 /*
564 563 * Get from_sdp (what we will duplicate)
565 564 */
566 565 if (ddi_copyin((void *) arg, &ufd, sizeof (ufd), flags))
567 566 return (EFAULT);
568 567 if ((fp = getf(ufd)) == NULL)
569 568 return (EBADF);
570 569 /* rele fp below */
571 570 vp = fp->f_vnode;
572 571 dev = vp->v_rdev;
573 572 if (dev == 0 || dev == NODEV ||
574 573 getmajor(dev) != nsmb_major) {
575 574 err = EINVAL;
576 575 goto out;
577 576 }
578 577 from_sdp = ddi_get_soft_state(statep, getminor(dev));
579 578 if (from_sdp == NULL) {
580 579 err = EINVAL;
581 580 goto out;
582 581 }
583 582
584 583 /*
585 584 * Duplicate VC and share references onto this FD.
586 585 */
587 586 if ((sdp->sd_vc = from_sdp->sd_vc) != NULL)
588 587 smb_vc_hold(sdp->sd_vc);
589 588 if ((sdp->sd_share = from_sdp->sd_share) != NULL)
590 589 smb_share_hold(sdp->sd_share);
591 590 sdp->sd_level = from_sdp->sd_level;
592 591 err = 0;
593 592
594 593 out:
595 594 if (fp)
596 595 releasef(ufd);
597 596 return (err);
598 597 }
599 598
600 599
601 600 /*
602 601 * Helper used by smbfs_mount
603 602 */
604 603 int
605 604 smb_dev2share(int fd, struct smb_share **sspp)
606 605 {
607 606 file_t *fp = NULL;
608 607 vnode_t *vp;
609 608 smb_dev_t *sdp;
610 609 smb_share_t *ssp;
611 610 dev_t dev;
612 611 int err;
613 612
614 613 if ((fp = getf(fd)) == NULL)
615 614 return (EBADF);
616 615 /* rele fp below */
617 616
618 617 vp = fp->f_vnode;
619 618 dev = vp->v_rdev;
620 619 if (dev == 0 || dev == NODEV ||
621 620 getmajor(dev) != nsmb_major) {
622 621 err = EINVAL;
623 622 goto out;
624 623 }
625 624
626 625 sdp = ddi_get_soft_state(statep, getminor(dev));
627 626 if (sdp == NULL) {
628 627 err = EINVAL;
629 628 goto out;
630 629 }
631 630
632 631 ssp = sdp->sd_share;
633 632 if (ssp == NULL) {
634 633 err = ENOTCONN;
635 634 goto out;
636 635 }
637 636
638 637 /*
639 638 * Our caller gains a ref. to this share.
640 639 */
641 640 *sspp = ssp;
642 641 smb_share_hold(ssp);
643 642 err = 0;
644 643
645 644 out:
646 645 if (fp)
647 646 releasef(fd);
648 647 return (err);
649 648 }
↓ open down ↓ |
479 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX