Print this page
NEX-7823 ipmgmtd can't properly remove interface from the old ipadm.conf format
Reviewed by: Dan Fields <dan.fields@nexenta.com>
Reviewed by: Jean McCormack <jean.mccormack@nexenta.com>
OS-161: Integrate IPMP changes
| Split |
Close |
| Expand all |
| Collapse all |
--- old/usr/src/cmd/cmd-inet/lib/ipmgmtd/ipmgmt_door.c
+++ new/usr/src/cmd/cmd-inet/lib/ipmgmtd/ipmgmt_door.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
|
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
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 /*
23 23 * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
24 + * Copyright 2016 Nexenta Systems, Inc.
24 25 * Copyright (c) 2016-2017, Chris Fraire <cfraire@me.com>.
25 26 */
26 27
27 28 /*
28 29 * Main door handler functions used by ipmgmtd to process the different door
29 30 * call requests, issued by the library libipadm.so.
30 31 */
31 32
32 33 #include <alloca.h>
33 34 #include <pwd.h>
34 35 #include <auth_attr.h>
|
↓ open down ↓ |
1 lines elided |
↑ open up ↑ |
35 36 #include <secdb.h>
36 37 #include <stdlib.h>
37 38 #include <stdio.h>
38 39 #include <string.h>
39 40 #include <strings.h>
40 41 #include <errno.h>
41 42 #include <assert.h>
42 43 #include <libnvpair.h>
43 44 #include "ipmgmt_impl.h"
44 45
46 +
47 +static void ipmgmt_common_handler(char *, char *, db_wfunc_t *);
48 +
45 49 /* Handler declaration for each door command */
46 50 typedef void ipmgmt_door_handler_t(void *argp);
47 51
48 52 static ipmgmt_door_handler_t ipmgmt_getaddr_handler,
49 53 ipmgmt_getprop_handler,
50 54 ipmgmt_getif_handler,
51 55 ipmgmt_initif_handler,
52 56 ipmgmt_aobjop_handler,
53 57 ipmgmt_resetaddr_handler,
54 58 ipmgmt_setif_handler,
55 59 ipmgmt_resetif_handler,
56 60 ipmgmt_resetprop_handler,
57 61 ipmgmt_setaddr_handler,
58 - ipmgmt_setprop_handler;
62 + ipmgmt_setprop_handler,
63 + ipmgmt_ipmp_update_handler;
59 64
60 65 typedef struct ipmgmt_door_info_s {
61 66 uint_t idi_cmd;
62 67 boolean_t idi_set;
63 68 ipmgmt_door_handler_t *idi_handler;
64 69 } ipmgmt_door_info_t;
65 70
66 71 /* maps door commands to door handler functions */
67 72 static ipmgmt_door_info_t i_ipmgmt_door_info_tbl[] = {
68 73 { IPMGMT_CMD_SETPROP, B_TRUE, ipmgmt_setprop_handler },
69 74 { IPMGMT_CMD_SETIF, B_TRUE, ipmgmt_setif_handler },
70 75 { IPMGMT_CMD_SETADDR, B_TRUE, ipmgmt_setaddr_handler },
71 76 { IPMGMT_CMD_GETPROP, B_FALSE, ipmgmt_getprop_handler },
72 77 { IPMGMT_CMD_GETIF, B_FALSE, ipmgmt_getif_handler },
|
↓ open down ↓ |
4 lines elided |
↑ open up ↑ |
73 78 { IPMGMT_CMD_GETADDR, B_FALSE, ipmgmt_getaddr_handler },
74 79 { IPMGMT_CMD_RESETIF, B_TRUE, ipmgmt_resetif_handler },
75 80 { IPMGMT_CMD_RESETADDR, B_TRUE, ipmgmt_resetaddr_handler },
76 81 { IPMGMT_CMD_RESETPROP, B_TRUE, ipmgmt_resetprop_handler },
77 82 { IPMGMT_CMD_INITIF, B_TRUE, ipmgmt_initif_handler },
78 83 { IPMGMT_CMD_ADDROBJ_LOOKUPADD, B_TRUE, ipmgmt_aobjop_handler },
79 84 { IPMGMT_CMD_ADDROBJ_SETLIFNUM, B_TRUE, ipmgmt_aobjop_handler },
80 85 { IPMGMT_CMD_ADDROBJ_ADD, B_TRUE, ipmgmt_aobjop_handler },
81 86 { IPMGMT_CMD_AOBJNAME2ADDROBJ, B_FALSE, ipmgmt_aobjop_handler },
82 87 { IPMGMT_CMD_LIF2ADDROBJ, B_FALSE, ipmgmt_aobjop_handler },
88 + { IPMGMT_CMD_IPMP_UPDATE, B_FALSE, ipmgmt_ipmp_update_handler},
83 89 { 0, 0, NULL },
84 90 };
85 91
86 92 /*
87 93 * The main server procedure function that gets invoked for any of the incoming
88 94 * door commands. Inside this function we identify the incoming command and
89 95 * invoke the right door handler function.
90 96 */
91 97 /* ARGSUSED */
92 98 void
93 99 ipmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp,
94 100 uint_t n_desc)
95 101 {
96 102 ipmgmt_door_info_t *infop = NULL;
97 103 ipmgmt_retval_t retval;
98 104 int i;
99 105 uint_t err;
100 106 ucred_t *cred = NULL;
101 107
102 108 for (i = 0; i_ipmgmt_door_info_tbl[i].idi_cmd != 0; i++) {
103 109 if (i_ipmgmt_door_info_tbl[i].idi_cmd ==
104 110 ((ipmgmt_arg_t *)(void *)argp)->ia_cmd) {
105 111 infop = &i_ipmgmt_door_info_tbl[i];
106 112 break;
107 113 }
108 114 }
109 115
110 116 if (infop == NULL) {
111 117 ipmgmt_log(LOG_ERR, "Invalid door command specified");
112 118 err = EINVAL;
113 119 goto fail;
114 120 }
115 121
116 122 /* check for solaris.network.interface.config authorization */
117 123 if (infop->idi_set) {
118 124 uid_t uid;
119 125 struct passwd pwd;
120 126 char buf[1024];
121 127
122 128 if (door_ucred(&cred) != 0) {
123 129 err = errno;
124 130 ipmgmt_log(LOG_ERR, "Could not get user credentials.");
125 131 goto fail;
126 132 }
127 133 uid = ucred_getruid(cred);
128 134 if ((int)uid < 0) {
129 135 err = errno;
130 136 ipmgmt_log(LOG_ERR, "Could not get user id.");
131 137 goto fail;
132 138 }
133 139 if (getpwuid_r(uid, &pwd, buf, sizeof (buf)) ==
134 140 NULL) {
135 141 err = errno;
136 142 ipmgmt_log(LOG_ERR, "Could not get password entry.");
137 143 goto fail;
138 144 }
139 145 if (chkauthattr(NETWORK_INTERFACE_CONFIG_AUTH,
140 146 pwd.pw_name) != 1) {
141 147 err = EPERM;
142 148 ipmgmt_log(LOG_ERR, "Not authorized for operation.");
143 149 goto fail;
144 150 }
145 151 ucred_free(cred);
146 152 }
147 153
148 154 /* individual handlers take care of calling door_return */
149 155 infop->idi_handler((void *)argp);
150 156 return;
151 157 fail:
152 158 ucred_free(cred);
153 159 retval.ir_err = err;
154 160 (void) door_return((char *)&retval, sizeof (retval), NULL, 0);
155 161 }
156 162
157 163 /*
158 164 * Handles the door command IPMGMT_CMD_GETPROP. It retrieves the persisted
159 165 * property value for the given property.
160 166 */
161 167 static void
162 168 ipmgmt_getprop_handler(void *argp)
163 169 {
164 170 ipmgmt_prop_arg_t *pargp = argp;
165 171 ipmgmt_getprop_rval_t rval, *rvalp = &rval;
166 172
167 173 assert(pargp->ia_cmd == IPMGMT_CMD_GETPROP);
168 174
169 175 rvalp->ir_err = ipmgmt_db_walk(ipmgmt_db_getprop, pargp, IPADM_DB_READ);
170 176 if (rvalp->ir_err == 0)
171 177 (void) strlcpy(rvalp->ir_pval, pargp->ia_pval,
172 178 sizeof (rvalp->ir_pval));
173 179 (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
174 180 }
175 181
176 182 /*
177 183 * Handles the door command IPMGMT_CMD_SETPROP. It persists the property value
178 184 * for the given property in the DB.
179 185 */
180 186 static void
181 187 ipmgmt_setprop_handler(void *argp)
182 188 {
183 189 ipmgmt_prop_arg_t *pargp = argp;
184 190 ipmgmt_retval_t rval;
185 191 ipadm_dbwrite_cbarg_t cb;
186 192 nvlist_t *nvl = NULL;
187 193 int err;
188 194
189 195 assert(pargp->ia_cmd == IPMGMT_CMD_SETPROP);
190 196
191 197 if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
192 198 goto fail;
193 199 if (pargp->ia_module[0] != '\0' &&
194 200 (err = nvlist_add_string(nvl, IPADM_NVP_PROTONAME,
195 201 pargp->ia_module)) != 0) {
196 202 goto fail;
197 203 }
198 204 if (pargp->ia_ifname[0] != '\0' &&
199 205 (err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
200 206 pargp->ia_ifname)) != 0)
201 207 goto fail;
202 208 if (pargp->ia_aobjname[0] != '\0' &&
203 209 (err = nvlist_add_string(nvl, IPADM_NVP_AOBJNAME,
204 210 pargp->ia_aobjname)) != 0)
205 211 goto fail;
206 212 if ((err = nvlist_add_string(nvl, pargp->ia_pname,
207 213 pargp->ia_pval)) != 0)
208 214 goto fail;
209 215
210 216 cb.dbw_nvl = nvl;
211 217 cb.dbw_flags = pargp->ia_flags;
212 218 err = ipmgmt_db_walk(ipmgmt_db_update, &cb, IPADM_DB_WRITE);
213 219 fail:
214 220 nvlist_free(nvl);
215 221 rval.ir_err = err;
216 222 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
217 223 }
218 224
219 225 /*
220 226 * Helper function for ipmgmt_setaddr_handler().
221 227 * It converts the nvlist_t, `nvl', to aobjmap node `nodep'.
222 228 */
223 229 static int
224 230 i_ipmgmt_nvl2aobjnode(nvlist_t *nvl, ipmgmt_aobjmap_t *nodep)
225 231 {
226 232 char *aobjname = NULL, *ifname = NULL;
227 233 int32_t lnum;
228 234 nvlist_t *nvladdr;
229 235 sa_family_t af = AF_UNSPEC;
230 236 ipadm_addr_type_t addrtype = IPADM_ADDR_NONE;
231 237 int err = 0;
232 238
233 239 /*
234 240 * Retrieve all the information needed to build '*nodep' from
235 241 * nvlist_t nvl.
236 242 */
237 243 if ((err = nvlist_lookup_string(nvl, IPADM_NVP_AOBJNAME,
238 244 &aobjname)) != 0 ||
239 245 (err = nvlist_lookup_string(nvl, IPADM_NVP_IFNAME, &ifname)) != 0 ||
240 246 (err = nvlist_lookup_int32(nvl, IPADM_NVP_LIFNUM, &lnum)) != 0) {
241 247 return (err);
242 248 }
243 249 if (nvlist_exists(nvl, IPADM_NVP_IPV4ADDR)) {
244 250 af = AF_INET;
245 251 addrtype = IPADM_ADDR_STATIC;
246 252 } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_DHCP, &nvladdr) == 0) {
247 253 char *reqhost;
248 254
249 255 af = AF_INET;
250 256 addrtype = IPADM_ADDR_DHCP;
251 257
252 258 /*
253 259 * ipmgmt_am_reqhost comes through in `nvl' for purposes of
254 260 * updating the cached representation, but it is persisted as
255 261 * a stand-alone DB line; so remove it after copying it.
256 262 */
257 263 if (!nvlist_exists(nvl, IPADM_NVP_REQHOST)) {
258 264 *nodep->ipmgmt_am_reqhost = '\0';
259 265 } else {
260 266 if ((err = nvlist_lookup_string(nvl, IPADM_NVP_REQHOST,
261 267 &reqhost)) != 0)
262 268 return (err);
263 269
264 270 (void) strlcpy(nodep->ipmgmt_am_reqhost, reqhost,
265 271 sizeof (nodep->ipmgmt_am_reqhost));
266 272 (void) nvlist_remove(nvl, IPADM_NVP_REQHOST,
267 273 DATA_TYPE_STRING);
268 274 }
269 275 } else if (nvlist_exists(nvl, IPADM_NVP_IPV6ADDR)) {
270 276 af = AF_INET6;
271 277 addrtype = IPADM_ADDR_STATIC;
272 278 } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_INTFID, &nvladdr) == 0) {
273 279 struct sockaddr_in6 sin6 = {0};
274 280 uint8_t *addr6;
275 281 uint32_t plen;
276 282 uint_t n;
277 283
278 284 af = AF_INET6;
279 285 addrtype = IPADM_ADDR_IPV6_ADDRCONF;
280 286 if (nvlist_lookup_uint32(nvladdr, IPADM_NVP_PREFIXLEN,
281 287 &plen) != 0)
282 288 return (EINVAL);
283 289 if (plen != 0) {
284 290 if (nvlist_lookup_uint8_array(nvladdr,
285 291 IPADM_NVP_IPNUMADDR, &addr6, &n) != 0)
286 292 return (EINVAL);
287 293 bcopy(addr6, &sin6.sin6_addr, n);
288 294 }
289 295
290 296 nodep->ipmgmt_am_linklocal = B_TRUE;
291 297 nodep->ipmgmt_am_ifid = sin6;
292 298 }
293 299
294 300 /*
295 301 * populate the non-addrtype-specific `*nodep' with retrieved values.
296 302 */
297 303 (void) strlcpy(nodep->am_ifname, ifname, sizeof (nodep->am_ifname));
298 304 (void) strlcpy(nodep->am_aobjname, aobjname,
299 305 sizeof (nodep->am_aobjname));
300 306 nodep->am_lnum = lnum;
301 307 nodep->am_family = af;
302 308 nodep->am_atype = addrtype;
303 309 nodep->am_next = NULL;
304 310
305 311 /*
306 312 * Do not store logical interface number in persistent store as it
307 313 * takes different value on reboot. So remove it from `nvl'.
308 314 */
309 315 if (nvlist_exists(nvl, IPADM_NVP_LIFNUM))
310 316 (void) nvlist_remove(nvl, IPADM_NVP_LIFNUM, DATA_TYPE_INT32);
311 317
312 318 return (0);
313 319 }
314 320
315 321 /*
316 322 * Handles the door command IPMGMT_CMD_SETADDR. It adds a new address object
317 323 * node to the list `aobjmap' and optionally persists the address
318 324 * information in the DB.
319 325 */
320 326 static void
321 327 ipmgmt_setaddr_handler(void *argp)
322 328 {
323 329 ipmgmt_setaddr_arg_t *sargp = argp;
324 330 ipmgmt_retval_t rval;
325 331 ipmgmt_aobjmap_t node = {0};
326 332 nvlist_t *nvl = NULL;
327 333 char *nvlbuf;
328 334 size_t nvlsize = sargp->ia_nvlsize;
329 335 uint32_t flags = sargp->ia_flags;
330 336 int err = 0;
331 337
332 338 nvlbuf = (char *)argp + sizeof (ipmgmt_setaddr_arg_t);
333 339 if ((err = nvlist_unpack(nvlbuf, nvlsize, &nvl, NV_ENCODE_NATIVE)) != 0)
334 340 goto ret;
335 341 if (flags & (IPMGMT_ACTIVE|IPMGMT_INIT)) {
336 342 if ((err = i_ipmgmt_nvl2aobjnode(nvl, &node)) != 0)
337 343 goto ret;
338 344 if (flags & IPMGMT_INIT)
339 345 node.am_flags = (IPMGMT_ACTIVE|IPMGMT_PERSIST);
340 346 else
341 347 node.am_flags = flags & ~IPMGMT_PROPS_ONLY;
342 348 if ((err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD)) != 0)
343 349 goto ret;
344 350 }
345 351 if ((flags & IPMGMT_PERSIST) && !(flags & IPMGMT_PROPS_ONLY)) {
346 352 ipadm_dbwrite_cbarg_t cb;
347 353
348 354 cb.dbw_nvl = nvl;
349 355 cb.dbw_flags = 0;
350 356 err = ipmgmt_db_walk(ipmgmt_db_add, &cb, IPADM_DB_WRITE);
351 357 }
352 358 ret:
353 359 nvlist_free(nvl);
354 360 rval.ir_err = err;
355 361 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
356 362 }
357 363
358 364 /*
359 365 * Handles the door commands that read or modify the `aobjmap' structure.
360 366 *
361 367 * IPMGMT_CMD_ADDROBJ_LOOKUPADD - places a stub address object in `aobjmap'
362 368 * after ensuring that the namespace is not taken. If required, also
363 369 * generates an `aobjname' for address object for the library to use.
364 370 * IPMGMT_CMD_ADDROBJ_ADD - add/update address object in `aobjmap'
365 371 * IPMGMT_CMD_LIF2ADDROBJ - given a logical interface, return address object
366 372 * associated with that logical interface.
367 373 * IPMGMT_CMD_AOBJNAME2ADDROBJ - given an address object name return logical
368 374 * interface associated with that address object.
369 375 */
370 376 static void
371 377 ipmgmt_aobjop_handler(void *argp)
372 378 {
373 379 ipmgmt_aobjop_arg_t *largp = argp;
374 380 ipmgmt_retval_t rval;
375 381 ipmgmt_aobjop_rval_t aobjrval;
376 382 void *rvalp;
377 383 size_t rsize;
378 384 ipmgmt_aobjmap_t node;
379 385 int err = 0;
380 386 char *ifname = largp->ia_ifname;
381 387 char *aobjname = largp->ia_aobjname;
382 388 int32_t lnum = largp->ia_lnum;
383 389 sa_family_t af = largp->ia_family;
384 390 ipadm_addr_type_t atype = largp->ia_atype;
385 391 ipmgmt_aobjmap_t *head;
386 392
387 393 switch (largp->ia_cmd) {
388 394 case IPMGMT_CMD_ADDROBJ_LOOKUPADD:
389 395 rsize = sizeof (ipmgmt_aobjop_rval_t);
390 396 rvalp = &aobjrval;
391 397 bzero(&node, sizeof (node));
392 398 (void) strlcpy(node.am_aobjname, aobjname,
393 399 sizeof (node.am_aobjname));
394 400 (void) strlcpy(node.am_ifname, ifname,
395 401 sizeof (node.am_ifname));
396 402 node.am_family = af;
397 403 node.am_atype = atype;
398 404 /* no logical number is associated with this addrobj yet */
399 405 node.am_lnum = -1;
400 406 /* The address object is not persisted yet. */
401 407 node.am_flags = IPMGMT_ACTIVE;
402 408 err = ipmgmt_aobjmap_op(&node, ADDROBJ_LOOKUPADD);
403 409 if (err == 0) {
404 410 (void) strlcpy(aobjrval.ir_aobjname, node.am_aobjname,
405 411 sizeof (aobjrval.ir_aobjname));
406 412 }
407 413 break;
408 414 case IPMGMT_CMD_ADDROBJ_SETLIFNUM:
409 415 rsize = sizeof (ipmgmt_retval_t);
410 416 rvalp = &rval;
411 417 bzero(&node, sizeof (node));
412 418 (void) strlcpy(node.am_aobjname, aobjname,
413 419 sizeof (node.am_aobjname));
414 420 (void) strlcpy(node.am_ifname, ifname,
415 421 sizeof (node.am_ifname));
416 422 node.am_family = af;
417 423 node.am_lnum = lnum;
418 424 err = ipmgmt_aobjmap_op(&node, ADDROBJ_SETLIFNUM);
419 425 break;
420 426 case IPMGMT_CMD_ADDROBJ_ADD:
421 427 rsize = sizeof (ipmgmt_retval_t);
422 428 rvalp = &rval;
423 429 if (aobjname[0] == '\0' || ifname[0] == '\0' || lnum == -1 ||
424 430 af == AF_UNSPEC) {
425 431 err = EINVAL;
426 432 break;
427 433 }
428 434 bzero(&node, sizeof (node));
429 435 (void) strlcpy(node.am_aobjname, aobjname,
430 436 sizeof (node.am_aobjname));
431 437 (void) strlcpy(node.am_ifname, ifname,
432 438 sizeof (node.am_ifname));
433 439 node.am_atype = atype;
434 440 node.am_lnum = lnum;
435 441 node.am_family = af;
436 442 /* The address object is not persisted. */
437 443 node.am_flags = IPMGMT_ACTIVE;
438 444 err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD);
439 445 break;
440 446 case IPMGMT_CMD_AOBJNAME2ADDROBJ:
441 447 rsize = sizeof (ipmgmt_aobjop_rval_t);
442 448 rvalp = &aobjrval;
443 449 bzero(&aobjrval, sizeof (aobjrval));
444 450 if (aobjname[0] == '\0') {
445 451 err = EINVAL;
446 452 break;
447 453 }
448 454 (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock);
449 455 head = aobjmap.aobjmap_head;
450 456 for (; head; head = head->am_next) {
451 457 if (strcmp(head->am_aobjname, aobjname) != 0)
452 458 continue;
453 459 /*
454 460 * For an auto-configured interface, return
455 461 * the lifnum that has the link-local on it.
456 462 * Other logical interfaces were created for
457 463 * prefixes and dhcpv6 addresses and do not
458 464 * have am_ifid set.
459 465 */
460 466 if (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF ||
461 467 head->ipmgmt_am_linklocal) {
462 468 break;
463 469 }
464 470 }
465 471 if (head == NULL) {
466 472 err = ENOENT;
467 473 (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
468 474 break;
469 475 }
470 476 (void) strlcpy(aobjrval.ir_ifname, head->am_ifname,
471 477 sizeof (aobjrval.ir_ifname));
472 478 aobjrval.ir_lnum = head->am_lnum;
473 479 aobjrval.ir_family = head->am_family;
474 480 aobjrval.ir_flags = head->am_flags;
475 481 aobjrval.ir_atype = head->am_atype;
476 482 aobjrval.ir_atype_cache = head->am_atype_cache;
477 483 (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
478 484 break;
479 485 case IPMGMT_CMD_LIF2ADDROBJ:
480 486 rsize = sizeof (ipmgmt_aobjop_rval_t);
481 487 rvalp = &aobjrval;
482 488 bzero(&aobjrval, sizeof (aobjrval));
483 489 if (ifname[0] == '\0') {
484 490 err = EINVAL;
485 491 break;
486 492 }
487 493 (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock);
488 494 head = aobjmap.aobjmap_head;
489 495 for (; head; head = head->am_next) {
490 496 if (strcmp(head->am_ifname, ifname) == 0 &&
491 497 head->am_lnum == lnum &&
492 498 head->am_family == af) {
493 499 break;
494 500 }
495 501 }
496 502 if (head == NULL) {
497 503 err = ENOENT;
498 504 (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
499 505 break;
500 506 }
501 507 (void) strlcpy(aobjrval.ir_aobjname, head->am_aobjname,
502 508 sizeof (aobjrval.ir_aobjname));
503 509 aobjrval.ir_atype = head->am_atype;
504 510 aobjrval.ir_flags = head->am_flags;
505 511 aobjrval.ir_atype_cache = head->am_atype_cache;
506 512 (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
507 513 break;
508 514 default:
509 515 rsize = sizeof (ipmgmt_retval_t);
510 516 rvalp = &rval;
511 517 err = EINVAL;
512 518 }
513 519 ((ipmgmt_retval_t *)rvalp)->ir_err = err;
514 520 (void) door_return((char *)rvalp, rsize, NULL, 0);
515 521 }
516 522
517 523 /*
518 524 * Given an interface name and family, deletes all the address objects
519 525 * associated with it.
520 526 */
521 527 void
522 528 i_ipmgmt_delif_aobjs(char *ifname, sa_family_t af, uint32_t flags)
523 529 {
524 530 ipmgmt_aobjmap_t *head, *next, *prev;
525 531 ipadm_db_op_t db_op;
526 532
527 533 prev = NULL;
528 534
529 535 (void) pthread_rwlock_wrlock(&aobjmap.aobjmap_rwlock);
530 536 head = aobjmap.aobjmap_head;
531 537 for (; head; head = next) {
532 538 next = head->am_next;
533 539 if (strcmp(head->am_ifname, ifname) != 0 ||
534 540 head->am_family != af) {
535 541 prev = head;
536 542 continue;
537 543 }
538 544
539 545 if (head->am_flags == (IPMGMT_ACTIVE|IPMGMT_PERSIST) &&
540 546 flags == IPMGMT_ACTIVE) {
541 547 /*
542 548 * If the addres is present in both active and
543 549 * persistent store, and if we are performing
544 550 * a temporary delete, we update the node to
545 551 * indicate that the address is only present in
546 552 * persistent store and we proceed. Otherwise
547 553 * we always delete the node from aobjmap.
548 554 */
549 555 head->am_flags &= ~IPMGMT_ACTIVE;
550 556 head->am_lnum = -1;
551 557 db_op = IPADM_DB_WRITE;
552 558 } else {
553 559 db_op = IPADM_DB_DELETE;
554 560 if (prev == NULL)
555 561 aobjmap.aobjmap_head = next;
556 562 else
557 563 prev->am_next = next;
558 564 }
559 565 (void) ipmgmt_persist_aobjmap(head, db_op);
560 566 if (db_op == IPADM_DB_DELETE)
561 567 free(head);
562 568 }
563 569 (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock);
564 570 }
565 571
566 572 /*
567 573 * Handles the door command IPMGMT_CMD_SETIF. It persists the interface
568 574 * information in the DB.
569 575 */
570 576 static void
571 577 ipmgmt_setif_handler(void *argp)
572 578 {
573 579 ipmgmt_retval_t rval;
574 580
575 581 rval.ir_err = ipmgmt_persist_if(argp);
576 582 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
577 583 }
578 584
579 585 /*
580 586 * Handles the door command IPMGMT_CMD_RESETIF. For the given interface,
581 587 * deletes all the persisted interface configuration. It also deletes, from
582 588 * `aobjmap', all the address objects configured on the given interface.
583 589 */
584 590 static void
|
↓ open down ↓ |
492 lines elided |
↑ open up ↑ |
585 591 ipmgmt_resetif_handler(void *argp)
586 592 {
587 593 ipmgmt_if_arg_t *rargp = argp;
588 594 ipmgmt_retval_t rval;
589 595 ipmgmt_if_cbarg_t cbarg;
590 596 uint32_t flags = rargp->ia_flags;
591 597 int err = 0;
592 598
593 599 cbarg.cb_family = rargp->ia_family;
594 600 cbarg.cb_ifname = rargp->ia_ifname;
601 +
602 + cbarg.cb_ipv4exists = B_TRUE;
603 + cbarg.cb_ipv6exists = B_TRUE;
604 +
595 605 if (flags & IPMGMT_PERSIST)
596 606 err = ipmgmt_db_walk(ipmgmt_db_resetif, &cbarg,
597 607 IPADM_DB_DELETE);
598 608
599 609 if (flags & IPMGMT_ACTIVE)
600 610 i_ipmgmt_delif_aobjs(rargp->ia_ifname, rargp->ia_family,
601 611 flags);
602 612
603 613 rval.ir_err = err;
604 614 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
605 615 }
606 616
607 617 /*
608 618 * Handles the door command IPMGMT_CMD_RESETADDR. For the given addrobj
609 619 * deletes all the persisted addrobj configuration. It also deletes the
610 620 * corresponding node, from `aobjmap'.
611 621 */
612 622 static void
613 623 ipmgmt_resetaddr_handler(void *argp)
614 624 {
615 625 ipmgmt_addr_arg_t *rargp = argp;
616 626 ipmgmt_retval_t rval;
617 627 ipmgmt_aobjmap_t node;
618 628 uint32_t flags = rargp->ia_flags;
619 629 int err = 0;
620 630 ipmgmt_resetaddr_cbarg_t cbarg;
621 631
622 632 cbarg.cb_aobjname = rargp->ia_aobjname;
623 633
624 634 if (flags & IPMGMT_PERSIST)
625 635 err = ipmgmt_db_walk(ipmgmt_db_resetaddr, &cbarg,
626 636 IPADM_DB_DELETE);
627 637
628 638 if (flags & IPMGMT_ACTIVE) {
629 639 bzero(&node, sizeof (node));
630 640 (void) strlcpy(node.am_aobjname, rargp->ia_aobjname,
631 641 sizeof (node.am_aobjname));
632 642
633 643 /*
634 644 * am_lnum is used only for IPv6 autoconf case, since there
635 645 * can be multiple nodes with the same aobjname.
636 646 */
637 647 node.am_lnum = rargp->ia_lnum;
638 648 node.am_flags = flags;
639 649 (void) ipmgmt_aobjmap_op(&node, ADDROBJ_DELETE);
640 650 }
641 651
642 652 rval.ir_err = err;
643 653 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
644 654 }
645 655
|
↓ open down ↓ |
41 lines elided |
↑ open up ↑ |
646 656 /*
647 657 * Handles the door command IPMGMT_CMD_GETADDR. It retrieves the persisted
648 658 * address for a given `gargp->ia_aobjname'. If it is not defined then it
649 659 * retrieves all the addresses configured on `gargp->ia_ifname'. The
650 660 * "ipadm show-addr addrobj" or "ipadm show-addr <ifname>/\*" will call this
651 661 * handler through library.
652 662 */
653 663 static void
654 664 ipmgmt_getaddr_handler(void *argp)
655 665 {
656 - size_t buflen, onvlsize;
657 - char *buf, *onvlbuf;
658 - ipmgmt_getaddr_arg_t *gargp = argp;
659 - ipmgmt_getaddr_cbarg_t cbarg;
660 - ipmgmt_get_rval_t rval, *rvalp = &rval;
661 - int err = 0;
666 + ipmgmt_getaddr_arg_t *gargp = argp;
662 667
663 - cbarg.cb_ifname = gargp->ia_ifname;
664 - cbarg.cb_aobjname = gargp->ia_aobjname;
665 - cbarg.cb_ocnt = 0;
666 - if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0)
667 - goto fail;
668 - err = ipmgmt_db_walk(ipmgmt_db_getaddr, &cbarg, IPADM_DB_READ);
669 - if (err == ENOENT && cbarg.cb_ocnt > 0) {
670 - /*
671 - * If there is atleast one entry in the nvlist,
672 - * do not return error.
673 - */
674 - err = 0;
675 - }
676 - if (err != 0)
677 - goto fail;
678 -
679 - if ((err = nvlist_size(cbarg.cb_onvl, &onvlsize,
680 - NV_ENCODE_NATIVE)) != 0) {
681 - goto fail;
682 - }
683 - buflen = onvlsize + sizeof (ipmgmt_get_rval_t);
684 - /*
685 - * We cannot use malloc() here because door_return never returns, and
686 - * memory allocated by malloc() would get leaked. Use alloca() instead.
687 - */
688 - buf = alloca(buflen);
689 - onvlbuf = buf + sizeof (ipmgmt_get_rval_t);
690 - if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf, &onvlsize,
691 - NV_ENCODE_NATIVE, 0)) != 0) {
692 - goto fail;
693 - }
694 - nvlist_free(cbarg.cb_onvl);
695 - rvalp = (ipmgmt_get_rval_t *)(void *)buf;
696 - rvalp->ir_err = 0;
697 - rvalp->ir_nvlsize = onvlsize;
698 -
699 - (void) door_return(buf, buflen, NULL, 0);
700 - return;
701 -fail:
702 - nvlist_free(cbarg.cb_onvl);
703 - rvalp->ir_err = err;
704 - (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
668 + ipmgmt_common_handler(gargp->ia_ifname, gargp->ia_aobjname,
669 + ipmgmt_db_getaddr);
705 670 }
706 671
707 672 /*
708 673 * Handles the door command IPMGMT_CMD_RESETPROP. It deletes the property line
709 674 * from the DB.
710 675 */
711 676 static void
712 677 ipmgmt_resetprop_handler(void *argp)
713 678 {
714 679 ipmgmt_prop_arg_t *pargp = argp;
715 680 ipmgmt_retval_t rval;
716 681
717 682 assert(pargp->ia_cmd == IPMGMT_CMD_RESETPROP);
718 683
719 684 rval.ir_err = ipmgmt_db_walk(ipmgmt_db_resetprop, pargp,
720 685 IPADM_DB_DELETE);
721 686 (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
722 687 }
723 688
724 689 /*
725 - * Handles the door command IPMGMT_CMD_GETIF. It retrieves the name of all the
726 - * persisted interfaces and the IP protocols (IPv4 or IPv6) they support.
690 + * Handles the door command IPMGMT_CMD_GETIF. It retrieves names of all
691 + * persisted interfaces and the IP protocol families (IPv4 or IPv6) they
692 + * support. Returns the info as a nvlist using door_return() from
693 + * ipmgmt_common_handler().
727 694 */
728 695 static void
729 696 ipmgmt_getif_handler(void *argp)
730 697 {
731 - ipmgmt_getif_arg_t *getif = argp;
732 - ipmgmt_getif_rval_t *rvalp;
733 - ipmgmt_retval_t rval;
734 - ipmgmt_getif_cbarg_t cbarg;
735 - ipadm_if_info_t *ifp, *rifp, *curifp;
736 - int i, err = 0, count = 0;
737 - size_t rbufsize;
698 + ipmgmt_getif_arg_t *getif = argp;
738 699
739 700 assert(getif->ia_cmd == IPMGMT_CMD_GETIF);
740 701
741 - bzero(&cbarg, sizeof (cbarg));
742 - cbarg.cb_ifname = getif->ia_ifname;
743 - err = ipmgmt_db_walk(ipmgmt_db_getif, &cbarg, IPADM_DB_READ);
744 - if (err == ENOENT && cbarg.cb_ifinfo) {
745 - /*
746 - * If there is atleast one entry in the nvlist,
747 - * do not return error.
748 - */
749 - err = 0;
750 - }
751 - if (err != 0) {
752 - rval.ir_err = err;
753 - (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
754 - return;
755 - }
756 -
757 - /* allocate sufficient buffer to return the interface info */
758 - for (ifp = cbarg.cb_ifinfo; ifp != NULL; ifp = ifp->ifi_next)
759 - ++count;
760 - rbufsize = sizeof (*rvalp) + count * sizeof (*ifp);
761 - rvalp = alloca(rbufsize);
762 - bzero(rvalp, rbufsize);
763 -
764 - rvalp->ir_ifcnt = count;
765 - rifp = rvalp->ir_ifinfo;
766 - ifp = cbarg.cb_ifinfo;
767 -
768 - /*
769 - * copy the interface info to buffer allocated on stack. The reason
770 - * we do this is to avoid memory leak, as door_return() would never
771 - * return
772 - */
773 - for (i = 0; i < count; i++) {
774 - rifp = rvalp->ir_ifinfo + i;
775 - (void) bcopy(ifp, rifp, sizeof (*rifp));
776 - rifp->ifi_next = NULL;
777 - curifp = ifp->ifi_next;
778 - free(ifp);
779 - ifp = curifp;
780 - }
781 - rvalp->ir_err = err;
782 - (void) door_return((char *)rvalp, rbufsize, NULL, 0);
702 + ipmgmt_common_handler(getif->ia_ifname, NULL,
703 + ipmgmt_db_getif);
783 704 }
784 705
785 706 /*
786 707 * Handles the door command IPMGMT_CMD_INITIF. It retrieves all the persisted
787 708 * interface configuration (interface properties and addresses), for all those
788 709 * interfaces that need to be initialized.
789 710 */
790 711 static void
791 712 ipmgmt_initif_handler(void *argp)
792 713 {
793 714 ipmgmt_initif_arg_t *initif = argp;
794 715 size_t buflen, nvlsize;
795 716 char *buf = NULL, *onvlbuf, *invlbuf;
796 717 ipmgmt_get_rval_t rval, *rvalp = &rval;
797 718 ipmgmt_initif_cbarg_t cbarg;
798 719 int err;
799 720
800 721 assert(initif->ia_cmd == IPMGMT_CMD_INITIF);
801 722
802 723 bzero(&cbarg, sizeof (cbarg));
803 724 invlbuf = (char *)argp + sizeof (ipmgmt_initif_arg_t);
804 725 nvlsize = initif->ia_nvlsize;
805 726 err = nvlist_unpack(invlbuf, nvlsize, &cbarg.cb_invl, NV_ENCODE_NATIVE);
|
↓ open down ↓ |
13 lines elided |
↑ open up ↑ |
806 727 if (err != 0)
807 728 goto fail;
808 729
809 730 cbarg.cb_family = initif->ia_family;
810 731 if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0)
811 732 goto fail;
812 733
813 734 err = ipmgmt_db_walk(ipmgmt_db_initif, &cbarg, IPADM_DB_READ);
814 735 if (err == ENOENT && cbarg.cb_ocnt > 0) {
815 736 /*
816 - * If there is atleast one entry in the nvlist,
737 + * If there is at least one entry in the nvlist,
817 738 * do not return error.
818 739 */
819 740 err = 0;
820 741 }
821 742 if (err != 0)
822 743 goto fail;
823 744
824 745 if ((err = nvlist_size(cbarg.cb_onvl, &nvlsize, NV_ENCODE_NATIVE)) != 0)
825 746 goto fail;
826 747 buflen = nvlsize + sizeof (ipmgmt_get_rval_t);
827 748 /*
828 749 * We cannot use malloc() here because door_return never returns, and
829 750 * memory allocated by malloc() would get leaked. Use alloca() instead.
830 751 */
831 752 buf = alloca(buflen);
832 753 onvlbuf = buf + sizeof (ipmgmt_get_rval_t);
833 754 if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf, &nvlsize,
834 755 NV_ENCODE_NATIVE, 0)) != 0) {
835 756 goto fail;
836 757 }
837 758 nvlist_free(cbarg.cb_invl);
838 759 nvlist_free(cbarg.cb_onvl);
839 760 rvalp = (ipmgmt_get_rval_t *)(void *)buf;
840 761 rvalp->ir_err = 0;
841 762 rvalp->ir_nvlsize = nvlsize;
842 763
843 764 (void) door_return(buf, buflen, NULL, 0);
844 765 return;
845 766 fail:
|
↓ open down ↓ |
19 lines elided |
↑ open up ↑ |
846 767 nvlist_free(cbarg.cb_invl);
847 768 nvlist_free(cbarg.cb_onvl);
848 769 rvalp->ir_err = err;
849 770 (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
850 771 }
851 772
852 773 int
853 774 ipmgmt_persist_if(ipmgmt_if_arg_t *sargp)
854 775 {
855 776 ipadm_dbwrite_cbarg_t cb;
856 - uint32_t flags = sargp->ia_flags;
857 - nvlist_t *nvl = NULL;
858 - int err = 0;
859 - char strval[IPMGMT_STRSIZE];
777 + uint32_t flags = sargp->ia_flags;
778 + nvlist_t *nvl = NULL;
779 + char strval[IPMGMT_STRSIZE];
780 + int err = 0;
860 781
861 782 if (!(flags & IPMGMT_PERSIST) || sargp->ia_family == AF_UNSPEC ||
862 783 sargp->ia_ifname[0] == '\0') {
863 784 err = EINVAL;
864 785 goto ret;
865 786 }
866 787 if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
867 788 goto ret;
789 +
868 790 if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
869 791 sargp->ia_ifname)) != 0)
870 792 goto ret;
871 - (void) snprintf(strval, IPMGMT_STRSIZE, "%d", sargp->ia_family);
872 - if ((err = nvlist_add_string(nvl, IPADM_NVP_FAMILY, strval)) != 0)
793 +
794 + if ((err = ipmgmt_update_family_nvp(nvl, sargp->ia_family,
795 + IPMGMT_APPEND)) != 0)
873 796 goto ret;
797 +
798 + (void) snprintf(strval, IPMGMT_STRSIZE, "%d", sargp->ia_ifclass);
799 + if ((err = nvlist_add_string(nvl, IPADM_NVP_IFCLASS, strval)) != 0)
800 + goto ret;
801 +
874 802 cb.dbw_nvl = nvl;
875 - cb.dbw_flags = 0;
876 - err = ipmgmt_db_walk(ipmgmt_db_add, &cb, IPADM_DB_WRITE);
803 + cb.dbw_flags = IPMGMT_APPEND | IPMGMT_UPDATE_IF;
804 + err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE);
877 805 ret:
878 806 nvlist_free(nvl);
879 807 return (err);
808 +}
809 +
810 +/*
811 + * The helper for ipmgmt_getif_handler and ipmgmt_getaddr_handler
812 + */
813 +static void
814 +ipmgmt_common_handler(char *if_name, char *aobj_name, db_wfunc_t worker)
815 +{
816 + ipmgmt_get_rval_t rval, *rvalp = &rval;
817 + ipmgmt_get_cbarg_t cbarg;
818 + int err = 0;
819 + size_t buflen, onvlsize;
820 + char *buf, *onvlbuf;
821 +
822 + cbarg.cb_ifname = if_name;
823 + cbarg.cb_aobjname = aobj_name;
824 + cbarg.cb_ocnt = 0;
825 +
826 + if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0)
827 + goto fail;
828 +
829 + err = ipmgmt_db_walk(worker, &cbarg, IPADM_DB_READ);
830 + if (err == ENOENT && cbarg.cb_ocnt > 0) {
831 + /*
832 + * If there is atleast one entry in the nvlist,
833 + * do not return error.
834 + */
835 + err = 0;
836 + }
837 + if (err != 0)
838 + goto fail;
839 +
840 + if ((err = nvlist_size(cbarg.cb_onvl, &onvlsize,
841 + NV_ENCODE_NATIVE)) != 0)
842 + goto fail;
843 +
844 + buflen = onvlsize + sizeof (ipmgmt_get_rval_t);
845 + /* We cannot use malloc() here because door_return never returns */
846 + buf = alloca(buflen);
847 + onvlbuf = buf + sizeof (ipmgmt_get_rval_t);
848 + if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf,
849 + &onvlsize, NV_ENCODE_NATIVE, 0)) != 0)
850 + goto fail;
851 +
852 + nvlist_free(cbarg.cb_onvl);
853 + rvalp = (ipmgmt_get_rval_t *)(void *)buf;
854 + rvalp->ir_err = 0;
855 + rvalp->ir_nvlsize = onvlsize;
856 +
857 + (void) door_return(buf, buflen, NULL, 0);
858 +
859 +fail:
860 + nvlist_free(cbarg.cb_onvl);
861 + rvalp->ir_err = err;
862 + (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0);
863 +}
864 +
865 +/*
866 + * Handles the door command IPMGMT_CMD_IPMP_UPDATE
867 + */
868 +static void
869 +ipmgmt_ipmp_update_handler(void *argp)
870 +{
871 + ipmgmt_ipmp_update_arg_t *uargp = argp;
872 + ipmgmt_retval_t rval;
873 + ipadm_dbwrite_cbarg_t cb;
874 +
875 + boolean_t gif_exists;
876 + char gifname[LIFNAMSIZ];
877 + nvlist_t *nvl = NULL;
878 + uint32_t flags = uargp->ia_flags;
879 + int err = 0;
880 +
881 + assert(uargp->ia_cmd == IPMGMT_CMD_IPMP_UPDATE);
882 +
883 + gif_exists = ipmgmt_persist_if_exists(uargp->ia_gifname,
884 + AF_UNSPEC);
885 +
886 + if (!ipmgmt_persist_if_exists(uargp->ia_mifname, AF_UNSPEC)) {
887 + err = EINVAL;
888 + goto ret;
889 + }
890 +
891 + ipmgmt_get_group_interface(uargp->ia_mifname, gifname, LIFNAMSIZ);
892 +
893 + if (flags & IPMGMT_APPEND) {
894 + /* Group interface should be available in the DB */
895 + if (!gif_exists) {
896 + err = ENOENT;
897 + goto ret;
898 + }
899 +
900 + if (gifname[0] != '\0') {
901 + err = EEXIST;
902 + goto ret;
903 + }
904 + }
905 +
906 + if (flags & IPMGMT_REMOVE) {
907 + /* We cannot remove something that does not exist */
908 + if (!gif_exists || gifname[0] == '\0') {
909 + err = ENOENT;
910 + goto ret;
911 + }
912 + if (strcmp(uargp->ia_gifname, gifname) != 0) {
913 + err = EINVAL;
914 + goto ret;
915 + }
916 + }
917 +
918 + if (flags & IPMGMT_PERSIST) {
919 + if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0)
920 + goto ret;
921 +
922 + if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME,
923 + uargp->ia_gifname)) != 0)
924 + goto ret;
925 +
926 + if ((err = nvlist_add_string(nvl, IPADM_NVP_MIFNAMES,
927 + uargp->ia_mifname)) != 0)
928 + goto ret;
929 +
930 + if ((err = nvlist_add_string(nvl, IPADM_NVP_GIFNAME,
931 + uargp->ia_gifname)) != 0)
932 + goto ret;
933 +
934 + cb.dbw_nvl = nvl;
935 + cb.dbw_flags = flags | IPMGMT_UPDATE_IF | IPMGMT_UPDATE_IPMP;
936 + err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE);
937 + }
938 +ret:
939 + nvlist_free(nvl);
940 + rval.ir_err = err;
941 + (void) door_return((char *)&rval, sizeof (rval), NULL, 0);
880 942 }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX