blob: e685973a09db5ba4f8a3d5d2ec4ef78cdb4bbfa3 [file] [log] [blame]
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +01001/* SMS queue to continously attempt to deliver SMS */
2/*
3 * (C) 2010 by Holger Hans Peter Freyther <zecke@selfish.org>
4 * All Rights Reserved
5 *
6 * This program is free software; you can redistribute it and/or modify
Harald Welte9af6ddf2011-01-01 15:25:50 +01007 * it under the terms of the GNU Affero General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +01009 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
Harald Welte9af6ddf2011-01-01 15:25:50 +010014 * GNU Affero General Public License for more details.
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010015 *
Harald Welte9af6ddf2011-01-01 15:25:50 +010016 * You should have received a copy of the GNU Affero General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010018 *
19 */
20
21/**
22 * The difficulty of such a queue is to send a lot of SMS without
23 * overloading the paging subsystem and the database and other users
24 * of the MSC. To make the best use we would need to know the number
25 * of pending paging requests, then throttle the number of SMS we
26 * want to send and such.
27 * We will start with a very simple SMS Queue and then try to speed
28 * things up by collecting data from other parts of the system.
29 */
30
31#include <openbsc/sms_queue.h>
32#include <openbsc/chan_alloc.h>
33#include <openbsc/db.h>
34#include <openbsc/debug.h>
35#include <openbsc/gsm_data.h>
36#include <openbsc/gsm_04_11.h>
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010037#include <openbsc/gsm_subscriber.h>
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010038#include <openbsc/signal.h>
39
Pablo Neira Ayuso136f4532011-03-22 16:47:59 +010040#include <osmocom/core/talloc.h>
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010041
Holger Hans Peter Freyther81c0e252010-12-25 14:08:00 +010042#include <osmocom/vty/vty.h>
43
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010044/*
45 * One pending SMS that we wait for.
46 */
47struct gsm_sms_pending {
48 struct llist_head entry;
49
50 struct gsm_subscriber *subscr;
51 unsigned long long sms_id;
52 int failed_attempts;
53 int resend;
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +020054
55 int no_detach;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010056};
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010057
58struct gsm_sms_queue {
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +020059 struct osmo_timer_list resend_pending;
60 struct osmo_timer_list push_queue;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010061 struct gsm_network *network;
Holger Hans Peter Freyther7e59c832010-12-25 14:46:54 +010062 int max_fail;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010063 int max_pending;
64 int pending;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010065
66 struct llist_head pending_sms;
67 unsigned long long last_subscr_id;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +010068};
69
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010070static int sms_subscr_cb(unsigned int, unsigned int, void *, void *);
71static int sms_sms_cb(unsigned int, unsigned int, void *, void *);
72
73static struct gsm_sms_pending *sms_find_pending(struct gsm_sms_queue *smsq,
74 struct gsm_sms *sms)
75{
76 struct gsm_sms_pending *pending;
77
78 llist_for_each_entry(pending, &smsq->pending_sms, entry) {
79 if (pending->sms_id == sms->id)
80 return pending;
81 }
82
83 return NULL;
84}
85
86static int sms_is_in_pending(struct gsm_sms_queue *smsq, struct gsm_sms *sms)
87{
88 return sms_find_pending(smsq, sms) != NULL;
89}
90
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +020091static struct gsm_sms_pending *sms_subscriber_find_pending(
92 struct gsm_sms_queue *smsq,
93 struct gsm_subscriber *subscr)
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +010094{
95 struct gsm_sms_pending *pending;
96
97 llist_for_each_entry(pending, &smsq->pending_sms, entry) {
98 if (pending->subscr == subscr)
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +020099 return pending;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100100 }
101
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200102 return NULL;
103}
104
105static int sms_subscriber_is_pending(struct gsm_sms_queue *smsq,
106 struct gsm_subscriber *subscr)
107{
108 return sms_subscriber_find_pending(smsq, subscr) != NULL;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100109}
110
111static struct gsm_sms_pending *sms_pending_from(struct gsm_sms_queue *smsq,
112 struct gsm_sms *sms)
113{
114 struct gsm_sms_pending *pending;
115
116 pending = talloc_zero(smsq, struct gsm_sms_pending);
117 if (!pending)
118 return NULL;
119
120 pending->subscr = subscr_get(sms->receiver);
121 pending->sms_id = sms->id;
122 return pending;
123}
124
125static void sms_pending_free(struct gsm_sms_pending *pending)
126{
127 subscr_put(pending->subscr);
128 llist_del(&pending->entry);
129 talloc_free(pending);
130}
131
132static void sms_pending_resend(struct gsm_sms_pending *pending)
133{
134 struct gsm_sms_queue *smsq;
135 LOGP(DSMS, LOGL_DEBUG,
136 "Scheduling resend of SMS %llu.\n", pending->sms_id);
137
138 pending->resend = 1;
139
140 smsq = pending->subscr->net->sms_queue;
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +0200141 if (osmo_timer_pending(&smsq->resend_pending))
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100142 return;
143
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +0200144 osmo_timer_schedule(&smsq->resend_pending, 1, 0);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100145}
146
147static void sms_pending_failed(struct gsm_sms_pending *pending, int paging_error)
148{
149 struct gsm_sms_queue *smsq;
150
151 LOGP(DSMS, LOGL_NOTICE, "Sending SMS %llu failed %d times.\n",
152 pending->sms_id, pending->failed_attempts);
153
154 smsq = pending->subscr->net->sms_queue;
Holger Hans Peter Freyther7e59c832010-12-25 14:46:54 +0100155 if (++pending->failed_attempts < smsq->max_fail)
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100156 return sms_pending_resend(pending);
157
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200158 if (paging_error && !pending->no_detach) {
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100159 LOGP(DSMS, LOGL_NOTICE,
160 "Subscriber %llu is not reachable. Setting LAC=0.\n", pending->subscr->id);
161 pending->subscr->lac = GSM_LAC_RESERVED_DETACHED;
162 db_sync_subscriber(pending->subscr);
Holger Hans Peter Freythera3a659b2010-12-25 16:53:24 +0100163
164 /* Workaround a failing sync */
165 db_subscriber_update(pending->subscr);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100166 }
167
168 sms_pending_free(pending);
169 smsq->pending -= 1;
170 sms_queue_trigger(smsq);
171}
172
173/*
174 * Resend all SMS that are scheduled for a resend. This is done to
175 * avoid an immediate failure.
176 */
177static void sms_resend_pending(void *_data)
178{
179 struct gsm_sms_pending *pending, *tmp;
180 struct gsm_sms_queue *smsq = _data;
181
182 llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
183 struct gsm_sms *sms;
184 if (!pending->resend)
185 continue;
186
187 sms = db_sms_get(smsq->network, pending->sms_id);
188
189 /* the sms is gone? Move to the next */
190 if (!sms) {
191 sms_pending_free(pending);
192 smsq->pending -= 1;
193 sms_queue_trigger(smsq);
194 } else {
195 pending->resend = 0;
196 gsm411_send_sms_subscr(sms->receiver, sms);
197 }
198 }
199}
200
Holger Hans Peter Freytherf7e23892010-12-25 17:45:23 +0100201static struct gsm_sms *take_next_sms(struct gsm_sms_queue *smsq)
202{
203 struct gsm_sms *sms;
204
205 sms = db_sms_get_unsent_by_subscr(smsq->network, smsq->last_subscr_id, 10);
206 if (sms) {
207 smsq->last_subscr_id = sms->receiver->id + 1;
208 return sms;
209 }
210
211 /* need to wrap around */
212 smsq->last_subscr_id = 0;
213 sms = db_sms_get_unsent_by_subscr(smsq->network,
214 smsq->last_subscr_id, 10);
215 if (sms)
216 smsq->last_subscr_id = sms->receiver->id + 1;
217 return sms;
218}
219
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100220/**
221 * I will submit up to max_pending - pending SMS to the
222 * subsystem.
223 */
224static void sms_submit_pending(void *_data)
225{
226 struct gsm_sms_queue *smsq = _data;
227 int attempts = smsq->max_pending - smsq->pending;
Holger Hans Peter Freyther20384572010-12-25 19:28:44 +0100228 int initialized = 0;
229 unsigned long long first_sub = 0;
Holger Hans Peter Freyther5479fc82010-12-25 19:32:12 +0100230 int attempted = 0, rounds = 0;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100231
232 LOGP(DSMS, LOGL_NOTICE, "Attempting to send %d SMS\n", attempts);
233
Holger Hans Peter Freyther20384572010-12-25 19:28:44 +0100234 do {
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100235 struct gsm_sms_pending *pending;
236 struct gsm_sms *sms;
237
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100238
Holger Hans Peter Freytherf7e23892010-12-25 17:45:23 +0100239 sms = take_next_sms(smsq);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100240 if (!sms)
241 break;
242
Holger Hans Peter Freyther5479fc82010-12-25 19:32:12 +0100243 rounds += 1;
244
Holger Hans Peter Freyther20384572010-12-25 19:28:44 +0100245 /*
246 * This code needs to detect a loop. It assumes that no SMS
247 * will vanish during the time this is executed. We will remember
248 * the id of the first GSM subscriber we see and then will
249 * compare this. The Database code should make sure that we will
250 * see all other subscribers first before seeing this one again.
251 *
252 * It is always scary to have an infinite loop like this.
253 */
254 if (!initialized) {
255 first_sub = sms->receiver->id;
256 initialized = 1;
257 } else if (first_sub == sms->receiver->id) {
258 sms_free(sms);
259 break;
260 }
261
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100262 /* no need to send a pending sms */
263 if (sms_is_in_pending(smsq, sms)) {
264 LOGP(DSMS, LOGL_DEBUG,
Holger Hans Peter Freytherdc53af62010-12-27 20:12:25 +0100265 "SMSqueue with pending sms: %llu. Skipping\n", sms->id);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100266 sms_free(sms);
267 continue;
268 }
269
270 /* no need to send a SMS with the same receiver */
271 if (sms_subscriber_is_pending(smsq, sms->receiver)) {
272 LOGP(DSMS, LOGL_DEBUG,
273 "SMSqueue with pending sub: %llu. Skipping\n", sms->receiver->id);
274 sms_free(sms);
275 continue;
276 }
277
278 pending = sms_pending_from(smsq, sms);
279 if (!pending) {
280 LOGP(DSMS, LOGL_ERROR,
281 "Failed to create pending SMS entry.\n");
282 sms_free(sms);
283 continue;
284 }
285
Holger Hans Peter Freyther20384572010-12-25 19:28:44 +0100286 attempted += 1;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100287 smsq->pending += 1;
Holger Hans Peter Freyther701076e2010-12-28 14:25:25 +0100288 llist_add_tail(&pending->entry, &smsq->pending_sms);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100289 gsm411_send_sms_subscr(sms->receiver, sms);
Holger Hans Peter Freyther5479fc82010-12-25 19:32:12 +0100290 } while (attempted < attempts && rounds < 1000);
Holger Hans Peter Freyther20384572010-12-25 19:28:44 +0100291
Holger Hans Peter Freyther5479fc82010-12-25 19:32:12 +0100292 LOGP(DSMS, LOGL_DEBUG, "SMSqueue added %d messages in %d rounds\n", attempted, rounds);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100293}
294
295/*
296 * Kick off the queue again.
297 */
298int sms_queue_trigger(struct gsm_sms_queue *smsq)
299{
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +0200300 if (osmo_timer_pending(&smsq->push_queue))
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100301 return 0;
302
Pablo Neira Ayusobf540cb2011-05-06 12:11:06 +0200303 osmo_timer_schedule(&smsq->push_queue, 1, 0);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100304 return 0;
305}
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100306
307int sms_queue_start(struct gsm_network *network, int max_pending)
308{
309 struct gsm_sms_queue *sms = talloc_zero(network, struct gsm_sms_queue);
310 if (!sms) {
311 LOGP(DMSC, LOGL_ERROR, "Failed to create the SMS queue.\n");
312 return -1;
313 }
314
Pablo Neira Ayusobbc5b992011-05-06 12:12:31 +0200315 osmo_signal_register_handler(SS_SUBSCR, sms_subscr_cb, network);
316 osmo_signal_register_handler(SS_SMS, sms_sms_cb, network);
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100317
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100318 network->sms_queue = sms;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100319 INIT_LLIST_HEAD(&sms->pending_sms);
Holger Hans Peter Freythera37e3bc2010-12-25 17:43:03 +0100320 sms->max_fail = 1;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100321 sms->network = network;
322 sms->max_pending = max_pending;
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100323 sms->push_queue.data = sms;
324 sms->push_queue.cb = sms_submit_pending;
325 sms->resend_pending.data = sms;
326 sms->resend_pending.cb = sms_resend_pending;
327
328 sms_submit_pending(sms);
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100329
330 return 0;
331}
332
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200333static int sub_ready_for_sm(struct gsm_network *net, struct gsm_subscriber *subscr)
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100334{
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100335 struct gsm_sms *sms;
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200336 struct gsm_sms_pending *pending;
337 struct gsm_subscriber_connection *conn;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100338
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200339 /*
340 * The code used to be very clever and tried to submit
341 * a SMS during the Location Updating Request. This has
342 * two issues:
343 * 1.) The Phone might not be ready yet, e.g. the C155
344 * will not respond to the Submit when it is booting.
345 * 2.) The queue is already trying to submit SMS to the
346 * user and by not responding to the paging request
347 * we will set the LAC back to 0. We would have to
348 * stop the paging and move things over.
349 *
350 * We need to be careful in what we try here.
351 */
352
353 /* check if we have pending requests */
354 pending = sms_subscriber_find_pending(net->sms_queue, subscr);
355 if (pending) {
356 LOGP(DMSC, LOGL_NOTICE,
357 "Pending paging while subscriber %llu attached.\n",
358 subscr->id);
359 pending->no_detach = 1;
360 return 0;
361 }
362
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100363 conn = connection_for_subscr(subscr);
364 if (!conn)
365 return -1;
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200366
367 /* Now try to deliver any pending SMS to this sub */
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100368 sms = db_sms_get_unsent_for_subscr(subscr);
369 if (!sms)
370 return -1;
371 gsm411_send_sms(conn, sms);
372 return 0;
373}
374
375static int sms_subscr_cb(unsigned int subsys, unsigned int signal,
376 void *handler_data, void *signal_data)
377{
378 struct gsm_subscriber *subscr = signal_data;
379
380 if (signal != S_SUBSCR_ATTACHED)
381 return 0;
382
383 /* this is readyForSM */
Holger Hans Peter Freyther074b2b22011-07-25 00:13:06 +0200384 return sub_ready_for_sm(handler_data, subscr);
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100385}
386
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100387static int sms_sms_cb(unsigned int subsys, unsigned int signal,
388 void *handler_data, void *signal_data)
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100389{
Holger Hans Peter Freyther17164062010-12-24 21:39:55 +0100390 struct gsm_network *network = handler_data;
391 struct sms_signal_data *sig_sms = signal_data;
392 struct gsm_sms_pending *pending;
393
394 /* We got a new SMS and maybe should launch the queue again. */
395 if (signal == S_SMS_SUBMITTED || signal == S_SMS_SMMA) {
396 sms_queue_trigger(network->sms_queue);
397 return 0;
398 }
399
400 if (!sig_sms->sms)
401 return -1;
402
403
404 /*
405 * Find the entry of our queue. The SMS subsystem will submit
406 * sms that are not in our control as we just have a channel
407 * open anyway.
408 */
409 pending = sms_find_pending(network->sms_queue, sig_sms->sms);
410 if (!pending)
411 return 0;
412
413 switch (signal) {
414 case S_SMS_DELIVERED:
415 /*
416 * Create place for a new SMS but keep the pending data
417 * so we will not attempt to send the SMS for this subscriber
418 * as we still have an open channel and will attempt to submit
419 * SMS to it anyway.
420 */
421 network->sms_queue->pending -= 1;
422 sms_submit_pending(network->sms_queue);
423 sms_pending_free(pending);
424 break;
425 case S_SMS_MEM_EXCEEDED:
426 network->sms_queue->pending -= 1;
427 sms_pending_free(pending);
428 sms_queue_trigger(network->sms_queue);
429 break;
430 case S_SMS_UNKNOWN_ERROR:
431 /*
432 * There can be many reasons for this failure. E.g. the paging
433 * timed out, the subscriber was not paged at all, or there was
434 * a protocol error. The current strategy is to try sending the
435 * next SMS for busy/oom and to retransmit when we have paged.
436 *
437 * When the paging expires three times we will disable the
438 * subscriber. If we have some kind of other transmit error we
439 * should flag the SMS as bad.
440 */
441 switch (sig_sms->paging_result) {
442 case 0:
443 /* BAD SMS? */
444 db_sms_inc_deliver_attempts(sig_sms->sms);
445 sms_pending_failed(pending, 0);
446 break;
447 case GSM_PAGING_EXPIRED:
448 sms_pending_failed(pending, 1);
449 break;
450
451 case GSM_PAGING_OOM:
452 case GSM_PAGING_BUSY:
453 network->sms_queue->pending -= 1;
454 sms_pending_free(pending);
455 sms_queue_trigger(network->sms_queue);
456 break;
457 default:
458 LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
459 sig_sms->paging_result);
460 }
461 break;
462 default:
463 LOGP(DSMS, LOGL_ERROR, "Unhandled result: %d\n",
464 sig_sms->paging_result);
465 }
466
467 return 0;
Holger Hans Peter Freyther11b28f92010-12-24 13:48:27 +0100468}
Holger Hans Peter Freyther81c0e252010-12-25 14:08:00 +0100469
470/* VTY helper functions */
471int sms_queue_stats(struct gsm_sms_queue *smsq, struct vty *vty)
472{
473 struct gsm_sms_pending *pending;
474
475 vty_out(vty, "SMSqueue with max_pending: %d pending: %d%s",
476 smsq->max_pending, smsq->pending, VTY_NEWLINE);
477
478 llist_for_each_entry(pending, &smsq->pending_sms, entry)
Holger Hans Peter Freyther583e9ae2010-12-27 20:19:48 +0100479 vty_out(vty, " SMS Pending for Subscriber: %llu SMS: %llu Failed: %d.%s",
480 pending->subscr->id, pending->sms_id,
481 pending->failed_attempts, VTY_NEWLINE);
Holger Hans Peter Freyther81c0e252010-12-25 14:08:00 +0100482 return 0;
483}
Holger Hans Peter Freyther3c6f6c22010-12-25 14:25:12 +0100484
485int sms_queue_set_max_pending(struct gsm_sms_queue *smsq, int max_pending)
486{
487 LOGP(DSMS, LOGL_NOTICE, "SMSqueue old max: %d new: %d\n",
488 smsq->max_pending, max_pending);
489 smsq->max_pending = max_pending;
490 return 0;
491}
Holger Hans Peter Freyther4dcc5e52010-12-25 14:38:30 +0100492
Holger Hans Peter Freyther994dcbb2010-12-25 14:50:50 +0100493int sms_queue_set_max_failure(struct gsm_sms_queue *smsq, int max_fail)
494{
495 LOGP(DSMS, LOGL_NOTICE, "SMSqueue max failure old: %d new: %d\n",
496 smsq->max_fail, max_fail);
497 smsq->max_fail = max_fail;
498 return 0;
499}
500
Holger Hans Peter Freyther4dcc5e52010-12-25 14:38:30 +0100501int sms_queue_clear(struct gsm_sms_queue *smsq)
502{
503 struct gsm_sms_pending *pending, *tmp;
504
505 llist_for_each_entry_safe(pending, tmp, &smsq->pending_sms, entry) {
506 LOGP(DSMS, LOGL_NOTICE,
507 "SMSqueue clearing for sub %llu\n", pending->subscr->id);
508 sms_pending_free(pending);
509 }
510
Holger Hans Peter Freyther96e9f082010-12-28 14:09:07 +0100511 smsq->pending = 0;
Holger Hans Peter Freyther4dcc5e52010-12-25 14:38:30 +0100512 return 0;
513}