From ef712780bcda326157a4e991d2fb2def1bde523f Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Sun, 5 Jun 2022 07:57:56 +0200 Subject: more sms storage WIP --- configure.ac | 1 + include/osmocom/msc/gsm_data.h | 17 ++- include/osmocom/msc/sms_storage.h | 31 +++++ src/libmsc/gsm_04_11.c | 31 +++++ src/libmsc/msc_vty.c | 16 +-- src/libmsc/sms_queue.c | 212 ++++++++--------------------------- src/libmsc/sms_storage.c | 128 ++++++++++++++++++--- src/libmsc/smsc_vty.c | 13 +++ tests/Makefile.am | 1 + tests/sms_storage/Makefile.am | 54 +++++++++ tests/sms_storage/sms_storage_test.c | 49 ++++++++ 11 files changed, 362 insertions(+), 191 deletions(-) create mode 100644 include/osmocom/msc/sms_storage.h create mode 100644 tests/sms_storage/Makefile.am create mode 100644 tests/sms_storage/sms_storage_test.c diff --git a/configure.ac b/configure.ac index 6e32ae620..e37f434ae 100644 --- a/configure.ac +++ b/configure.ac @@ -243,6 +243,7 @@ AC_OUTPUT( tests/atlocal tests/smpp/Makefile tests/db_sms/Makefile + tests/sms_storage/Makefile tests/sms_queue/Makefile tests/msc_vlr/Makefile tests/sdp_msg/Makefile diff --git a/include/osmocom/msc/gsm_data.h b/include/osmocom/msc/gsm_data.h index 465d2f41e..fac1176b5 100644 --- a/include/osmocom/msc/gsm_data.h +++ b/include/osmocom/msc/gsm_data.h @@ -280,6 +280,8 @@ enum gsm_sms_source_id { SMS_SOURCE_SMPP, /* received via SMPP */ }; +extern const struct value_string gsm_sms_source_name[]; + #define SMS_TEXT_SIZE 256 struct gsm_sms_addr { @@ -288,8 +290,21 @@ struct gsm_sms_addr { char addr[21+1]; }; +enum gsm_sms_state { + GSM_SMS_ST_ALLOCATED, /* memory allocated */ + GSM_SMS_ST_STORAGE_PENDING, /* waiting for it to be stored on disk */ + GSM_SMS_ST_DELIVERY_PENDING, + GSM_SMS_ST_PAGING, /* delivery pending, paging started */ + GSM_SMS_ST_DELIVERING, /* delivery ongoing (after paging succeeded) */ + GSM_SMS_ST_DELIVERED, /* successfully delivered */ +}; + +extern const struct value_string gsm_sms_state_name[]; + struct gsm_sms { - struct llist_head list; + struct llist_head list; /* entry in global list of pending SMS */ + struct llist_head vsub_list; /* entry in vlr_subscr.sms.pending */ + enum gsm_sms_state state; unsigned long long id; struct vlr_subscr *receiver; struct gsm_sms_addr src, dst; diff --git a/include/osmocom/msc/sms_storage.h b/include/osmocom/msc/sms_storage.h new file mode 100644 index 000000000..a70d9bfa0 --- /dev/null +++ b/include/osmocom/msc/sms_storage.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +struct sms_storage_inst; +struct gsm_sms; + + +/* configuration of SMS storage */ +struct sms_storage_cfg { + char storage_dir[PATH_MAX+1]; + /* unlink messages after delivery, or just move them? */ + bool unlink_delivered; + /* unlink messages after expiration, or just move them? */ + bool unlink_expired; +}; + +enum smss_delete_cause { + SMSS_DELETE_CAUSE_UNKNOWN, + SMSS_DELETE_CAUSE_DELIVERED, + SMSS_DELETE_CAUSE_EXPIRED, +}; + + +struct sms_storage_inst *sms_storage_init(void *ctx, const struct sms_storage_cfg *scfg); + +int sms_storage_to_disk_req(struct sms_storage_inst *ssi, struct gsm_sms *sms); + +int sms_storage_delete_from_disk_req(struct sms_storage_inst *ssi, unsigned long long id, + enum smss_delete_cause cause); diff --git a/src/libmsc/gsm_04_11.c b/src/libmsc/gsm_04_11.c index 81d58ad71..896270d3b 100644 --- a/src/libmsc/gsm_04_11.c +++ b/src/libmsc/gsm_04_11.c @@ -61,6 +61,26 @@ #include "smpp_smsc.h" #endif +const struct value_string gsm_sms_source_name[] = { + { SMS_SOURCE_UNKNOWN, "UNKNOWN" }, + { SMS_SOURCE_MS_GSM, "MS-GSM" }, + { SMS_SOURCE_MS_UMTS, "MS-UMTS" }, + { SMS_SOURCE_MS_SGS, "MS-SGs" }, + { SMS_SOURCE_VTY, "VTY" }, + { SMS_SOURCE_SMPP, "SMPP" }, + { 0, NULL } +}; + +const struct value_string gsm_sms_state_name[] = { + { GSM_SMS_ST_ALLOCATED, "ALLOCATED" }, + { GSM_SMS_ST_STORAGE_PENDING, "STORAGE_PENDING" }, + { GSM_SMS_ST_DELIVERY_PENDING, "DELIVERY_PENDING" }, + { GSM_SMS_ST_PAGING, "PAGING" }, + { GSM_SMS_ST_DELIVERING, "DELIVERING" }, + { GSM_SMS_ST_DELIVERED, "DELIVERED" }, + { 0, NULL } +}; + void *tall_gsms_ctx; static pthread_mutex_t tall_sms_mutex; static uint32_t new_callref = 0x40000001; @@ -76,12 +96,17 @@ struct gsm_sms *sms_alloc(void) pthread_mutex_lock(&tall_sms_mutex); sms = talloc_zero(tall_gsms_ctx, struct gsm_sms); pthread_mutex_unlock(&tall_sms_mutex); + if (sms) + sms->state = GSM_SMS_ST_ALLOCATED; return sms; } /* MUST ONLY BE CALLED ON MAIN THREAD */ void sms_free(struct gsm_sms *sms) { + llist_del(&sms->list); + llist_del(&sms->vsub_list); + /* drop references to subscriber structure */ if (sms->receiver) vlr_subscr_put(sms->receiver, VSUB_USE_SMS_RECEIVER); @@ -893,6 +918,10 @@ static int gsm411_rx_rp_ack(struct gsm_trans *trans, /* mark this SMS as sent in database */ sms_storage_delete_from_disk_req(trans->net->sms_storage, sms->id, SMSS_DELETE_CAUSE_DELIVERED); + /* delete from lists before sending signal, as the latter will attempt to send another SMS */ + llist_del(&sms->list); + llist_del(&sms->vsub_list); + send_signal(S_SMS_DELIVERED, trans, sms, 0); if (sms->status_rep_req) @@ -1192,6 +1221,8 @@ int gsm411_send_sms(struct gsm_network *net, struct msgb *msg; int rc; + sms->state = GSM_SMS_ST_DELIVERING; + /* Allocate a new transaction for MT SMS */ trans = gsm411_alloc_mt_trans(net, vsub); if (!trans) { diff --git a/src/libmsc/msc_vty.c b/src/libmsc/msc_vty.c index 85bba56d6..fc2e195d3 100644 --- a/src/libmsc/msc_vty.c +++ b/src/libmsc/msc_vty.c @@ -1180,6 +1180,7 @@ DEFUN(show_subscr_cache, show_subscr_cache_cmd, return CMD_SUCCESS; } +#if 0 DEFUN(sms_send_pend, sms_send_pend_cmd, "sms send pending", @@ -1237,6 +1238,7 @@ DEFUN(sms_delete_expired, vty_out(vty, "Deleted %llu expired SMS from database%s", num_deleted, VTY_NEWLINE); return CMD_SUCCESS; } +#endif static int _send_sms_str(struct vlr_subscr *receiver, const char *sender_msisdn, @@ -1255,15 +1257,13 @@ static int _send_sms_str(struct vlr_subscr *receiver, sms->source = SMS_SOURCE_VTY; /* store in database for the queue */ - if (db_sms_store(sms) != 0) { + if (sms_storage_to_disk_req(net->sms_storage, sms) != 0) { LOGP(DLSMS, LOGL_ERROR, "Failed to store SMS in Database\n"); sms_free(sms); return CMD_WARNING; } LOGP(DLSMS, LOGL_DEBUG, "SMS stored in DB\n"); - sms_free(sms); - sms_queue_trigger(net->sms_queue); return CMD_SUCCESS; } @@ -1333,6 +1333,7 @@ DEFUN_DEPRECATED(subscriber_create, subscriber_create_cmd, return CMD_WARNING; } +#if 0 DEFUN(subscriber_send_pending_sms, subscriber_send_pending_sms_cmd, "subscriber " SUBSCR_TYPES " ID sms pending-send", @@ -1380,6 +1381,7 @@ DEFUN(subscriber_sms_delete_all, return CMD_SUCCESS; } +#endif DEFUN(subscriber_send_sms, subscriber_send_sms_cmd, @@ -2085,8 +2087,8 @@ void msc_vty_init(struct gsm_network *msc_network) install_element_ve(&show_msc_transaction_cmd); install_element_ve(&show_nri_cmd); - install_element_ve(&sms_send_pend_cmd); - install_element_ve(&sms_delete_expired_cmd); + //install_element_ve(&sms_send_pend_cmd); + //install_element_ve(&sms_delete_expired_cmd); install_element_ve(&subscriber_create_cmd); install_element_ve(&subscriber_send_sms_cmd); @@ -2101,8 +2103,8 @@ void msc_vty_init(struct gsm_network *msc_network) install_element_ve(&logging_fltr_imsi_cmd); install_element(ENABLE_NODE, &ena_subscr_expire_cmd); - install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd); - install_element(ENABLE_NODE, &subscriber_sms_delete_all_cmd); + //install_element(ENABLE_NODE, &subscriber_send_pending_sms_cmd); + //install_element(ENABLE_NODE, &subscriber_sms_delete_all_cmd); install_element(CONFIG_NODE, &cfg_mncc_int_cmd); install_node(&mncc_int_node, config_write_mncc_int); diff --git a/src/libmsc/sms_queue.c b/src/libmsc/sms_queue.c index 2f5186438..05588c5de 100644 --- a/src/libmsc/sms_queue.c +++ b/src/libmsc/sms_queue.c @@ -106,28 +106,11 @@ static const struct rate_ctr_group_desc smsq_ctrg_desc = { osmo_stat_item_set(osmo_stat_item_group_get_item((smsq)->statg, idx), val) -/* One in-RAM record of a "pending SMS". This is not the SMS itself, but merely - * a pointer to the database record. It holds a reference on the vlr_subscriber - * and some counters. While this object exists in RAM, we are regularly attempting - * to deliver the related SMS. */ -#if 0 -struct gsm_sms_pending { - struct llist_head entry; /* gsm_sms_queue.pending_sms */ - - struct vlr_subscr *vsub; /* destination subscriber for this SMS */ - struct msc_a *msc_a; /* MSC_A associated with this SMS */ - unsigned long long sms_id; /* unique ID (in SQL database) of this SMS */ - int failed_attempts; /* count of failed deliver attempts so far */ - int resend; /* should we try re-sending it (now) ? */ -}; -#endif - /* (global) state of the SMS queue. */ struct gsm_sms_queue { struct osmo_timer_list resend_pending; /* timer triggering sms_resend_pending() */ struct osmo_timer_list push_queue; /* timer triggering sms_submit_pending() */ struct gsm_network *network; - struct llist_head pending_sms; /* list of gsm_sms_pending */ struct sms_queue_config *cfg; int pending; /* current number of gsm_sms_pending in RAM */ @@ -149,6 +132,7 @@ static void _gsm411_send_sms(struct gsm_network *net, struct vlr_subscr *vsub, s static int sms_subscr_cb(unsigned int, unsigned int, void *, void *); static int sms_sms_cb(unsigned int, unsigned int, void *, void *); +#if 0 /* look-up a 'gsm_sms_pending' for the given sms_id; return NULL if none */ static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq, unsigned long long sms_id) @@ -168,94 +152,42 @@ int sms_queue_sms_is_pending(struct gsm_sms_queue *smsq, unsigned long long sms_ { return sms_find_pending(smsq, sms_id) != NULL; } +#endif /* find the first pending SMS (in RAM) for the given subscriber */ -static struct gsm_sms_pending *sms_subscriber_find_pending( - struct gsm_sms_queue *smsq, - struct vlr_subscr *vsub) +static struct gsm_sms *sms_subscriber_find_pending(struct vlr_subscr *vsub) { - struct gsm_sms_pending *pending; + struct gsm_sms *sms; - llist_for_each_entry(pending, &smsq->pending_sms, entry) { - if (pending->vsub == vsub) - return pending; + llist_for_each_entry(sms, &vsub->sms.pending, vsub_list) { + if (sms->state == GSM_SMS_ST_DELIVERY_PENDING) + return sms; } return NULL; } +#if 0 /* do we have any pending SMS (in RAM) for the given subscriber? */ static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq, struct vlr_subscr *vsub) { - return sms_subscriber_find_pending(smsq, vsub) != NULL; -} - -/* allocate a new gsm_sms_pending record and fill it with information from 'sms' */ -static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq, - struct gsm_sms *sms) -{ - struct gsm_sms_pending *pending; - - pending = talloc_zero(smsq, struct gsm_sms_pending); - if (!pending) - return NULL; - - vlr_subscr_get(sms->receiver, VSUB_USE_SMS_PENDING); - pending->vsub = sms->receiver; - pending->sms_id = sms->id; - llist_add_tail(&pending->entry, &smsq->pending_sms); - - smsq->pending += 1; - smsq_stat_item_inc(smsq, SMSQ_STAT_SMS_RAM_PENDING); - - return pending; + return !llist_empty(&vsub->sms.pending); } +#endif -/* release a gsm_sms_pending object */ -static void sms_pending_free(struct gsm_sms_queue *smsq, struct gsm_sms_pending *pending) -{ - smsq->pending -= 1; - smsq_stat_item_dec(smsq, SMSQ_STAT_SMS_RAM_PENDING); - vlr_subscr_put(pending->vsub, VSUB_USE_SMS_PENDING); - llist_del(&pending->entry); - talloc_free(pending); -} - -/* this sets the 'resend' flag of the gsm_sms_pending and schedules - * the timer for re-sending */ -static void sms_pending_resend(struct gsm_sms_pending *pending) -{ - struct gsm_network *net = pending->vsub->vlr->user_ctx; - struct gsm_sms_queue *smsq; - LOGP(DLSMS, LOGL_DEBUG, - "Scheduling resend of SMS %llu.\n", pending->sms_id); - - pending->resend = 1; - - smsq = net->sms_queue; - if (osmo_timer_pending(&smsq->resend_pending)) - return; - - osmo_timer_schedule(&smsq->resend_pending, 1, 0); -} /* call-back when a pending SMS has failed; try another re-send if number of * attempts is < smsq->max_fail */ -static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error) +static void sms_pending_failed(struct gsm_sms_queue *smsq, struct gsm_sms *sms, int paging_error) { - struct gsm_network *net = pending->vsub->vlr->user_ctx; - struct gsm_sms_queue *smsq; - - pending->failed_attempts++; + sms->failed_attempts++; + sms->state = GSM_SMS_ST_DELIVERY_PENDING; LOGP(DLSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n", - pending->sms_id, pending->failed_attempts); - - smsq = net->sms_queue; - if (pending->failed_attempts < smsq->cfg->max_fail) - return sms_pending_resend(pending); + sms->id, sms->failed_attempts); - sms_pending_free(smsq, pending); + if (sms->failed_attempts >= smsq->cfg->max_fail) + sms_free(sms); } /* Resend all SMS that are scheduled for a resend. This is done to @@ -264,27 +196,22 @@ static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error * DB and attempts to send them via _gsm411_send_sms() */ static void sms_resend_pending(void *_data) { - struct gsm_sms_pending *pending, *tmp; +#if 0 + struct gsm_sms *sms, *tmp; struct gsm_sms_queue *smsq = _data; - llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) { - struct gsm_sms *sms; + llist_for_each_entry_safe(sms, tmp, &smsq->pending_sms, list) { if (!pending->resend) continue; - sms = db_sms_get(smsq->network, pending->sms_id); - - /* the sms is gone? Move to the next */ - if (!sms) { - sms_pending_free(smsq, pending); - sms_queue_trigger(smsq); - } else { - pending->resend = 0; - _gsm411_send_sms(smsq->network, sms->receiver, sms); - } + /* FIXME: limit the number of concurrent attempted SMS */ + /* FIXME: have some per-sms state to avoid sending the same twice */ + _gsm411_send_sms(smsq->network, sms->receiver, sms); } +#endif } +#if 0 /* Find the next pending SMS by cycling through the recipients. We could also * cycle through the pending SMS, but that might cause us to keep trying to * send SMS to the same few subscribers repeatedly while not servicing other @@ -335,6 +262,7 @@ struct gsm_sms *smsq_take_next_sms(struct gsm_network *net, DEBUGP(DLSMS, "SMS queue: no SMS to be sent\n"); return NULL; } +#endif /* read up to 'max_pending' pending SMS from the database and add them to the in-memory * sms_queue; trigger the first delivery attempt. 'submit' in this context means @@ -342,6 +270,7 @@ struct gsm_sms *smsq_take_next_sms(struct gsm_network *net, * confused with the SMS SUBMIT operation a MS performs when sending a MO-SMS. */ static void sms_submit_pending(void *_data) { +#if 0 struct gsm_sms_queue *smsq = _data; int attempts = smsq->cfg->max_pending - smsq->pending; int initialized = 0; @@ -415,45 +344,19 @@ static void sms_submit_pending(void *_data) } while (attempted < attempts && rounds < 1000); LOGP(DLSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds); +#endif } -/* obtain the next pending SMS for given subscriber from database, - * create gsm_sms_pending object and attempt first delivery. If there - * are no SMS pending for the given subscriber, call sms_submit_pending() - * to read more SMS (for any subscriber) into the in-RAM pending queue */ +/* obtain the next pending SMS for given subscriber and attempt to deliver it. */ static void sms_send_next(struct vlr_subscr *vsub) { struct gsm_network *net = vsub->vlr->user_ctx; - struct gsm_sms_queue *smsq = net->sms_queue; - struct gsm_sms_pending *pending; - struct gsm_sms *sms; - - /* the subscriber should not be in the queue */ - OSMO_ASSERT(!sms_subscriber_is_pending(smsq, vsub)); + struct gsm_sms *sms = sms_subscriber_find_pending(vsub); - /* check for more messages for this subscriber */ - sms = db_sms_get_unsent_for_subscr(vsub, INT_MAX); if (!sms) - goto no_pending_sms; - - /* The sms should not be scheduled right now */ - OSMO_ASSERT(!sms_queue_sms_is_pending(smsq, sms->id)); - - /* Remember that we deliver this SMS and send it */ - pending = sms_pending_from(smsq, sms); - if (!pending) { - LOGP(DLSMS, LOGL_ERROR, - "Failed to create pending SMS entry.\n"); - sms_free(sms); - goto no_pending_sms; - } - - _gsm411_send_sms(smsq->network, sms->receiver, sms); - return; + return; -no_pending_sms: - /* Try to send the SMS to avoid the queue being stuck */ - sms_submit_pending(net->sms_queue); + _gsm411_send_sms(net, sms->receiver, sms); } /* Trigger a call to sms_submit_pending() in one second */ @@ -504,7 +407,7 @@ int sms_queue_start(struct gsm_network *network) goto err_statg; network->sms_queue = sms; - INIT_LLIST_HEAD(&sms->pending_sms); + //INIT_LLIST_HEAD(&sms->pending_sms); sms->network = network; osmo_timer_setup(&sms->push_queue, sms_submit_pending, sms); osmo_timer_setup(&sms->resend_pending, sms_resend_pending, sms); @@ -512,6 +415,7 @@ int sms_queue_start(struct gsm_network *network) osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network); osmo_signal_register_handler(SS_SMS, sms_sms_cb, network); +#if 0 if (db_init(sms, sms->cfg->db_file_path, true)) { LOGP(DMSC, LOGL_FATAL, "DB: Failed to init database: %s\n", osmo_quote_str(sms->cfg->db_file_path, -1)); @@ -522,6 +426,7 @@ int sms_queue_start(struct gsm_network *network) LOGP(DMSC, LOGL_FATAL, "DB: Failed to prepare database.\n"); return -1; } +#endif sms_submit_pending(sms); @@ -538,9 +443,6 @@ err_free: /* call-back: Given subscriber is now ready for short messages. */ static int sub_ready_for_sm(struct gsm_network *net, struct vlr_subscr *vsub) { - struct gsm_sms *sms; - struct gsm_sms_pending *pending; - /* * The code used to be very clever and tried to submit * a SMS during the Location Updating Request. This has @@ -555,21 +457,8 @@ static int sub_ready_for_sm(struct gsm_network *net, struct vlr_subscr *vsub) * We need to be careful in what we try here. */ - /* check if we have pending requests */ - pending = sms_subscriber_find_pending(net->sms_queue, vsub); - if (pending) { - LOGP(DMSC, LOGL_NOTICE, - "Pending paging while subscriber %llu attached.\n", - vsub->id); - return 0; - } - - /* Now try to deliver any pending SMS to this sub */ - sms = db_sms_get_unsent_for_subscr(vsub, INT_MAX); - if (!sms) - return -1; - - _gsm411_send_sms(net, vsub, sms); + /* check if we have pending SMS + send the first of them */ + sms_send_next(vsub); return 0; } @@ -593,8 +482,6 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, struct gsm_network *network = handler_data; struct gsm_sms_queue *smq = network->sms_queue; struct sms_signal_data *sig_sms = signal_data; - struct gsm_sms_pending *pending; - struct vlr_subscr *vsub; /* We got a new SMS and maybe should launch the queue again. */ if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) { @@ -607,28 +494,16 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, return -1; - /* - * Find the entry of our queue. The SMS subsystem will submit - * sms that are not in our control as we just have a channel - * open anyway. - */ - pending = sms_find_pending(smq, sig_sms->sms->id); - if (!pending) - return 0; - switch (signal) { case S_SMS_DELIVERED: + /* core SMS code has already requested deletion from storage */ smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_ACK); - /* ask SMS thread to delete message from storage */ - ms_storage_delete_from_disk_req(network->sms_storage, sms->id, - SMSS_DELETE_CAUSE_DELIVERED); - sms_free(network->sms_storage, sms); - /* Attempt to send another SMS to this subscriber */ - sms_send_next(vsub); + /* Attempt to send another SMS (if any pending) to this subscriber */ + sms_send_next(sig_sms->sms->receiver); break; case S_SMS_MEM_EXCEEDED: smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_NOMEM); - sms_pending_free(smq, pending); + /* TODO: set flag for subscriber in VLR; skip delivery until cleared */ sms_queue_trigger(smq); break; case S_SMS_UNKNOWN_ERROR: @@ -645,11 +520,10 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, if (sig_sms->paging_result) { smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_ERR); /* BAD SMS? */ - db_sms_inc_deliver_attempts(sig_sms->sms); - sms_pending_failed(pending, 0); + sms_pending_failed(smq, sig_sms->sms, 0); } else { smsq_rate_ctr_inc(smq, SMSQ_CTR_SMS_DELIVERY_TIMEOUT); - sms_pending_failed(pending, 1); + sms_pending_failed(smq, sig_sms->sms, 1); } break; default: @@ -660,6 +534,7 @@ static int sms_sms_cb(unsigned int subsys, unsigned int signal, return 0; } +#if 0 /* VTY helper functions */ int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty) { @@ -687,3 +562,4 @@ int sms_queue_clear(struct gsm_sms_queue *smsq) return 0; } +#endif diff --git a/src/libmsc/sms_storage.c b/src/libmsc/sms_storage.c index 7c9578da8..0d3a80ee6 100644 --- a/src/libmsc/sms_storage.c +++ b/src/libmsc/sms_storage.c @@ -83,10 +83,11 @@ #include #include #include +#include /* all the state of a SMS storage instance */ struct sms_storage_inst { - struct sms_storage_cfg *cfg; + const struct sms_storage_cfg *cfg; pthread_t thread; struct { @@ -102,6 +103,10 @@ struct sms_storage_inst { int wd; } inotify; #endif + + /* global list of penidng SMSs */ + struct llist_head pending; + /* inter-thread message queues for both directions */ struct { struct osmo_it_q *itq; @@ -226,7 +231,7 @@ static int _sms_gen_fq_path(struct sms_storage_inst *ssi, char *fq_path, size_t int rc; rc = snprintf(fq_path, fq_path_len, "%s/%s/%llu.osms", ssi->cfg->storage_dir, subdir, id); - if (rc >= sizeof(fq_path)) { + if (rc >= fq_path_len) { LOGP(DSMSS, LOGL_ERROR, "Overflowing buffer while composing file path\n"); return -EINVAL; } @@ -677,6 +682,9 @@ static void boot_read_tmr_cb(void *data) /* skip anything that's not a normal file */ if (dent->d_type != DT_REG) { + /* suppress printing log messages about . and .. */ + if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, "..")) + goto next; LOGP(DSMSS, LOGL_NOTICE, "bootstrap read: skipping '%s' (not a regular file)\n", dent->d_name); goto next; @@ -710,6 +718,8 @@ static void boot_read_tmr_cb(void *data) if (rc < 0) s2m_free(ssi, evt); + ssi->boot_read.count++; + next: /* read next message in 50ms to avoid overloading the it_q or the MSC in general */ osmo_timer_schedule(&ssi->boot_read.timer, 0, 50000); @@ -719,8 +729,13 @@ next: static void *sms_storage_main(void *arg) { struct sms_storage_inst *ssi = arg; + char current_dir[PATH_MAX+8+1]; + + osmo_ctx_init("sms-storage"); + osmo_select_init(); - ssi->boot_read.dir = opendir(ssi->cfg->storage_dir); + snprintf(current_dir, sizeof(current_dir), "%s/%s", ssi->cfg->storage_dir, SUBDIR_CURRENT); + ssi->boot_read.dir = opendir(current_dir); if (!ssi->boot_read.dir) { LOGP(DSMSS, LOGL_ERROR, "Cannot open SMS directory '%s': %s\n", ssi->cfg->storage_dir, strerror(errno)); @@ -753,18 +768,34 @@ static void storage2main_read_cb(struct osmo_it_q *q, struct llist_head *item) { struct smss_s2m_evt *evt = container_of(item, struct smss_s2m_evt, list); struct sms_storage_inst *ssi = q->data; + struct gsm_sms *sms = NULL; switch (evt->op) { case SMSS_S2M_OP_NULL: break; case SMSS_S2M_OP_SMS_FROM_DISK_IND: /* SMS storage has read a SMS from disk, asks main thread to add it to queue */ + sms = evt->sms_from_disk_ind.sms; + sms->state = GSM_SMS_ST_DELIVERY_PENDING; + /* add to global list of pending SMS */ + llist_add_tail(&sms->list, &ssi->pending); + /* add to per-subscriber list of pending SMS */ + if (sms->receiver) + llist_add_tail(&sms->vsub_list, &sms->receiver->sms.pending); break; case SMSS_S2M_OP_SMS_TO_DISK_CFM: /* SMS storage confirms having written SMS to disk; main thread adds it to queue */ + sms = evt->sms_to_disk_cfm.sms; + sms->state = GSM_SMS_ST_DELIVERY_PENDING; + /* add to global list of pending SMS */ + llist_add_tail(&sms->list, &ssi->pending); + /* add to per-subscriber list of pending SMS */ + if (sms->receiver) + llist_add_tail(&sms->vsub_list, &sms->receiver->sms.pending); break; case SMSS_S2M_OP_SMS_DELETED_ON_DISK_IND: /* SMS storage has detected a sms was deleted from disk; main thread must forget it */ + sms_free(sms); break; default: break; @@ -778,16 +809,19 @@ static void storage2main_read_cb(struct osmo_it_q *q, struct llist_head *item) int sms_storage_to_disk_req(struct sms_storage_inst *ssi, struct gsm_sms *sms) { struct smss_m2s_evt *evt = m2s_alloc(ssi, SMSS_M2S_OP_SMS_TO_DISK_REQ); + enum gsm_sms_state st = sms->state; int rc; if (!evt) return -ENOMEM; + sms->state = GSM_SMS_ST_STORAGE_PENDING; evt->sms_to_disk_req.sms = sms; rc = osmo_it_q_enqueue(ssi->main2storage.itq, evt, list); if (rc < 0) { m2s_free(ssi, evt); + sms->state = st; return rc; } return 0; @@ -819,31 +853,97 @@ int sms_storage_delete_from_disk_req(struct sms_storage_inst *ssi, unsigned long * Initialization ***********************************************************************/ -int sms_storage_init(void *ctx, struct sms_storage_cfg *scfg) +static int sms_storage_ensure_subdir(const struct sms_storage_cfg *scfg, const char *subdir) +{ + char sub_dir[PATH_MAX+8+1]; + struct stat st; + int rc; + + snprintf(sub_dir, sizeof(sub_dir), "%s/%s", scfg->storage_dir, subdir); + + rc = stat(sub_dir, &st); + if (rc < 0) { + if (errno == ENOENT) { + LOGP(DSMSS, LOGL_NOTICE, "SMS storage sub-dir '%s' doesn't exist, attempting to " + "create it\n", sub_dir); + if (mkdir(sub_dir, 0700) != 0) { + LOGP(DSMSS, LOGL_ERROR, "Unable to create SMS storage sub-dir '%s': %s\n", + sub_dir, strerror(errno)); + return -errno; + } + } else { + LOGP(DSMSS, LOGL_ERROR, "Unable to access SMS storage sub-dir '%s': %s\n", + sub_dir, strerror(errno)); + return -errno; + } + } + /* TODO: test if we can write */ + + return 0; +} + +static int sms_storage_ensure_subdirs(const struct sms_storage_cfg *scfg) +{ + int rc; + + rc = sms_storage_ensure_subdir(scfg, SUBDIR_CURRENT); + if (rc < 0) + return rc; + + rc = sms_storage_ensure_subdir(scfg, SUBDIR_DELIVERED); + if (rc < 0) + return rc; + + rc = sms_storage_ensure_subdir(scfg, SUBDIR_EXPIRED); + if (rc < 0) + return rc; + + return 0; +} + + +struct sms_storage_inst *sms_storage_init(void *ctx, const struct sms_storage_cfg *scfg) { struct sms_storage_inst *ssi = talloc_zero(ctx, struct sms_storage_inst); struct stat st; - int rc, ret = -1; + int rc; if (!ssi) - return -ENOMEM; + return NULL; - /* test if scfq->storage_dir exists */ + ssi->cfg = scfg; + INIT_LLIST_HEAD(&ssi->pending); + + /* test if scfg->storage_dir exists */ rc = stat(scfg->storage_dir, &st); if (rc < 0) { - LOGP(DSMSS, LOGL_ERROR, "Unable to access storage path '%s': %s\n", - scfg->storage_dir, strerror(errno)); - return -errno; + if (errno == ENOENT) { + LOGP(DSMSS, LOGL_NOTICE, "SMS storage path '%s' doesn't exist, attempting to " + "create it\n", scfg->storage_dir); + if (mkdir(scfg->storage_dir, 0700) != 0) { + LOGP(DSMSS, LOGL_ERROR, "Unable to create SMS storage dir '%s': %s\n", + scfg->storage_dir, strerror(errno)); + return NULL; + } + } else { + LOGP(DSMSS, LOGL_ERROR, "Unable to access storage path '%s': %s\n", + scfg->storage_dir, strerror(errno)); + return NULL; + } } /* TODO: test if we can write */ + rc = sms_storage_ensure_subdirs(scfg); + if (rc < 0) + goto out_free; + ssi->main2storage.itq = osmo_it_q_alloc(ssi, "sms_main2storage", 1000, main2storage_read_cb, ssi); if (!ssi->main2storage.itq) goto out_free; pthread_mutex_init(&ssi->main2storage.ctx_mutex, NULL); ssi->storage2main.itq = osmo_it_q_alloc(ssi, "sms_storage2main", 1000, storage2main_read_cb, ssi); - if (!ssi->main2storage.itq) + if (!ssi->storage2main.itq) goto out_main2storage; pthread_mutex_init(&ssi->storage2main.ctx_mutex, NULL); @@ -858,7 +958,6 @@ int sms_storage_init(void *ctx, struct sms_storage_cfg *scfg) if (inotify_fd < 0) { LOGP(DSMSS, LOGL_ERROR, "Error during inotify_init(): %s\n", strerror(errno)); - ret = inotify_fd; goto out_m2s_unreg; } /* just setup, don't register. We later register this in the storage thread! */ @@ -869,7 +968,6 @@ int sms_storage_init(void *ctx, struct sms_storage_cfg *scfg) if (rc < 0) { LOGP(DSMSS, LOGL_ERROR, "Cannot add inotify watcher for '%s': %s\n", current_dir, strerror(errno)); - ret = -errno; goto out_close_inotify; } ssi->inotify.wd = rc; @@ -880,7 +978,7 @@ int sms_storage_init(void *ctx, struct sms_storage_cfg *scfg) goto out_all; } - return 0; + return ssi; out_all: #ifdef HAVE_INOTIFY @@ -896,5 +994,5 @@ out_main2storage: out_free: talloc_free(ssi); - return ret; + return NULL; } diff --git a/src/libmsc/smsc_vty.c b/src/libmsc/smsc_vty.c index e99b23657..a6212b366 100644 --- a/src/libmsc/smsc_vty.c +++ b/src/libmsc/smsc_vty.c @@ -117,6 +117,19 @@ DEFUN(cfg_sms_def_val_per, cfg_sms_def_val_per_cmd, * View / Enable Node ***********************************************************************/ +void vty_out_sms(struct vty *vty, const struct gsm_sms *sms) +{ + vty_out(vty, "SMS ID: %llu, State: %s, Source: %s, %s -> %s %s", sms->id, + get_value_string(gsm_sms_state_name, sms->state), + get_value_string(gsm_sms_source_name, sms->source), + sms->src.addr, sms->dst.addr, VTY_NEWLINE); + vty_out(vty, " Created: %s, Validity Minutes: %lu, Failed Attempts: %d%s", + ctime(&sms->created), sms->validity_minutes, sms->failed_attempts, VTY_NEWLINE); + vty_out(vty, " PID: 0x%02x, DCS: 0x%02x, MsgRef: 0x%02x, UDHI: %d, IsReport: %d%s", + sms->protocol_id, sms->data_coding_scheme, sms->msg_ref, sms->ud_hdr_ind, sms->is_report, + VTY_NEWLINE); +} + DEFUN(show_smsqueue, show_smsqueue_cmd, "show sms-queue", diff --git a/tests/Makefile.am b/tests/Makefile.am index d25fe16ec..3a02ee51b 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,4 +1,5 @@ SUBDIRS = \ + sms_storage \ sms_queue \ msc_vlr \ db_sms \ diff --git a/tests/sms_storage/Makefile.am b/tests/sms_storage/Makefile.am new file mode 100644 index 000000000..9befdb619 --- /dev/null +++ b/tests/sms_storage/Makefile.am @@ -0,0 +1,54 @@ +AM_CPPFLAGS = \ + $(all_includes) \ + -I$(top_srcdir)/include \ + $(NULL) + +AM_CFLAGS = \ + -Wall \ + -ggdb3 \ + $(LIBOSMOCORE_CFLAGS) \ + $(LIBOSMOGSM_CFLAGS) \ + $(LIBOSMOVTY_CFLAGS) \ + $(LIBOSMOABIS_CFLAGS) \ + $(LIBOSMONETIF_CFLAGS) \ + $(LIBOSMOSIGTRAN_CFLAGS) \ + $(LIBOSMORANAP_CFLAGS) \ + $(LIBASN1C_CFLAGS) \ + $(LIBOSMOMGCPCLIENT_CFLAGS) \ + $(LIBOSMOGSUPCLIENT_CFLAGS) \ + $(NULL) + +EXTRA_DIST = \ + sms_storage_test.ok \ + sms_storage_test.err \ + $(NULL) + +check_PROGRAMS = \ + sms_storage_test \ + $(NULL) + +sms_storage_test_SOURCES = \ + sms_storage_test.c \ + $(NULL) + +sms_storage_test_LDADD = \ + -lsctp \ + $(top_builddir)/src/libmsc/libmsc.a \ + $(top_builddir)/src/libvlr/libvlr.a \ + $(LIBSMPP34_LIBS) \ + $(LIBOSMOCORE_LIBS) \ + $(LIBOSMOGSM_LIBS) \ + $(LIBOSMOVTY_LIBS) \ + $(LIBOSMOABIS_LIBS) \ + $(LIBOSMONETIF_LIBS) \ + $(LIBOSMOSIGTRAN_LIBS) \ + $(LIBOSMORANAP_LIBS) \ + $(LIBASN1C_LIBS) \ + $(LIBOSMOMGCPCLIENT_LIBS) \ + $(LIBOSMOGSUPCLIENT_LIBS) \ + $(LIBRARY_GSM) \ + $(NULL) + +sms_storage_test_LDFLAGS = \ + -Wl,--wrap=db_sms_get_next_unsent_rr_msisdn \ + $(NULL) diff --git a/tests/sms_storage/sms_storage_test.c b/tests/sms_storage/sms_storage_test.c new file mode 100644 index 000000000..d545dc13c --- /dev/null +++ b/tests/sms_storage/sms_storage_test.c @@ -0,0 +1,49 @@ +#include + +#include + +#include +#include +#include + +static const struct sms_storage_cfg scfg = { + .storage_dir = "/tmp/sms_storage", + .unlink_delivered = false, + .unlink_expired = false, +}; +static struct sms_storage_inst *g_ssi; + +static struct gsm_sms *generate_sms(unsigned long long id, const char *src, const char *dst, + uint8_t pid, uint8_t dcs, uint8_t msg_ref) +{ + struct gsm_sms *sms = sms_alloc(); + OSMO_ASSERT(sms); + + sms->id = id; + OSMO_STRLCPY_ARRAY(sms->src.addr, src); + OSMO_STRLCPY_ARRAY(sms->dst.addr, dst); + sms->protocol_id = pid; + sms->data_coding_scheme = dcs; + sms->msg_ref = msg_ref; + + return sms; +} + +static void to_storage(void) +{ + struct gsm_sms *sms = generate_sms(1234, "1111", "2222", 1, 2, 3); + sms_storage_to_disk_req(g_ssi, sms); + sms_storage_delete_from_disk_req(g_ssi, sms->id, SMSS_DELETE_CAUSE_DELIVERED); +} + +int main(int argc, char **argv) +{ + void *ctx = NULL; + + g_ssi = sms_storage_init(ctx, &scfg); + + to_storage(); + + usleep(10000000); +} + -- cgit v1.2.3