This source file includes following definitions.
- cib_file_inputfd
- cib_file_set_connection_dnotify
- cib_file_register_notification
- cib_file_verify_digest
- cib_file_read_and_verify
- cib_file_is_live
- cib_file_backup
- cib_file_prepare_xml
- cib_file_write_with_digest
- cib_file_new
- load_file_cib
- cib_file_signon
- cib_file_write_live
- cib_file_signoff
- cib_file_free
- cib_file_perform_op
- cib_file_perform_op_delegate
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 #include <crm_internal.h>
20 #include <unistd.h>
21 #include <limits.h>
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <pwd.h>
27
28 #include <sys/stat.h>
29 #include <sys/types.h>
30 #include <glib.h>
31
32 #include <crm/crm.h>
33 #include <crm/cib/internal.h>
34 #include <crm/msg_xml.h>
35 #include <crm/common/ipc.h>
36 #include <crm/common/xml.h>
37
38 #define cib_flag_dirty 0x00001
39 #define cib_flag_live 0x00002
40
41 typedef struct cib_file_opaque_s {
42 int flags;
43 char *filename;
44
45 } cib_file_opaque_t;
46
47 int cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section,
48 xmlNode * data, xmlNode ** output_data, int call_options);
49
50 int cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section,
51 xmlNode * data, xmlNode ** output_data, int call_options,
52 const char *user_name);
53
54 int cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type);
55 int cib_file_signoff(cib_t * cib);
56 int cib_file_free(cib_t * cib);
57
58 static int
59 cib_file_inputfd(cib_t * cib)
60 {
61 return -EPROTONOSUPPORT;
62 }
63
64 static int
65 cib_file_set_connection_dnotify(cib_t * cib, void (*dnotify) (gpointer user_data))
66 {
67 return -EPROTONOSUPPORT;
68 }
69
70 static int
71 cib_file_register_notification(cib_t * cib, const char *callback, int enabled)
72 {
73 return -EPROTONOSUPPORT;
74 }
75
76
77
78
79
80
81
82
83
84
85 static gboolean
86 cib_file_verify_digest(xmlNode *root, const char *sigfile)
87 {
88 gboolean passed = FALSE;
89 char *expected = crm_read_contents(sigfile);
90
91 if (expected == NULL) {
92 switch (errno) {
93 case 0:
94 crm_err("On-disk digest at %s is empty", sigfile);
95 return FALSE;
96 case ENOENT:
97 crm_warn("No on-disk digest present at %s", sigfile);
98 return TRUE;
99 default:
100 crm_perror(LOG_ERR, "Could not read on-disk digest from %s", sigfile);
101 return FALSE;
102 }
103 }
104 passed = crm_digest_verify(root, expected);
105 free(expected);
106 return passed;
107 }
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 int
125 cib_file_read_and_verify(const char *filename, const char *sigfile, xmlNode **root)
126 {
127 int s_res;
128 struct stat buf;
129 char *local_sigfile = NULL;
130 xmlNode *local_root = NULL;
131
132 CRM_ASSERT(filename != NULL);
133 if (root) {
134 *root = NULL;
135 }
136
137
138 s_res = stat(filename, &buf);
139 if (s_res < 0) {
140 crm_perror(LOG_WARNING, "Could not verify cluster configuration file %s", filename);
141 return -errno;
142 } else if (buf.st_size == 0) {
143 crm_warn("Cluster configuration file %s is corrupt (size is zero)", filename);
144 return -pcmk_err_cib_corrupt;
145 }
146
147
148 local_root = filename2xml(filename);
149 if (local_root == NULL) {
150 crm_warn("Cluster configuration file %s is corrupt (unparseable as XML)", filename);
151 return -pcmk_err_cib_corrupt;
152 }
153
154
155 if (sigfile == NULL) {
156 sigfile = local_sigfile = crm_concat(filename, "sig", '.');
157 }
158
159
160 if (cib_file_verify_digest(local_root, sigfile) == FALSE) {
161 free(local_sigfile);
162 free_xml(local_root);
163 return -pcmk_err_cib_modified;
164 }
165
166 free(local_sigfile);
167 if (root) {
168 *root = local_root;
169 } else {
170 free_xml(local_root);
171 }
172 return pcmk_ok;
173 }
174
175 #define CIB_SERIES "cib"
176 #define CIB_SERIES_MAX 100
177 #define CIB_SERIES_BZIP FALSE
178
179
180
181 #define CIB_LIVE_NAME CIB_SERIES ".xml"
182
183
184
185
186
187
188
189
190
191 static gboolean
192 cib_file_is_live(const char *filename)
193 {
194 if (filename != NULL) {
195
196 char *real_filename = crm_compat_realpath(filename);
197
198 if (real_filename != NULL) {
199 const char *livenames[] = {
200 CRM_CONFIG_DIR "/" CIB_LIVE_NAME,
201 CRM_LEGACY_CONFIG_DIR "/" CIB_LIVE_NAME
202 };
203 char *real_livename;
204 int i;
205
206
207 for (i = 0; i < DIMOF(livenames); ++i) {
208 real_livename = crm_compat_realpath(livenames[i]);
209 if (real_livename && !strcmp(real_filename, real_livename)) {
210 free(real_livename);
211 return TRUE;
212 }
213 free(real_livename);
214 }
215 free(real_filename);
216 }
217 }
218 return FALSE;
219 }
220
221
222
223
224
225 static uid_t cib_file_owner = 0;
226 static uid_t cib_file_group = 0;
227 static gboolean cib_do_chown = FALSE;
228
229
230
231
232
233
234
235
236
237
238 static int
239 cib_file_backup(const char *cib_dirname, const char *cib_filename)
240 {
241 int rc = 0;
242 char *cib_path = crm_concat(cib_dirname, cib_filename, '/');
243 char *cib_digest = crm_concat(cib_path, "sig", '.');
244
245
246 int seq = get_last_sequence(cib_dirname, CIB_SERIES);
247 char *backup_path = generate_series_filename(cib_dirname, CIB_SERIES, seq,
248 CIB_SERIES_BZIP);
249 char *backup_digest = crm_concat(backup_path, "sig", '.');
250
251 CRM_ASSERT((cib_path != NULL) && (cib_digest != NULL)
252 && (backup_path != NULL) && (backup_digest != NULL));
253
254
255 unlink(backup_path);
256 unlink(backup_digest);
257
258
259 if ((link(cib_path, backup_path) < 0) && (errno != ENOENT)) {
260 crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
261 cib_path, backup_path);
262 rc = -1;
263
264
265 } else if ((link(cib_digest, backup_digest) < 0) && (errno != ENOENT)) {
266 crm_perror(LOG_ERR, "Could not archive %s by linking to %s",
267 cib_digest, backup_digest);
268 rc = -1;
269
270
271 } else {
272 write_last_sequence(cib_dirname, CIB_SERIES, seq + 1, CIB_SERIES_MAX);
273 if (cib_do_chown) {
274 if ((chown(backup_path, cib_file_owner, cib_file_group) < 0)
275 && (errno != ENOENT)) {
276 crm_perror(LOG_ERR, "Could not set owner of %s", backup_path);
277 rc = -1;
278 }
279 if ((chown(backup_digest, cib_file_owner, cib_file_group) < 0)
280 && (errno != ENOENT)) {
281 crm_perror(LOG_ERR, "Could not set owner of %s", backup_digest);
282 rc = -1;
283 }
284 if (crm_chown_last_sequence(cib_dirname, CIB_SERIES, cib_file_owner,
285 cib_file_group) < 0) {
286 crm_perror(LOG_ERR,
287 "Could not set owner of %s last sequence file",
288 cib_dirname);
289 rc = -1;
290 }
291 }
292 crm_sync_directory(cib_dirname);
293 crm_info("Archived previous version as %s", backup_path);
294 }
295
296 free(cib_path);
297 free(cib_digest);
298 free(backup_path);
299 free(backup_digest);
300 return rc;
301 }
302
303
304
305
306
307
308
309
310
311
312
313
314 static void
315 cib_file_prepare_xml(xmlNode *root)
316 {
317 xmlNode *cib_status_root = NULL;
318
319
320 crm_xml_add(root, XML_ATTR_NUMUPDATES, "0");
321 crm_xml_add_last_written(root);
322
323
324
325 cib_status_root = find_xml_node(root, XML_CIB_TAG_STATUS, TRUE);
326 CRM_LOG_ASSERT(cib_status_root != NULL);
327 if (cib_status_root != NULL) {
328 free_xml(cib_status_root);
329 }
330 }
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345 int
346 cib_file_write_with_digest(xmlNode *cib_root, const char *cib_dirname,
347 const char *cib_filename)
348 {
349 int exit_rc = pcmk_ok;
350 int rc, fd;
351 char *digest = NULL;
352
353
354 const char *epoch = crm_element_value(cib_root, XML_ATTR_GENERATION);
355 const char *admin_epoch = crm_element_value(cib_root,
356 XML_ATTR_GENERATION_ADMIN);
357
358
359 char *cib_path = crm_concat(cib_dirname, cib_filename, '/');
360 char *digest_path = crm_concat(cib_path, "sig", '.');
361
362
363 char *tmp_cib = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
364 char *tmp_digest = crm_strdup_printf("%s/cib.XXXXXX", cib_dirname);
365
366 CRM_ASSERT((cib_path != NULL) && (digest_path != NULL)
367 && (tmp_cib != NULL) && (tmp_digest != NULL));
368
369
370 crm_trace("Reading cluster configuration file %s", cib_path);
371 rc = cib_file_read_and_verify(cib_path, NULL, NULL);
372 if ((rc != pcmk_ok) && (rc != -ENOENT)) {
373 crm_err("%s was manually modified while the cluster was active!",
374 cib_path);
375 exit_rc = pcmk_err_cib_modified;
376 goto cleanup;
377 }
378
379
380 if (cib_file_backup(cib_dirname, cib_filename) < 0) {
381 exit_rc = pcmk_err_cib_backup;
382 goto cleanup;
383 }
384
385 crm_debug("Writing CIB to disk");
386 umask(S_IWGRP | S_IWOTH | S_IROTH);
387 cib_file_prepare_xml(cib_root);
388
389
390 fd = mkstemp(tmp_cib);
391 if (fd < 0) {
392 crm_perror(LOG_ERR, "Couldn't open temporary file %s for writing CIB",
393 tmp_cib);
394 exit_rc = pcmk_err_cib_save;
395 goto cleanup;
396 }
397
398
399 if (fchmod(fd, S_IRUSR | S_IWUSR) < 0) {
400 crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
401 tmp_cib);
402 exit_rc = pcmk_err_cib_save;
403 goto cleanup;
404 }
405 if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
406 crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
407 tmp_cib);
408 exit_rc = pcmk_err_cib_save;
409 goto cleanup;
410 }
411
412
413 if (write_xml_fd(cib_root, tmp_cib, fd, FALSE) <= 0) {
414 crm_err("Changes couldn't be written to %s", tmp_cib);
415 exit_rc = pcmk_err_cib_save;
416 goto cleanup;
417 }
418
419
420 digest = calculate_on_disk_digest(cib_root);
421 CRM_ASSERT(digest != NULL);
422 crm_info("Wrote version %s.%s.0 of the CIB to disk (digest: %s)",
423 (admin_epoch ? admin_epoch : "0"), (epoch ? epoch : "0"), digest);
424
425
426 fd = mkstemp(tmp_digest);
427 if (fd < 0) {
428 crm_perror(LOG_ERR, "Could not create temporary file for CIB digest");
429 exit_rc = pcmk_err_cib_save;
430 goto cleanup;
431 }
432 if (cib_do_chown && (fchown(fd, cib_file_owner, cib_file_group) < 0)) {
433 crm_perror(LOG_ERR, "Couldn't protect temporary file %s for writing CIB",
434 tmp_cib);
435 exit_rc = pcmk_err_cib_save;
436 close(fd);
437 goto cleanup;
438 }
439 if (crm_write_sync(fd, digest) < 0) {
440 crm_perror(LOG_ERR, "Could not write digest to file %s", tmp_digest);
441 exit_rc = pcmk_err_cib_save;
442 close(fd);
443 goto cleanup;
444 }
445 close(fd);
446 crm_debug("Wrote digest %s to disk", digest);
447
448
449 crm_info("Reading cluster configuration file %s (digest: %s)",
450 tmp_cib, tmp_digest);
451 rc = cib_file_read_and_verify(tmp_cib, tmp_digest, NULL);
452 CRM_ASSERT(rc == 0);
453
454
455 crm_debug("Activating %s", tmp_cib);
456 if (rename(tmp_cib, cib_path) < 0) {
457 crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_cib, cib_path);
458 exit_rc = pcmk_err_cib_save;
459 }
460 if (rename(tmp_digest, digest_path) < 0) {
461 crm_perror(LOG_ERR, "Couldn't rename %s as %s", tmp_digest,
462 digest_path);
463 exit_rc = pcmk_err_cib_save;
464 }
465 crm_sync_directory(cib_dirname);
466
467 cleanup:
468 free(cib_path);
469 free(digest_path);
470 free(digest);
471 free(tmp_digest);
472 free(tmp_cib);
473 return exit_rc;
474 }
475
476 cib_t *
477 cib_file_new(const char *cib_location)
478 {
479 cib_file_opaque_t *private = NULL;
480 cib_t *cib = cib_new_variant();
481
482 private = calloc(1, sizeof(cib_file_opaque_t));
483 CRM_ASSERT((cib != NULL) && (private != NULL));
484
485 cib->variant = cib_file;
486 cib->variant_opaque = private;
487
488 if (cib_location == NULL) {
489 cib_location = getenv("CIB_file");
490 }
491 private->flags = 0;
492 if (cib_file_is_live(cib_location)) {
493 set_bit(private->flags, cib_flag_live);
494 crm_trace("File %s detected as live CIB", cib_location);
495 }
496 private->filename = strdup(cib_location);
497
498
499 cib->delegate_fn = cib_file_perform_op_delegate;
500 cib->cmds->signon = cib_file_signon;
501 cib->cmds->signoff = cib_file_signoff;
502 cib->cmds->free = cib_file_free;
503 cib->cmds->inputfd = cib_file_inputfd;
504
505 cib->cmds->register_notification = cib_file_register_notification;
506 cib->cmds->set_connection_dnotify = cib_file_set_connection_dnotify;
507
508 return cib;
509 }
510
511 static xmlNode *in_mem_cib = NULL;
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527 static int
528 load_file_cib(const char *filename)
529 {
530 struct stat buf;
531 xmlNode *root = NULL;
532 const char *ignore_dtd = NULL;
533
534
535 if (stat(filename, &buf) < 0) {
536 return -ENXIO;
537 }
538
539
540 root = filename2xml(filename);
541 if (root == NULL) {
542 return -pcmk_err_schema_validation;
543 }
544
545
546 if (find_xml_node(root, XML_CIB_TAG_STATUS, FALSE) == NULL) {
547 create_xml_node(root, XML_CIB_TAG_STATUS);
548 }
549
550
551 ignore_dtd = crm_element_value(root, XML_ATTR_VALIDATION);
552 if (validate_xml(root, NULL, TRUE) == FALSE) {
553 crm_err("CIB does not validate against %s", ignore_dtd);
554 free_xml(root);
555 return -pcmk_err_schema_validation;
556 }
557
558
559 in_mem_cib = root;
560 return pcmk_ok;
561 }
562
563 int
564 cib_file_signon(cib_t * cib, const char *name, enum cib_conn_type type)
565 {
566 int rc = pcmk_ok;
567 cib_file_opaque_t *private = cib->variant_opaque;
568
569 if (private->filename == NULL) {
570 rc = -EINVAL;
571 } else {
572 rc = load_file_cib(private->filename);
573 }
574
575 if (rc == pcmk_ok) {
576 crm_debug("%s: Opened connection to local file '%s'", name, private->filename);
577 cib->state = cib_connected_command;
578 cib->type = cib_command;
579
580 } else {
581 fprintf(stderr, "%s: Connection to local file '%s' failed: %s\n",
582 name, private->filename, pcmk_strerror(rc));
583 }
584
585 return rc;
586 }
587
588
589
590
591
592
593
594
595
596 static int
597 cib_file_write_live(char *path)
598 {
599 uid_t uid = geteuid();
600 struct passwd *daemon_pwent;
601 char *sep = strrchr(path, '/');
602 const char *cib_dirname, *cib_filename;
603 int rc = 0;
604
605
606 errno = 0;
607 daemon_pwent = getpwnam(CRM_DAEMON_USER);
608 if (daemon_pwent == NULL) {
609 crm_perror(LOG_ERR, "Could not find %s user", CRM_DAEMON_USER);
610 return -1;
611 }
612
613
614
615
616
617 if ((uid != 0) && (uid != daemon_pwent->pw_uid)) {
618 crm_perror(LOG_ERR, "Must be root or %s to modify live CIB",
619 CRM_DAEMON_USER);
620 return 0;
621 }
622
623
624
625
626
627 if (sep == NULL) {
628 cib_dirname = "./";
629 cib_filename = path;
630 } else if (sep == path) {
631 cib_dirname = "/";
632 cib_filename = path + 1;
633 } else {
634 *sep = '\0';
635 cib_dirname = path;
636 cib_filename = sep + 1;
637 }
638
639
640 if (uid == 0) {
641 cib_file_owner = daemon_pwent->pw_uid;
642 cib_file_group = daemon_pwent->pw_gid;
643 cib_do_chown = TRUE;
644 }
645
646
647 if (cib_file_write_with_digest(in_mem_cib, cib_dirname,
648 cib_filename) != pcmk_ok) {
649 rc = -1;
650 }
651
652
653 if (uid == 0) {
654 cib_do_chown = FALSE;
655 }
656
657
658 if ((sep != NULL) && (*sep == '\0')) {
659 *sep = '/';
660 }
661
662 return rc;
663 }
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678 int
679 cib_file_signoff(cib_t * cib)
680 {
681 int rc = pcmk_ok;
682 cib_file_opaque_t *private = cib->variant_opaque;
683
684 crm_debug("Signing out of the CIB Service");
685 cib->state = cib_disconnected;
686 cib->type = cib_no_connection;
687
688
689 if (is_set(private->flags, cib_flag_dirty)) {
690
691
692 if (is_set(private->flags, cib_flag_live)) {
693 if (cib_file_write_live(private->filename) < 0) {
694 rc = pcmk_err_generic;
695 }
696
697
698 } else {
699 gboolean do_bzip = crm_ends_with_ext(private->filename, ".bz2");
700
701 if (write_xml_file(in_mem_cib, private->filename, do_bzip) <= 0) {
702 rc = pcmk_err_generic;
703 }
704 }
705
706 if (rc == pcmk_ok) {
707 crm_info("Wrote CIB to %s", private->filename);
708 clear_bit(private->flags, cib_flag_dirty);
709 } else {
710 crm_err("Could not write CIB to %s", private->filename);
711 }
712 }
713
714
715 free_xml(in_mem_cib);
716 in_mem_cib = NULL;
717 return rc;
718 }
719
720 int
721 cib_file_free(cib_t * cib)
722 {
723 int rc = pcmk_ok;
724
725 if (cib->state != cib_disconnected) {
726 rc = cib_file_signoff(cib);
727 }
728
729 if (rc == pcmk_ok) {
730 cib_file_opaque_t *private = cib->variant_opaque;
731
732 free(private->filename);
733 free(cib->cmds);
734 free(private);
735 free(cib);
736
737 } else {
738 fprintf(stderr, "Couldn't sign off: %d\n", rc);
739 }
740
741 return rc;
742 }
743
744 struct cib_func_entry {
745 const char *op;
746 gboolean read_only;
747 cib_op_t fn;
748 };
749
750
751 static struct cib_func_entry cib_file_ops[] = {
752 {CIB_OP_QUERY, TRUE, cib_process_query},
753 {CIB_OP_MODIFY, FALSE, cib_process_modify},
754 {CIB_OP_APPLY_DIFF, FALSE, cib_process_diff},
755 {CIB_OP_BUMP, FALSE, cib_process_bump},
756 {CIB_OP_REPLACE, FALSE, cib_process_replace},
757 {CIB_OP_CREATE, FALSE, cib_process_create},
758 {CIB_OP_DELETE, FALSE, cib_process_delete},
759 {CIB_OP_ERASE, FALSE, cib_process_erase},
760 {CIB_OP_UPGRADE, FALSE, cib_process_upgrade},
761 };
762
763
764 int
765 cib_file_perform_op(cib_t * cib, const char *op, const char *host, const char *section,
766 xmlNode * data, xmlNode ** output_data, int call_options)
767 {
768 return cib_file_perform_op_delegate(cib, op, host, section, data, output_data, call_options,
769 NULL);
770 }
771
772 int
773 cib_file_perform_op_delegate(cib_t * cib, const char *op, const char *host, const char *section,
774 xmlNode * data, xmlNode ** output_data, int call_options,
775 const char *user_name)
776 {
777 int rc = pcmk_ok;
778 char *effective_user = NULL;
779 gboolean query = FALSE;
780 gboolean changed = FALSE;
781 xmlNode *request = NULL;
782 xmlNode *output = NULL;
783 xmlNode *cib_diff = NULL;
784 xmlNode *result_cib = NULL;
785 cib_op_t *fn = NULL;
786 int lpc = 0;
787 static int max_msg_types = DIMOF(cib_file_ops);
788 cib_file_opaque_t *private = cib->variant_opaque;
789
790 crm_info("%s on %s", op, section);
791 call_options |= (cib_no_mtime | cib_inhibit_bcast | cib_scope_local);
792
793 if (cib->state == cib_disconnected) {
794 return -ENOTCONN;
795 }
796
797 if (output_data != NULL) {
798 *output_data = NULL;
799 }
800
801 if (op == NULL) {
802 return -EINVAL;
803 }
804
805 for (lpc = 0; lpc < max_msg_types; lpc++) {
806 if (safe_str_eq(op, cib_file_ops[lpc].op)) {
807 fn = &(cib_file_ops[lpc].fn);
808 query = cib_file_ops[lpc].read_only;
809 break;
810 }
811 }
812
813 if (fn == NULL) {
814 return -EPROTONOSUPPORT;
815 }
816
817 cib->call_id++;
818 request = cib_create_op(cib->call_id, "dummy-token", op, host, section, data, call_options, user_name);
819 #if ENABLE_ACL
820 if(user_name) {
821 crm_xml_add(request, XML_ACL_TAG_USER, user_name);
822 }
823 crm_trace("Performing %s operation as %s", op, user_name);
824 #endif
825
826
827 if (section != NULL && data != NULL && crm_str_eq(crm_element_name(data), XML_TAG_CIB, TRUE)) {
828 data = get_object_root(section, data);
829 }
830
831 rc = cib_perform_op(op, call_options, fn, query,
832 section, request, data, TRUE, &changed, in_mem_cib, &result_cib, &cib_diff,
833 &output);
834
835 free_xml(request);
836 if (rc == -pcmk_err_schema_validation) {
837 validate_xml_verbose(result_cib);
838 }
839
840 if (rc != pcmk_ok) {
841 free_xml(result_cib);
842
843 } else if (query == FALSE) {
844 xml_log_patchset(LOG_DEBUG, "cib:diff", cib_diff);
845 free_xml(in_mem_cib);
846 in_mem_cib = result_cib;
847 set_bit(private->flags, cib_flag_dirty);
848 }
849
850 free_xml(cib_diff);
851
852 if (cib->op_callback != NULL) {
853 cib->op_callback(NULL, cib->call_id, rc, output);
854 }
855
856 if (output_data && output) {
857 if(output == in_mem_cib) {
858 *output_data = copy_xml(output);
859 } else {
860 *output_data = output;
861 }
862
863 } else if(output != in_mem_cib) {
864 free_xml(output);
865 }
866
867 free(effective_user);
868 return rc;
869 }