Open Source Telephony
 help / color / mirror / Atom feed
* [RFC PATCH 2/5] history: print SMS status
  2010-06-16 14:08 Pasi Miettinen
@ 2010-06-16 14:08 ` Pasi Miettinen
  0 siblings, 0 replies; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-16 14:08 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 1115 bytes --]

---
 src/history.c |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)

diff --git a/src/history.c b/src/history.c
index f868ca2..0eef130 100644
--- a/src/history.c
+++ b/src/history.c
@@ -238,6 +238,8 @@ void __ofono_history_sms_send_status(struct ofono_modem *modem,
 					enum ofono_history_sms_status status)
 {
 	struct history_sms_foreach_data hfd;
+	struct tm  *ts;
+	char buf[80];
 
 	hfd.msg_id = msg_id;
 	hfd.address = NULL;
@@ -245,6 +247,15 @@ void __ofono_history_sms_send_status(struct ofono_modem *modem,
 	hfd.when = when;
 	hfd.status = status;
 
+	/* Format time, "ddd yyyy-mm-dd hh:mm:ss zzz" */
+	ts = localtime(&when);
+	strftime(buf, sizeof(buf), "%a %Y-%m-%d %H:%M:%S %Z", ts);
+
+	if (status == OFONO_HISTORY_SMS_STATUS_DELIVERED)
+		DBG("SMS delivered, msg_id: %i, time: %s", msg_id, buf);
+	else if (status == OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED)
+		DBG("SMS undeliverable, msg_id: %i, time: %s", msg_id, buf);
+
 	__ofono_modem_foreach_atom(modem, OFONO_ATOM_TYPE_HISTORY,
 					history_sms_send_status, &hfd);
 }
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* SMS status report
@ 2010-06-17 13:14 Pasi Miettinen
  2010-06-17 13:14 ` [RFC PATCH 1/5] smsutil: Status report assembly Pasi Miettinen
  0 siblings, 1 reply; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 482 bytes --]


Hi,

Here are the latest modifications for the status report.

Most common use cases are tested in practise. For example:
-Send concatenated message from ofono to phone that is
 powered off.
-Reboot ofono and ofono reads the persistent data from imsi file.
-Power on the phone.
-Ofono receives the status reports and handles them correctly.

Ofono was run under the supervision of valgrind during these tests
and no memory leaks were detected.

Br,
Pasi






^ permalink raw reply	[flat|nested] 10+ messages in thread

* [RFC PATCH 1/5] smsutil: Status report assembly
  2010-06-17 13:14 SMS status report Pasi Miettinen
@ 2010-06-17 13:14 ` Pasi Miettinen
  2010-06-17 13:14   ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen
  2010-06-21 21:09   ` [RFC PATCH 1/5] smsutil: Status report assembly Denis Kenzior
  0 siblings, 2 replies; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 7495 bytes --]

---
 src/smsutil.c |  183 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/smsutil.h |   31 ++++++++++
 2 files changed, 214 insertions(+), 0 deletions(-)

diff --git a/src/smsutil.c b/src/smsutil.c
index 95eca06..3db3d28 100644
--- a/src/smsutil.c
+++ b/src/smsutil.c
@@ -2625,6 +2625,189 @@ void sms_assembly_expire(struct sms_assembly *assembly, time_t before)
 	}
 }
 
