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 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 27 28 #include <libgen.h> 29 #include "cfga_fp.h" 30 31 /* The following are used by update_fabric_wwn_list() */ 32 #define COPY_EXT ".cpy." /* Extn used in naming backup file */ 33 #define TMP_EXT ".tmp." /* Extn used in naming temp file */ 34 static char *HDR = 35 "#\n" 36 "# fabric_WWN_map\n" 37 "#\n" 38 "# The physical ap_id list of configured fabric devices.\n" 39 "# Do NOT edit this file by hand -- refer to the cfgadm_fp(1M)\n" 40 "# man page and use cfgadm(1m) instead.\n" 41 "#\n"; 42 43 /* 44 * This function searches for "srch_str" (of length "slen") in "buf" (of length 45 * "buflen"). If it is not found, "write_offset" has the offset in "buf" where 46 * "srch_str" would have to be added in "buf". If "srch_str" is found in "buf", 47 * "write_offset" has its offset in "buf" 48 * 49 * ARGUMENTS : 50 * buf - buffer to search in 51 * buflen - length of buffer 52 * srch_str - string to search 53 * slen - length of srch_str 54 * write_offset - Set in function on exit 55 * - It is the offset in buf where srch_str is or should be 56 * bytes_left - Set in function on exit 57 * - It is the # of bytes left beyond write_offset in buf 58 * 59 * Notes : 60 * - This function assumes "buf" is sorted in ascending order 61 * - If 'buflen' is > 0, it assumes it has a header on top and skips it 62 * - "srch_str" has '\n' at the end, but when update_fabric_wwn_list() calls 63 * this function, 'slen' does not include the last `\n' 64 * 65 * RETURN VALUES : 66 * Zero - "srch_str" found in "buf"... "write_offset" has offset in "buf" 67 * > 0 - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf" 68 * where "srch_str" can fit in. 69 * "buf" had contents > "srch_str" 70 * < 0 - "srch_str" NOT found in "buf" ... "write_offset" has offset in "buf" 71 * where "srch_str" can fit in. 72 * "buf" had contents < "srch_str" 73 */ 74 static int 75 search_line(char *buf, int buflen, char *srch_str, int slen, 76 int *write_offset, int *bytes_left) 77 { 78 int retval, sizeof_rep_hdr = strlen(HDR); 79 char *sol; /* Pointer to Start-Of-Line */ 80 char *cur_pos; /* current position */ 81 82 *bytes_left = buflen; 83 *write_offset = 0; 84 85 if (buf == NULL || *buf == NULL || buflen <= 0) 86 return (-2); /* Arbitrary -ve val. srch_str not found */ 87 88 if (srch_str == NULL || *srch_str == NULL || slen <= 0) 89 return (0); /* This says srch_str was found */ 90 91 sol = cur_pos = buf; 92 if (buflen >= sizeof_rep_hdr) { 93 /* skip header */ 94 sol = cur_pos = buf + sizeof_rep_hdr; 95 *bytes_left -= sizeof_rep_hdr; 96 } 97 98 while (*bytes_left >= slen) { 99 if ((retval = strncmp(sol, srch_str, slen)) >= 0) { 100 /* strncmp will pass if srch_str is a substring */ 101 if ((retval == 0) && (*bytes_left > slen) && 102 (*(sol+slen) != '\n')) 103 retval = 1; /* Force it to be > 0 */ 104 *write_offset = sol - buf; 105 return (retval); 106 } 107 108 /* retval < 0 */ 109 if ((cur_pos = strchr(sol, (int)'\n')) == NULL) { 110 *write_offset = buflen; 111 return (retval); 112 } 113 114 /* Get the length of this line */ 115 *cur_pos = '\0'; /* kludge to get string length */ 116 *bytes_left -= (strlen(sol) + 1); 117 *cur_pos = '\n'; /* Put back the original char */ 118 119 sol = cur_pos = cur_pos + 1; 120 } 121 122 if (*bytes_left > 0) { 123 /* In this case the bytes left will be less than slen */ 124 if ((retval = strncmp(sol, srch_str, *bytes_left)) >= 0) { 125 *write_offset = sol - buf; 126 } else { 127 *write_offset = buflen; 128 } 129 return (retval); 130 } 131 *write_offset = sol - buf; 132 /* Should return a value < 0 to show that search string goes to eof */ 133 return (-1); 134 } 135 136 /* 137 * This function sets an advisory lock on the file pointed to by the argument 138 * fd, which is a file descriptor. The lock is set using fcntl() which uses 139 * flock structure. 140 */ 141 int 142 lock_register(int fd, int cmd, int type, off_t offset, int whence, off_t len) 143 { 144 struct flock lock; 145 146 lock.l_type = type; 147 lock.l_start = offset; 148 lock.l_whence = whence; 149 lock.l_len = len; 150 151 return (fcntl(fd, cmd, &lock)); 152 } 153 154 /* Lot of places to cleanup - Less chance of missing out using this macro */ 155 #define CLEANUP_N_RET(ret) \ 156 if (fd != -1) { \ 157 close(fd); \ 158 } \ 159 if (copy_fd != -1) { \ 160 close(copy_fd); \ 161 } \ 162 if (tmp_fd != -1) { \ 163 close(tmp_fd); \ 164 } \ 165 if (copy_rep != NULL) { \ 166 remove(copy_rep); \ 167 free(copy_rep); \ 168 } \ 169 if (tmp_rep != NULL) { \ 170 remove(tmp_rep); \ 171 free(tmp_rep); \ 172 } \ 173 if (upd_str != NULL) { \ 174 free(upd_str); \ 175 } \ 176 if (repbuf != NULL) { \ 177 munmap(repbuf, filesize); \ 178 } \ 179 if (c_repbuf != NULL) { \ 180 munmap(c_repbuf, filesize); \ 181 } \ 182 if (t_repbuf != NULL) { \ 183 munmap(t_repbuf, size); \ 184 } \ 185 return (ret) 186 187 /* 188 * INPUTS: 189 * cmd - ADD_ENTRY or REMOVE_ENTRY 190 * update_str - string for repository operation 191 * - Assumed NOT to have a '\n' and that it is null terminated 192 * errstring - Pointer that will be updated by this function 193 * - Any error msgs that has to be sent back to caller 194 * 195 * RETURNS : 196 * FPCFGA_OK on success 197 * FPCFGA_LIB_ERR on error 198 * 199 * SYNOPSIS: 200 * This function adds or deletes 'update_str' from FAB_REPOSITORY based on 201 * value of 'cmd'. The repository has a warning line on the top to disallow 202 * manual editing of the file. If the repository is being created fresh or if 203 * it is of zero length or if it has only warning lines in it, the operation 204 * speicified by 'cmd' is performed and returned. If the repository exists 205 * and has some data, it is expected to be of atleast the size of the lenght 206 * of the warning header. This is the only check that is performed on the 207 * validity of the file. No other checks are performed. On a valid 208 * repository, to perform the update, this function basically makes use of 209 * 3 buffers - the original buffer (repbuf), a copy buffer (c_repbuf) and a 210 * temp buffer (t_repbuf). 211 * The contents of the repository are mmap-ed into the repbuf and then 212 * copied into the c_repbuf. All further operations are done using the copy. 213 * t_repbuf is created to be the size of c_repbuf +/- 'slen' (based on 214 * whether it is add or remove operation). After adding/removing the 215 * 'update_str', the c_repbuf is copied to a OLD_FAB_REPOSITORY and t_repbuf 216 * is made FAB_REPOSITORY. 217 * 218 */ 219 int 220 update_fabric_wwn_list(int cmd, const char *update_str, char **errstring) 221 { 222 int fd, copy_fd, tmp_fd, new_file_flag = 0; 223 int len, write_offset, bytes_left; 224 int sizeof_rep_hdr = strlen(HDR); 225 char *repbuf, *c_repbuf, *t_repbuf; 226 char *copy_rep, *tmp_rep, *upd_str; 227 off_t filesize, size; 228 struct stat stbuf; 229 230 /* Do some initializations */ 231 fd = copy_fd = tmp_fd = -1; 232 repbuf = c_repbuf = t_repbuf = NULL; 233 copy_rep = tmp_rep = upd_str = NULL; 234 size = filesize = write_offset = bytes_left = 0; 235 236 /* 237 * Set the mode to read only. Root user can still open as RDWR. 238 * We ignore errors in general here. But, just notice ENOENTs 239 */ 240 if ((chmod(FAB_REPOSITORY, S_IRUSR|S_IRGRP|S_IROTH) == -1) && 241 (errno == ENOENT)) { 242 new_file_flag = 1; 243 mkdirp(FAB_REPOSITORY_DIR, 244 S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH); 245 } 246 247 /* Create the repository if its not there */ 248 if ((fd = open(FAB_REPOSITORY, O_RDWR | O_CREAT)) == -1) { 249 cfga_err(errstring, errno, ERR_UPD_REP, 0); 250 return (FPCFGA_LIB_ERR); 251 } 252 253 /* Now try to chmod again. This time we dont ignore errors */ 254 if (fchmod(fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { 255 close(fd); 256 cfga_err(errstring, errno, ERR_UPD_REP, 0); 257 return (FPCFGA_LIB_ERR); 258 } 259 260 if (lock_register(fd, F_SETLKW, F_WRLCK, 0, SEEK_SET, 0) < 0) { 261 close(fd); 262 cfga_err(errstring, 0, ERR_UPD_REP, 0); 263 return (FPCFGA_LIB_ERR); 264 } 265 266 if (fstat(fd, &stbuf) == -1) { 267 close(fd); 268 cfga_err(errstring, errno, ERR_UPD_REP, 0); 269 return (FPCFGA_LIB_ERR); 270 } 271 272 filesize = size = stbuf.st_size; 273 274 /* A very Minimal check on repository */ 275 if (filesize && filesize < sizeof_rep_hdr) { 276 /* 277 * If there is some data, it should be atleast the size of 278 * the header 279 */ 280 close(fd); 281 cfga_err(errstring, errno, ERR_UPD_REP, 0); 282 return (FPCFGA_LIB_ERR); 283 } 284 285 if ((len = strlen(update_str)) == 0) { 286 /* 287 * We are trying to add/remove a NULL string. 288 * Just return success 289 */ 290 close(fd); 291 return (FPCFGA_OK); 292 } 293 294 if ((upd_str = calloc(1, len + 2)) == NULL) { 295 close(fd); 296 cfga_err(errstring, errno, ERR_UPD_REP, 0); 297 return (FPCFGA_LIB_ERR); 298 } 299 300 strcpy(upd_str, update_str); 301 strcat(upd_str, "\n"); /* Append a new line char */ 302 len = strlen(upd_str); 303 304 if (filesize > 0) { 305 if ((copy_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + 306 sizeof (COPY_EXT) + sizeof (pid_t))) == NULL) { 307 cfga_err(errstring, errno, ERR_UPD_REP, 0); 308 CLEANUP_N_RET(FPCFGA_LIB_ERR); 309 } 310 311 (void) sprintf(copy_rep, "%s%s%ld", FAB_REPOSITORY, COPY_EXT, 312 getpid()); 313 314 if ((copy_fd = open(copy_rep, O_RDWR | O_CREAT | O_TRUNC, 315 S_IRUSR | S_IWUSR)) < 0) { 316 cfga_err(errstring, errno, ERR_UPD_REP, 0); 317 CLEANUP_N_RET(FPCFGA_LIB_ERR); 318 } 319 320 if ((repbuf = (char *)mmap(0, filesize, PROT_READ, 321 MAP_SHARED, fd, 0)) == MAP_FAILED) { 322 close(fd); 323 free(upd_str); 324 cfga_err(errstring, errno, ERR_UPD_REP, 0); 325 return (FPCFGA_LIB_ERR); 326 } 327 328 if (lseek(copy_fd, filesize - 1, SEEK_SET) == -1) { 329 cfga_err(errstring, errno, ERR_UPD_REP, 0); 330 CLEANUP_N_RET(FPCFGA_LIB_ERR); 331 } 332 333 if (write(copy_fd, "", 1) != 1) { 334 cfga_err(errstring, errno, ERR_UPD_REP, 0); 335 CLEANUP_N_RET(FPCFGA_LIB_ERR); 336 } 337 338 if ((c_repbuf = (char *)mmap(0, filesize, 339 PROT_READ | PROT_WRITE, 340 MAP_SHARED, copy_fd, 0)) == MAP_FAILED) { 341 cfga_err(errstring, errno, ERR_UPD_REP, 0); 342 CLEANUP_N_RET(FPCFGA_LIB_ERR); 343 } 344 345 memcpy(c_repbuf, repbuf, filesize); 346 /* 347 * We cannot close the repository since we hold a lock 348 * But we'll free up the mmap-ed area. 349 */ 350 munmap(repbuf, filesize); 351 repbuf = NULL; 352 } 353 354 /* 355 * If we just created this file, or it was an empty repository file 356 * add a header to the beginning of file. 357 * If it had was a repository file with just the header, 358 */ 359 if (new_file_flag != 0 || filesize == 0 || filesize == sizeof_rep_hdr) { 360 if ((filesize != sizeof_rep_hdr) && 361 (write(fd, HDR, sizeof_rep_hdr) != sizeof_rep_hdr)) { 362 cfga_err(errstring, errno, ERR_UPD_REP, 0); 363 CLEANUP_N_RET(FPCFGA_LIB_ERR); 364 } 365 366 /* 367 * We know its a new file, empty file or a file with only a 368 * header so lets get the update operation done with 369 */ 370 switch (cmd) { 371 case ADD_ENTRY: 372 /* If there is a header, we have to skip it */ 373 if (lseek(fd, 0, SEEK_END) == -1) { 374 cfga_err(errstring, errno, ERR_UPD_REP, 0); 375 CLEANUP_N_RET(FPCFGA_LIB_ERR); 376 } 377 378 if (write(fd, upd_str, len) != len) { 379 cfga_err(errstring, errno, ERR_UPD_REP, 0); 380 CLEANUP_N_RET(FPCFGA_LIB_ERR); 381 } 382 383 if (filesize > 0) { 384 /* Now create the '.old' file */ 385 if (msync(c_repbuf, filesize, MS_SYNC) == -1) { 386 cfga_err(errstring, errno, 387 ERR_UPD_REP, 0); 388 CLEANUP_N_RET(FPCFGA_LIB_ERR); 389 } 390 391 if (fchmod(copy_fd, 392 S_IRUSR | S_IRGRP | S_IROTH) < 0) { 393 cfga_err(errstring, errno, 394 ERR_UPD_REP, 0); 395 CLEANUP_N_RET(FPCFGA_LIB_ERR); 396 } 397 rename(copy_rep, OLD_FAB_REPOSITORY); 398 } 399 400 CLEANUP_N_RET(FPCFGA_OK); 401 402 case REMOVE_ENTRY: 403 /* 404 * So, the side effect of a remove on an empty or 405 * non-existing repository is that the repository got 406 * created 407 */ 408 CLEANUP_N_RET(FPCFGA_OK); 409 410 default: 411 cfga_err(errstring, 0, ERR_UPD_REP, 0); 412 CLEANUP_N_RET(FPCFGA_LIB_ERR); 413 } 414 } 415 416 /* Now, size and filesize are > sizeof_rep_hdr */ 417 418 switch (cmd) { 419 case ADD_ENTRY: 420 size += len; 421 /* 422 * We'll search the full repository, header included, since 423 * we dont expect upd_str to match anything in the header. 424 */ 425 if (search_line(c_repbuf, filesize, upd_str, 426 len - 1, &write_offset, &bytes_left) == 0) { 427 /* line already exists in repository or len == 0 */ 428 CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ 429 } 430 431 /* construct temp file name using pid. */ 432 if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + 433 sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) { 434 cfga_err(errstring, errno, ERR_UPD_REP, 0); 435 CLEANUP_N_RET(FPCFGA_LIB_ERR); 436 } 437 438 (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY, 439 TMP_EXT, getpid()); 440 441 /* Open tmp repository file in absolute mode */ 442 if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC, 443 S_IRUSR | S_IWUSR)) < 0) { 444 cfga_err(errstring, errno, ERR_UPD_REP, 0); 445 CLEANUP_N_RET(FPCFGA_LIB_ERR); 446 } 447 448 if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) { 449 cfga_err(errstring, errno, ERR_UPD_REP, 0); 450 CLEANUP_N_RET(FPCFGA_LIB_ERR); 451 } 452 453 if (write(tmp_fd, "", 1) != 1) { 454 cfga_err(errstring, errno, ERR_UPD_REP, 0); 455 CLEANUP_N_RET(FPCFGA_LIB_ERR); 456 } 457 458 if ((t_repbuf = (char *)mmap(0, size, PROT_READ|PROT_WRITE, 459 MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) { 460 cfga_err(errstring, errno, ERR_UPD_REP, 0); 461 CLEANUP_N_RET(FPCFGA_LIB_ERR); 462 } 463 464 memcpy(t_repbuf, c_repbuf, write_offset); 465 strncpy(t_repbuf + write_offset, upd_str, len); 466 if (write_offset != filesize) { 467 memcpy(t_repbuf + write_offset + len, 468 c_repbuf + write_offset, bytes_left); 469 } 470 471 /* 472 * we are using the copy of FAB_REPOSITORY and will 473 * do msync first since it will be renamed to '.old' file. 474 */ 475 if (msync(c_repbuf, filesize, MS_SYNC) == -1) { 476 cfga_err(errstring, errno, ERR_UPD_REP, 0); 477 CLEANUP_N_RET(FPCFGA_LIB_ERR); 478 } 479 480 if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { 481 cfga_err(errstring, errno, ERR_UPD_REP, 0); 482 CLEANUP_N_RET(FPCFGA_LIB_ERR); 483 } 484 485 if (msync(t_repbuf, size, MS_SYNC) == -1) { 486 cfga_err(errstring, errno, ERR_UPD_REP, 0); 487 CLEANUP_N_RET(FPCFGA_LIB_ERR); 488 } 489 490 if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { 491 cfga_err(errstring, errno, ERR_UPD_REP, 0); 492 CLEANUP_N_RET(FPCFGA_LIB_ERR); 493 } 494 495 close(copy_fd); copy_fd = -1; 496 close(tmp_fd); tmp_fd = -1; 497 498 /* here we do rename and rename before close fd */ 499 rename(copy_rep, OLD_FAB_REPOSITORY); 500 rename(tmp_rep, FAB_REPOSITORY); 501 502 if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) { 503 cfga_err(errstring, errno, ERR_UPD_REP, 0); 504 CLEANUP_N_RET(FPCFGA_LIB_ERR); 505 } 506 507 CLEANUP_N_RET(FPCFGA_OK); 508 509 case REMOVE_ENTRY: 510 if (size >= sizeof_rep_hdr + len - 1) { 511 size -= len; 512 /* 513 * No need to init the 'else' part (size < len) because 514 * in that case, there will be nothing to delete from 515 * the file and so 'size' will not be used in the code 516 * below since search_line() will not find upd_str. 517 */ 518 } 519 520 if (search_line(c_repbuf, filesize, upd_str, len - 1, 521 &write_offset, &bytes_left) != 0) { 522 /* this line does not exists - nothing to remove */ 523 CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ 524 } 525 526 /* construct temp file name using pid. */ 527 if ((tmp_rep = (char *)calloc(1, strlen(FAB_REPOSITORY) + 528 sizeof (TMP_EXT) + sizeof (pid_t))) == NULL) { 529 cfga_err(errstring, errno, ERR_UPD_REP, 0); 530 CLEANUP_N_RET(FPCFGA_LIB_ERR); 531 } 532 533 (void) sprintf(tmp_rep, "%s%s%ld", FAB_REPOSITORY, 534 TMP_EXT, getpid()); 535 536 /* Open tmp repository file in absolute mode */ 537 if ((tmp_fd = open(tmp_rep, O_RDWR|O_CREAT|O_TRUNC, 538 S_IRUSR | S_IWUSR)) < 0) { 539 cfga_err(errstring, errno, ERR_UPD_REP, 0); 540 CLEANUP_N_RET(FPCFGA_LIB_ERR); 541 } 542 543 if (size > 0) { 544 if (lseek(tmp_fd, size - 1, SEEK_SET) == -1) { 545 cfga_err(errstring, errno, ERR_UPD_REP, 0); 546 CLEANUP_N_RET(FPCFGA_LIB_ERR); 547 } 548 549 if (write(tmp_fd, "", 1) != 1) { 550 cfga_err(errstring, errno, ERR_UPD_REP, 0); 551 CLEANUP_N_RET(FPCFGA_LIB_ERR); 552 } 553 554 if ((t_repbuf = (char *)mmap(0, size, 555 PROT_READ|PROT_WRITE, 556 MAP_SHARED, tmp_fd, 0)) == MAP_FAILED) { 557 cfga_err(errstring, errno, ERR_UPD_REP, 0); 558 CLEANUP_N_RET(FPCFGA_LIB_ERR); 559 } 560 561 memcpy(t_repbuf, c_repbuf, write_offset); 562 if ((bytes_left - len) > 0) { 563 memcpy(t_repbuf + write_offset, 564 c_repbuf + write_offset + len, 565 bytes_left - len); 566 } 567 568 if (msync(t_repbuf, size, MS_SYNC) == -1) { 569 cfga_err(errstring, errno, ERR_UPD_REP, 0); 570 CLEANUP_N_RET(FPCFGA_LIB_ERR); 571 } 572 } 573 574 if (fchmod(tmp_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { 575 cfga_err(errstring, errno, ERR_UPD_REP, 0); 576 CLEANUP_N_RET(FPCFGA_LIB_ERR); 577 } 578 579 /* 580 * we are using the copy of FAB_REPOSITORY and will 581 * do msync first since it will be renamed to bak file. 582 */ 583 if (msync(c_repbuf, filesize, MS_SYNC) == -1) { 584 cfga_err(errstring, errno, ERR_UPD_REP, 0); 585 CLEANUP_N_RET(FPCFGA_LIB_ERR); 586 } 587 588 if (fchmod(copy_fd, S_IRUSR | S_IRGRP | S_IROTH) < 0) { 589 cfga_err(errstring, errno, ERR_UPD_REP, 0); 590 CLEANUP_N_RET(FPCFGA_LIB_ERR); 591 } 592 593 /* Close and invalidate the fd's */ 594 close(copy_fd); copy_fd = -1; 595 close(tmp_fd); tmp_fd = -1; 596 597 /* here we do rename and rename before close fd */ 598 rename(copy_rep, OLD_FAB_REPOSITORY); 599 rename(tmp_rep, FAB_REPOSITORY); 600 601 if (lock_register(fd, F_SETLK, F_UNLCK, 0, SEEK_SET, 0) < 0) { 602 cfga_err(errstring, errno, ERR_UPD_REP, 0); 603 CLEANUP_N_RET(FPCFGA_LIB_ERR); 604 } 605 606 CLEANUP_N_RET(FPCFGA_OK); 607 608 default: 609 /* Unexpected - just getout */ 610 break; 611 } 612 613 CLEANUP_N_RET(FPCFGA_OK); /* SUCCESS */ 614 } 615