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