+struct status_report_assembly *status_report_assembly_new(const char *imsi)
+{
+	struct status_report_assembly *ret =
+				g_new0(struct status_report_assembly, 1);
+
+	ret->assembly_table = g_hash_table_new_full(g_str_hash, g_str_equal,
+				g_free, (GDestroyNotify)g_hash_table_destroy);
+
+	if (imsi)
+		ret->imsi = imsi;
+
+	return ret;
+}
+
+void status_report_assembly_free(struct status_report_assembly *assembly)
+{
+	g_hash_table_destroy(assembly->assembly_table);
+	g_free(assembly);
+}
+
+gboolean status_report_assembly_report(struct status_report_assembly *assembly,
+					const struct sms *status_report,
+					unsigned int *msg_id,
+					gboolean *msg_delivered)
+{
+	unsigned int offset = status_report->status_report.mr / 32;
+	unsigned int bit = 1 << (status_report->status_report.mr % 32);
+	GHashTable *id_table;
+	struct id_table_node *node;
+	unsigned int *key;
+	gpointer value;
+	GHashTableIter iter;
+	int i;
+	gboolean pending = FALSE;
+	gboolean update_history = FALSE;
+
+	id_table = g_hash_table_lookup(assembly->assembly_table,
+				status_report->status_report.raddr.address);
+
+	/* ERROR, key (receiver address) does not exist in assembly */
+	if (!id_table) {
+		*msg_delivered = FALSE;
+		return FALSE;
+	}
+
+	g_hash_table_iter_init(&iter, id_table);
+	while (g_hash_table_iter_next(&iter, (gpointer)&key, &value)) {
+		node = value;
+
+		if (!(node->mrs[offset] & bit))
+			continue;
+
+		/* Mr belongs to this node. */
+		node->mrs[offset] ^= bit;
+		*msg_id = *key;
+
+		for (i = 0; i < 8; i++) {
+				/* There are still pending mr(s). */
+				if (node->mrs[i] != 0 ||
+					(node->sent_mrs < node->total_mrs)) {
+					pending = TRUE;
+					break;
+				}
+		}
+		/* Mr is not delivered. */
+		if (status_report->status_report.st !=
+				SMS_ST_COMPLETED_RECEIVED) {
+			/* First mr which is not delivered. Update ofono history
+			 * and mark the whole message as undeliverable. Upcoming
+			 * mrs can not change the status to deliverable even if
+			 * they are considered as delivered.
+			 */
+			if (node->deliverable) {
+				node->deliverable = FALSE;
+				update_history = TRUE;
+			}
+		}
+
+		/* If there are pending mrs that relate to this message, we do
+		 * not delete the node yet.
+		 */
+		if (pending) {
+			*msg_delivered = FALSE;
+			return update_history;
+		} else {
+			*msg_delivered = node->deliverable;
+
+			g_hash_table_iter_remove(&iter);
+
+			if (g_hash_table_size(id_table) == 0)
+				g_hash_table_remove(assembly->assembly_table,
+				status_report->status_report.raddr.address);
+			/* If there has not been undelivered mrs, message is
+			 * delivered and the ofono history needs to be updated.
+			 * If the message is concidered as undelivered, the
+			 * ofono history has already been updated when the first
+			 * undelivered mr arrived, unless this one is the only
+			 * related mr and was marked undelivered.
+			 */
+			return *msg_delivered || update_history;
+		}
+	}
+	/* ERROR, mr not found. */
+	*msg_delivered = FALSE;
+	return FALSE;
+}
+
+void status_report_assembly_add_fragment(
+					struct status_report_assembly *assembly,
+					unsigned int msg_id,
+					const struct sms_address *to,
+					unsigned char mr, time_t expiration,
+					unsigned char total_mrs)
+{
+	unsigned int offset = mr / 32;
+	unsigned int bit = 1 << (mr % 32);
+	GHashTable *id_table;
+	struct id_table_node *node;
+	char *assembly_table_key;
+	unsigned int *id_table_key;
+
+	id_table = g_hash_table_lookup(assembly->assembly_table, to->address);
+	/* Create id_table and node */
+	if (id_table == NULL) {
+		id_table = g_hash_table_new_full(g_int_hash, g_int_equal,
+								g_free, g_free);
+		id_table_key = g_new0(unsigned int, 1);
+
+		node = g_new0(struct id_table_node, 1);
+		node->to = *to;
+		node->mrs[offset] |= bit;
+		node->expiration = expiration;
+		node->total_mrs = total_mrs;
+		node->sent_mrs = 1;
+		node->deliverable = TRUE;
+
+		*id_table_key = msg_id;
+		g_hash_table_insert(id_table, id_table_key, node);
+
+		assembly_table_key = g_try_malloc(sizeof(to->address));
+
+		if (assembly_table_key == NULL) {
+			g_free(node);
+			return;
+		}
+
+		g_strlcpy(assembly_table_key, to->address, sizeof(to->address));
+
+		g_hash_table_insert(assembly->assembly_table,
+					assembly_table_key, id_table);
+		return;
+	}
+
+	node = g_hash_table_lookup(id_table, &msg_id);
+	/* id_table exists, create new node */
+	if (node == NULL) {
+		id_table_key = g_new0(unsigned int, 1);
+		node = g_new0(struct id_table_node, 1);
+		node->to = *to;
+		node->mrs[offset] |= bit;
+		node->expiration = expiration;
+		node->total_mrs = total_mrs;
+		node->sent_mrs = 1;
+		node->deliverable = TRUE;
+
+		*id_table_key = msg_id;
+		g_hash_table_insert(id_table, id_table_key, node);
+
+		return;
+	}
+	/* id_table and node both exists */
+	node->mrs[offset] |= bit;
+	node->expiration = expiration;
+	node->sent_mrs++;
+}
+
+void status_report_assembly_expire(struct status_report_assembly *assembly,
+					time_t before, GFunc foreach_func,
+					gpointer data)
+{
+	/*TODO*/
+}
+
 static inline GSList *sms_list_append(GSList *l, const struct sms *in)
 {
 	struct sms *sms;
diff --git a/src/smsutil.h b/src/smsutil.h
index 1bd42bb..5fb187b 100644
--- a/src/smsutil.h
+++ b/src/smsutil.h
@@ -19,6 +19,8 @@
  *
  */
 
+#include "history.h"
+
 #define CBS_MAX_GSM_CHARS 93
 
 enum sms_type {
@@ -364,6 +366,20 @@ struct sms_assembly {
 	GSList *assembly_list;
 };
 
+struct id_table_node {
+	struct sms_address to;
+	unsigned int mrs[8];
+	time_t expiration;
+	unsigned char total_mrs;
+	unsigned char sent_mrs;
+	gboolean deliverable;
+};
+
+struct status_report_assembly {
+	const char *imsi;
+	GHashTable *assembly_table;
+};
+
 struct cbs {
 	enum cbs_geo_scope gs;			/* 2 bits */
 	guint16 message_code;			/* 10 bits */
@@ -481,6 +497,21 @@ GSList *sms_assembly_add_fragment(struct sms_assembly *assembly,
 					guint16 ref, guint8 max, guint8 seq);
 void sms_assembly_expire(struct sms_assembly *assembly, time_t before);
 
+struct status_report_assembly *status_report_assembly_new(const char *imsi);
+void status_report_assembly_free(struct status_report_assembly *assembly);
+gboolean status_report_assembly_report(struct status_report_assembly *assembly,
+					const struct sms *status_report,
+					unsigned int *msg_id,
+					gboolean *msg_delivered);
+void status_report_assembly_add_fragment(struct status_report_assembly
+					*assembly, unsigned int msg_id,
+					const struct sms_address *to,
+					unsigned char mr, time_t expiration,
+					unsigned char total_mrs);
+void status_report_assembly_expire(struct status_report_assembly *assembly,
+					time_t before, GFunc foreach_func,
+					gpointer data);
+
 GSList *sms_text_prepare(const char *utf8, guint16 ref,
 				gboolean use_16bit, int *ref_offset,
 				gboolean use_delivery_reports);
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 2/5] history: print SMS status
  2010-06-17 13:14 ` [RFC PATCH 1/5] smsutil: Status report assembly Pasi Miettinen
@ 2010-06-17 13:14   ` Pasi Miettinen
  2010-06-17 13:14     ` [RFC PATCH 3/5] history: API change for status report notify Pasi Miettinen
  2010-06-21 23:35     ` [RFC PATCH 2/5] history: print SMS status Denis Kenzior
  2010-06-21 21:09   ` [RFC PATCH 1/5] smsutil: Status report assembly Denis Kenzior
  1 sibling, 2 replies; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 1115 bytes --]

---
 src/history.c |   11 +++++++++++
 1 files changed, 11 insertions(+), 0 deletions(-)

diff --git a/src/history.c b/src/history.c
index f868ca2..0eef130 100644
--- a/src/history.c
+++ b/src/history.c
@@ -238,6 +238,8 @@ void __ofono_history_sms_send_status(struct ofono_modem *modem,
 					enum ofono_history_sms_status status)
 {
 	struct history_sms_foreach_data hfd;
+	struct tm  *ts;
+	char buf[80];
 
 	hfd.msg_id = msg_id;
 	hfd.address = NULL;
@@ -245,6 +247,15 @@ void __ofono_history_sms_send_status(struct ofono_modem *modem,
 	hfd.when = when;
 	hfd.status = status;
 
+	/* Format time, "ddd yyyy-mm-dd hh:mm:ss zzz" */
+	ts = localtime(&when);
+	strftime(buf, sizeof(buf), "%a %Y-%m-%d %H:%M:%S %Z", ts);
+
+	if (status == OFONO_HISTORY_SMS_STATUS_DELIVERED)
+		DBG("SMS delivered, msg_id: %i, time: %s", msg_id, buf);
+	else if (status == OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED)
+		DBG("SMS undeliverable, msg_id: %i, time: %s", msg_id, buf);
+
 	__ofono_modem_foreach_atom(modem, OFONO_ATOM_TYPE_HISTORY,
 					history_sms_send_status, &hfd);
 }
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 3/5] history: API change for status report notify
  2010-06-17 13:14   ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen
@ 2010-06-17 13:14     ` Pasi Miettinen
  2010-06-17 13:14       ` [RFC PATCH 4/5] sms: Status " Pasi Miettinen
  2010-06-21 23:35     ` [RFC PATCH 2/5] history: print SMS status Denis Kenzior
  1 sibling, 1 reply; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 533 bytes --]

---
 include/history.h |    2 ++
 1 files changed, 2 insertions(+), 0 deletions(-)

diff --git a/include/history.h b/include/history.h
index 300a4fb..17445f0 100644
--- a/include/history.h
+++ b/include/history.h
@@ -33,6 +33,8 @@ enum ofono_history_sms_status {
 	OFONO_HISTORY_SMS_STATUS_PENDING,
 	OFONO_HISTORY_SMS_STATUS_SUBMITTED,
 	OFONO_HISTORY_SMS_STATUS_SUBMIT_FAILED,
+	OFONO_HISTORY_SMS_STATUS_DELIVERED,
+	OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED,
 };
 
 struct ofono_history_context {
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 4/5] sms: Status report notify
  2010-06-17 13:14     ` [RFC PATCH 3/5] history: API change for status report notify Pasi Miettinen
@ 2010-06-17 13:14       ` Pasi Miettinen
  2010-06-17 13:14         ` [RFC PATCH 5/5] smsutil: save pending status reports to imsi file Pasi Miettinen
  0 siblings, 1 reply; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 4122 bytes --]

---
 src/sms.c |   71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 70 insertions(+), 1 deletions(-)

diff --git a/src/sms.c b/src/sms.c
index bf6d261..e6a2dca 100644
--- a/src/sms.c
+++ b/src/sms.c
@@ -68,6 +68,8 @@ struct ofono_sms {
 	void *driver_data;
 	struct ofono_atom *atom;
 	ofono_bool_t use_delivery_reports;
+	struct status_report_assembly *sr_assembly;
+
 };
 
 struct pending_pdu {
@@ -83,6 +85,8 @@ struct tx_queue_entry {
 	unsigned int msg_id;
 	unsigned int retry;
 	DBusMessage *msg;
+	gboolean status_report;
+	struct sms_address receiver;
 };
 
 static void set_sca(struct ofono_sms *sms,
@@ -331,6 +335,13 @@ static void tx_finished(const struct ofono_error *error, int mr, void *data)
 	entry->cur_pdu += 1;
 	entry->retry = 0;
 
+	if (entry->status_report)
+		status_report_assembly_add_fragment(sms->sr_assembly,
+							entry->msg_id,
+							&entry->receiver,
+							mr, time(NULL),
+							entry->num_pdus);
+
 	if (entry->cur_pdu < entry->num_pdus) {
 		sms->tx_source = g_timeout_add(0, tx_next, sms);
 		return;
@@ -462,6 +473,8 @@ static DBusMessage *sms_send_message(DBusConnection *conn, DBusMessage *msg,
 	set_ref_and_to(msg_list, sms->ref, ref_offset, to);
 	entry = create_tx_queue_entry(msg_list);
 
+	sms_address_from_string(&entry->receiver, to);
+
 	g_slist_foreach(msg_list, (GFunc)g_free, NULL);
 	g_slist_free(msg_list);
 
@@ -474,6 +487,7 @@ static DBusMessage *sms_send_message(DBusConnection *conn, DBusMessage *msg,
 
 	entry->msg = dbus_message_ref(msg);
 	entry->msg_id = sms->next_msg_id++;
+	entry->status_report = sms->use_delivery_reports;
 
 	g_queue_push_tail(sms->txq, entry);
 
@@ -718,6 +732,30 @@ static void handle_deliver(struct ofono_sms *sms, const struct sms *incoming)
 	g_slist_free(l);
 }
 
+static void handle_sms_status_report(struct ofono_sms *sms,
+						const struct sms *incoming)
+{
+	gboolean delivered;
+	unsigned int msg_id;
+	gboolean update_history;
+	struct ofono_modem *modem = __ofono_atom_get_modem(sms->atom);
+
+	update_history = status_report_assembly_report(sms->sr_assembly,
+						incoming, &msg_id, &delivered);
+
+	if (update_history) {
+
+		if (delivered)
+			__ofono_history_sms_send_status(modem, msg_id,
+				time(NULL), OFONO_HISTORY_SMS_STATUS_DELIVERED);
+		else
+			__ofono_history_sms_send_status(modem, msg_id,
+				time(NULL),
+				OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED);
+	}
+}
+
+
 static inline gboolean handle_mwi(struct ofono_sms *sms, struct sms *s)
 {
 	gboolean discard;
@@ -849,7 +887,30 @@ out:
 void ofono_sms_status_notify(struct ofono_sms *sms, unsigned char *pdu,
 				int len, int tpdu_len)
 {
-	ofono_error("SMS Status-Report not yet handled");
+	struct sms s;
+	enum sms_class cls;
+
+	if (!sms_decode(pdu, len, FALSE, tpdu_len, &s)) {
+		ofono_error("Unable to decode PDU");
+		return;
+	}
+
+	if (s.type != SMS_TYPE_STATUS_REPORT) {
+		ofono_error("Expecting a STATUS REPORT pdu");
+		return;
+	}
+
+	if (s.status_report.srq) {
+		ofono_error("Waiting an answer to SMS-SUBMIT, not SMS-COMMAND");
+		return;
+	}
+
+	if (!sms_dcs_decode(s.deliver.dcs, &cls, NULL, NULL, NULL)) {
+		ofono_error("Unknown / Reserved DCS.  Ignoring");
+		return;
+	}
+
+	handle_sms_status_report(sms, &s);
 }
 
 int ofono_sms_driver_register(const struct ofono_sms_driver *d)
@@ -932,6 +993,11 @@ static void sms_remove(struct ofono_atom *atom)
 		sms->settings = NULL;
 	}
 
+	if (sms->sr_assembly) {
+		status_report_assembly_free(sms->sr_assembly);
+		sms->sr_assembly = NULL;
+	}
+
 	g_free(sms);
 }
 
@@ -1069,9 +1135,12 @@ void ofono_sms_register(struct ofono_sms *sms)
 		imsi = ofono_sim_get_imsi(sms->sim);
 		sms->assembly = sms_assembly_new(imsi);
 
+		sms->sr_assembly = status_report_assembly_new(imsi);
+
 		sms_load_settings(sms, imsi);
 	} else {
 		sms->assembly = sms_assembly_new(NULL);
+		sms->sr_assembly = status_report_assembly_new(NULL);
 	}
 
 	__ofono_atom_register(sms->atom, sms_unregister);
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [RFC PATCH 5/5] smsutil: save pending status reports to imsi file
  2010-06-17 13:14       ` [RFC PATCH 4/5] sms: Status " Pasi Miettinen
@ 2010-06-17 13:14         ` Pasi Miettinen
  2010-06-21 21:07           ` Denis Kenzior
  0 siblings, 1 reply; 10+ messages in thread
From: Pasi Miettinen @ 2010-06-17 13:14 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 8069 bytes --]

---
 src/smsutil.c |  205 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 204 insertions(+), 1 deletions(-)

diff --git a/src/smsutil.c b/src/smsutil.c
index 3db3d28..a30a281 100644
--- a/src/smsutil.c
+++ b/src/smsutil.c
@@ -45,6 +45,10 @@
 #define SMS_BACKUP_PATH_DIR SMS_BACKUP_PATH "/%s-%i-%i"
 #define SMS_BACKUP_PATH_FILE SMS_BACKUP_PATH_DIR "/%03i"
 
+#define SMS_SR_BACKUP_PATH STORAGEDIR "/%s/sms_sr"
+#define SMS_SR_BACKUP_PATH_DIR SMS_SR_BACKUP_PATH "/%s-%i-%i"
+#define SMS_SR_BACKUP_PATH_FILE SMS_SR_BACKUP_PATH_DIR "/%i"
+
 #define SMS_ADDR_FMT "%24[0-9A-F]"
 
 static GSList *sms_assembly_add_fragment_backup(struct sms_assembly *assembly,
@@ -2625,20 +2629,209 @@ void sms_assembly_expire(struct sms_assembly *assembly, time_t before)
 	}
 }
 
+static void sr_assembly_load_backup(GHashTable *assembly_table,
+					const char *imsi,
+					const struct dirent *addr_dir)
+{
+	char *path;
+	struct dirent **ids;
+	struct sms_address *addr;
+	struct id_table_node *node;
+	GHashTable *id_table;
+	int len;
+	int r;
+	unsigned char buf[sizeof(node->mrs) + sizeof(node->total_mrs) +
+			sizeof(node->sent_mrs) + sizeof(node->deliverable)];
+	char *assembly_table_key;
+	unsigned int *id_table_key;
+	struct stat segment_stat;
+
+	if (addr_dir->d_type != DT_DIR)
+		return;
+
+	addr = g_new0(struct sms_address, 1);
+
+	if (sscanf(addr_dir->d_name, "%[0-9]-%i-%i",
+			addr->address, (int *) &addr->number_type,
+			(int *) &addr->numbering_plan) < 3) {
+		g_free(addr);
+		return;
+	}
+
+	/* Go through different msg_ids. */
+	path = g_strdup_printf(SMS_SR_BACKUP_PATH "/%s", imsi,
+							addr_dir->d_name);
+	len = scandir(path, &ids, NULL, versionsort);
+
+	g_free(path);
+
+	if (len < 0) {
+		g_free(addr);
+		return;
+	}
+
+	id_table = g_hash_table_new_full(g_int_hash, g_int_equal,
+							g_free, g_free);
+
+	assembly_table_key = g_try_malloc(sizeof(addr->address));
+
+	if (assembly_table_key == NULL) {
+		g_free(addr);
+		return;
+	}
+
+	g_strlcpy(assembly_table_key, addr->address, sizeof(addr->address));
+	g_hash_table_insert(assembly_table, assembly_table_key, id_table);
+
+	while (len--) {
+		path = g_strdup_printf(SMS_SR_BACKUP_PATH "/%s/%s",
+				imsi, addr_dir->d_name, ids[len]->d_name);
+		r = read_file(buf, sizeof(buf), SMS_SR_BACKUP_PATH "/%s/%s",
+				imsi, addr_dir->d_name, ids[len]->d_name);
+
+		if (r < 0) {
+			g_free(path);
+			g_free(ids[len]);
+			continue;
+		}
+
+		r = stat(path, &segment_stat);
+
+		if (r != 0) {
+			g_free(path);
+			g_free(ids[len]);
+			continue;
+		}
+		/* Gather the data for id_table node */
+		node = g_new0(struct id_table_node, 1);
+		node->to = *addr;
+		node->expiration = segment_stat.st_mtime;
+		memcpy(node->mrs, buf, sizeof(node->mrs));
+		memcpy(&node->total_mrs, buf + sizeof(node->mrs),
+						sizeof(node->total_mrs));
+		memcpy(&node->sent_mrs,
+			buf + sizeof(node->mrs) + sizeof(node->total_mrs),
+			sizeof(node->sent_mrs));
+
+		memcpy(&node->deliverable, buf + sizeof(node->mrs) +
+			sizeof(node->total_mrs) + sizeof(node->sent_mrs),
+			sizeof(node->deliverable));
+		/* Node ready, create key and add them to the table */
+		id_table_key = g_new0(unsigned int, 1);
+		*id_table_key = atoi(ids[len]->d_name);
+
+		g_hash_table_insert(id_table, id_table_key, node);
+
+		g_free(path);
+		g_free(ids[len]);
+	}
+	g_free(addr);
+	g_free(ids);
+}
+
 struct status_report_assembly *status_report_assembly_new(const char *imsi)
 {
+	char *path;
+	int len;
+	struct dirent **addresses;
 	struct status_report_assembly *ret =
 				g_new0(struct status_report_assembly, 1);
 
 	ret->assembly_table = g_hash_table_new_full(g_str_hash, g_str_equal,
 				g_free, (GDestroyNotify)g_hash_table_destroy);
 
-	if (imsi)
+	if (imsi) {
 		ret->imsi = imsi;
 
+		/* Restore state from backup */
+		path = g_strdup_printf(SMS_SR_BACKUP_PATH, imsi);
+		len = scandir(path, &addresses, NULL, alphasort);
+
+		g_free(path);
+
+		if (len < 0)
+			return ret;
+		/* Go through different addresses. Each address can relate to
+		 * 1-n msg_ids. Do not try to load . and .. directories.
+		 */
+		g_free(addresses[0]);
+		g_free(addresses[1]);
+
+		while (2 < len--) {
+			sr_assembly_load_backup(ret->assembly_table, imsi,
+								addresses[len]);
+			g_free(addresses[len]);
+		}
+		g_free(addresses);
+	}
 	return ret;
 }
 
