Print this page
7127 remove -Wno-missing-braces from Makefile.uts
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/uts/common/fs/nfs/nfs_common.c
+++ new/usr/src/uts/common/fs/nfs/nfs_common.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) 1990, 2010, Oracle and/or its affiliates. All rights reserved.
23 23 * Copyright (c) 2011 Bayard G. Bell. All rights reserved.
24 24 * Copyright 2013 Joyent, Inc. All rights reserved.
25 25 */
26 26
27 27 /*
28 28 * Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T
29 29 * All rights reserved.
30 30 */
31 31
32 32 #include <sys/errno.h>
33 33 #include <sys/param.h>
34 34 #include <sys/types.h>
35 35 #include <sys/user.h>
36 36 #include <sys/stat.h>
37 37 #include <sys/time.h>
38 38 #include <sys/utsname.h>
39 39 #include <sys/vfs.h>
40 40 #include <sys/vfs_opreg.h>
41 41 #include <sys/vnode.h>
42 42 #include <sys/pathname.h>
43 43 #include <sys/bootconf.h>
44 44 #include <fs/fs_subr.h>
45 45 #include <rpc/types.h>
46 46 #include <nfs/nfs.h>
47 47 #include <nfs/nfs4.h>
48 48 #include <nfs/nfs_clnt.h>
49 49 #include <nfs/rnode.h>
50 50 #include <nfs/mount.h>
51 51 #include <nfs/nfssys.h>
52 52 #include <sys/debug.h>
53 53 #include <sys/cmn_err.h>
54 54 #include <sys/file.h>
55 55 #include <sys/fcntl.h>
56 56 #include <sys/zone.h>
57 57
58 58 /*
59 59 * This is the loadable module wrapper.
60 60 */
61 61 #include <sys/systm.h>
62 62 #include <sys/modctl.h>
63 63 #include <sys/syscall.h>
64 64 #include <sys/ddi.h>
65 65
66 66 #include <rpc/types.h>
67 67 #include <rpc/auth.h>
68 68 #include <rpc/clnt.h>
69 69 #include <rpc/svc.h>
70 70
71 71 /*
72 72 * The pseudo NFS filesystem to allow diskless booting to dynamically
73 73 * mount either a NFS V2, NFS V3, or NFS V4 filesystem. This only implements
74 74 * the VFS_MOUNTROOT op and is only intended to be used by the
75 75 * diskless booting code until the real root filesystem is mounted.
76 76 * Nothing else should ever call this!
77 77 *
78 78 * The strategy is that if the initial rootfs type is set to "nfsdyn"
79 79 * by loadrootmodules() this filesystem is called to mount the
80 80 * root filesystem. It first attempts to mount a V4 filesystem, and if that
81 81 * fails due to an RPC version mismatch it tries V3 and finally V2.
82 82 * Once the real mount succeeds the vfsops and rootfs name are changed
83 83 * to reflect the real filesystem type.
84 84 */
85 85 static int nfsdyninit(int, char *);
86 86 static int nfsdyn_mountroot(vfs_t *, whymountroot_t);
87 87
88 88 vfsops_t *nfsdyn_vfsops;
89 89
90 90 /*
91 91 * The following data structures are used to configure the NFS
92 92 * system call, the NFS Version 2 client VFS, and the NFS Version
93 93 * 3 client VFS into the system. The NFS Version 4 structures are defined in
94 94 * nfs4_common.c
95 95 */
96 96
97 97 /*
98 98 * The NFS system call.
99 99 */
100 100 static struct sysent nfssysent = {
101 101 2,
102 102 SE_32RVAL1 | SE_ARGC | SE_NOUNLOAD,
103 103 nfssys
104 104 };
105 105
106 106 static struct modlsys modlsys = {
107 107 &mod_syscallops,
108 108 "NFS syscall, client, and common",
109 109 &nfssysent
110 110 };
111 111
112 112 #ifdef _SYSCALL32_IMPL
113 113 static struct modlsys modlsys32 = {
114 114 &mod_syscallops32,
115 115 "NFS syscall, client, and common (32-bit)",
116 116 &nfssysent
117 117 };
118 118 #endif /* _SYSCALL32_IMPL */
119 119
120 120 /*
121 121 * The NFS Dynamic client VFS.
122 122 */
123 123 static vfsdef_t vfw = {
124 124 VFSDEF_VERSION,
125 125 "nfsdyn",
126 126 nfsdyninit,
127 127 0,
128 128 NULL
129 129 };
130 130
131 131 static struct modlfs modlfs = {
132 132 &mod_fsops,
133 133 "network filesystem",
134 134 &vfw
135 135 };
136 136
137 137 /*
138 138 * The NFS Version 2 client VFS.
139 139 */
140 140 static vfsdef_t vfw2 = {
141 141 VFSDEF_VERSION,
142 142 "nfs",
143 143 nfsinit,
144 144 VSW_CANREMOUNT|VSW_NOTZONESAFE|VSW_STATS,
145 145 NULL
146 146 };
147 147
148 148 static struct modlfs modlfs2 = {
149 149 &mod_fsops,
150 150 "network filesystem version 2",
151 151 &vfw2
152 152 };
153 153
154 154 /*
155 155 * The NFS Version 3 client VFS.
156 156 */
157 157 static vfsdef_t vfw3 = {
158 158 VFSDEF_VERSION,
159 159 "nfs3",
160 160 nfs3init,
161 161 VSW_CANREMOUNT|VSW_NOTZONESAFE|VSW_STATS,
162 162 NULL
163 163 };
164 164
165 165 static struct modlfs modlfs3 = {
166 166 &mod_fsops,
167 167 "network filesystem version 3",
168 168 &vfw3
169 169 };
170 170
171 171 extern struct modlfs modlfs4;
172 172
173 173 /*
174 174 * We have too many linkage structures so we define our own XXX
175 175 */
176 176 struct modlinkage_big {
177 177 int ml_rev; /* rev of loadable modules system */
↓ open down ↓ |
177 lines elided |
↑ open up ↑ |
178 178 void *ml_linkage[7]; /* NULL terminated list of */
179 179 /* linkage structures */
180 180 };
181 181
182 182 /*
183 183 * All of the module configuration linkages required to configure
184 184 * the system call and client VFS's into the system.
185 185 */
186 186 static struct modlinkage_big modlinkage = {
187 187 MODREV_1,
188 - &modlsys,
188 + { &modlsys,
189 189 #ifdef _SYSCALL32_IMPL
190 - &modlsys32,
190 + &modlsys32,
191 191 #endif
192 - &modlfs,
193 - &modlfs2,
194 - &modlfs3,
195 - &modlfs4,
196 - NULL
192 + &modlfs,
193 + &modlfs2,
194 + &modlfs3,
195 + &modlfs4,
196 + NULL
197 + }
197 198 };
198 199
199 200 /*
200 201 * This routine is invoked automatically when the kernel module
201 202 * containing this routine is loaded. This allows module specific
202 203 * initialization to be done when the module is loaded.
203 204 */
204 205 int
205 206 _init(void)
206 207 {
207 208 int status;
208 209
209 210 if ((status = nfs_clntinit()) != 0) {
210 211 cmn_err(CE_WARN, "_init: nfs_clntinit failed");
211 212 return (status);
212 213 }
213 214
214 215 /*
215 216 * Create the version specific kstats.
216 217 *
217 218 * PSARC 2001/697 Contract Private Interface
218 219 * All nfs kstats are under SunMC contract
219 220 * Please refer to the PSARC listed above and contact
220 221 * SunMC before making any changes!
221 222 *
222 223 * Changes must be reviewed by Solaris File Sharing
223 224 * Changes must be communicated to contract-2001-697@sun.com
224 225 *
225 226 */
226 227
227 228 zone_key_create(&nfsstat_zone_key, nfsstat_zone_init, NULL,
228 229 nfsstat_zone_fini);
229 230 status = mod_install((struct modlinkage *)&modlinkage);
230 231
231 232 if (status) {
232 233 (void) zone_key_delete(nfsstat_zone_key);
233 234
234 235 /*
235 236 * Failed to install module, cleanup previous
236 237 * initialization work.
237 238 */
238 239 nfs_clntfini();
239 240
240 241 /*
241 242 * Clean up work performed indirectly by mod_installfs()
242 243 * as a result of our call to mod_install().
243 244 */
244 245 nfs4fini();
245 246 nfs3fini();
246 247 nfsfini();
247 248 }
248 249 return (status);
249 250 }
250 251
251 252 int
252 253 _fini(void)
253 254 {
254 255 /* Don't allow module to be unloaded */
255 256 return (EBUSY);
256 257 }
257 258
258 259 int
259 260 _info(struct modinfo *modinfop)
260 261 {
261 262 return (mod_info((struct modlinkage *)&modlinkage, modinfop));
262 263 }
263 264
264 265 /*
265 266 * General utilities
266 267 */
267 268
268 269 /*
269 270 * Returns the preferred transfer size in bytes based on
270 271 * what network interfaces are available.
271 272 */
272 273 int
273 274 nfstsize(void)
274 275 {
275 276 /*
276 277 * For the moment, just return NFS_MAXDATA until we can query the
277 278 * appropriate transport.
278 279 */
279 280 return (NFS_MAXDATA);
280 281 }
281 282
282 283 /*
283 284 * Returns the preferred transfer size in bytes based on
284 285 * what network interfaces are available.
285 286 */
286 287
287 288 /* this should reflect the largest transfer size possible */
288 289 static int nfs3_max_transfer_size = 1024 * 1024;
289 290
290 291 int
291 292 nfs3tsize(void)
292 293 {
293 294 /*
294 295 * For the moment, just return nfs3_max_transfer_size until we
295 296 * can query the appropriate transport.
296 297 */
297 298 return (nfs3_max_transfer_size);
298 299 }
299 300
300 301 static uint_t nfs3_max_transfer_size_clts = 32 * 1024;
301 302 static uint_t nfs3_max_transfer_size_cots = 1024 * 1024;
302 303 static uint_t nfs3_max_transfer_size_rdma = 1024 * 1024;
303 304
304 305 uint_t
305 306 nfs3_tsize(struct knetconfig *knp)
306 307 {
307 308
308 309 if (knp->knc_semantics == NC_TPI_COTS_ORD ||
309 310 knp->knc_semantics == NC_TPI_COTS)
310 311 return (nfs3_max_transfer_size_cots);
311 312 if (knp->knc_semantics == NC_TPI_RDMA)
312 313 return (nfs3_max_transfer_size_rdma);
313 314 return (nfs3_max_transfer_size_clts);
314 315 }
315 316
316 317 uint_t
317 318 rfs3_tsize(struct svc_req *req)
318 319 {
319 320
320 321 if (req->rq_xprt->xp_type == T_COTS_ORD ||
321 322 req->rq_xprt->xp_type == T_COTS)
322 323 return (nfs3_max_transfer_size_cots);
↓ open down ↓ |
116 lines elided |
↑ open up ↑ |
323 324 if (req->rq_xprt->xp_type == T_RDMA)
324 325 return (nfs3_max_transfer_size_rdma);
325 326 return (nfs3_max_transfer_size_clts);
326 327 }
327 328
328 329 /* ARGSUSED */
329 330 static int
330 331 nfsdyninit(int fstyp, char *name)
331 332 {
332 333 static const fs_operation_def_t nfsdyn_vfsops_template[] = {
333 - VFSNAME_MOUNTROOT, { .vfs_mountroot = nfsdyn_mountroot },
334 - NULL, NULL
334 + { VFSNAME_MOUNTROOT, { .vfs_mountroot = nfsdyn_mountroot } },
335 + { NULL, { NULL } }
335 336 };
336 337 int error;
337 338
338 339 error = vfs_setfsops(fstyp, nfsdyn_vfsops_template, &nfsdyn_vfsops);
339 340 if (error != 0)
340 341 return (error);
341 342
342 343 return (0);
343 344 }
344 345
345 346 /* ARGSUSED */
346 347 static int
347 348 nfsdyn_mountroot(vfs_t *vfsp, whymountroot_t why)
348 349 {
349 350 char root_hostname[SYS_NMLN+1];
350 351 struct servinfo *svp;
351 352 int error;
352 353 int vfsflags;
353 354 char *root_path;
354 355 struct pathname pn;
355 356 char *name;
356 357 static char token[10];
357 358 struct nfs_args args; /* nfs mount arguments */
358 359
359 360 bzero(&args, sizeof (args));
360 361
361 362 /* do this BEFORE getfile which causes xid stamps to be initialized */
362 363 clkset(-1L); /* hack for now - until we get time svc? */
363 364
364 365 if (why == ROOT_REMOUNT) {
365 366 /*
366 367 * Shouldn't happen.
367 368 */
368 369 panic("nfs3_mountroot: why == ROOT_REMOUNT\n");
369 370 }
370 371
371 372 if (why == ROOT_UNMOUNT) {
372 373 /*
373 374 * Nothing to do for NFS.
374 375 */
375 376 return (0);
376 377 }
377 378
378 379 /*
379 380 * why == ROOT_INIT
380 381 */
381 382
382 383 name = token;
383 384 *name = 0;
384 385 getfsname("root", name, sizeof (token));
385 386
386 387 pn_alloc(&pn);
387 388 root_path = pn.pn_path;
388 389
389 390 svp = kmem_zalloc(sizeof (*svp), KM_SLEEP);
390 391 mutex_init(&svp->sv_lock, NULL, MUTEX_DEFAULT, NULL);
391 392 svp->sv_knconf = kmem_zalloc(sizeof (*svp->sv_knconf), KM_SLEEP);
392 393 svp->sv_knconf->knc_protofmly = kmem_alloc(KNC_STRSIZE, KM_SLEEP);
393 394 svp->sv_knconf->knc_proto = kmem_alloc(KNC_STRSIZE, KM_SLEEP);
394 395
395 396 /*
396 397 * First try version 4
397 398 */
398 399 vfs_setops(vfsp, nfs4_vfsops);
399 400 args.addr = &svp->sv_addr;
400 401 args.fh = (char *)&svp->sv_fhandle;
401 402 args.knconf = svp->sv_knconf;
402 403 args.hostname = root_hostname;
403 404 vfsflags = 0;
404 405
405 406 if (error = mount_root(*name ? name : "root", root_path, NFS_V4,
406 407 &args, &vfsflags)) {
407 408 if (error != EPROTONOSUPPORT) {
408 409 nfs_cmn_err(error, CE_WARN,
409 410 "Unable to mount NFS root filesystem: %m");
410 411 sv_free(svp);
411 412 pn_free(&pn);
412 413 vfs_setops(vfsp, nfsdyn_vfsops);
413 414 return (error);
414 415 }
415 416
416 417 /*
417 418 * Then try version 3
418 419 */
419 420 bzero(&args, sizeof (args));
420 421 vfs_setops(vfsp, nfs3_vfsops);
421 422 args.addr = &svp->sv_addr;
422 423 args.fh = (char *)&svp->sv_fhandle;
423 424 args.knconf = svp->sv_knconf;
424 425 args.hostname = root_hostname;
425 426 vfsflags = 0;
426 427
427 428 if (error = mount_root(*name ? name : "root", root_path,
428 429 NFS_V3, &args, &vfsflags)) {
429 430 if (error != EPROTONOSUPPORT) {
430 431 nfs_cmn_err(error, CE_WARN,
431 432 "Unable to mount NFS root filesystem: %m");
432 433 sv_free(svp);
433 434 pn_free(&pn);
434 435 vfs_setops(vfsp, nfsdyn_vfsops);
435 436 return (error);
436 437 }
437 438
438 439 /*
439 440 * Finally, try version 2
440 441 */
441 442 bzero(&args, sizeof (args));
442 443 args.addr = &svp->sv_addr;
443 444 args.fh = (char *)&svp->sv_fhandle.fh_buf;
444 445 args.knconf = svp->sv_knconf;
445 446 args.hostname = root_hostname;
446 447 vfsflags = 0;
447 448
448 449 vfs_setops(vfsp, nfs_vfsops);
449 450
450 451 if (error = mount_root(*name ? name : "root",
451 452 root_path, NFS_VERSION, &args, &vfsflags)) {
452 453 nfs_cmn_err(error, CE_WARN,
453 454 "Unable to mount NFS root filesystem: %m");
454 455 sv_free(svp);
455 456 pn_free(&pn);
456 457 vfs_setops(vfsp, nfsdyn_vfsops);
457 458 return (error);
458 459 }
459 460 }
460 461 }
461 462
462 463 sv_free(svp);
463 464 pn_free(&pn);
464 465 return (VFS_MOUNTROOT(vfsp, why));
465 466 }
466 467
467 468 int
468 469 nfs_setopts(vnode_t *vp, model_t model, struct nfs_args *buf)
469 470 {
470 471 mntinfo_t *mi; /* mount info, pointed at by vfs */
471 472 STRUCT_HANDLE(nfs_args, args);
472 473 int flags;
473 474
474 475 #ifdef lint
475 476 model = model;
476 477 #endif
477 478
478 479 STRUCT_SET_HANDLE(args, model, buf);
479 480
480 481 flags = STRUCT_FGET(args, flags);
481 482
482 483 /*
483 484 * Set option fields in mount info record
484 485 */
485 486 mi = VTOMI(vp);
486 487
487 488 if (flags & NFSMNT_NOAC) {
488 489 mi->mi_flags |= MI_NOAC;
489 490 PURGE_ATTRCACHE(vp);
490 491 }
491 492 if (flags & NFSMNT_NOCTO)
492 493 mi->mi_flags |= MI_NOCTO;
493 494 if (flags & NFSMNT_LLOCK)
494 495 mi->mi_flags |= MI_LLOCK;
495 496 if (flags & NFSMNT_GRPID)
496 497 mi->mi_flags |= MI_GRPID;
497 498 if (flags & NFSMNT_RETRANS) {
498 499 if (STRUCT_FGET(args, retrans) < 0)
499 500 return (EINVAL);
500 501 mi->mi_retrans = STRUCT_FGET(args, retrans);
501 502 }
502 503 if (flags & NFSMNT_TIMEO) {
503 504 if (STRUCT_FGET(args, timeo) <= 0)
504 505 return (EINVAL);
505 506 mi->mi_timeo = STRUCT_FGET(args, timeo);
506 507 /*
507 508 * The following scales the standard deviation and
508 509 * and current retransmission timer to match the
509 510 * initial value for the timeout specified.
510 511 */
511 512 mi->mi_timers[NFS_CALLTYPES].rt_deviate =
512 513 (mi->mi_timeo * hz * 2) / 5;
513 514 mi->mi_timers[NFS_CALLTYPES].rt_rtxcur =
514 515 mi->mi_timeo * hz / 10;
515 516 }
516 517 if (flags & NFSMNT_RSIZE) {
517 518 if (STRUCT_FGET(args, rsize) <= 0)
518 519 return (EINVAL);
519 520 mi->mi_tsize = MIN(mi->mi_tsize, STRUCT_FGET(args, rsize));
520 521 mi->mi_curread = MIN(mi->mi_curread, mi->mi_tsize);
521 522 }
522 523 if (flags & NFSMNT_WSIZE) {
523 524 if (STRUCT_FGET(args, wsize) <= 0)
524 525 return (EINVAL);
525 526 mi->mi_stsize = MIN(mi->mi_stsize, STRUCT_FGET(args, wsize));
526 527 mi->mi_curwrite = MIN(mi->mi_curwrite, mi->mi_stsize);
527 528 }
528 529 if (flags & NFSMNT_ACREGMIN) {
529 530 if (STRUCT_FGET(args, acregmin) < 0)
530 531 mi->mi_acregmin = ACMINMAX;
531 532 else
532 533 mi->mi_acregmin = MIN(STRUCT_FGET(args, acregmin),
533 534 ACMINMAX);
534 535 mi->mi_acregmin = SEC2HR(mi->mi_acregmin);
535 536 }
536 537 if (flags & NFSMNT_ACREGMAX) {
537 538 if (STRUCT_FGET(args, acregmax) < 0)
538 539 mi->mi_acregmax = ACMAXMAX;
539 540 else
540 541 mi->mi_acregmax = MIN(STRUCT_FGET(args, acregmax),
541 542 ACMAXMAX);
542 543 mi->mi_acregmax = SEC2HR(mi->mi_acregmax);
543 544 }
544 545 if (flags & NFSMNT_ACDIRMIN) {
545 546 if (STRUCT_FGET(args, acdirmin) < 0)
546 547 mi->mi_acdirmin = ACMINMAX;
547 548 else
548 549 mi->mi_acdirmin = MIN(STRUCT_FGET(args, acdirmin),
549 550 ACMINMAX);
550 551 mi->mi_acdirmin = SEC2HR(mi->mi_acdirmin);
551 552 }
552 553 if (flags & NFSMNT_ACDIRMAX) {
553 554 if (STRUCT_FGET(args, acdirmax) < 0)
554 555 mi->mi_acdirmax = ACMAXMAX;
555 556 else
556 557 mi->mi_acdirmax = MIN(STRUCT_FGET(args, acdirmax),
557 558 ACMAXMAX);
558 559 mi->mi_acdirmax = SEC2HR(mi->mi_acdirmax);
559 560 }
560 561
561 562 if (flags & NFSMNT_LOOPBACK)
562 563 mi->mi_flags |= MI_LOOPBACK;
563 564
564 565 return (0);
565 566 }
566 567
567 568 /*
568 569 * Set or Clear direct I/O flag
569 570 * VOP_RWLOCK() is held for write access to prevent a race condition
570 571 * which would occur if a process is in the middle of a write when
571 572 * directio flag gets set. It is possible that all pages may not get flushed.
572 573 */
573 574
574 575 /* ARGSUSED */
575 576 int
576 577 nfs_directio(vnode_t *vp, int cmd, cred_t *cr)
577 578 {
578 579 int error = 0;
579 580 rnode_t *rp;
580 581
581 582 rp = VTOR(vp);
582 583
583 584 if (cmd == DIRECTIO_ON) {
584 585
585 586 if (rp->r_flags & RDIRECTIO)
586 587 return (0);
587 588
588 589 /*
589 590 * Flush the page cache.
590 591 */
591 592
592 593 (void) VOP_RWLOCK(vp, V_WRITELOCK_TRUE, NULL);
593 594
594 595 if (rp->r_flags & RDIRECTIO) {
595 596 VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
596 597 return (0);
597 598 }
598 599
599 600 if (vn_has_cached_data(vp) &&
600 601 ((rp->r_flags & RDIRTY) || rp->r_awcount > 0)) {
601 602 error = VOP_PUTPAGE(vp, (offset_t)0, (uint_t)0,
602 603 B_INVAL, cr, NULL);
603 604 if (error) {
604 605 if (error == ENOSPC || error == EDQUOT) {
605 606 mutex_enter(&rp->r_statelock);
606 607 if (!rp->r_error)
607 608 rp->r_error = error;
608 609 mutex_exit(&rp->r_statelock);
609 610 }
610 611 VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
611 612 return (error);
612 613 }
613 614 }
614 615
615 616 mutex_enter(&rp->r_statelock);
616 617 rp->r_flags |= RDIRECTIO;
617 618 mutex_exit(&rp->r_statelock);
618 619 VOP_RWUNLOCK(vp, V_WRITELOCK_TRUE, NULL);
619 620 return (0);
620 621 }
621 622
622 623 if (cmd == DIRECTIO_OFF) {
623 624 mutex_enter(&rp->r_statelock);
624 625 rp->r_flags &= ~RDIRECTIO; /* disable direct mode */
625 626 mutex_exit(&rp->r_statelock);
626 627 return (0);
627 628 }
628 629
629 630 return (EINVAL);
630 631 }
↓ open down ↓ |
286 lines elided |
↑ open up ↑ |
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX