static void create_main_server(void) { pool *main_pool; main_pool = make_sub_pool(permanent_pool); pr_pool_tag(main_pool, "testsuite#main_server pool"); server_list = xaset_create(main_pool, NULL); main_server = (server_rec *) pcalloc(main_pool, sizeof(server_rec)); xaset_insert(server_list, (xasetmember_t *) main_server); main_server->pool = main_pool; main_server->conf = xaset_create(main_pool, NULL); main_server->set = server_list; main_server->sid = 1; main_server->notes = pr_table_nalloc(main_pool, 0, 8); /* TCP KeepAlive is enabled by default, with the system defaults. */ main_server->tcp_keepalive = palloc(main_server->pool, sizeof(struct tcp_keepalive)); main_server->tcp_keepalive->keepalive_enabled = TRUE; main_server->tcp_keepalive->keepalive_idle = -1; main_server->tcp_keepalive->keepalive_count = -1; main_server->tcp_keepalive->keepalive_intvl = -1; main_server->ServerName = "Test Server"; main_server->ServerPort = 21; }
int child_add(pid_t pid, int fd) { pool *p; pr_child_t *ch; /* If no child-tracking list has been allocated, create one. */ if (!child_pool) { child_pool = make_sub_pool(permanent_pool); pr_pool_tag(child_pool, "Child Pool"); } if (!child_list) child_list = xaset_create(make_sub_pool(child_pool), NULL); p = make_sub_pool(child_pool); pr_pool_tag(p, "child session pool"); ch = pcalloc(p, sizeof(pr_child_t)); ch->ch_pool = p; ch->ch_pid = pid; time(&ch->ch_when); ch->ch_pipefd = fd; ch->ch_dead = FALSE; xaset_insert(child_list, (xasetmember_t *) ch); child_listlen++; return 0; }
void schedule(void (*f)(void*,void*,void*,void*),int nloops, void *a1, void *a2, void *a3, void *a4) { pool *p, *sub_pool; sched_t *s; if (scheds == NULL) { p = make_sub_pool(permanent_pool); pr_pool_tag(p, "Schedules Pool"); scheds = xaset_create(p, NULL); } else { p = scheds->pool; } sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "schedule pool"); s = pcalloc(sub_pool, sizeof(sched_t)); s->pool = sub_pool; s->f = f; s->a1 = a1; s->a2 = a2; s->a3 = a3; s->a4 = a4; s->loops = nloops; xaset_insert(scheds, (xasetmember_t *) s); }
xaset_t *xaset_union(pool *work_pool, xaset_t *set1, xaset_t *set2, size_t msize, XASET_MCOPY copyf) { xaset_t *newset; xasetmember_t *setp,*n; xaset_t *setv[3]; xaset_t **setcp; setv[0] = set1; setv[1] = set2; setv[2] = NULL; if (!set1 || !set2 || (!msize && !copyf)) { errno = EINVAL; return NULL; } work_pool = (work_pool ? work_pool : (set1->pool ? set1->pool : set2->pool)); if (!(newset = xaset_create(work_pool, set1->xas_compare))) return NULL; for (setcp = setv; *setcp; setcp++) for (setp = (*setcp)->xas_list; setp; setp=setp->next) { n = copyf ? copyf(setp) : (xasetmember_t*) palloc(work_pool, msize); if (!n) return NULL; /* Could cleanup here */ if (!copyf) memcpy(n, setp, msize); if (xaset_insert_sort(newset, n, 0) == -1) return NULL; } return newset; }
void schedule(void (*cb)(void *, void *, void *, void *), int nloops, void *arg1, void *arg2, void *arg3, void *arg4) { pool *p, *sub_pool; sched_t *s; if (cb == NULL || nloops < 0) { return; } if (scheds == NULL) { p = make_sub_pool(permanent_pool); pr_pool_tag(p, "Schedules Pool"); scheds = xaset_create(p, NULL); } else { p = scheds->pool; } sub_pool = make_sub_pool(p); pr_pool_tag(sub_pool, "schedule pool"); s = pcalloc(sub_pool, sizeof(sched_t)); s->pool = sub_pool; s->cb = cb; s->arg1 = arg1; s->arg2 = arg2; s->arg3 = arg3; s->arg4 = arg4; s->nloops = nloops; xaset_insert(scheds, (xasetmember_t *) s); }
xaset_t *xaset_subtract(pool *work_pool, xaset_t *set1, xaset_t *set2, size_t msize, XASET_MCOPY copyf) { xaset_t *newset; xasetmember_t *set1p,*set2p,*n,**pos; int c; if (!set1 || !set2 || (!msize && !copyf)) { errno = EINVAL; return NULL; } work_pool = (work_pool ? work_pool : (set1->pool ? set1->pool : set2->pool)); if (!(newset = xaset_create(work_pool, set1->xas_compare))) return NULL; pos = &newset->xas_list; /* NOTE: xaset_insert_sort is not used here for performance reasons. */ for (set1p = set1->xas_list, set2p = set2->xas_list; set1p; set1p = set1p->next) { if (!set2p || (c = set1->xas_compare(set1p, set2p)) < 0) { /* Copy if set2 is exhausted or set1p's "value" is less than set2p's. */ n = copyf ? copyf(set1p) : (xasetmember_t *) palloc(work_pool, msize); if (!n) return NULL; /* Could cleanup here */ if (!copyf) memcpy(n, set1p, msize); /* Create links */ n->prev = *pos; n->next = NULL; if (*pos) pos = &(*pos)->next; *pos = n; } else if (c >= 0) { /* Traverse set2 until we reach a point where set2 is exhausted or set2p is "greater" than set1p */ while ((set2p = set2p->next) != NULL && set1->xas_compare(set1p, set2p) > 0) ; /* In case there are dupes in set1, examine the next (if any) value(s) and skip if necessary */ while (set1p->next && set1->xas_compare(set1p, set1p->next) == 0) set1p = set1p->next; } } return newset; }
int pr_timer_reset(int timerno, module *mod) { struct timer *t = NULL; if (!timers) { errno = EPERM; return -1; } if (_indispatch) { errno = EINTR; return -1; } pr_alarms_block(); if (!recycled) recycled = xaset_create(timer_pool, NULL); for (t = (struct timer *) timers->xas_list; t; t = t->next) { if (t->timerno == timerno && (t->mod == mod || mod == ANY_MODULE)) { t->count = t->interval; xaset_remove(timers, (xasetmember_t *) t); xaset_insert(recycled, (xasetmember_t *) t); nalarms++; /* The handle_alarm() function also readjusts the timers lists * as part of its processing, so it needs to be called when a timer * is reset. */ handle_alarm(); break; } } pr_alarms_unblock(); if (t != NULL) { pr_trace_msg("timer", 7, "reset timer ID %d ('%s', for module '%s')", t->timerno, t->desc, t->mod ? t->mod->name : "[none]"); return t->timerno; } return 0; }
/* Perform an exact copy of the entire set, returning the new set. msize * specifies the size of each member. If copyfunc is non-NULL, it is called * instead to copy each member. Returns NULL if out of memory condition * occurs. */ xaset_t *xaset_copy(pool *p, xaset_t *set, size_t msize, XASET_MCOPY copyfunc) { xaset_t *new_set; xasetmember_t *n, *m, **pos; if (set == NULL) { errno = EINVAL; return NULL; } if (!copyfunc && !msize) { errno = EINVAL; return NULL; } p = (p ? p : set->pool); new_set = xaset_create(p, set->xas_compare); if (new_set == NULL) return NULL; pos = &new_set->xas_list; /* NOTE: xaset_insert_sort is not used here for performance reasons. */ for (m = set->xas_list; m; m = m->next) { n = copyfunc ? copyfunc(m) : (xasetmember_t *) palloc(p, msize); if (!n) return NULL; /* Could clean up here */ if (!copyfunc) memcpy(n, m, msize); /* Create links */ n->prev = *pos; n->next = NULL; if (*pos) pos = &(*pos)->next; *pos = n; } return new_set; }
xaset_t *xaset_copy(pool *work_pool, xaset_t *set, size_t msize, XASET_MCOPY copyf) { xaset_t *newset; xasetmember_t *n,*p,**pos; if (!copyf && !msize) { errno = EINVAL; return NULL; } work_pool = (work_pool ? work_pool : set->pool); if (!(newset = xaset_create(work_pool, set->xas_compare))) return NULL; pos = &newset->xas_list; /* NOTE: xaset_insert_sort is not used here for performance reasons. */ for (p = set->xas_list; p; p=p->next) { n = copyf ? copyf(p) : (xasetmember_t *) palloc(work_pool, msize); if (!n) return NULL; /* Could clean up here */ if (!copyf) memcpy(n, p, msize); /* Create links */ n->prev = *pos; n->next = NULL; if (*pos) pos = &(*pos)->next; *pos = n; } return newset; }
/* This function does the work of iterating through the list of registered * timers, checking to see if their callbacks should be invoked and whether * they should be removed from the registration list. Its return value is * the amount of time remaining on the first timer in the list. */ static int process_timers(int elapsed) { struct timer *t = NULL, *next = NULL; if (!recycled) recycled = xaset_create(timer_pool, NULL); if (!elapsed && !recycled->xas_list) { if (!timers) return 0; return (timers->xas_list ? ((struct timer *) timers->xas_list)->count : 0); } /* Critical code, no interruptions please */ if (_indispatch) return 0; pr_alarms_block(); _indispatch++; if (elapsed) { for (t = (struct timer *) timers->xas_list; t; t = next) { /* If this timer has already been handled, skip */ next = t->next; if (t->remove) { /* Move the timer onto the free_timers chain, for later reuse. */ xaset_remove(timers, (xasetmember_t *) t); xaset_insert(free_timers, (xasetmember_t *) t); } else if ((t->count -= elapsed) <= 0) { /* This timer's interval has elapsed, so trigger its callback. */ pr_trace_msg("timer", 4, "%ld %s for timer ID %d ('%s', for module '%s') elapsed, invoking " "callback (%p)", t->interval, t->interval != 1 ? "seconds" : "second", t->timerno, t->desc ? t->desc : "<unknown>", t->mod ? t->mod->name : "<none>", t->callback); if (t->callback(t->interval, t->timerno, t->interval - t->count, t->mod) == 0) { /* A return value of zero means this timer is done, and can be * removed. */ xaset_remove(timers, (xasetmember_t *) t); xaset_insert(free_timers, (xasetmember_t *) t); } else { /* A non-zero return value from a timer callback signals that * the timer should be reused/restarted. */ pr_trace_msg("timer", 6, "restarting timer ID %d ('%s'), as per " "callback", t->timerno, t->desc ? t->desc : "<unknown>"); xaset_remove(timers, (xasetmember_t *) t); t->count = t->interval; xaset_insert(recycled, (xasetmember_t *) t); } } } } /* Put the recycled timers back into the main timer list. */ while ((t = (struct timer *) recycled->xas_list) != NULL) { xaset_remove(recycled, (xasetmember_t *) t); xaset_insert_sort(timers, (xasetmember_t *) t, TRUE); } _indispatch--; pr_alarms_unblock(); /* If no active timers remain in the list, there is no reason to set the * SIGALRM handle. */ return (timers->xas_list ? ((struct timer *) timers->xas_list)->count : 0); }
int pr_timer_add(int seconds, int timerno, module *mod, callback_t cb, const char *desc) { struct timer *t = NULL; if (seconds <= 0 || cb == NULL || desc == NULL) { errno = EINVAL; return -1; } if (!timers) timers = xaset_create(timer_pool, (XASET_COMPARE) timer_cmp); /* Check to see that, if specified, the timerno is not already in use. */ if (timerno >= 0) { for (t = (struct timer *) timers->xas_list; t; t = t->next) { if (t->timerno == timerno) { errno = EPERM; return -1; } } } if (!free_timers) free_timers = xaset_create(timer_pool, NULL); /* Try to use an old timer first */ pr_alarms_block(); t = (struct timer *) free_timers->xas_list; if (t != NULL) { xaset_remove(free_timers, (xasetmember_t *) t); } else { if (timer_pool == NULL) { timer_pool = make_sub_pool(permanent_pool); pr_pool_tag(timer_pool, "Timer Pool"); } /* Must allocate a new one */ t = palloc(timer_pool, sizeof(struct timer)); } if (timerno < 0) { /* Dynamic timer */ if (dynamic_timerno < PR_TIMER_DYNAMIC_TIMERNO) { dynamic_timerno = PR_TIMER_DYNAMIC_TIMERNO; } timerno = dynamic_timerno++; } t->timerno = timerno; t->count = t->interval = seconds; t->callback = cb; t->mod = mod; t->remove = 0; t->desc = desc; /* If called while _indispatch, add to the recycled list to prevent * list corruption */ if (_indispatch) { if (!recycled) recycled = xaset_create(timer_pool, NULL); xaset_insert(recycled, (xasetmember_t *) t); } else { xaset_insert_sort(timers, (xasetmember_t *) t, TRUE); nalarms++; set_sig_alarm(); /* The handle_alarm() function also readjusts the timers lists * as part of its processing, so it needs to be called when a timer * is added. */ handle_alarm(); } pr_alarms_unblock(); pr_trace_msg("timer", 7, "added timer ID %d ('%s', for module '%s'), " "triggering in %ld %s", t->timerno, t->desc, t->mod ? t->mod->name : "[none]", t->interval, t->interval != 1 ? "seconds" : "second"); return timerno; }
int pr_stash_add_symbol(pr_stash_type_t sym_type, void *data) { struct stash *sym = NULL; unsigned int hash; int idx = 0; xaset_t **symbol_table; size_t sym_namelen = 0; if (data == NULL) { errno = EINVAL; return -1; } switch (sym_type) { case PR_SYM_CONF: sym = sym_alloc(); sym->sym_type = PR_SYM_CONF; sym->sym_name = ((conftable *) data)->directive; sym->sym_module = ((conftable *) data)->m; sym->ptr.sym_conf = data; symbol_table = conf_symbol_table; break; case PR_SYM_CMD: sym = sym_alloc(); sym->sym_type = PR_SYM_CMD; sym->sym_name = ((cmdtable *) data)->command; sym->sym_module = ((cmdtable *) data)->m; sym->ptr.sym_cmd = data; symbol_table = cmd_symbol_table; break; case PR_SYM_AUTH: sym = sym_alloc(); sym->sym_type = PR_SYM_AUTH; sym->sym_name = ((authtable *) data)->name; sym->sym_module = ((authtable *) data)->m; sym->ptr.sym_auth = data; symbol_table = auth_symbol_table; break; case PR_SYM_HOOK: sym = sym_alloc(); sym->sym_type = PR_SYM_HOOK; sym->sym_name = ((cmdtable *) data)->command; sym->sym_module = ((cmdtable *) data)->m; sym->ptr.sym_hook = data; symbol_table = hook_symbol_table; break; default: errno = EINVAL; return -1; } /* XXX Should we check for null sym->sym_module as well? */ if (sym->sym_name == NULL) { destroy_pool(sym->sym_pool); errno = EPERM; return -1; } sym_namelen = strlen(sym->sym_name); if (sym_namelen == 0) { destroy_pool(sym->sym_pool); errno = EPERM; return -1; } /* Don't forget to include one for the terminating NUL. */ sym->sym_namelen = sym_namelen + 1; hash = sym_type_hash(sym_type, sym->sym_name, sym->sym_namelen); idx = hash % PR_TUNABLE_HASH_TABLE_SIZE; sym->sym_hash = hash; if (!symbol_table[idx]) { symbol_table[idx] = xaset_create(symbol_pool, (XASET_COMPARE) sym_cmp); } xaset_insert_sort(symbol_table[idx], (xasetmember_t *) sym, TRUE); return 0; }
config_rec *pr_parser_config_ctxt_open(const char *name) { config_rec *c = NULL, *parent = *parser_curr_config; pool *c_pool = NULL, *parent_pool = NULL; xaset_t **set = NULL; if (name == NULL) { errno = EINVAL; return NULL; } if (parent) { parent_pool = parent->pool; set = &parent->subset; } else { parent_pool = (*parser_curr_server)->pool; set = &(*parser_curr_server)->conf; } /* Allocate a sub-pool for this config_rec. * * Note: special exception for <Global> configs: the parent pool is * 'global_config_pool' (a pool just for that context), not the pool of the * parent server. This keeps <Global> config recs from being freed * prematurely, and helps to avoid memory leaks. */ if (strncasecmp(name, "<Global>", 9) == 0) { if (global_config_pool == NULL) { global_config_pool = make_sub_pool(permanent_pool); pr_pool_tag(global_config_pool, "<Global> Pool"); } parent_pool = global_config_pool; } c_pool = make_sub_pool(parent_pool); pr_pool_tag(c_pool, "sub-config pool"); c = (config_rec *) pcalloc(c_pool, sizeof(config_rec)); if (!*set) { pool *set_pool = make_sub_pool(parent_pool); *set = xaset_create(set_pool, NULL); (*set)->pool = set_pool; } xaset_insert(*set, (xasetmember_t *) c); c->pool = c_pool; c->set = *set; c->parent = parent; c->name = pstrdup(c->pool, name); if (parent) { if (parent->config_type == CONF_DYNDIR) { c->flags |= CF_DYNAMIC; } } (void) pr_parser_config_ctxt_push(c); return c; }
static int counter_sess_init(void) { config_rec *c; c = find_config(main_server->conf, CONF_PARAM, "CounterEngine", FALSE); if (c != NULL) { counter_engine = *((int *) c->argv[0]); } if (counter_engine == FALSE) { return 0; } c = find_config(main_server->conf, CONF_PARAM, "CounterLog", FALSE); if (c != NULL) { const char *path = c->argv[0]; if (strcasecmp(path, "none") != 0) { int res, xerrno; PRIVS_ROOT res = pr_log_openfile(path, &counter_logfd, 0660); xerrno = errno; PRIVS_RELINQUISH; if (res < 0) { pr_log_debug(DEBUG2, MOD_COUNTER_VERSION ": error opening CounterLog '%s': %s", path, strerror(xerrno)); counter_logfd = -1; } } } /* Find all CounterFile directives for this vhost, and make sure they * have open handles. We need to do this here, and not in a POST_CMD * PASS handler because of the need to open handles that may be outside * of a chroot. */ c = find_config(main_server->conf, CONF_PARAM, "CounterFile", TRUE); while (c != NULL) { int xerrno = 0; const char *area = NULL, *path; pr_fh_t *fh; struct counter_fh *cfh; pr_signals_handle(); path = c->argv[0]; if (c->parent != NULL) { if (c->parent->config_type == CONF_ANON || c->parent->config_type == CONF_DIR) { area = c->parent->name; } else { (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "unhandled configuration parent type (%d) for CounterFile, skipping", c->parent->config_type); c = find_config_next(c, c->next, CONF_PARAM, "CounterFile", TRUE); continue; } } else { /* Toplevel CounterFile directive, in "server config" or <VirtualHost> * sections. */ area = "/"; } PRIVS_ROOT fh = pr_fsio_open(path, O_RDWR|O_CREAT); xerrno = errno; PRIVS_RELINQUISH if (fh == NULL) { pr_log_debug(DEBUG1, MOD_COUNTER_VERSION ": error opening CounterFile '%s': %s", path, strerror(xerrno)); counter_engine = FALSE; if (counter_fhs != NULL) { for (cfh = (struct counter_fh *) counter_fhs->xas_list; cfh; cfh = cfh->next) { (void) pr_fsio_close(cfh->fh); } } return 0; } (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "opened CounterFile '%s'", path); if (counter_fhs == NULL) { counter_fhs = xaset_create(counter_pool, NULL); } cfh = pcalloc(counter_pool, sizeof(struct counter_fh)); /* Ignore any trailing slash. */ cfh->arealen = strlen(area); if (cfh->arealen > 1 && area[cfh->arealen-1] == '/') { cfh->arealen--; } cfh->area = pstrndup(counter_pool, area, cfh->arealen); /* Mark any areas that use glob(3) characters. */ if (strpbrk(cfh->area, "[*?") != NULL) { cfh->isglob = TRUE; } cfh->fh = fh; xaset_insert(counter_fhs, (xasetmember_t *) cfh); c = find_config_next(c, c->next, CONF_PARAM, "CounterFile", TRUE); } if (counter_fhs == NULL) { (void) pr_log_writefile(counter_logfd, MOD_COUNTER_VERSION, "no CounterFiles configured, disabling module"); counter_engine = FALSE; return 0; } pr_event_register(&counter_module, "core.exit", counter_exit_ev, NULL); /* If mod_vroot is present, we need to do a little more magic to counter * the mod_vroot magic. */ if (pr_module_exists("mod_vroot.c") == TRUE) { pr_event_register(&counter_module, "core.chroot", counter_chroot_ev, NULL); } return 0; }
conn_t *pr_ipbind_get_listening_conn(server_rec *server, pr_netaddr_t *addr, unsigned int port) { conn_t *l; pool *p; struct listener_rec *lr; if (listening_conn_list) { for (lr = (struct listener_rec *) listening_conn_list->xas_list; lr; lr = lr->next) { int use_elt = FALSE; pr_signals_handle(); if (addr != NULL && lr->addr != NULL) { const char *lr_ipstr = NULL; lr_ipstr = pr_netaddr_get_ipstr(lr->addr); /* Note: lr_ipstr should never be null. If it is, it means that * the lr->addr object never had its IP address resolved/stashed, * and in attempting to do, getnameinfo(3) failed for some reason. * * The IP address on which it's listening, if not available via * lr->addr, should thus be available via lr->conn->local_addr. */ if (lr_ipstr == NULL && lr->conn != NULL) { lr_ipstr = pr_netaddr_get_ipstr(lr->conn->local_addr); } if (lr_ipstr != NULL) { if (strcmp(pr_netaddr_get_ipstr(addr), lr_ipstr) == 0 && port == lr->port) { use_elt = TRUE; } } } else if (addr == NULL && port == lr->port) { use_elt = TRUE; } if (use_elt) { lr->claimed = TRUE; return lr->conn; } } } if (listening_conn_pool == NULL) { listening_conn_pool = make_sub_pool(permanent_pool); pr_pool_tag(listening_conn_pool, "Listening Connection Pool"); listening_conn_list = xaset_create(listening_conn_pool, NULL); } p = make_sub_pool(listening_conn_pool); pr_pool_tag(p, "Listening conn subpool"); l = pr_inet_create_conn(p, -1, addr, port, FALSE); if (l == NULL) { return NULL; } /* Inform any interested listeners that this socket was opened. */ pr_inet_generate_socket_event("core.ctrl-listen", server, l->local_addr, l->listen_fd); lr = pcalloc(p, sizeof(struct listener_rec)); lr->pool = p; lr->conn = l; lr->addr = pr_netaddr_dup(p, addr); if (lr->addr == NULL && errno != EINVAL) { return NULL; } lr->port = port; lr->claimed = TRUE; xaset_insert(listening_conn_list, (xasetmember_t *) lr); return l; }
END_TEST START_TEST (config_merge_down_test) { xaset_t *set; config_rec *c, *src, *dst; const char *name; mark_point(); pr_config_merge_down(NULL, FALSE); mark_point(); set = xaset_create(p, NULL); pr_config_merge_down(set, FALSE); name = "foo"; c = add_config_param_set(&set, name, 0); mark_point(); pr_config_merge_down(set, FALSE); name = "bar"; c = add_config_param_set(&set, name, 1, "baz"); c->flags |= CF_MERGEDOWN; mark_point(); pr_config_merge_down(set, FALSE); name = "BAZ"; c = add_config_param_set(&set, name, 2, "quxx", "Quzz"); c->flags |= CF_MERGEDOWN_MULTI; mark_point(); pr_config_merge_down(set, FALSE); /* Add a config to the subsets, with the same name and same args. */ name = "<Anonymous>"; src = add_config_param_set(&set, name, 0); src->config_type = CONF_ANON; mark_point(); pr_config_merge_down(set, FALSE); name = "<Directory>"; dst = add_config_param_set(&set, name, 1, "/baz"); dst->config_type = CONF_DIR; name = "foo"; c = add_config_param_set(&(src->subset), name, 1, "alef"); c->flags |= CF_MERGEDOWN; c = add_config_param_set(&(dst->subset), name, 1, "alef"); c->flags |= CF_MERGEDOWN; mark_point(); pr_config_merge_down(set, FALSE); /* Add a config to the subsets, with the same name and diff args. */ name = "alef"; c = add_config_param_set(&(src->subset), name, 1, "alef"); c->flags |= CF_MERGEDOWN; c = add_config_param_set(&(dst->subset), name, 2, "bet", "vet"); c->flags |= CF_MERGEDOWN; c = add_config_param_set(&(src->subset), "Bet", 3, "1", "2", "3"); c->config_type = CONF_LIMIT; c->flags |= CF_MERGEDOWN; mark_point(); pr_config_merge_down(set, FALSE); }