+static gboolean sr_assembly_add_fragment_backup(const char *imsi,
+					const struct id_table_node *node,
+					unsigned int msg_id)
+{
+	int len = sizeof(node->mrs) + sizeof(node->total_mrs) +
+			sizeof(node->sent_mrs) + sizeof(node->deliverable);
+	unsigned char buf[len];
+
+	if (!imsi)
+		return FALSE;
+
+	memcpy(buf, node->mrs, sizeof(node->mrs));
+
+	memcpy(buf + sizeof(node->mrs), &node->total_mrs,
+					sizeof(node->total_mrs));
+
+	memcpy(buf + sizeof(node->mrs) + sizeof(node->total_mrs),
+				&node->sent_mrs, sizeof(node->sent_mrs));
+
+	memcpy(buf + sizeof(node->mrs) + sizeof(node->total_mrs) +
+	sizeof(node->sent_mrs),	&node->deliverable, sizeof(node->deliverable));
+
+	/* storagedir/%s/sms_sr/%s-%i-%i/%i */
+	if (write_file(buf, len, SMS_BACKUP_MODE, SMS_SR_BACKUP_PATH_FILE, imsi,
+			node->to.address, node->to.number_type,
+			node->to.numbering_plan, msg_id) != len)
+		return FALSE;
+
+	return TRUE;
+}
+
+static gboolean sr_assembly_remove_fragment_backup(const char *imsi,
+					const struct id_table_node *node,
+					unsigned int msg_id)
+{
+	char *path;
+
+	if (!imsi)
+		return FALSE;
+
+	path = g_strdup_printf(SMS_SR_BACKUP_PATH_FILE, imsi, node->to.address,
+				node->to.number_type, node->to.numbering_plan,
+				msg_id);
+
+	unlink(path);
+	g_free(path);
+
+	path = g_strdup_printf(SMS_SR_BACKUP_PATH_DIR, imsi, node->to.address,
+				node->to.number_type, node->to.numbering_plan);
+
+	/* If the address does not have relating msg_ids anymore, remove it */
+	rmdir(path);
+	g_free(path);
+
+	return TRUE;
+}
+
+static gboolean sr_assembly_update_fragment_backup(const char *imsi,
+					const struct id_table_node *node,
+					unsigned int msg_id)
+{
+	return sr_assembly_remove_fragment_backup(imsi, node, msg_id) &&
+			sr_assembly_add_fragment_backup(imsi, node, msg_id);
+}
+
 void status_report_assembly_free(struct status_report_assembly *assembly)
 {
 	g_hash_table_destroy(assembly->assembly_table);
@@ -2707,10 +2900,14 @@ gboolean status_report_assembly_report(struct status_report_assembly *assembly,
 		 * not delete the node yet.
 		 */
 		if (pending) {
+			sr_assembly_update_fragment_backup(assembly->imsi, node,
+								*msg_id);
 			*msg_delivered = FALSE;
 			return update_history;
 		} else {
 			*msg_delivered = node->deliverable;
+			sr_assembly_remove_fragment_backup(assembly->imsi, node,
+								*msg_id);
 
 			g_hash_table_iter_remove(&iter);
 
@@ -2747,6 +2944,7 @@ void status_report_assembly_add_fragment(
 	unsigned int *id_table_key;
 
 	id_table = g_hash_table_lookup(assembly->assembly_table, to->address);
+
 	/* Create id_table and node */
 	if (id_table == NULL) {
 		id_table = g_hash_table_new_full(g_int_hash, g_int_equal,
@@ -2775,6 +2973,9 @@ void status_report_assembly_add_fragment(
 
 		g_hash_table_insert(assembly->assembly_table,
 					assembly_table_key, id_table);
+
+		sr_assembly_add_fragment_backup(assembly->imsi, node, msg_id);
+
 		return;
 	}
 
@@ -2793,12 +2994,14 @@ void status_report_assembly_add_fragment(
 		*id_table_key = msg_id;
 		g_hash_table_insert(id_table, id_table_key, node);
 
+		sr_assembly_add_fragment_backup(assembly->imsi, node, msg_id);
 		return;
 	}
 	/* id_table and node both exists */
 	node->mrs[offset] |= bit;
 	node->expiration = expiration;
 	node->sent_mrs++;
+	sr_assembly_update_fragment_backup(assembly->imsi, node, msg_id);
 }
 
 void status_report_assembly_expire(struct status_report_assembly *assembly,
-- 
1.6.0.4



^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 5/5] smsutil: save pending status reports to imsi file
  2010-06-17 13:14         ` [RFC PATCH 5/5] smsutil: save pending status reports to imsi file Pasi Miettinen
@ 2010-06-21 21:07           ` Denis Kenzior
  0 siblings, 0 replies; 10+ messages in thread
From: Denis Kenzior @ 2010-06-21 21:07 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 10143 bytes --]

Hi Pasi,

> ---
>  src/smsutil.c |  205
>  ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files 
changed,
>  204 insertions(+), 1 deletions(-)
> 
> diff --git a/src/smsutil.c b/src/smsutil.c
> index 3db3d28..a30a281 100644
> --- a/src/smsutil.c
> +++ b/src/smsutil.c
> @@ -45,6 +45,10 @@
>  #define SMS_BACKUP_PATH_DIR SMS_BACKUP_PATH "/%s-%i-%i"
>  #define SMS_BACKUP_PATH_FILE SMS_BACKUP_PATH_DIR "/%03i"
> 
> +#define SMS_SR_BACKUP_PATH STORAGEDIR "/%s/sms_sr"
> +#define SMS_SR_BACKUP_PATH_DIR SMS_SR_BACKUP_PATH "/%s-%i-%i"
> +#define SMS_SR_BACKUP_PATH_FILE SMS_SR_BACKUP_PATH_DIR "/%i"
> +
>  #define SMS_ADDR_FMT "%24[0-9A-F]"
> 
>  static GSList *sms_assembly_add_fragment_backup(struct sms_assembly
>  *assembly, @@ -2625,20 +2629,209 @@ void sms_assembly_expire(struct
>  sms_assembly *assembly, time_t before) }
>  }
> 
> +static void sr_assembly_load_backup(GHashTable *assembly_table,
> +					const char *imsi,
> +					const struct dirent *addr_dir)
> +{
> +	char *path;
> +	struct dirent **ids;
> +	struct sms_address *addr;
> +	struct id_table_node *node;
> +	GHashTable *id_table;
> +	int len;
> +	int r;
> +	unsigned char buf[sizeof(node->mrs) + sizeof(node->total_mrs) +
> +			sizeof(node->sent_mrs) + sizeof(node->deliverable)];
> +	char *assembly_table_key;
> +	unsigned int *id_table_key;
> +	struct stat segment_stat;
> +
> +	if (addr_dir->d_type != DT_DIR)
> +		return;
> +
> +	addr = g_new0(struct sms_address, 1);

Why are you mallocing addr here? Seems that this only makes the code more 
complicated.

> +
> +	if (sscanf(addr_dir->d_name, "%[0-9]-%i-%i",
> +			addr->address, (int *) &addr->number_type,
> +			(int *) &addr->numbering_plan) < 3) {

Lets make this consistent with sms assembly, please use SMS_ADDR_FMT.  You're 
also free to use sms_assembly utility functions if that helps you.

> +		g_free(addr);
> +		return;
> +	}
> +
> +	/* Go through different msg_ids. */
> +	path = g_strdup_printf(SMS_SR_BACKUP_PATH "/%s", imsi,
> +							addr_dir->d_name);
> +	len = scandir(path, &ids, NULL, versionsort);
> +
> +	g_free(path);
> +
> +	if (len < 0) {
> +		g_free(addr);
> +		return;
> +	}
> +
> +	id_table = g_hash_table_new_full(g_int_hash, g_int_equal,
> +							g_free, g_free);
> +
> +	assembly_table_key = g_try_malloc(sizeof(addr->address));
> +
> +	if (assembly_table_key == NULL) {
> +		g_free(addr);
> +		return;
> +	}
> +
> +	g_strlcpy(assembly_table_key, addr->address, sizeof(addr->address));
> +	g_hash_table_insert(assembly_table, assembly_table_key, id_table);
> +
> +	while (len--) {
> +		path = g_strdup_printf(SMS_SR_BACKUP_PATH "/%s/%s",
> +				imsi, addr_dir->d_name, ids[len]->d_name);
> +		r = read_file(buf, sizeof(buf), SMS_SR_BACKUP_PATH "/%s/%s",
> +				imsi, addr_dir->d_name, ids[len]->d_name);
> +
> +		if (r < 0) {
> +			g_free(path);
> +			g_free(ids[len]);
> +			continue;
> +		}
> +
> +		r = stat(path, &segment_stat);
> +
> +		if (r != 0) {
> +			g_free(path);
> +			g_free(ids[len]);
> +			continue;
> +		}
> +		/* Gather the data for id_table node */
> +		node = g_new0(struct id_table_node, 1);
> +		node->to = *addr;

Please use memcpy for copying structures.

> +		node->expiration = segment_stat.st_mtime;
> +		memcpy(node->mrs, buf, sizeof(node->mrs));
> +		memcpy(&node->total_mrs, buf + sizeof(node->mrs),
> +						sizeof(node->total_mrs));
> +		memcpy(&node->sent_mrs,
> +			buf + sizeof(node->mrs) + sizeof(node->total_mrs),
> +			sizeof(node->sent_mrs));
> +
> +		memcpy(&node->deliverable, buf + sizeof(node->mrs) +
> +			sizeof(node->total_mrs) + sizeof(node->sent_mrs),
> +			sizeof(node->deliverable));
> +		/* Node ready, create key and add them to the table */
> +		id_table_key = g_new0(unsigned int, 1);
> +		*id_table_key = atoi(ids[len]->d_name);
> +
> +		g_hash_table_insert(id_table, id_table_key, node);
> +
> +		g_free(path);
> +		g_free(ids[len]);
> +	}
> +	g_free(addr);
> +	g_free(ids);
> +}
> +

General comment here is that you're duplicating much of the code that inserts 
the information into the data structure.  You can play the same trick as 
sms_assembly which uses a single function to do the heavy lifting, but takes 
an argument whether to store this info on disk.  Obviously during load you 
don't want to re-save this information on disk.  Then your load function 
becomes much smaller and easier to understand.

>  struct status_report_assembly *status_report_assembly_new(const char
>  *imsi) {
> +	char *path;
> +	int len;
> +	struct dirent **addresses;
>  	struct status_report_assembly *ret =
>  				g_new0(struct status_report_assembly, 1);
> 
>  	ret->assembly_table = g_hash_table_new_full(g_str_hash, g_str_equal,
>  				g_free, (GDestroyNotify)g_hash_table_destroy);
> 
> -	if (imsi)
> +	if (imsi) {
>  		ret->imsi = imsi;
> 
> +		/* Restore state from backup */
> +		path = g_strdup_printf(SMS_SR_BACKUP_PATH, imsi);
> +		len = scandir(path, &addresses, NULL, alphasort);
> +
> +		g_free(path);
> +
> +		if (len < 0)
> +			return ret;

There should be an empty line here

> +		/* Go through different addresses. Each address can relate to
> +		 * 1-n msg_ids. Do not try to load . and .. directories.
> +		 */
> +		g_free(addresses[0]);
> +		g_free(addresses[1]);

This really isn't necessary, the '.' and '..' directories will not pass the 
sscanf test above.

> +
> +		while (2 < len--) {
> +			sr_assembly_load_backup(ret->assembly_table, imsi,
> +								addresses[len]);
> +			g_free(addresses[len]);
> +		}

Empty line here please

> +		g_free(addresses);
> +	}

And another empty line here.  Please put an empty line after each block unless 
it is at the end of the function.
>  	return ret;
>  }
> 
> +static gboolean sr_assembly_add_fragment_backup(const char *imsi,
> +					const struct id_table_node *node,
> +					unsigned int msg_id)

Lets be consistent with how sms assembly works.  E.g. take the main assembly 
object and return void.  Just helps to read the code when it is consistent.

> +{
> +	int len = sizeof(node->mrs) + sizeof(node->total_mrs) +
> +			sizeof(node->sent_mrs) + sizeof(node->deliverable);
> +	unsigned char buf[len];
> +
> +	if (!imsi)
> +		return FALSE;
> +
> +	memcpy(buf, node->mrs, sizeof(node->mrs));
> +
> +	memcpy(buf + sizeof(node->mrs), &node->total_mrs,
> +					sizeof(node->total_mrs));
> +
> +	memcpy(buf + sizeof(node->mrs) + sizeof(node->total_mrs),
> +				&node->sent_mrs, sizeof(node->sent_mrs));
> +
> +	memcpy(buf + sizeof(node->mrs) + sizeof(node->total_mrs) +
> +	sizeof(node->sent_mrs),	&node->deliverable, sizeof(node->deliverable));
> +
> +	/* storagedir/%s/sms_sr/%s-%i-%i/%i */
> +	if (write_file(buf, len, SMS_BACKUP_MODE, SMS_SR_BACKUP_PATH_FILE, imsi,
> +			node->to.address, node->to.number_type,
> +			node->to.numbering_plan, msg_id) != len)
> +		return FALSE;
> +
> +	return TRUE;
> +}
> +
> +static gboolean sr_assembly_remove_fragment_backup(const char *imsi,
> +					const struct id_table_node *node,
> +					unsigned int msg_id)

Same consistency comment here.  Take main assembly object and return void.

> +{
> +	char *path;
> +
> +	if (!imsi)
> +		return FALSE;
> +
> +	path = g_strdup_printf(SMS_SR_BACKUP_PATH_FILE, imsi, node->to.address,
> +				node->to.number_type, node->to.numbering_plan,
> +				msg_id);
> +
> +	unlink(path);
> +	g_free(path);
> +
> +	path = g_strdup_printf(SMS_SR_BACKUP_PATH_DIR, imsi, node->to.address,
> +				node->to.number_type, node->to.numbering_plan);
> +
> +	/* If the address does not have relating msg_ids anymore, remove it */
> +	rmdir(path);
> +	g_free(path);
> +
> +	return TRUE;
> +}
> +
> +static gboolean sr_assembly_update_fragment_backup(const char *imsi,
> +					const struct id_table_node *node,
> +					unsigned int msg_id)
> +{
> +	return sr_assembly_remove_fragment_backup(imsi, node, msg_id) &&
> +			sr_assembly_add_fragment_backup(imsi, node, msg_id);
> +}
> +

Is this function really necessary? write_file overwrites the file, so simply 
using add_fragment_backup seems sufficient.

>  void status_report_assembly_free(struct status_report_assembly *assembly)
>  {
>  	g_hash_table_destroy(assembly->assembly_table);
> @@ -2707,10 +2900,14 @@ gboolean status_report_assembly_report(struct
>  status_report_assembly *assembly, * not delete the node yet.
>  		 */
>  		if (pending) {
> +			sr_assembly_update_fragment_backup(assembly->imsi, node,
> +								*msg_id);

It seems to me fragment is the wrong name here.  What about 
'store_node_backup'?

>  			*msg_delivered = FALSE;
>  			return update_history;
>  		} else {
>  			*msg_delivered = node->deliverable;
> +			sr_assembly_remove_fragment_backup(assembly->imsi, node,
> +								*msg_id);

maybe 'remove_node_backup'

> 
>  			g_hash_table_iter_remove(&iter);
> 
> @@ -2747,6 +2944,7 @@ void status_report_assembly_add_fragment(
>  	unsigned int *id_table_key;
> 
>  	id_table = g_hash_table_lookup(assembly->assembly_table, to->address);
> +
>  	/* Create id_table and node */
>  	if (id_table == NULL) {
>  		id_table = g_hash_table_new_full(g_int_hash, g_int_equal,
> @@ -2775,6 +2973,9 @@ void status_report_assembly_add_fragment(
> 
>  		g_hash_table_insert(assembly->assembly_table,
>  					assembly_table_key, id_table);
> +
> +		sr_assembly_add_fragment_backup(assembly->imsi, node, msg_id);
> +
>  		return;
>  	}
> 
> @@ -2793,12 +2994,14 @@ void status_report_assembly_add_fragment(
>  		*id_table_key = msg_id;
>  		g_hash_table_insert(id_table, id_table_key, node);
> 
> +		sr_assembly_add_fragment_backup(assembly->imsi, node, msg_id);
>  		return;
>  	}
>  	/* id_table and node both exists */
>  	node->mrs[offset] |= bit;
>  	node->expiration = expiration;
>  	node->sent_mrs++;
> +	sr_assembly_update_fragment_backup(assembly->imsi, node, msg_id);
>  }
> 
>  void status_report_assembly_expire(struct status_report_assembly
>  *assembly,
> 

Regards,
-Denis

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 1/5] smsutil: Status report assembly
  2010-06-17 13:14 ` [RFC PATCH 1/5] smsutil: Status report assembly Pasi Miettinen
  2010-06-17 13:14   ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen
@ 2010-06-21 21:09   ` Denis Kenzior
  1 sibling, 0 replies; 10+ messages in thread
From: Denis Kenzior @ 2010-06-21 21:09 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 454 bytes --]

Hi Pasi,

> ---
>  src/smsutil.c |  183
>  +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 
src/smsutil.h | 
>   31 ++++++++++
>  2 files changed, 214 insertions(+), 0 deletions(-)
> 

I pushed patches 1, 3 and 4 in this series upstream and refactored them.  
Mostly just the code-flow was tweaked and made easier to follow.

I'm happy to report status reports are now working for me on T-Mobile USA & 
mbm.

Regards,
-Denis 

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [RFC PATCH 2/5] history: print SMS status
  2010-06-17 13:14   ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen
  2010-06-17 13:14     ` [RFC PATCH 3/5] history: API change for status report notify Pasi Miettinen
@ 2010-06-21 23:35     ` Denis Kenzior
  1 sibling, 0 replies; 10+ messages in thread
From: Denis Kenzior @ 2010-06-21 23:35 UTC (permalink / raw)
  To: ofono

[-- Attachment #1: Type: text/plain, Size: 1203 bytes --]

Hi Pasi,

> ---
>  src/history.c |   11 +++++++++++
>  1 files changed, 11 insertions(+), 0 deletions(-)
> 
> diff --git a/src/history.c b/src/history.c
> index f868ca2..0eef130 100644
> --- a/src/history.c
> +++ b/src/history.c
> @@ -238,6 +238,8 @@ void __ofono_history_sms_send_status(struct ofono_modem
>  *modem, enum ofono_history_sms_status status)
>  {
>  	struct history_sms_foreach_data hfd;
> +	struct tm  *ts;
> +	char buf[80];
> 
>  	hfd.msg_id = msg_id;
>  	hfd.address = NULL;
> @@ -245,6 +247,15 @@ void __ofono_history_sms_send_status(struct
>  ofono_modem *modem, hfd.when = when;
>  	hfd.status = status;
> 
> +	/* Format time, "ddd yyyy-mm-dd hh:mm:ss zzz" */
> +	ts = localtime(&when);
> +	strftime(buf, sizeof(buf), "%a %Y-%m-%d %H:%M:%S %Z", ts);
> +
> +	if (status == OFONO_HISTORY_SMS_STATUS_DELIVERED)
> +		DBG("SMS delivered, msg_id: %i, time: %s", msg_id, buf);
> +	else if (status == OFONO_HISTORY_SMS_STATUS_DELIVER_FAILED)
> +		DBG("SMS undeliverable, msg_id: %i, time: %s", msg_id, buf);
> +

I didn't take this patch because these debugs really don't belong here.  
Instead I put them in plugins/example_history.c.

Regards,
-Denis

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2010-06-21 23:35 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2010-06-17 13:14 SMS status report Pasi Miettinen
2010-06-17 13:14 ` [RFC PATCH 1/5] smsutil: Status report assembly Pasi Miettinen
2010-06-17 13:14   ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen
2010-06-17 13:14     ` [RFC PATCH 3/5] history: API change for status report notify Pasi Miettinen
2010-06-17 13:14       ` [RFC PATCH 4/5] sms: Status " Pasi Miettinen
2010-06-17 13:14         ` [RFC PATCH 5/5] smsutil: save pending status reports to imsi file Pasi Miettinen
2010-06-21 21:07           ` Denis Kenzior
2010-06-21 23:35     ` [RFC PATCH 2/5] history: print SMS status Denis Kenzior
2010-06-21 21:09   ` [RFC PATCH 1/5] smsutil: Status report assembly Denis Kenzior
  -- strict thread matches above, loose matches on Subject: below --
2010-06-16 14:08 Pasi Miettinen
2010-06-16 14:08 ` [RFC PATCH 2/5] history: print SMS status Pasi Miettinen

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox