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 * Copyright (c) 2018, Joyent, Inc.
25 */
26
27 #include <stdio.h>
28 #include <fcntl.h>
29 #include <ctype.h>
30 #include <string.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <errno.h>
34 #include <limits.h>
35 #include <libintl.h>
36 #include <locale.h>
37 #include <sys/stat.h>
38 #include <sys/corectl.h>
39 #include <libproc.h>
40 #include <libscf.h>
41 #include <libscf_priv.h>
42 #include <assert.h>
43
44 #define E_SUCCESS 0 /* Exit status for success */
45 #define E_ERROR 1 /* Exit status for error */
46 #define E_USAGE 2 /* Exit status for usage error */
47
48 static const char PATH_CONFIG[] = "/etc/coreadm.conf";
49 static const char PATH_CONFIG_OLD[] = "/etc/coreadm.conf.old";
50
51 #define COREADM_INST_NAME "system/coreadm:default"
52 #define COREADM_INST_FMRI \
53 SCF_FMRI_SVC_PREFIX SCF_FMRI_SERVICE_PREFIX COREADM_INST_NAME
54
55 #define CONFIG_PARAMS "config_params"
56 #define GLOBAL_ENABLED "global_enabled"
57 #define PROCESS_ENABLED "process_enabled"
58 #define GLOBAL_SETID_ENABLED "global_setid_enabled"
59 #define PROCESS_SETID_ENABLED "process_setid_enabled"
60 #define GLOBAL_LOG_ENABLED "global_log_enabled"
61 #define GLOBAL_PATTERN "global_pattern"
62 #define GLOBAL_CONTENT "global_content"
63 #define INIT_PATTERN "init_pattern"
64 #define INIT_CONTENT "init_content"
65
66 static char *command;
67 static uint64_t options;
68 static int alloptions;
69 static char *glob_pattern;
70 static char gpattern[PATH_MAX];
71 static core_content_t glob_content = CC_CONTENT_INVALID;
72 static char *init_pattern;
73 static char ipattern[PATH_MAX];
74 static core_content_t init_content = CC_CONTENT_INVALID;
75 static char *proc_pattern;
76 static size_t proc_size;
77 static core_content_t proc_content = CC_CONTENT_INVALID;
78
79 static int report_settings(void);
80 static int do_processes(int, char **);
81 static int do_modify(boolean_t);
82 static int do_update(void);
83 static int do_legacy(void);
84
85 static scf_propvec_t prop_gpattern = { GLOBAL_PATTERN, NULL, SCF_TYPE_ASTRING };
86 static scf_propvec_t prop_gcontent = { GLOBAL_CONTENT, NULL, SCF_TYPE_ASTRING };
87 static scf_propvec_t prop_ipattern = { INIT_PATTERN, NULL, SCF_TYPE_ASTRING };
88 static scf_propvec_t prop_icontent = { INIT_CONTENT, NULL, SCF_TYPE_ASTRING };
89 static scf_propvec_t prop_option[] = {
90 { GLOBAL_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_PATH },
91 { PROCESS_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_PROCESS_PATH },
92 { GLOBAL_SETID_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_SETID },
93 { PROCESS_SETID_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_PROCESS_SETID },
94 { GLOBAL_LOG_ENABLED, NULL, SCF_TYPE_BOOLEAN, NULL, CC_GLOBAL_LOG },
95 { NULL }
96 };
97 #define MAX_PROPS (4 + (sizeof (prop_option) / sizeof (scf_propvec_t)))
98
99 static void
usage(void)100 usage(void)
101 {
102 (void) fprintf(stderr, gettext(
103 "usage:\n"));
104 (void) fprintf(stderr, gettext(
105 " %s [ -g pattern ] [ -i pattern ] [ -G content ] [ -I content ]\n"),
106 command);
107 (void) fprintf(stderr, gettext(
108 " [ -e {global | process | global-setid | proc-setid | log} ]\n"));
109 (void) fprintf(stderr, gettext(
110 " [ -d {global | process | global-setid | proc-setid | log} ]\n"));
111 (void) fprintf(stderr, gettext(
112 " %s [ -p pattern ] [ -P content ] [ pid ... ]\n"), command);
113 exit(E_USAGE);
114 }
115
116 static int
perm(void)117 perm(void)
118 {
119 (void) fprintf(stderr, gettext("%s: insufficient privileges to "
120 "exercise the -[GIgied] options\n"), command);
121 return (E_USAGE);
122 }
123
124 static int
parse_content(char * arg,core_content_t * content)125 parse_content(char *arg, core_content_t *content)
126 {
127 if (proc_str2content(arg, content) == 0)
128 return (0);
129 (void) fprintf(stderr, gettext("%s: invalid content string '%s'\n"),
130 command, arg);
131 return (1);
132 }
133
134 int
main(int argc,char ** argv)135 main(int argc, char **argv)
136 {
137 int flag;
138 int opt;
139 int modify;
140 int update = 0;
141 int legacy_update = 0;
142 int error = 0;
143 int npids;
144 char **pidlist;
145
146 char curpid[11];
147 char *curpid_ptr = &curpid[0];
148
149 (void) setlocale(LC_ALL, "");
150 (void) textdomain(TEXT_DOMAIN);
151
152 /* command name (e.g., "coreadm") */
153 if ((command = strrchr(argv[0], '/')) != NULL)
154 command++;
155 else
156 command = argv[0];
157
158 while ((opt = getopt(argc, argv, "g:G:i:I:p:P:e:d:uU?")) != EOF) {
159 switch (opt) {
160 case 'g':
161 glob_pattern = optarg;
162 break;
163 case 'i':
164 init_pattern = optarg;
165 break;
166 case 'p':
167 proc_pattern = optarg;
168 proc_size = strlen(proc_pattern) + 1;
169 break;
170 case 'G':
171 error |= parse_content(optarg, &glob_content);
172 break;
173 case 'I':
174 error |= parse_content(optarg, &init_content);
175 break;
176 case 'P':
177 error |= parse_content(optarg, &proc_content);
178 break;
179 case 'e':
180 case 'd':
181 if (strcmp(optarg, "global") == 0)
182 flag = CC_GLOBAL_PATH;
183 else if (strcmp(optarg, "process") == 0)
184 flag = CC_PROCESS_PATH;
185 else if (strcmp(optarg, "global-setid") == 0)
186 flag = CC_GLOBAL_SETID;
187 else if (strcmp(optarg, "proc-setid") == 0)
188 flag = CC_PROCESS_SETID;
189 else if (strcmp(optarg, "log") == 0)
190 flag = CC_GLOBAL_LOG;
191 else {
192 flag = 0;
193 error = 1;
194 }
195 if (opt == 'e')
196 options |= flag;
197 else
198 options &= ~flag;
199 alloptions |= flag;
200 break;
201 case 'U':
202 update = 1;
203 break;
204 case 'u':
205 legacy_update = 1;
206 break;
207 case '?':
208 default:
209 error = 1;
210 break;
211 }
212 }
213
214 npids = argc - optind;
215 pidlist = argv + optind;
216
217 if (error)
218 usage();
219
220 /*
221 * If 'modify' is true, we must modify the system settings
222 * and update the configuration file with the new parameters.
223 */
224 modify = glob_pattern != NULL || glob_content != CC_CONTENT_INVALID ||
225 init_pattern != NULL || init_content != CC_CONTENT_INVALID ||
226 alloptions != 0;
227
228 if ((update || legacy_update) && (modify || proc_pattern != NULL ||
229 proc_content != CC_CONTENT_INVALID || npids != 0)) {
230 (void) fprintf(stderr,
231 gettext("%s: the -u option must stand alone\n"), command);
232 usage();
233 }
234 if (modify &&
235 (proc_pattern != NULL || proc_content != CC_CONTENT_INVALID)) {
236 (void) fprintf(stderr, gettext(
237 "%s: -[GIgied] and -[Pp] options are mutually exclusive\n"),
238 command);
239 usage();
240 }
241 if (modify && npids != 0) {
242 (void) fprintf(stderr, gettext(
243 "%s: -[GIgied] options cannot have a process-id list\n"),
244 command);
245 usage();
246 }
247 if ((proc_pattern != NULL || proc_content != CC_CONTENT_INVALID) &&
248 npids == 0) {
249 (void) sprintf(curpid, "%u", (uint_t)getppid());
250 npids = 1;
251 pidlist = &curpid_ptr;
252 }
253
254 if (legacy_update)
255 return (do_legacy());
256 if (update)
257 return (do_update());
258 if (modify)
259 return (do_modify(B_FALSE));
260 if (npids != 0)
261 return (do_processes(npids, pidlist));
262
263 return (report_settings());
264 }
265
266 static int
report_settings(void)267 report_settings(void)
268 {
269 char content_str[PRCONTENTBUFSZ];
270
271 if ((options = core_get_options()) == -1) {
272 perror("core_get_options()");
273 return (E_ERROR);
274 }
275 if (core_get_global_path(gpattern, sizeof (gpattern)) != 0) {
276 perror("core_get_global_path()");
277 return (E_ERROR);
278 }
279 if (core_get_default_path(ipattern, sizeof (ipattern)) != 0) {
280 perror("core_get_default_path()");
281 return (E_ERROR);
282 }
283 if (core_get_global_content(&glob_content) != 0) {
284 perror("core_get_global_content()");
285 return (E_ERROR);
286 }
287 if (core_get_default_content(&init_content) != 0) {
288 perror("core_get_default_content()");
289 return (E_ERROR);
290 }
291
292 (void) printf(gettext(" global core file pattern: %s\n"),
293 gpattern);
294 (void) proc_content2str(glob_content, content_str,
295 sizeof (content_str));
296 (void) printf(gettext(" global core file content: %s\n"),
297 content_str);
298 (void) printf(gettext(" init core file pattern: %s\n"),
299 ipattern);
300 (void) proc_content2str(init_content, content_str,
301 sizeof (content_str));
302 (void) printf(gettext(" init core file content: %s\n"),
303 content_str);
304 (void) printf(gettext(" global core dumps: %s\n"),
305 (options & CC_GLOBAL_PATH)? "enabled" : "disabled");
306 (void) printf(gettext(" per-process core dumps: %s\n"),
307 (options & CC_PROCESS_PATH)? "enabled" : "disabled");
308 (void) printf(gettext(" global setid core dumps: %s\n"),
309 (options & CC_GLOBAL_SETID)? "enabled" : "disabled");
310 (void) printf(gettext(" per-process setid core dumps: %s\n"),
311 (options & CC_PROCESS_SETID)? "enabled" : "disabled");
312 (void) printf(gettext(" global core dump logging: %s\n"),
313 (options & CC_GLOBAL_LOG)? "enabled" : "disabled");
314 return (E_SUCCESS);
315 }
316
317 static int
do_processes(int npids,char ** pidlist)318 do_processes(int npids, char **pidlist)
319 {
320 char process_path[PATH_MAX];
321 core_content_t content;
322 pid_t pid;
323 char *next;
324 int rc = E_SUCCESS;
325 char content_str[PRCONTENTBUFSZ];
326
327 if (proc_pattern == NULL && proc_content == CC_CONTENT_INVALID) {
328 while (npids-- > 0) {
329 pid = strtol(*pidlist, &next, 10);
330 if (*next != '\0' || !isdigit(**pidlist)) {
331 (void) fprintf(stderr,
332 gettext("%s: invalid process-id\n"),
333 *pidlist);
334 rc = E_USAGE;
335 } else if (core_get_process_path(process_path,
336 sizeof (process_path), pid) != 0 ||
337 core_get_process_content(&content, pid) != 0) {
338 perror(*pidlist);
339 rc = E_USAGE;
340 } else {
341 (void) proc_content2str(content, content_str,
342 sizeof (content_str));
343 (void) printf(gettext("%s:\t%s\t%s\n"),
344 *pidlist, process_path, content_str);
345 }
346 pidlist++;
347 }
348 } else {
349 while (npids-- > 0) {
350 pid = strtol(*pidlist, &next, 10);
351 if (*next != '\0') {
352 (void) fprintf(stderr,
353 gettext("%s: invalid process-id\n"),
354 *pidlist);
355 rc = E_USAGE;
356 } else {
357 if (proc_pattern != NULL &&
358 core_set_process_path(proc_pattern,
359 proc_size, pid) != 0) {
360 perror(*pidlist);
361 rc = E_USAGE;
362 }
363
364 if (proc_content != CC_CONTENT_INVALID &&
365 core_set_process_content(
366 &proc_content, pid) != 0) {
367 perror(*pidlist);
368 rc = E_USAGE;
369 }
370 }
371 pidlist++;
372 }
373 }
374
375 return (rc);
376 }
377
378 static void
addprop(scf_propvec_t * props,int size,int count,scf_propvec_t * pv,void * ptr)379 addprop(scf_propvec_t *props, int size, int count, scf_propvec_t *pv, void *ptr)
380 {
381 assert(count + 1 < size);
382 props[count] = *pv;
383 props[count].pv_ptr = ptr;
384 }
385
386 static boolean_t
is_online(const char * fmri)387 is_online(const char *fmri)
388 {
389 char *state = smf_get_state(fmri);
390 boolean_t result = state != NULL &&
391 strcmp(state, SCF_STATE_STRING_ONLINE) == 0;
392
393 free(state);
394 return (result);
395 }
396
397 /*
398 * The user has specified the -g, -G, -i, -I, -d, or -e options to
399 * modify the given configuration parameter. Perform the modification
400 * in the smf repository and then perform a smf_refresh_instance which
401 * will cause a coreadm -u to occur which will transfer ALL coreadm
402 * configuration information from the repository to the kernel.
403 */
404 static int
do_modify(boolean_t method)405 do_modify(boolean_t method)
406 {
407 char gcontentstr[PRCONTENTBUFSZ];
408 char icontentstr[PRCONTENTBUFSZ];
409 scf_propvec_t *prop;
410 scf_propvec_t properties[MAX_PROPS + 1];
411 int count = 0;
412
413 if (!method && !is_online(COREADM_INST_FMRI)) {
414 (void) fprintf(stderr,
415 gettext("%s: coreadm service not online\n"), command);
416 return (E_ERROR);
417 }
418
419 if (glob_pattern != NULL)
420 addprop(properties, MAX_PROPS, count++, &prop_gpattern,
421 glob_pattern);
422
423 if (glob_content != CC_CONTENT_INVALID) {
424 (void) proc_content2str(glob_content, gcontentstr,
425 sizeof (gcontentstr));
426 addprop(properties, MAX_PROPS, count++, &prop_gcontent,
427 gcontentstr);
428 }
429
430 if (init_pattern != NULL)
431 addprop(properties, MAX_PROPS, count++, &prop_ipattern,
432 init_pattern);
433
434 if (init_content != CC_CONTENT_INVALID) {
435 (void) proc_content2str(init_content, icontentstr,
436 sizeof (icontentstr));
437 addprop(properties, MAX_PROPS, count++, &prop_icontent,
438 icontentstr);
439 }
440
441 for (prop = prop_option; prop->pv_prop != NULL; prop++)
442 if ((alloptions & prop->pv_aux) != 0)
443 addprop(properties, MAX_PROPS, count++, prop, &options);
444
445 properties[count].pv_prop = NULL;
446
447 prop = NULL;
448 if (scf_write_propvec(COREADM_INST_FMRI, CONFIG_PARAMS, properties,
449 &prop) == SCF_FAILED) {
450 if (prop != NULL) {
451 (void) fprintf(stderr, gettext(
452 "%s: Unable to write property '%s': %s"), command,
453 prop->pv_prop, scf_strerror(scf_error()));
454 } else {
455 (void) fprintf(stderr, gettext(
456 "%s: Unable to write configuration: %s\n"),
457 command, scf_strerror(scf_error()));
458 }
459 return (E_ERROR);
460 }
461
462 if (smf_refresh_instance(COREADM_INST_FMRI) != 0) {
463 (void) fprintf(stderr,
464 gettext("%s: Unable to refresh %s: %s\n"
465 "Configuration stored but not made active.\n"),
466 command, COREADM_INST_FMRI, scf_strerror(scf_error()));
467 return (E_ERROR);
468 }
469
470 return (E_SUCCESS);
471 }
472
473 static const char *
write_kernel(void)474 write_kernel(void)
475 {
476 if (core_set_global_path(glob_pattern, strlen(glob_pattern) + 1) != 0)
477 return ("core_set_global_path()");
478
479 if (core_set_global_content(&glob_content) != 0)
480 return ("core_set_global_content()");
481
482 if (core_set_default_path(init_pattern, strlen(init_pattern) + 1) != 0)
483 return ("core_set_default_path()");
484
485 if (core_set_default_content(&init_content) != 0)
486 return ("core_set_init_content()");
487
488 if (core_set_options((int)options) != 0)
489 return ("core_set_options()");
490
491 return (NULL);
492 }
493
494 /*
495 * BUFSIZE must be large enough to contain the longest path plus some more.
496 */
497 #define BUFSIZE (PATH_MAX + 80)
498
499 static int
yes(char * name,char * value,int line)500 yes(char *name, char *value, int line)
501 {
502 if (strcmp(value, "yes") == 0)
503 return (1);
504 if (strcmp(value, "no") == 0)
505 return (0);
506 (void) fprintf(stderr, gettext(
507 "\"%s\", line %d: warning: value must be yes or no: %s=%s\n"),
508 PATH_CONFIG, line, name, value);
509 return (0);
510 }
511
512 static int
read_legacy(void)513 read_legacy(void)
514 {
515 FILE *fp;
516 int line;
517 char buf[BUFSIZE];
518 char name[BUFSIZE], value[BUFSIZE];
519 int n, len;
520
521 /* defaults */
522 alloptions = CC_OPTIONS;
523 options = CC_PROCESS_PATH;
524 gpattern[0] = '\0';
525 (void) strcpy(ipattern, "core");
526 glob_content = init_content = CC_CONTENT_DEFAULT;
527
528 glob_pattern = gpattern;
529 init_pattern = ipattern;
530
531 if ((fp = fopen(PATH_CONFIG, "r")) == NULL)
532 return (0);
533
534 for (line = 1; fgets(buf, sizeof (buf), fp) != NULL; line++) {
535 /*
536 * Skip comment lines and empty lines.
537 */
538 if (buf[0] == '#' || buf[0] == '\n')
539 continue;
540 /*
541 * Look for "name=value", with optional whitespace on either
542 * side, terminated by a newline, and consuming the whole line.
543 */
544 /* LINTED - unbounded string specifier */
545 n = sscanf(buf, " %[^=]=%s \n%n", name, value, &len);
546 if (n >= 1 && name[0] != '\0' &&
547 (n == 1 || len == strlen(buf))) {
548 if (n == 1)
549 value[0] = '\0';
550 if (strcmp(name, "COREADM_GLOB_PATTERN") == 0) {
551 (void) strlcpy(gpattern, value,
552 sizeof (gpattern));
553 continue;
554 }
555 if (strcmp(name, "COREADM_GLOB_CONTENT") == 0) {
556 (void) proc_str2content(value, &glob_content);
557 continue;
558 }
559 if (strcmp(name, "COREADM_INIT_PATTERN") == 0) {
560 (void) strlcpy(ipattern, value,
561 sizeof (ipattern));
562 continue;
563 }
564 if (strcmp(name, "COREADM_INIT_CONTENT") == 0) {
565 (void) proc_str2content(value, &init_content);
566 continue;
567 }
568 if (strcmp(name, "COREADM_GLOB_ENABLED") == 0) {
569 if (yes(name, value, line))
570 options |= CC_GLOBAL_PATH;
571 continue;
572 }
573 if (strcmp(name, "COREADM_PROC_ENABLED") == 0) {
574 if (yes(name, value, line))
575 options |= CC_PROCESS_PATH;
576 else
577 options &= ~CC_PROCESS_PATH;
578 continue;
579 }
580 if (strcmp(name, "COREADM_GLOB_SETID_ENABLED") == 0) {
581 if (yes(name, value, line))
582 options |= CC_GLOBAL_SETID;
583 continue;
584 }
585 if (strcmp(name, "COREADM_PROC_SETID_ENABLED") == 0) {
586 if (yes(name, value, line))
587 options |= CC_PROCESS_SETID;
588 continue;
589 }
590 if (strcmp(name, "COREADM_GLOB_LOG_ENABLED") == 0) {
591 if (yes(name, value, line))
592 options |= CC_GLOBAL_LOG;
593 continue;
594 }
595 (void) fprintf(stderr, gettext(
596 "\"%s\", line %d: warning: invalid token: %s\n"),
597 PATH_CONFIG, line, name);
598 } else {
599 (void) fprintf(stderr,
600 gettext("\"%s\", line %d: syntax error\n"),
601 PATH_CONFIG, line);
602 }
603 }
604 (void) fclose(fp);
605
606 return (1);
607 }
608
609 /*
610 * Loads and applies the coreadm configuration stored in the default
611 * coreadm instance. As this option is (only) used from within an SMF
612 * service method, this function must return an SMF_EXIT_* exit status
613 * to its caller.
614 */
615 static int
do_update(void)616 do_update(void)
617 {
618 char *gcstr, *icstr;
619 scf_propvec_t properties[MAX_PROPS + 1];
620 scf_propvec_t *prop;
621 int count = 0;
622 const char *errstr;
623
624 if (read_legacy()) {
625 if ((errstr = write_kernel()) != NULL)
626 goto error;
627
628 if (do_modify(B_TRUE) != 0 ||
629 rename(PATH_CONFIG, PATH_CONFIG_OLD) != 0) {
630 (void) fprintf(stderr, gettext(
631 "%s: failed to import legacy configuration.\n"),
632 command);
633 return (SMF_EXIT_ERR_FATAL);
634 }
635 return (SMF_EXIT_OK);
636 }
637
638 addprop(properties, MAX_PROPS, count++, &prop_gpattern, &glob_pattern);
639 addprop(properties, MAX_PROPS, count++, &prop_gcontent, &gcstr);
640 addprop(properties, MAX_PROPS, count++, &prop_ipattern, &init_pattern);
641 addprop(properties, MAX_PROPS, count++, &prop_icontent, &icstr);
642 for (prop = prop_option; prop->pv_prop != NULL; prop++)
643 addprop(properties, MAX_PROPS, count++, prop, &options);
644 properties[count].pv_prop = NULL;
645
646 alloptions = CC_OPTIONS;
647 if (scf_read_propvec(COREADM_INST_FMRI, CONFIG_PARAMS, B_TRUE,
648 properties, &prop) == SCF_FAILED) {
649 if (prop != NULL) {
650 (void) fprintf(stderr, gettext(
651 "%s: configuration property '%s' not found.\n"),
652 command, prop->pv_prop);
653 } else {
654 (void) fprintf(stderr, gettext(
655 "%s: unable to read configuration: %s\n"),
656 command, scf_strerror(scf_error()));
657 }
658 return (SMF_EXIT_ERR_FATAL);
659 }
660
661 (void) proc_str2content(gcstr, &glob_content);
662 (void) proc_str2content(icstr, &init_content);
663
664 errstr = write_kernel();
665 scf_clean_propvec(properties);
666 if (errstr == NULL)
667 return (SMF_EXIT_OK);
668
669 error:
670 if (errno == EPERM) {
671 (void) perm();
672 return (SMF_EXIT_ERR_PERM);
673 }
674 perror(errstr);
675 return (SMF_EXIT_ERR_FATAL);
676 }
677
do_legacy()678 static int do_legacy()
679 {
680 const char *errstr;
681
682 if (read_legacy() && (errstr = write_kernel()) != NULL) {
683 if (errno == EPERM)
684 return (perm());
685 perror(errstr);
686 return (E_ERROR);
687 }
688
689 return (E_SUCCESS);
690 }
691