static int init_device(struct sbd_context *st) { struct sector_header_s *s_header; struct sector_node_s *s_node; struct sector_mbox_s *s_mbox; struct stat s; char uuid[37]; int i; int rc = 0; s_header = sector_alloc(); s_node = sector_alloc(); s_mbox = sector_alloc(); memcpy(s_header->magic, sbd_magic, sizeof(s_header->magic)); s_header->version = sbd_version; s_header->slots = 255; s_header->sector_size = sector_size; s_header->timeout_watchdog = timeout_watchdog; s_header->timeout_allocate = timeout_allocate; s_header->timeout_loop = timeout_loop; s_header->timeout_msgwait = timeout_msgwait; s_header->minor_version = 1; uuid_generate(s_header->uuid); uuid_unparse_lower(s_header->uuid, uuid); fstat(st->devfd, &s); /* printf("st_size = %ld, st_blksize = %ld, st_blocks = %ld\n", s.st_size, s.st_blksize, s.st_blocks); */ cl_log(LOG_INFO, "Creating version %d.%d header on device %d (uuid: %s)", s_header->version, s_header->minor_version, st->devfd, uuid); fprintf(stdout, "Creating version %d.%d header on device %d (uuid: %s)\n", s_header->version, s_header->minor_version, st->devfd, uuid); if (header_write(st, s_header) < 0) { rc = -1; goto out; } cl_log(LOG_INFO, "Initializing %d slots on device %d", s_header->slots, st->devfd); fprintf(stdout, "Initializing %d slots on device %d\n", s_header->slots, st->devfd); for (i=0;i < s_header->slots;i++) { if (slot_write(st, i, s_node) < 0) { rc = -1; goto out; } if (mbox_write(st, i, s_mbox) < 0) { rc = -1; goto out; } } out: free(s_node); free(s_header); free(s_mbox); return(rc); }
static int slot_allocate(struct sbd_context *st, const char *name) { struct sector_header_s *s_header = NULL; struct sector_node_s *s_node = NULL; struct sector_mbox_s *s_mbox = NULL; int i; int rc = 0; if (!name) { cl_log(LOG_ERR, "slot_allocate(): No name specified.\n"); fprintf(stderr, "slot_allocate(): No name specified.\n"); rc = -1; goto out; } s_header = header_get(st); if (!s_header) { rc = -1; goto out; } s_node = sector_alloc(); s_mbox = sector_alloc(); while (1) { i = slot_lookup(st, s_header, name); if ((i >= 0) || (i == -2)) { /* -1 is "no slot found", in which case we * proceed to allocate a new one. * -2 is "read error during lookup", in which * case we error out too * >= 0 is "slot already allocated" */ rc = i; goto out; } i = slot_unused(st, s_header); if (i >= 0) { cl_log(LOG_INFO, "slot %d is unused - trying to own", i); fprintf(stdout, "slot %d is unused - trying to own\n", i); memset(s_node, 0, sizeof(*s_node)); s_node->in_use = 1; strncpy(s_node->name, name, sizeof(s_node->name)); if (slot_write(st, i, s_node) < 0) { rc = -1; goto out; } sleep(timeout_allocate); } else { cl_log(LOG_ERR, "No more free slots."); fprintf(stderr, "No more free slots.\n"); rc = -1; goto out; } } out: free(s_node); free(s_header); free(s_mbox); return(rc); }
/** * Create a logpack. * * @logh_sectdp pointer to sector data pointer * for logpack header (will be set). * @logd_sect_aryp pointer to sector data array pointer * for logpack data (will be set). * @pbs physical block size [byte]. * @bufsize buffer size for log data [byte]. * * RETURN: * allocated logpack in success, or NULL. */ struct logpack *alloc_logpack( unsigned int pbs, unsigned int n_sectors) { struct logpack *pack; ASSERT(is_valid_pbs(pbs)); ASSERT(0 < n_sectors); pack = (struct logpack *)malloc(sizeof(*pack)); if (!pack) { goto error1; } memset(pack, 0, sizeof(*pack)); /* Buffer for logpack header. */ pack->sectd = sector_alloc(pbs); if (!pack->sectd) { goto error1; } pack->header = get_logpack_header(pack->sectd); /* Buffer for logpack data. */ pack->sectd_ary = sector_array_alloc(pbs, n_sectors); if (!pack->sectd_ary) { goto error1; } return pack; error1: LOGe("Memory allocation failure.\n"); free_logpack(pack); return NULL; }
/* Check if there already is a slot allocated to said name; returns the * slot number. If not found, returns -1. * This is necessary because slots might not be continuous. */ static int slot_lookup(struct sbd_context *st, const struct sector_header_s *s_header, const char *name) { struct sector_node_s *s_node = NULL; int i; int rc = -1; if (!name) { cl_log(LOG_ERR, "slot_lookup(): No name specified.\n"); goto out; } s_node = sector_alloc(); for (i=0; i < s_header->slots; i++) { if (slot_read(st, i, s_node) < 0) { rc = -2; goto out; } if (s_node->in_use != 0) { if (strncasecmp(s_node->name, name, sizeof(s_node->name)) == 0) { DBGLOG(LOG_INFO, "%s owns slot %d", name, i); rc = i; goto out; } } } out: free(s_node); return rc; }
static int mbox_write_verify(struct sbd_context *st, int mbox, struct sector_mbox_s *s_mbox) { void *data; int rc = 0; if (sector_write(st, MBOX_TO_SECTOR(mbox), s_mbox) < 0) return -1; data = sector_alloc(); if (sector_read(st, MBOX_TO_SECTOR(mbox), data) < 0) { rc = -1; goto out; } if (memcmp(s_mbox, data, sector_size) != 0) { cl_log(LOG_ERR, "Write verification failed!"); rc = -1; goto out; } rc = 0; out: free(data); return rc; }
/** * Invalidate lsid inside ring buffer. */ bool invalidate_lsid(struct walb_dev *wdev, u64 lsid) { struct sector_data *zero_sector; struct walb_super_sector *super; u64 off; bool ret; ASSERT(lsid != INVALID_LSID); zero_sector = sector_alloc( wdev->physical_bs, GFP_KERNEL | __GFP_ZERO); if (!zero_sector) { LOGe("sector allocation failed.\n"); return false; } spin_lock(&wdev->lsuper0_lock); super = get_super_sector(wdev->lsuper0); off = get_offset_of_lsid_2(super, lsid); spin_unlock(&wdev->lsuper0_lock); ret = sector_io(WRITE, wdev->ldev, off, zero_sector); if (!ret) { LOGe("sector write failed.\n"); iocore_set_readonly(wdev); } sector_free(zero_sector); return ret; }
static int slot_list(struct sbd_context *st) { struct sector_header_s *s_header = NULL; struct sector_node_s *s_node = NULL; struct sector_mbox_s *s_mbox = NULL; int i; int rc = 0; s_header = header_get(st); if (!s_header) { rc = -1; goto out; } s_node = sector_alloc(); s_mbox = sector_alloc(); for (i=0; i < s_header->slots; i++) { if (slot_read(st, i, s_node) < 0) { rc = -1; goto out; } if (s_node->in_use > 0) { if (mbox_read(st, i, s_mbox) < 0) { rc = -1; goto out; } printf("%d\t%s\t%s\t%s\n", i, s_node->name, char2cmd(s_mbox->cmd), s_mbox->from); } } out: free(s_node); free(s_header); free(s_mbox); return rc; }
/** * Check logpack of the given lsid exists. * * @lsid lsid to check. * * @return Non-zero if valid, or 0. */ int walb_check_lsid_valid(struct walb_dev *wdev, u64 lsid) { struct sector_data *sect; struct walb_logpack_header *logh; u64 off; ASSERT(wdev); sect = sector_alloc(wdev->physical_bs, GFP_NOIO); if (!sect) { LOGe("walb_check_lsid_valid: alloc sector failed.\n"); goto error0; } ASSERT(is_same_size_sector(sect, wdev->lsuper0)); logh = get_logpack_header(sect); spin_lock(&wdev->lsuper0_lock); off = get_offset_of_lsid_2(get_super_sector(wdev->lsuper0), lsid); spin_unlock(&wdev->lsuper0_lock); if (!sector_io(READ, wdev->ldev, off, sect)) { LOGe("walb_check_lsid_valid: read sector failed.\n"); goto error1; } /* Check valid logpack header. */ if (!is_valid_logpack_header_with_checksum( logh, wdev->physical_bs, wdev->log_checksum_salt)) { goto error1; } /* Check lsid. */ if (logh->logpack_lsid != lsid) { goto error1; } sector_free(sect); return 1; error1: sector_free(sect); error0: return 0; }
static int slot_unused(struct sbd_context *st, const struct sector_header_s *s_header) { struct sector_node_s *s_node; int i; int rc = -1; s_node = sector_alloc(); for (i=0; i < s_header->slots; i++) { if (slot_read(st, i, s_node) < 0) { rc = -1; goto out; } if (s_node->in_use == 0) { rc = i; goto out; } } out: free(s_node); return rc; }
static struct sector_header_s * header_get(struct sbd_context *st) { struct sector_header_s *s_header; s_header = sector_alloc(); if (header_read(st, s_header) < 0) { cl_log(LOG_ERR, "Unable to read header from device %d", st->devfd); return NULL; } if (valid_header(s_header) < 0) { cl_log(LOG_ERR, "header on device %d is not valid.", st->devfd); return NULL; } /* cl_log(LOG_INFO, "Found version %d header with %d slots", s_header->version, s_header->slots); */ return s_header; }
/** * Write an end logpack header block. */ bool write_end_logpack_header(int fd, unsigned int pbs, u32 salt) { bool ret = false; struct walb_logpack_header *h; struct sector_data *sect = sector_alloc(pbs); if (!sect) { LOGe("sector_alloc failed.\n"); return false; } h = get_logpack_header(sect); memset(h, 0, pbs); h->sector_type = SECTOR_TYPE_LOGPACK; h->n_records = 0; h->logpack_lsid = (u64)(-1); h->checksum = 0; h->checksum = checksum((const u8 *)h, pbs, salt); ret = write_data(fd, (const u8 *)h, pbs); if (!ret) LOGe("write_data failed.\n"); sector_free(sect); return ret; }
/**************************************************************** * * * cree la fenˆtre du tampon * * * ****************************************************************/ void creer_tampon(void) { register int k; register windowptr thewin; /* Allocate space for window record. */ if ((Tampon = (windowptr)malloc(sizeof(windowrec))) == NULL) return; thewin = Tampon; /* Initialize window data structure. */ thewin -> next = NULL; thewin -> kind_c = SIZER | MOVER | FULLER | CLOSER | NAME | UPARROW | DNARROW | VSLIDE | LFARROW | RTARROW | HSLIDE; thewin -> type = TAMPON; thewin -> menu_entry = FAIL; thewin -> place = 0; strcpy(thewin -> title, Messages(TAMPON_14)); if ((thewin -> fonction.tampon.secteurBin = sector_alloc(512)) == NULL) { free(thewin); Tampon = NULL; return; } if ((thewin -> fonction.tampon.Text = malloc((size_t)SECTEURSIZE)) == NULL) { error_msg(Z_NOT_ENOUGH_MEMORY); free(thewin -> fonction.tampon.secteurBin); free(thewin); Tampon = NULL; return; } if ((thewin -> fonction.tampon.Ligne = (char **)malloc((size_t)SECTEURLINE * sizeof(char *))) == NULL) { error_msg(Z_NOT_ENOUGH_MEMORY); free(thewin -> fonction.tampon.secteurBin); free(thewin -> fonction.tampon.Text); free(thewin); Tampon = NULL; return; } /* le bloc contient d‚ja toutes les lignes remplies */ thewin -> fonction.tampon.TextSize = SECTEURSIZE; thewin -> fonction.tampon.LineNumberMax = SECTEURLINE; thewin -> fonction.tampon.LineNumber = SECTEURLINE-2; thewin -> fonction.tampon.CurrentLine = SECTEURLINE-2; thewin -> fonction.tampon.taille_pt = gr_ch == 8 ? 9 : 10; thewin -> fonction.tampon.taille_w = gr_cw; thewin -> fonction.tampon.taille_h = gr_ch; thewin -> fonction.tampon.PrintLine = SECTEURLINE-3; thewin -> fonction.tampon.ligne = SECTEURLINE-3; thewin -> fonction.tampon.colonne = 0; /* chaque ligne … 0 */ *thewin -> fonction.tampon.Text = '\0'; for (k=1; k<SECTEURLINE; k++) /* la premiŠre ligne est deux fois plus grande */ thewin -> fonction.tampon.Text[(k+1) * SECTEURLINESIZE] = '\0'; /* un pointeur sur chaque ligne */ thewin -> fonction.tampon.Ligne[0] = thewin -> fonction.tampon.Text; for (k=1; k<SECTEURLINE; k++) thewin -> fonction.tampon.Ligne[k] = &thewin -> fonction.tampon.Text[(k+1) * SECTEURLINESIZE]; thewin -> fonction.tampon.ascii = TRUE; thewin -> fonction.tampon.curseur_x = thewin -> fonction.tampon.curseur_y = FAIL; thewin -> fonction.tampon.page = 0; /* les champs suivant ne sont pas utilis‚ mais sont initialis‚s quand mˆme au cas ou... */ thewin -> fonction.tampon.dirty = FALSE; thewin -> fonction.tampon.slide = NULL; thewin -> fonction.tampon.goto_liste = NULL; thewin -> fonction.tampon.max = 1; thewin -> fonction.tampon.secteur = 0; thewin -> fonction.tampon.sector_size = 1; /* secteur … 0 */ memset(thewin -> fonction.tampon.secteurBin, 0, 512L); strncpy(thewin -> fonction.tampon.Ligne[0], " Tampon", SECTEURLINESIZE*2); strncpy(thewin -> fonction.tampon.Ligne[1], " 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[2], " 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[3], " ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[4], "0000 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[5], "0020 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[6], "0040 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[7], "0060 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[8], "0080 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[9], "00A0 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[10], "00C0 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[11], "00E0 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[12], "0100 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[13], "0120 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[14], "0140 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[15], "0160 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[16], "0180 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[17], "01A0 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[18], "01C0 ", SECTEURLINESIZE); strncpy(thewin -> fonction.tampon.Ligne[19], "01E0 ", SECTEURLINESIZE); } /* creer_tampon */
static int slot_ping(struct sbd_context *st, const char *name) { struct sector_header_s *s_header = NULL; struct sector_mbox_s *s_mbox = NULL; int mbox; int waited = 0; int rc = 0; if (!name) { cl_log(LOG_ERR, "slot_ping(): No recipient specified.\n"); rc = -1; goto out; } s_header = header_get(st); if (!s_header) { rc = -1; goto out; } if (strcmp(name, "LOCAL") == 0) { name = local_uname; } mbox = slot_lookup(st, s_header, name); if (mbox < 0) { cl_log(LOG_ERR, "slot_msg(): No slot found for %s.", name); rc = -1; goto out; } s_mbox = sector_alloc(); s_mbox->cmd = SBD_MSG_TEST; strncpy(s_mbox->from, local_uname, sizeof(s_mbox->from)-1); DBGLOG(LOG_DEBUG, "Pinging node %s", name); if (mbox_write(st, mbox, s_mbox) < -1) { rc = -1; goto out; } rc = -1; while (waited <= timeout_msgwait) { if (mbox_read(st, mbox, s_mbox) < 0) break; if (s_mbox->cmd != SBD_MSG_TEST) { rc = 0; break; } sleep(1); waited++; } if (rc == 0) { cl_log(LOG_DEBUG, "%s successfully pinged.", name); } else { cl_log(LOG_ERR, "%s failed to ping.", name); } out: free(s_mbox); free(s_header); return rc; }
static int slot_msg(struct sbd_context *st, const char *name, const char *cmd) { struct sector_header_s *s_header = NULL; struct sector_mbox_s *s_mbox = NULL; int mbox; int rc = 0; char uuid[37]; if (!name || !cmd) { cl_log(LOG_ERR, "slot_msg(): No recipient / cmd specified.\n"); rc = -1; goto out; } s_header = header_get(st); if (!s_header) { rc = -1; goto out; } if (strcmp(name, "LOCAL") == 0) { name = local_uname; } if (s_header->minor_version > 0) { uuid_unparse_lower(s_header->uuid, uuid); cl_log(LOG_INFO, "Device UUID: %s", uuid); } mbox = slot_lookup(st, s_header, name); if (mbox < 0) { cl_log(LOG_ERR, "slot_msg(): No slot found for %s.", name); rc = -1; goto out; } s_mbox = sector_alloc(); s_mbox->cmd = cmd2char(cmd); if (s_mbox->cmd < 0) { cl_log(LOG_ERR, "slot_msg(): Invalid command %s.", cmd); rc = -1; goto out; } strncpy(s_mbox->from, local_uname, sizeof(s_mbox->from)-1); cl_log(LOG_INFO, "Writing %s to node slot %s", cmd, name); if (mbox_write_verify(st, mbox, s_mbox) < -1) { rc = -1; goto out; } if (strcasecmp(cmd, "exit") != 0) { cl_log(LOG_INFO, "Messaging delay: %d", (int)timeout_msgwait); sleep(timeout_msgwait); } cl_log(LOG_INFO, "%s successfully delivered to %s", cmd, name); out: free(s_mbox); free(s_header); return rc; }
int servant(const char *diskname, int mode, const void* argp) { struct sector_mbox_s *s_mbox = NULL; struct sector_node_s *s_node = NULL; struct sector_header_s *s_header = NULL; int mbox; int rc = 0; time_t t0, t1, latency; union sigval signal_value; sigset_t servant_masks; struct sbd_context *st; pid_t ppid; char uuid[37]; const struct servants_list_item *s = argp; if (!diskname) { cl_log(LOG_ERR, "Empty disk name %s.", diskname); return -1; } cl_log(LOG_INFO, "Servant starting for device %s", diskname); /* Block most of the signals */ sigfillset(&servant_masks); sigdelset(&servant_masks, SIGKILL); sigdelset(&servant_masks, SIGFPE); sigdelset(&servant_masks, SIGILL); sigdelset(&servant_masks, SIGSEGV); sigdelset(&servant_masks, SIGBUS); sigdelset(&servant_masks, SIGALRM); /* FIXME: check error */ sigprocmask(SIG_SETMASK, &servant_masks, NULL); atexit(servant_exit); servant_inform_parent = 1; st = open_device(diskname, LOG_WARNING); if (!st) { return -1; } s_header = header_get(st); if (!s_header) { cl_log(LOG_ERR, "Not a valid header on %s", diskname); return -1; } if (servant_check_timeout_inconsistent(s_header) < 0) { cl_log(LOG_ERR, "Timeouts on %s do not match first device", diskname); return -1; } if (s_header->minor_version > 0) { uuid_unparse_lower(s_header->uuid, uuid); cl_log(LOG_INFO, "Device %s uuid: %s", diskname, uuid); } mbox = slot_allocate(st, local_uname); if (mbox < 0) { cl_log(LOG_ERR, "No slot allocated, and automatic allocation failed for disk %s.", diskname); rc = -1; goto out; } s_node = sector_alloc(); if (slot_read(st, mbox, s_node) < 0) { cl_log(LOG_ERR, "Unable to read node entry on %s", diskname); exit(1); } DBGLOG(LOG_INFO, "Monitoring slot %d on disk %s", mbox, diskname); if (s_header->minor_version == 0) { set_proc_title("sbd: watcher: %s - slot: %d", diskname, mbox); } else { set_proc_title("sbd: watcher: %s - slot: %d - uuid: %s", diskname, mbox, uuid); } s_mbox = sector_alloc(); if (s->first_start) { if (mode > 0) { if (mbox_read(st, mbox, s_mbox) < 0) { cl_log(LOG_ERR, "mbox read failed during start-up in servant."); rc = -1; goto out; } if (s_mbox->cmd != SBD_MSG_EXIT && s_mbox->cmd != SBD_MSG_EMPTY) { /* Not a clean stop. Abort start-up */ cl_log(LOG_WARNING, "Found fencing message - aborting start-up. Manual intervention required!"); ppid = getppid(); sigqueue(ppid, SIG_EXITREQ, signal_value); rc = 0; goto out; } } DBGLOG(LOG_INFO, "First servant start - zeroing inbox"); memset(s_mbox, 0, sizeof(*s_mbox)); if (mbox_write(st, mbox, s_mbox) < 0) { rc = -1; goto out; } } memset(&signal_value, 0, sizeof(signal_value)); while (1) { struct sector_header_s *s_header_retry = NULL; struct sector_node_s *s_node_retry = NULL; t0 = time(NULL); sleep(timeout_loop); ppid = getppid(); if (ppid == 1) { /* Our parent died unexpectedly. Triggering * self-fence. */ do_reset(); } /* These attempts are, by definition, somewhat racy. If * the device is wiped out or corrupted between here and * us reading our mbox, there is nothing we can do about * that. But at least we tried. */ s_header_retry = header_get(st); if (!s_header_retry) { cl_log(LOG_ERR, "No longer found a valid header on %s", diskname); exit(1); } if (memcmp(s_header, s_header_retry, sizeof(*s_header)) != 0) { cl_log(LOG_ERR, "Header on %s changed since start-up!", diskname); exit(1); } free(s_header_retry); s_node_retry = sector_alloc(); if (slot_read(st, mbox, s_node_retry) < 0) { cl_log(LOG_ERR, "slot read failed in servant."); exit(1); } if (memcmp(s_node, s_node_retry, sizeof(*s_node)) != 0) { cl_log(LOG_ERR, "Node entry on %s changed since start-up!", diskname); exit(1); } free(s_node_retry); if (mbox_read(st, mbox, s_mbox) < 0) { cl_log(LOG_ERR, "mbox read failed in servant."); exit(1); } if (s_mbox->cmd > 0) { cl_log(LOG_INFO, "Received command %s from %s on disk %s", char2cmd(s_mbox->cmd), s_mbox->from, diskname); switch (s_mbox->cmd) { case SBD_MSG_TEST: memset(s_mbox, 0, sizeof(*s_mbox)); mbox_write(st, mbox, s_mbox); sigqueue(ppid, SIG_TEST, signal_value); break; case SBD_MSG_RESET: do_reset(); break; case SBD_MSG_OFF: do_off(); break; case SBD_MSG_EXIT: sigqueue(ppid, SIG_EXITREQ, signal_value); break; case SBD_MSG_CRASHDUMP: do_crashdump(); break; default: /* FIXME: An "unknown" message might result from a partial write. log it and clear the slot. */ cl_log(LOG_ERR, "Unknown message on disk %s", diskname); memset(s_mbox, 0, sizeof(*s_mbox)); mbox_write(st, mbox, s_mbox); break; } } sigqueue(ppid, SIG_LIVENESS, signal_value); t1 = time(NULL); latency = t1 - t0; if (timeout_watchdog_warn && (latency > timeout_watchdog_warn)) { cl_log(LOG_WARNING, "Latency: %d exceeded threshold %d on disk %s", (int)latency, (int)timeout_watchdog_warn, diskname); } else if (debug) { DBGLOG(LOG_INFO, "Latency: %d on disk %s", (int)latency, diskname); } } out: free(s_mbox); close_device(st); if (rc == 0) { servant_inform_parent = 0; } return rc; }