/* * (C) 2011 by Daniel Willmann * (C) 2011 by On-Waves * (C) 2011-2015 by Holger Hans Peter Freyther * (C) 2013-2015 by sysmocom s.f.m.c. GmbH * * All Rights Reserved * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int verify_net_apply_config_file(struct ctrl_cmd *cmd, const char *value, void *_data) { FILE *cfile; if (!cmd->value || cmd->value[0] == '\0') return -1; cfile = fopen(cmd->value, "r"); if (!cfile) return -1; fclose(cfile); return 0; } static int set_net_apply_config_file(struct ctrl_cmd *cmd, void *_data) { int rc; FILE *cfile; unsigned cmd_ret = CTRL_CMD_ERROR; LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s...\n", cmd->value); cfile = fopen(cmd->value, "r"); if (!cfile) { LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s: fopen() failed: %d\n", cmd->value, errno); cmd->reply = "NoFile"; return cmd_ret; } rc = vty_read_config_filep(cfile, NULL); LOGP(DCTRL, LOGL_NOTICE, "Applying VTY snippet from %s returned %d\n", cmd->value, rc); if (rc) { cmd->reply = talloc_asprintf(cmd, "ParseError=%d", rc); if (!cmd->reply) cmd->reply = "OOM"; goto close_ret; } rc = neighbors_check_cfg(); if (rc) { cmd->reply = talloc_asprintf(cmd, "Errors in neighbor configuration"); if (!cmd->reply) cmd->reply = "OOM"; goto close_ret; } cmd->reply = "OK"; cmd_ret = CTRL_CMD_REPLY; close_ret: fclose(cfile); return cmd_ret; } CTRL_CMD_DEFINE_WO(net_apply_config_file, "apply-config-file"); static int verify_net_write_config_file(struct ctrl_cmd *cmd, const char *value, void *_data) { return 0; } static int set_net_write_config_file(struct ctrl_cmd *cmd, void *_data) { const char *cfile_name; unsigned cmd_ret = CTRL_CMD_ERROR; if (strcmp(cmd->value, "overwrite")) host_config_set(cmd->value); cfile_name = host_config_file(); LOGP(DCTRL, LOGL_NOTICE, "Writing VTY config to file %s...\n", cfile_name); if (osmo_vty_write_config_file(cfile_name) < 0) goto ret; cmd->reply = "OK"; cmd_ret = CTRL_CMD_REPLY; ret: return cmd_ret; } CTRL_CMD_DEFINE_WO(net_write_config_file, "write-config-file"); CTRL_CMD_DEFINE(net_mcc, "mcc"); static int get_net_mcc(struct ctrl_cmd *cmd, void *_data) { struct gsm_network *net = cmd->node; cmd->reply = talloc_asprintf(cmd, "%s", osmo_mcc_name(net->plmn.mcc)); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } return CTRL_CMD_REPLY; } static int set_net_mcc(struct ctrl_cmd *cmd, void *_data) { struct gsm_network *net = cmd->node; uint16_t mcc; if (osmo_mcc_from_str(cmd->value, &mcc)) return -1; net->plmn.mcc = mcc; return get_net_mcc(cmd, _data); } static int verify_net_mcc(struct ctrl_cmd *cmd, const char *value, void *_data) { if (osmo_mcc_from_str(value, NULL)) return -1; return 0; } CTRL_CMD_DEFINE(net_mnc, "mnc"); static int get_net_mnc(struct ctrl_cmd *cmd, void *_data) { struct gsm_network *net = cmd->node; cmd->reply = talloc_asprintf(cmd, "%s", osmo_mnc_name(net->plmn.mnc, net->plmn.mnc_3_digits)); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } return CTRL_CMD_REPLY; } static int set_net_mnc(struct ctrl_cmd *cmd, void *_data) { struct gsm_network *net = cmd->node; struct osmo_plmn_id plmn = net->plmn; if (osmo_mnc_from_str(cmd->value, &plmn.mnc, &plmn.mnc_3_digits)) { cmd->reply = "Error while decoding MNC"; return CTRL_CMD_ERROR; } net->plmn = plmn; return get_net_mnc(cmd, _data); } static int verify_net_mnc(struct ctrl_cmd *cmd, const char *value, void *_data) { if (osmo_mnc_from_str(value, NULL, NULL)) return -1; return 0; } static int set_net_apply_config(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = cmd->node; struct gsm_bts *bts; llist_for_each_entry(bts, &net->bts_list, list) { if (!is_ipa_abisip_bts(bts)) continue; /* * The ip.access nanoBTS seems to be unreliable on BSSGP * so let's us just reboot it. For the sysmoBTS we can just * restart the process as all state is gone. */ if (!is_osmobts(bts) && strcmp(cmd->value, "restart") == 0) { struct gsm_bts_trx *trx; llist_for_each_entry_reverse(trx, &bts->trx_list, list) abis_nm_ipaccess_restart(trx); } else ipaccess_drop_oml(bts, "ctrl net.apply-configuration"); } cmd->reply = "Tried to drop the BTS"; return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_WO_NOVRF(net_apply_config, "apply-configuration"); static int verify_net_mcc_mnc_apply(struct ctrl_cmd *cmd, const char *value, void *d) { char *tmp, *saveptr, *mcc, *mnc; int rc = 0; tmp = talloc_strdup(cmd, value); if (!tmp) return 1; mcc = strtok_r(tmp, ",", &saveptr); mnc = strtok_r(NULL, ",", &saveptr); if (osmo_mcc_from_str(mcc, NULL) || osmo_mnc_from_str(mnc, NULL, NULL)) rc = -1; talloc_free(tmp); return rc; } static int set_net_mcc_mnc_apply(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = cmd->node; char *tmp, *saveptr, *mcc_str, *mnc_str; struct osmo_plmn_id plmn; tmp = talloc_strdup(cmd, cmd->value); if (!tmp) goto oom; mcc_str = strtok_r(tmp, ",", &saveptr); mnc_str = strtok_r(NULL, ",", &saveptr); if (osmo_mcc_from_str(mcc_str, &plmn.mcc)) { cmd->reply = "Error while decoding MCC"; talloc_free(tmp); return CTRL_CMD_ERROR; } if (osmo_mnc_from_str(mnc_str, &plmn.mnc, &plmn.mnc_3_digits)) { cmd->reply = "Error while decoding MNC"; talloc_free(tmp); return CTRL_CMD_ERROR; } talloc_free(tmp); if (!osmo_plmn_cmp(&net->plmn, &plmn)) { cmd->reply = "Nothing changed"; return CTRL_CMD_REPLY; } net->plmn = plmn; return set_net_apply_config(cmd, data); oom: cmd->reply = "OOM"; return CTRL_CMD_ERROR; } CTRL_CMD_DEFINE_WO(net_mcc_mnc_apply, "mcc-mnc-apply"); static int get_net_rf_lock(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = cmd->node; struct gsm_bts *bts; const char *policy_name; policy_name = osmo_bsc_rf_get_policy_name(net->rf_ctrl->policy); llist_for_each_entry(bts, &net->bts_list, list) { struct gsm_bts_trx *trx; /* Exclude the BTS from the global lock */ if (bts->excl_from_rf_lock) continue; llist_for_each_entry(trx, &bts->trx_list, list) { if (trx->mo.nm_state.availability == NM_AVSTATE_OK && trx->mo.nm_state.operational != NM_OPSTATE_DISABLED) { cmd->reply = talloc_asprintf(cmd, "state=on,policy=%s,bts=%u,trx=%u", policy_name, bts->nr, trx->nr); return CTRL_CMD_REPLY; } } } cmd->reply = talloc_asprintf(cmd, "state=off,policy=%s", policy_name); return CTRL_CMD_REPLY; } #define TIME_FORMAT_RFC2822 "%a, %d %b %Y %T %z" static int set_net_rf_lock(struct ctrl_cmd *cmd, void *data) { int locked = atoi(cmd->value); struct gsm_network *net = cmd->node; time_t now = time(NULL); char now_buf[64]; struct osmo_bsc_rf *rf; if (!net) { cmd->reply = "net not found."; return CTRL_CMD_ERROR; } rf = net->rf_ctrl; if (!rf) { cmd->reply = "RF Ctrl is not enabled in the BSC Configuration"; return CTRL_CMD_ERROR; } talloc_free(rf->last_rf_lock_ctrl_command); strftime(now_buf, sizeof(now_buf), TIME_FORMAT_RFC2822, gmtime(&now)); rf->last_rf_lock_ctrl_command = talloc_asprintf(rf, "rf_locked %u (%s)", locked, now_buf); osmo_bsc_rf_schedule_lock(rf, locked == 1 ? '0' : '1'); cmd->reply = talloc_asprintf(cmd, "%u", locked); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } return CTRL_CMD_REPLY; } static int verify_net_rf_lock(struct ctrl_cmd *cmd, const char *value, void *data) { int locked = atoi(cmd->value); if ((locked != 0) && (locked != 1)) return 1; return 0; } CTRL_CMD_DEFINE(net_rf_lock, "rf_locked"); static int get_net_bts_num(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = cmd->node; cmd->reply = talloc_asprintf(cmd, "%u", net->num_bts); return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(net_bts_num, "number-of-bts"); /* Return a list of the states of each TRX for all BTS: * ,,,,,;,,...;...; * For details on the string, see bsc_rf_states_c(); */ static int get_net_rf_states(struct ctrl_cmd *cmd, void *data) { cmd->reply = bsc_rf_states_c(cmd); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(net_rf_states, "rf_states"); CTRL_CMD_DEFINE(net_timezone, "timezone"); static int get_net_timezone(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = (struct gsm_network *)cmd->node; struct gsm_tz *tz = &net->tz; if (tz->override) cmd->reply = talloc_asprintf(cmd, "%d,%d,%d", tz->hr, tz->mn, tz->dst); else cmd->reply = talloc_asprintf(cmd, "off"); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } return CTRL_CMD_REPLY; } static int set_net_timezone(struct ctrl_cmd *cmd, void *data) { char *saveptr, *hourstr, *minstr, *dststr, *tmp = 0; int override = 0; struct gsm_network *net = (struct gsm_network *)cmd->node; struct gsm_tz *tz = &net->tz; tmp = talloc_strdup(cmd, cmd->value); if (!tmp) goto oom; hourstr = strtok_r(tmp, ",", &saveptr); minstr = strtok_r(NULL, ",", &saveptr); dststr = strtok_r(NULL, ",", &saveptr); if (hourstr != NULL) { override = strcasecmp(hourstr, "off") != 0; if (override) { tz->hr = atol(hourstr); tz->mn = minstr ? atol(minstr) : 0; tz->dst = dststr ? atol(dststr) : 0; } } tz->override = override; talloc_free(tmp); tmp = NULL; return get_net_timezone(cmd, data); oom: cmd->reply = "OOM"; return CTRL_CMD_ERROR; } static int verify_net_timezone(struct ctrl_cmd *cmd, const char *value, void *data) { char *saveptr, *hourstr, *minstr, *dststr, *tmp; int override, tz_hours, tz_mins, tz_dst; tmp = talloc_strdup(cmd, value); if (!tmp) return 1; hourstr = strtok_r(tmp, ",", &saveptr); minstr = strtok_r(NULL, ",", &saveptr); dststr = strtok_r(NULL, ",", &saveptr); if (hourstr == NULL) goto err; override = strcasecmp(hourstr, "off") != 0; if (!override) { talloc_free(tmp); return 0; } if (minstr == NULL || dststr == NULL) goto err; tz_hours = atol(hourstr); tz_mins = atol(minstr); tz_dst = atol(dststr); talloc_free(tmp); tmp = NULL; if ((tz_hours < -19) || (tz_hours > 19) || (tz_mins < 0) || (tz_mins >= 60) || (tz_mins % 15 != 0) || (tz_dst < 0) || (tz_dst > 2)) goto err; return 0; err: talloc_free(tmp); cmd->reply = talloc_strdup(cmd, "The format is ,, or 'off' where -19 <= hours <= 19, mins in {0, 15, 30, 45}, and 0 <= dst <= 2"); return 1; } CTRL_CMD_DEFINE_RO(bts_connection_status, "bts_connection_status"); static int bts_connection_status = 0; static int get_bts_connection_status(struct ctrl_cmd *cmd, void *data) { if (bts_connection_status) cmd->reply = "connected"; else cmd->reply = "disconnected"; return CTRL_CMD_REPLY; } static int bts_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct ctrl_cmd *cmd; struct gsm_network *gsmnet = (struct gsm_network *)handler_data; struct gsm_bts *bts; int bts_current_status; if (signal != S_L_INP_TEI_DN && signal != S_L_INP_TEI_UP) { return 0; } bts_current_status = 0; /* Check if OML on at least one BTS is up */ llist_for_each_entry(bts, &gsmnet->bts_list, list) { if (bts->oml_link) { bts_current_status = 1; break; } } if (bts_connection_status == 0 && bts_current_status == 1) { LOGP(DCTRL, LOGL_DEBUG, "BTS connection (re)established, sending TRAP.\n"); } else if (bts_connection_status == 1 && bts_current_status == 0) { LOGP(DCTRL, LOGL_DEBUG, "No more BTS connected, sending TRAP.\n"); } else { return 0; } cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); if (!cmd) { LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); return 0; } bts_connection_status = bts_current_status; cmd->id = "0"; cmd->variable = "bts_connection_status"; get_bts_connection_status(cmd, NULL); ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); talloc_free(cmd); return 0; } CTRL_CMD_DEFINE_RO(msc_connection_status, "connection_status"); static int get_msc_connection_status(struct ctrl_cmd *cmd, void *data) { struct bsc_msc_data *msc = (struct bsc_msc_data *)cmd->node; if (msc == NULL) { cmd->reply = "msc not found"; return CTRL_CMD_ERROR; } if (a_reset_conn_ready(msc)) cmd->reply = "connected"; else cmd->reply = "disconnected"; return CTRL_CMD_REPLY; } /* Backwards compat. */ CTRL_CMD_DEFINE_RO(msc0_connection_status, "msc_connection_status"); static int get_msc0_connection_status(struct ctrl_cmd *cmd, void *data) { struct bsc_msc_data *msc = osmo_msc_data_find(bsc_gsmnet, 0); void *old_node = cmd->node; int rc; cmd->node = msc; rc = get_msc_connection_status(cmd, data); cmd->node = old_node; return rc; } static int msc_connection_status_trap_cb(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct ctrl_cmd *cmd; struct gsm_network *gsmnet = (struct gsm_network *)handler_data; struct bsc_msc_data *msc = (struct bsc_msc_data *)signal_data; if (signal == S_MSC_LOST) { LOGP(DCTRL, LOGL_DEBUG, "MSC connection lost, sending TRAP.\n"); } else if (signal == S_MSC_CONNECTED) { LOGP(DCTRL, LOGL_DEBUG, "MSC connection (re)established, sending TRAP.\n"); } else { return 0; } cmd = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); if (!cmd) { LOGP(DCTRL, LOGL_ERROR, "Trap creation failed.\n"); return 0; } cmd->id = "0"; cmd->variable = talloc_asprintf(cmd, "msc.%d.connection_status", msc->nr); cmd->node = msc; get_msc_connection_status(cmd, NULL); ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); if (msc->nr == 0) { /* Backwards compat. */ cmd->variable = "msc_connection_status"; ctrl_cmd_send_to_all(gsmnet->ctrl, cmd); } talloc_free(cmd); return 0; } static int msc_signal_handler(unsigned int subsys, unsigned int signal, void *handler_data, void *signal_data) { struct msc_signal_data *msc; struct gsm_network *net; struct gsm_bts *bts; if (subsys != SS_MSC) return 0; if (signal != S_MSC_AUTHENTICATED) return 0; msc = signal_data; net = msc->data->network; llist_for_each_entry(bts, &net->bts_list, list) ctrl_generate_bts_location_state_trap(bts, msc->data); return 0; } /* Obtain SS7 application server currently handling given MSC (DPC) */ static struct osmo_ss7_as *msc_get_ss7_as(struct bsc_msc_data *msc) { struct osmo_ss7_route *rt; struct osmo_ss7_as *as; struct osmo_ss7_instance *ss7 = osmo_sccp_get_ss7(msc->a.sccp); rt = osmo_ss7_route_lookup(ss7, msc->a.msc_addr.pc); if (!rt) return NULL; as = osmo_ss7_route_get_dest_as(rt); if (!as) return NULL; return as; } int bsc_sccplite_msc_send(struct bsc_msc_data *msc, struct msgb *msg) { struct osmo_ss7_as *as; struct osmo_ss7_asp *asp; as = msc_get_ss7_as(msc); if (!as) { msgb_free(msg); return -1; } /* don't attempt to send CTRL on a non-SCCPlite AS */ if (osmo_ss7_as_get_asp_protocol(as) != OSMO_SS7_ASP_PROT_IPA) { msgb_free(msg); return 0; } asp = osmo_ss7_as_select_asp(as); if (!asp) { LOGP(DCTRL, LOGL_NOTICE, "No ASP found for AS, dropping message\n"); msgb_free(msg); return -1; } return osmo_ss7_asp_send(asp, msg); } /* Encode a CTRL command and send it to the given ASP * \param[in] asp ASP through which we shall send the encoded message * \param[in] cmd decoded CTRL command to be encoded and sent. Ownership is *NOT* * transferred, to permit caller to send the same CMD to several ASPs. * Caller must hence free 'cmd' itself. * \returns 0 on success; negative on error */ static int sccplite_asp_ctrl_cmd_send(struct osmo_ss7_asp *asp, struct ctrl_cmd *cmd) { /* this is basically like libosmoctrl:ctrl_cmd_send(), not for a dedicated * CTRL connection but for the CTRL piggy-back on the IPA/SCCPlite link */ struct msgb *msg; /* don't attempt to send CTRL on a non-SCCPlite ASP */ if (osmo_ss7_asp_get_proto(asp) != OSMO_SS7_ASP_PROT_IPA) return 0; msg = ctrl_cmd_make(cmd); if (!msg) return -1; ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); ipa_prepend_header(msg, IPAC_PROTO_OSMO); return osmo_ss7_asp_send(asp, msg); } /* Ownership of 'cmd' is *NOT* transferred, to permit caller to send the same CMD to several ASPs. * Caller must hence free 'cmd' itself. */ static int sccplite_msc_ctrl_cmd_send(struct bsc_msc_data *msc, struct ctrl_cmd *cmd) { struct msgb *msg; msg = ctrl_cmd_make(cmd); if (!msg) return -1; ipa_prepend_header_ext(msg, IPAC_PROTO_EXT_CTRL); ipa_prepend_header(msg, IPAC_PROTO_OSMO); return bsc_sccplite_msc_send(msc, msg); } /* receive + process a CTRL command from the piggy-back on the IPA/SCCPlite link. * Transfers msg ownership. */ int bsc_sccplite_rx_ctrl(struct osmo_ss7_asp *asp, struct msgb *msg) { struct ctrl_cmd *cmd; bool parse_failed; int rc; /* caller has already ensured ipaccess_head + ipaccess_head_ext */ OSMO_ASSERT(msg->l2h); /* prase raw (ASCII) CTRL command into ctrl_cmd */ cmd = ctrl_cmd_parse3(asp, msg, &parse_failed); OSMO_ASSERT(cmd); msgb_free(msg); if (cmd->type == CTRL_TYPE_ERROR && parse_failed) goto send_reply; /* handle the CTRL command */ ctrl_cmd_handle(bsc_gsmnet->ctrl, cmd, bsc_gsmnet); send_reply: rc = sccplite_asp_ctrl_cmd_send(asp, cmd); talloc_free(cmd); return rc; } void osmo_bsc_send_trap(struct ctrl_cmd *cmd, struct bsc_msc_data *msc_data) { struct ctrl_cmd *trap; struct ctrl_handle *ctrl; ctrl = msc_data->network->ctrl; trap = ctrl_cmd_trap(cmd); if (!trap) { LOGP(DCTRL, LOGL_ERROR, "Failed to create trap.\n"); return; } ctrl_cmd_send_to_all(ctrl, trap); sccplite_msc_ctrl_cmd_send(msc_data, trap); talloc_free(trap); } CTRL_CMD_DEFINE_WO_NOVRF(net_notification, "notification"); static int set_net_notification(struct ctrl_cmd *cmd, void *data) { struct ctrl_cmd *trap; struct gsm_network *net; net = cmd->node; trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); if (!trap) { LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n"); goto handled; } trap->id = "0"; trap->variable = "notification"; trap->reply = talloc_strdup(trap, cmd->value); /* * This should only be sent to local systems. In the future * we might even ask for systems to register to receive * the notifications. */ ctrl_cmd_send_to_all(net->ctrl, trap); talloc_free(trap); handled: return CTRL_CMD_HANDLED; } CTRL_CMD_DEFINE_WO_NOVRF(net_inform_msc, "inform-msc-v1"); static int set_net_inform_msc(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net; struct bsc_msc_data *msc; net = cmd->node; llist_for_each_entry(msc, &net->mscs, entry) { struct ctrl_cmd *trap; trap = ctrl_cmd_create(tall_bsc_ctx, CTRL_TYPE_TRAP); if (!trap) { LOGP(DCTRL, LOGL_ERROR, "Trap creation failed\n"); continue; } trap->id = "0"; trap->variable = "inform-msc-v1"; trap->reply = talloc_strdup(trap, cmd->value); sccplite_msc_ctrl_cmd_send(msc, trap); talloc_free(trap); } return CTRL_CMD_HANDLED; } /* Return full information about all logical channels. * format: show-lchan.full * result format: New line delimited list of ,,,,,,,,, * ,, ,,,,,, * ,,, */ static int get_net_show_lchan_full(struct ctrl_cmd *cmd, void *data) { struct gsm_network *net = cmd->node; int bts_nr; bool first_bts = true; char *bts_dump; cmd->reply = talloc_strdup(cmd, ""); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } for (bts_nr = 0; bts_nr < net->num_bts; bts_nr++) { bts_dump = bts_lchan_dump_full_ctrl(cmd, gsm_bts_num(net, bts_nr)); if (!bts_dump) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } if (!strlen(bts_dump)) continue; cmd->reply = talloc_asprintf_append(cmd->reply, first_bts ? "%s" : "\n%s", bts_dump); if (!cmd->reply) { cmd->reply = "OOM"; return CTRL_CMD_ERROR; } first_bts = false; } return CTRL_CMD_REPLY; } CTRL_CMD_DEFINE_RO(net_show_lchan_full, "show-lchan full"); static int bsc_base_ctrl_cmds_install(struct gsm_network *net) { int rc = 0; rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config_file); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_write_config_file); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mnc); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_apply_config); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_mcc_mnc_apply); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_lock); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_bts_num); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_rf_states); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_timezone); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_bts_connection_status); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_msc0_connection_status); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_notification); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_inform_msc); rc |= ctrl_cmd_install(CTRL_NODE_ROOT, &cmd_net_show_lchan_full); rc |= ctrl_cmd_install(CTRL_NODE_MSC, &cmd_msc_connection_status); rc |= osmo_signal_register_handler(SS_L_INPUT, &bts_connection_status_trap_cb, net); rc |= osmo_signal_register_handler(SS_MSC, &msc_connection_status_trap_cb, net); rc |= osmo_signal_register_handler(SS_MSC, msc_signal_handler, NULL); return rc; } int bsc_ctrl_cmds_install(struct gsm_network *net) { int rc; rc = bsc_base_ctrl_cmds_install(net); if (rc) goto end; rc = bsc_ho_ctrl_cmds_install(net); if (rc) goto end; rc = bsc_bts_ctrl_cmds_install(); if (rc) goto end; end: return rc; }