1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 24 * Use is subject to license terms. 25 */ 26 27 /* 28 * util.c contains a set of miscellaneous utility functions which, 29 * among other things: 30 * - start a child process 31 * - look up the zone name 32 * - look up/set SMF properties 33 * - drop/escalate privs 34 */ 35 36 #include <assert.h> 37 #include <errno.h> 38 #include <inetcfg.h> 39 #include <libdllink.h> 40 #include <limits.h> 41 #include <libscf.h> 42 #include <net/if.h> 43 #include <pthread.h> 44 #include <pwd.h> 45 #include <spawn.h> 46 #include <stdarg.h> 47 #include <stdio.h> 48 #include <stdlib.h> 49 #include <string.h> 50 #include <strings.h> 51 #include <stropts.h> 52 #include <sys/socket.h> 53 #include <sys/sockio.h> 54 #include <sys/types.h> 55 #include <unistd.h> 56 #include <wait.h> 57 #include <zone.h> 58 59 #include "util.h" 60 #include "llp.h" 61 62 extern char **environ; 63 extern sigset_t original_sigmask; 64 65 /* 66 * A holder for all the resources needed to get a property value 67 * using libscf. 68 */ 69 typedef struct scf_resources { 70 scf_handle_t *sr_handle; 71 scf_instance_t *sr_inst; 72 scf_snapshot_t *sr_snap; 73 scf_propertygroup_t *sr_pg; 74 scf_property_t *sr_prop; 75 scf_value_t *sr_val; 76 scf_transaction_t *sr_tx; 77 scf_transaction_entry_t *sr_ent; 78 } scf_resources_t; 79 80 static pthread_mutex_t uid_mutex = PTHREAD_MUTEX_INITIALIZER; 81 static uid_t uid; 82 static int uid_cnt; 83 84 void 85 nwamd_escalate(void) { 86 priv_set_t *priv_set; 87 priv_set = priv_str_to_set("zone", ",", NULL); 88 89 if (priv_set == NULL) 90 pfail("creating privilege set: %s", strerror(errno)); 91 92 (void) pthread_mutex_lock(&uid_mutex); 93 if (uid == 0) 94 uid = getuid(); 95 if (uid_cnt++ == 0) { 96 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { 97 priv_freeset(priv_set); 98 pfail("setppriv effective: %s", strerror(errno)); 99 } 100 } 101 (void) pthread_mutex_unlock(&uid_mutex); 102 103 priv_freeset(priv_set); 104 } 105 106 void 107 nwamd_deescalate(void) { 108 (void) pthread_mutex_lock(&uid_mutex); 109 110 assert(uid_cnt > 0); 111 if (--uid_cnt == 0) { 112 priv_set_t *priv_set, *allpriv_set; 113 114 /* build up our minimal set of privs. */ 115 priv_set = priv_str_to_set("basic", ",", NULL); 116 allpriv_set = priv_str_to_set("zone", ",", NULL); 117 if (priv_set == NULL || allpriv_set == NULL) 118 pfail("converting privilege sets: %s", strerror(errno)); 119 120 (void) priv_addset(priv_set, PRIV_FILE_CHOWN_SELF); 121 (void) priv_addset(priv_set, PRIV_FILE_DAC_READ); 122 (void) priv_addset(priv_set, PRIV_FILE_DAC_WRITE); 123 (void) priv_addset(priv_set, PRIV_NET_RAWACCESS); 124 (void) priv_addset(priv_set, PRIV_NET_PRIVADDR); 125 (void) priv_addset(priv_set, PRIV_PROC_AUDIT); 126 (void) priv_addset(priv_set, PRIV_PROC_OWNER); 127 (void) priv_addset(priv_set, PRIV_PROC_SETID); 128 (void) priv_addset(priv_set, PRIV_SYS_CONFIG); 129 (void) priv_addset(priv_set, PRIV_SYS_IP_CONFIG); 130 (void) priv_addset(priv_set, PRIV_SYS_IPC_CONFIG); 131 (void) priv_addset(priv_set, PRIV_SYS_MOUNT); 132 (void) priv_addset(priv_set, PRIV_SYS_NET_CONFIG); 133 (void) priv_addset(priv_set, PRIV_SYS_RES_CONFIG); 134 (void) priv_addset(priv_set, PRIV_SYS_RESOURCE); 135 136 /* 137 * Since our zone might not have all these privs, 138 * just ask for those that are available. 139 */ 140 priv_intersect(allpriv_set, priv_set); 141 142 if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { 143 priv_freeset(allpriv_set); 144 priv_freeset(priv_set); 145 pfail("setppriv inheritable: %s", strerror(errno)); 146 } 147 /* 148 * Need to ensure permitted set contains all privs so we can 149 * escalate later. 150 */ 151 if (setppriv(PRIV_SET, PRIV_PERMITTED, allpriv_set) == -1) { 152 priv_freeset(allpriv_set); 153 priv_freeset(priv_set); 154 pfail("setppriv permitted: %s", strerror(errno)); 155 } 156 /* 157 * We need to find a smaller set of privs that are important to 158 * us. Otherwise we really are not gaining much by doing this. 159 */ 160 if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { 161 priv_freeset(allpriv_set); 162 priv_freeset(priv_set); 163 pfail("setppriv effective: %s", strerror(errno)); 164 } 165 166 priv_freeset(priv_set); 167 priv_freeset(allpriv_set); 168 } 169 (void) pthread_mutex_unlock(&uid_mutex); 170 } 171 172 /* 173 * 174 * This starts a child process determined by command. If command contains a 175 * slash then it is assumed to be a full path; otherwise the path is searched 176 * for an executable file with the name command. Command is also used as 177 * argv[0] of the new process. The rest of the arguments of the function 178 * up to the first NULL make up pointers to arguments of the new process. 179 * 180 * This function returns child exit status on success and -1 on failure. 181 * 182 * NOTE: original_sigmask must be set before this function is called. 183 */ 184 int 185 nwamd_start_childv(const char *command, char const * const *argv) 186 { 187 posix_spawnattr_t attr; 188 sigset_t fullset; 189 int i, rc, status, n; 190 pid_t pid; 191 char vbuf[1024]; 192 193 vbuf[0] = 0; 194 n = sizeof (vbuf); 195 for (i = 1; argv[i] != NULL && n > 2; i++) { 196 n -= strlcat(vbuf, " ", n); 197 n -= strlcat(vbuf, argv[i], n); 198 } 199 if (argv[i] != NULL || n < 0) 200 nlog(LOG_ERR, "nwamd_start_childv can't log full arg vector"); 201 202 if ((rc = posix_spawnattr_init(&attr)) != 0) { 203 nlog(LOG_DEBUG, "posix_spawnattr_init %d %s\n", 204 rc, strerror(rc)); 205 return (-1); 206 } 207 (void) sigfillset(&fullset); 208 if ((rc = posix_spawnattr_setsigdefault(&attr, &fullset)) != 0) { 209 nlog(LOG_DEBUG, "setsigdefault %d %s\n", rc, strerror(rc)); 210 return (-1); 211 } 212 if ((rc = posix_spawnattr_setsigmask(&attr, &original_sigmask)) != 0) { 213 nlog(LOG_DEBUG, "setsigmask %d %s\n", rc, strerror(rc)); 214 return (-1); 215 } 216 if ((rc = posix_spawnattr_setflags(&attr, 217 POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK)) != 0) { 218 nlog(LOG_DEBUG, "setflags %d %s\n", rc, strerror(rc)); 219 return (-1); 220 } 221 222 if ((rc = posix_spawnp(&pid, command, NULL, &attr, (char * const *)argv, 223 environ)) > 0) { 224 nlog(LOG_DEBUG, "posix_spawnp failed errno %d", rc); 225 return (-1); 226 } 227 228 if ((rc = posix_spawnattr_destroy(&attr)) != 0) { 229 nlog(LOG_DEBUG, "posix_spawn_attr_destroy %d %s\n", 230 rc, strerror(rc)); 231 return (-1); 232 } 233 234 (void) waitpid(pid, &status, 0); 235 if (WIFSIGNALED(status) || WIFSTOPPED(status)) { 236 i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); 237 nlog(LOG_ERR, "'%s%s' %s with signal %d (%s)", command, vbuf, 238 (WIFSIGNALED(status) ? "terminated" : "stopped"), i, 239 strsignal(i)); 240 return (-2); 241 } else { 242 nlog(LOG_INFO, "'%s%s' completed normally: %d", command, vbuf, 243 WEXITSTATUS(status)); 244 return (WEXITSTATUS(status)); 245 } 246 } 247 248 /* 249 * For global zone, check if the link is used by a non-global 250 * zone, note that the non-global zones doesn't need this check, 251 * because zoneadm has taken care of this when the zone boots. 252 * In the global zone, we ignore events for local-zone-owned links 253 * since these are taken care of by the local zone's network 254 * configuration services. 255 */ 256 boolean_t 257 nwamd_link_belongs_to_this_zone(const char *linkname) 258 { 259 zoneid_t zoneid; 260 char zonename[ZONENAME_MAX]; 261 int ret; 262 263 zoneid = getzoneid(); 264 if (zoneid == GLOBAL_ZONEID) { 265 datalink_id_t linkid; 266 dladm_status_t status; 267 char errstr[DLADM_STRSIZE]; 268 269 if ((status = dladm_name2info(dld_handle, linkname, &linkid, 270 NULL, NULL, NULL)) != DLADM_STATUS_OK) { 271 nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " 272 "could not get linkid for %s: %s", 273 linkname, dladm_status2str(status, errstr)); 274 return (B_FALSE); 275 } 276 zoneid = ALL_ZONES; 277 ret = zone_check_datalink(&zoneid, linkid); 278 if (ret == 0) { 279 (void) getzonenamebyid(zoneid, zonename, ZONENAME_MAX); 280 nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " 281 "%s is used by non-global zone: %s", 282 linkname, zonename); 283 return (B_FALSE); 284 } 285 } 286 return (B_TRUE); 287 } 288 289 /* 290 * Inputs: 291 * res is a pointer to the scf_resources_t to be released. 292 */ 293 static void 294 release_scf_resources(scf_resources_t *res) 295 { 296 scf_entry_destroy(res->sr_ent); 297 scf_transaction_destroy(res->sr_tx); 298 scf_value_destroy(res->sr_val); 299 scf_property_destroy(res->sr_prop); 300 scf_pg_destroy(res->sr_pg); 301 scf_snapshot_destroy(res->sr_snap); 302 scf_instance_destroy(res->sr_inst); 303 (void) scf_handle_unbind(res->sr_handle); 304 scf_handle_destroy(res->sr_handle); 305 } 306 307 /* 308 * Inputs: 309 * fmri is the instance to look up 310 * Outputs: 311 * res is a pointer to an scf_resources_t. This is an internal 312 * structure that holds all the handles needed to get a specific 313 * property from the running snapshot; on a successful return it 314 * contains the scf_value_t that should be passed to the desired 315 * scf_value_get_foo() function, and must be freed after use by 316 * calling release_scf_resources(). On a failure return, any 317 * resources that may have been assigned to res are released, so 318 * the caller does not need to do any cleanup in the failure case. 319 * Returns: 320 * 0 on success 321 * -1 on failure 322 */ 323 324 static int 325 create_scf_resources(const char *fmri, scf_resources_t *res) 326 { 327 res->sr_tx = NULL; 328 res->sr_ent = NULL; 329 res->sr_inst = NULL; 330 res->sr_snap = NULL; 331 res->sr_pg = NULL; 332 res->sr_prop = NULL; 333 res->sr_val = NULL; 334 335 if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) { 336 return (-1); 337 } 338 339 if (scf_handle_bind(res->sr_handle) != 0) { 340 scf_handle_destroy(res->sr_handle); 341 return (-1); 342 } 343 if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) { 344 goto failure; 345 } 346 if (scf_handle_decode_fmri(res->sr_handle, fmri, NULL, NULL, 347 res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { 348 goto failure; 349 } 350 if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) { 351 goto failure; 352 } 353 if (scf_instance_get_snapshot(res->sr_inst, "running", 354 res->sr_snap) != 0) { 355 goto failure; 356 } 357 if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { 358 goto failure; 359 } 360 if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { 361 goto failure; 362 } 363 if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { 364 goto failure; 365 } 366 if ((res->sr_tx = scf_transaction_create(res->sr_handle)) == NULL) { 367 goto failure; 368 } 369 if ((res->sr_ent = scf_entry_create(res->sr_handle)) == NULL) { 370 goto failure; 371 } 372 return (0); 373 374 failure: 375 nlog(LOG_ERR, "create_scf_resources failed: %s", 376 scf_strerror(scf_error())); 377 release_scf_resources(res); 378 return (-1); 379 } 380 381 /* 382 * Inputs: 383 * fmri is the instance to look up 384 * pg is the property group to look up 385 * prop is the property within that group to look up 386 * running specifies if running snapshot is to be used 387 * Outputs: 388 * res is a pointer to an scf_resources_t. This is an internal 389 * structure that holds all the handles needed to get a specific 390 * property from the running snapshot; on a successful return it 391 * contains the scf_value_t that should be passed to the desired 392 * scf_value_get_foo() function, and must be freed after use by 393 * calling release_scf_resources(). On a failure return, any 394 * resources that may have been assigned to res are released, so 395 * the caller does not need to do any cleanup in the failure case. 396 * Returns: 397 * 0 on success 398 * -1 on failure 399 */ 400 static int 401 get_property_value(const char *fmri, const char *pg, const char *prop, 402 boolean_t running, scf_resources_t *res) 403 { 404 if (create_scf_resources(fmri, res) != 0) 405 return (-1); 406 407 if (scf_instance_get_pg_composed(res->sr_inst, 408 running ? res->sr_snap : NULL, pg, res->sr_pg) != 0) { 409 goto failure; 410 } 411 if (scf_pg_get_property(res->sr_pg, prop, res->sr_prop) != 0) { 412 goto failure; 413 } 414 if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) { 415 goto failure; 416 } 417 return (0); 418 419 failure: 420 release_scf_resources(res); 421 return (-1); 422 } 423 424 /* 425 * Inputs: 426 * lfmri is the instance fmri to look up 427 * lpg is the property group to look up 428 * lprop is the property within that group to look up 429 * Outputs: 430 * answer is a pointer to the property value 431 * Returns: 432 * 0 on success 433 * -1 on failure 434 * If successful, the property value is retured in *answer. 435 * Otherwise, *answer is undefined, and it is up to the caller to decide 436 * how to handle that case. 437 */ 438 int 439 nwamd_lookup_boolean_property(const char *lfmri, const char *lpg, 440 const char *lprop, boolean_t *answer) 441 { 442 int result = -1; 443 scf_resources_t res; 444 uint8_t prop_val; 445 446 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 447 448 /* 449 * an error was already logged by get_property_value, 450 * and it released any resources assigned to res before 451 * returning. 452 */ 453 return (result); 454 } 455 if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) { 456 goto cleanup; 457 } 458 *answer = (boolean_t)prop_val; 459 result = 0; 460 cleanup: 461 release_scf_resources(&res); 462 return (result); 463 } 464 465 /* 466 * Inputs: 467 * lfmri is the instance fmri to look up 468 * lpg is the property group to look up 469 * lprop is the property within that group to look up 470 * buf is the place to put the answer 471 * bufsz is the size of buf 472 * Outputs: 473 * 474 * Returns: 475 * 0 on success 476 * -1 on failure 477 * If successful, the property value is retured in buf. 478 * Otherwise, buf is undefined, and it is up to the caller to decide 479 * how to handle that case. 480 */ 481 int 482 nwamd_lookup_string_property(const char *lfmri, const char *lpg, 483 const char *lprop, char *buf, size_t bufsz) 484 { 485 int result = -1; 486 scf_resources_t res; 487 488 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 489 /* 490 * The above function fails when trying to get a 491 * non-persistent property group from the running snapshot. 492 * Try going for the non-running snapshot. 493 */ 494 if (get_property_value(lfmri, lpg, lprop, B_FALSE, &res) != 0) { 495 /* 496 * an error was already logged by get_property_value, 497 * and it released any resources assigned to res before 498 * returning. 499 */ 500 return (result); 501 } 502 } 503 if (scf_value_get_astring(res.sr_val, buf, bufsz) == 0) 504 goto cleanup; 505 506 result = 0; 507 cleanup: 508 release_scf_resources(&res); 509 return (result); 510 } 511 512 /* 513 * Inputs: 514 * lfmri is the instance fmri to look up 515 * lpg is the property group to look up 516 * lprop is the property within that group to look up 517 * Outputs: 518 * answer is a pointer to the property value 519 * Returns: 520 * 0 on success 521 * -1 on failure 522 * If successful, the property value is retured in *answer. 523 * Otherwise, *answer is undefined, and it is up to the caller to decide 524 * how to handle that case. 525 */ 526 int 527 nwamd_lookup_count_property(const char *lfmri, const char *lpg, 528 const char *lprop, uint64_t *answer) 529 { 530 int result = -1; 531 scf_resources_t res; 532 533 if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { 534 535 /* 536 * an error was already logged by get_property_value, 537 * and it released any resources assigned to res before 538 * returning. 539 */ 540 return (result); 541 } 542 if (scf_value_get_count(res.sr_val, answer) != 0) { 543 goto cleanup; 544 } 545 result = 0; 546 cleanup: 547 release_scf_resources(&res); 548 return (result); 549 } 550 551 static int 552 set_property_value(scf_resources_t *res, const char *propname, 553 scf_type_t proptype) 554 { 555 int result = -1; 556 boolean_t new; 557 558 retry: 559 new = (scf_pg_get_property(res->sr_pg, propname, res->sr_prop) != 0); 560 561 if (scf_transaction_start(res->sr_tx, res->sr_pg) == -1) { 562 goto failure; 563 } 564 if (new) { 565 if (scf_transaction_property_new(res->sr_tx, res->sr_ent, 566 propname, proptype) == -1) { 567 goto failure; 568 } 569 } else { 570 if (scf_transaction_property_change(res->sr_tx, res->sr_ent, 571 propname, proptype) == -1) { 572 goto failure; 573 } 574 } 575 576 if (scf_entry_add_value(res->sr_ent, res->sr_val) != 0) { 577 goto failure; 578 } 579 580 result = scf_transaction_commit(res->sr_tx); 581 if (result == 0) { 582 scf_transaction_reset(res->sr_tx); 583 if (scf_pg_update(res->sr_pg) == -1) { 584 goto failure; 585 } 586 nlog(LOG_INFO, "set_property_value: transaction commit failed " 587 "for %s; retrying", propname); 588 goto retry; 589 } 590 if (result == -1) 591 goto failure; 592 return (0); 593 594 failure: 595 return (-1); 596 } 597 598 int 599 nwamd_set_count_property(const char *fmri, const char *pg, const char *prop, 600 uint64_t count) 601 { 602 scf_resources_t res; 603 604 if (create_scf_resources(fmri, &res) != 0) 605 return (-1); 606 607 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 608 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 609 if (scf_error() != SCF_ERROR_EXISTS) 610 goto failure; 611 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 612 res.sr_pg) != 0) 613 goto failure; 614 } 615 616 scf_value_set_count(res.sr_val, (uint64_t)count); 617 618 if (set_property_value(&res, prop, SCF_TYPE_COUNT) != 0) 619 goto failure; 620 621 release_scf_resources(&res); 622 return (0); 623 624 failure: 625 nlog(LOG_INFO, "nwamd_set_count_property: scf failure %s while " 626 "setting %s", scf_strerror(scf_error()), prop); 627 release_scf_resources(&res); 628 return (-1); 629 } 630 631 int 632 nwamd_set_string_property(const char *fmri, const char *pg, const char *prop, 633 const char *str) 634 { 635 scf_resources_t res; 636 637 if (create_scf_resources(fmri, &res) != 0) 638 return (-1); 639 640 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 641 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 642 if (scf_error() != SCF_ERROR_EXISTS) 643 goto failure; 644 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 645 res.sr_pg) != 0) 646 goto failure; 647 } 648 649 if (scf_value_set_astring(res.sr_val, str) != 0) 650 goto failure; 651 652 if (set_property_value(&res, prop, SCF_TYPE_ASTRING) != 0) 653 goto failure; 654 655 release_scf_resources(&res); 656 return (0); 657 658 failure: 659 nlog(LOG_INFO, "nwamd_set_string_property: scf failure %s while " 660 "setting %s", scf_strerror(scf_error()), prop); 661 release_scf_resources(&res); 662 return (-1); 663 } 664 665 /* 666 * Deletes property prop from property group pg in SMF instance fmri. 667 * Returns 0 on success, -1 on failure. 668 */ 669 int 670 nwamd_delete_scf_property(const char *fmri, const char *pg, const char *prop) 671 { 672 scf_resources_t res; 673 int result = -1; 674 675 if (create_scf_resources(fmri, &res) != 0) 676 return (-1); 677 678 if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, 679 SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { 680 if (scf_error() != SCF_ERROR_EXISTS) 681 goto failure; 682 if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, 683 res.sr_pg) != 0) 684 goto failure; 685 } 686 687 if (scf_pg_get_property(res.sr_pg, prop, res.sr_prop) != 0) 688 goto failure; 689 retry: 690 if (scf_transaction_start(res.sr_tx, res.sr_pg) == -1) 691 goto failure; 692 693 if (scf_transaction_property_delete(res.sr_tx, res.sr_ent, prop) == -1) 694 goto failure; 695 696 result = scf_transaction_commit(res.sr_tx); 697 if (result == 0) { 698 scf_transaction_reset(res.sr_tx); 699 if (scf_pg_update(res.sr_pg) == -1) 700 goto failure; 701 goto retry; 702 } 703 if (result == -1) 704 goto failure; 705 706 release_scf_resources(&res); 707 return (0); 708 failure: 709 release_scf_resources(&res); 710 return (-1); 711 } 712