Main Page | Alphabetical List | Data Structures | Directories | File List | Data Fields | Globals

bb_store_file.c

Go to the documentation of this file.
00001 /* ====================================================================
00002  * The Kannel Software License, Version 1.0 
00003  * 
00004  * Copyright (c) 2001-2008 Kannel Group  
00005  * Copyright (c) 1998-2001 WapIT Ltd.   
00006  * All rights reserved. 
00007  * 
00008  * Redistribution and use in source and binary forms, with or without 
00009  * modification, are permitted provided that the following conditions 
00010  * are met: 
00011  * 
00012  * 1. Redistributions of source code must retain the above copyright 
00013  *    notice, this list of conditions and the following disclaimer. 
00014  * 
00015  * 2. Redistributions in binary form must reproduce the above copyright 
00016  *    notice, this list of conditions and the following disclaimer in 
00017  *    the documentation and/or other materials provided with the 
00018  *    distribution. 
00019  * 
00020  * 3. The end-user documentation included with the redistribution, 
00021  *    if any, must include the following acknowledgment: 
00022  *       "This product includes software developed by the 
00023  *        Kannel Group (http://www.kannel.org/)." 
00024  *    Alternately, this acknowledgment may appear in the software itself, 
00025  *    if and wherever such third-party acknowledgments normally appear. 
00026  * 
00027  * 4. The names "Kannel" and "Kannel Group" must not be used to 
00028  *    endorse or promote products derived from this software without 
00029  *    prior written permission. For written permission, please  
00030  *    contact org@kannel.org. 
00031  * 
00032  * 5. Products derived from this software may not be called "Kannel", 
00033  *    nor may "Kannel" appear in their name, without prior written 
00034  *    permission of the Kannel Group. 
00035  * 
00036  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 
00037  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 
00038  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
00039  * DISCLAIMED.  IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS 
00040  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,  
00041  * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
00042  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR  
00043  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,  
00044  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE  
00045  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,  
00046  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
00047  * ==================================================================== 
00048  * 
00049  * This software consists of voluntary contributions made by many 
00050  * individuals on behalf of the Kannel Group.  For more information on  
00051  * the Kannel Group, please see <http://www.kannel.org/>. 
00052  * 
00053  * Portions of this software are based upon software originally written at  
00054  * WapIT Ltd., Helsinki, Finland for the Kannel project.  
00055  */ 
00056 
00057 /*
00058  * bb_store.c : bearerbox box SMS storage/retrieval module
00059  *
00060  * Kalle Marjola 2001 for project Kannel
00061  *
00062  * Updated Oct 2004
00063  *
00064  * New features:
00065  *  - uses dict to save messages, for faster retrieval
00066  *  - acks are no longer saved (to memory), they simply delete
00067  *    messages from dict
00068  *  - better choice when dump done; configurable frequency
00069  */
00070 
00071 #include <errno.h>
00072 #include <stdlib.h>
00073 #include <stdio.h>
00074 #include <time.h>
00075 #include <string.h>
00076 #include <sys/time.h>
00077 #include <sys/types.h>
00078 #include <sys/socket.h>
00079 #include <unistd.h>
00080 #include <signal.h>
00081 
00082 #include "gwlib/gwlib.h"
00083 #include "msg.h"
00084 #include "bearerbox.h"
00085 #include "sms.h"
00086 
00087 static FILE *file = NULL;
00088 static Octstr *filename = NULL;
00089 static Octstr *newfile = NULL;
00090 static Octstr *bakfile = NULL;
00091 static Mutex *file_mutex = NULL;
00092 static long cleanup_thread = -1;
00093 static long dump_frequency = 0;
00094 
00095 static Dict *sms_dict = NULL;
00096 
00097 static int active = 1;
00098 static time_t last_dict_mod = 0;
00099 static List *loaded;
00100 
00101 
00102 static void write_msg(Msg *msg)
00103 {
00104     Octstr *pack;
00105     unsigned char buf[4];
00106     
00107     pack = store_msg_pack(msg);
00108     encode_network_long(buf, octstr_len(pack));
00109     octstr_insert_data(pack, 0, (char*)buf, 4);
00110 
00111     octstr_print(file, pack);
00112     fflush(file);
00113 
00114     octstr_destroy(pack);
00115 }
00116 
00117 
00118 static int read_msg(Msg **msg, Octstr *os, long *off)
00119 {
00120     unsigned char buf[4];
00121     long i;
00122     Octstr *pack;
00123 
00124     gw_assert(*off >= 0);
00125     if (*off + 4 > octstr_len(os)) {
00126         error(0, "Packet too short while unpacking Msg.");
00127         return -1;
00128     }
00129 
00130     octstr_get_many_chars((char*)buf, os, *off, 4);
00131     i = decode_network_long(buf);
00132     *off  +=  4;
00133     
00134     pack = octstr_copy(os, *off, i);
00135     *off += octstr_len(pack);
00136     *msg = store_msg_unpack(pack);
00137     octstr_destroy(pack);
00138     
00139     if (!*msg)
00140         return -1;
00141     
00142     return 0;
00143 }
00144 
00145 
00146 static int open_file(Octstr *name)
00147 {
00148     file = fopen(octstr_get_cstr(name), "w");
00149     if (file == NULL) {
00150         error(errno, "Failed to open '%s' for writing, cannot create store-file",
00151           octstr_get_cstr(name));
00152         return -1;
00153     }
00154     return 0;
00155 }
00156 
00157 
00158 static int rename_store(void)
00159 {
00160     if (rename(octstr_get_cstr(filename), octstr_get_cstr(bakfile)) == -1) {
00161         if (errno != ENOENT) {
00162             error(errno, "Failed to rename old store '%s' as '%s'",
00163             octstr_get_cstr(filename), octstr_get_cstr(bakfile));
00164             return -1;
00165         }
00166     }
00167     if (rename(octstr_get_cstr(newfile), octstr_get_cstr(filename)) == -1) {
00168         error(errno, "Failed to rename new store '%s' as '%s'",
00169               octstr_get_cstr(newfile), octstr_get_cstr(filename));
00170         return -1;
00171     }
00172     return 0;
00173 }
00174 
00175 
00176 static int do_dump(void)
00177 {
00178     Octstr *key;
00179     Msg *msg;
00180     List *sms_list;
00181     long l;
00182 
00183     if (filename == NULL)
00184         return 0;
00185 
00186     /* create a new store-file and save all non-acknowledged
00187      * messages into it
00188      */
00189     if (open_file(newfile)==-1)
00190         return -1;
00191 
00192     sms_list = dict_keys(sms_dict);
00193     for (l=0; l < gwlist_len(sms_list); l++) {
00194         key = gwlist_get(sms_list, l);
00195         msg = dict_get(sms_dict, key);
00196         if (msg != NULL)
00197             write_msg(msg);
00198     }
00199     fflush(file);
00200     gwlist_destroy(sms_list, octstr_destroy_item);
00201 
00202     /* rename old storefile as .bak, and then new as regular file
00203      * without .new ending */
00204 
00205     return rename_store();
00206 }
00207 
00208 
00209 /*
00210  * thread to write current store to file now and then, to prevent
00211  * it from becoming far too big (slows startup)
00212  */
00213 static void store_dumper(void *arg)
00214 {
00215     time_t now;
00216     int busy = 0;
00217 
00218     while (active) {
00219         now = time(NULL);
00220         /*
00221          * write store to file up to each N. second, providing
00222          * that something happened or if we are constantly busy.
00223          */
00224         if (now - last_dict_mod > dump_frequency || busy) {
00225             store_dump();
00226             /* 
00227              * make sure that no new dump is done for a while unless
00228              * something happens. This moves the trigger in the future
00229              * and allows the if statement to pass if nothing happened
00230              * in the mean time while sleeping. The busy flag is needed
00231              * to garantee we do dump in case we are constantly busy
00232              * and hence the difference between now and last dict
00233              * operation is less then dump frequency, otherwise we
00234              * would never dump. This is for constant high load.
00235              */
00236             last_dict_mod = time(NULL) + 3600*24;
00237             busy = 0;
00238         } else {
00239             busy = (now - last_dict_mod) > 0;
00240         }
00241         gwthread_sleep(dump_frequency);
00242     }
00243     store_dump();
00244     if (file != NULL)
00245        fclose(file);
00246     octstr_destroy(filename);
00247     octstr_destroy(newfile);
00248     octstr_destroy(bakfile);
00249     mutex_destroy(file_mutex);
00250 
00251     dict_destroy(sms_dict);
00252     /* set all vars to NULL */
00253     filename = newfile = bakfile = NULL;
00254     file_mutex = NULL;
00255     sms_dict = NULL;
00256 }
00257 
00258 
00259 /*------------------------------------------------------*/
00260 
00261 static Octstr *store_file_status(int status_type)
00262 {
00263     char *frmt;
00264     Octstr *ret, *key;
00265     unsigned long l;
00266     struct tm tm;
00267     Msg *msg;
00268     List *keys;
00269     char id[UUID_STR_LEN + 1];
00270 
00271     ret = octstr_create("");
00272 
00273     /* set the type based header */
00274     if (status_type == BBSTATUS_HTML) {
00275         octstr_append_cstr(ret, "<table border=1>\n"
00276             "<tr><td>SMS ID</td><td>Type</td><td>Time</td><td>Sender</td><td>Receiver</td>"
00277             "<td>SMSC ID</td><td>BOX ID</td><td>UDH</td><td>Message</td>"
00278             "</tr>\n");
00279     } else if (status_type == BBSTATUS_TEXT) {
00280         octstr_append_cstr(ret, "[SMS ID] [Type] [Time] [Sender] [Receiver] [SMSC ID] [BOX ID] [UDH] [Message]\n");
00281     }
00282    
00283     /* if there is no store-file, then don't loop in sms_store */
00284     if (filename == NULL)
00285         goto finish;
00286 
00287     keys = dict_keys(sms_dict);
00288 
00289     for (l = 0; l < gwlist_len(keys); l++) {
00290         key = gwlist_get(keys, l);
00291         msg = dict_get(sms_dict, key);
00292         if (msg == NULL)
00293             continue;
00294 
00295         if (msg_type(msg) == sms) {
00296 
00297             if (status_type == BBSTATUS_HTML) {
00298                 frmt = "<tr><td>%s</td><td>%s</td>"
00299                        "<td>%04d-%02d-%02d %02d:%02d:%02d</td>"
00300                        "<td>%s</td><td>%s</td><td>%s</td>"
00301                        "<td>%s</td><td>%s</td><td>%s</td></tr>\n";
00302             } else if (status_type == BBSTATUS_XML) {
00303                 frmt = "<message>\n\t<id>%s</id>\n\t<type>%s</type>\n\t"
00304                        "<time>%04d-%02d-%02d %02d:%02d:%02d</time>\n\t"
00305                        "<sender>%s</sender>\n\t"
00306                        "<receiver>%s</receiver>\n\t<smsc-id>%s</smsc-id>\n\t"
00307                        "<box-id>%s</box-id>\n\t"
00308                        "<udh-data>%s</udh-data>\n\t<msg-data>%s</msg-data>\n\t"
00309                        "</message>\n";
00310             } else {
00311                 frmt = "[%s] [%s] [%04d-%02d-%02d %02d:%02d:%02d] [%s] [%s] [%s] [%s] [%s] [%s]\n";
00312             }
00313 
00314             /* transform the time value */
00315 #if LOG_TIMESTAMP_LOCALTIME
00316             tm = gw_localtime(msg->sms.time);
00317 #else
00318             tm = gw_gmtime(msg->sms.time);
00319 #endif
00320             if (msg->sms.udhdata)
00321                 octstr_binary_to_hex(msg->sms.udhdata, 1);
00322             if (msg->sms.msgdata &&
00323                 (msg->sms.coding == DC_8BIT || msg->sms.coding == DC_UCS2 ||
00324                 (msg->sms.coding == DC_UNDEF && msg->sms.udhdata)))
00325                 octstr_binary_to_hex(msg->sms.msgdata, 1);
00326 
00327             uuid_unparse(msg->sms.id, id);
00328 
00329             octstr_format_append(ret, frmt, id,
00330                 (msg->sms.sms_type == mo ? "MO" :
00331                  msg->sms.sms_type == mt_push ? "MT-PUSH" :
00332                  msg->sms.sms_type == mt_reply ? "MT-REPLY" :
00333                  msg->sms.sms_type == report_mo ? "DLR-MO" :
00334                  msg->sms.sms_type == report_mt ? "DLR-MT" : ""),
00335                  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
00336                  tm.tm_hour, tm.tm_min, tm.tm_sec,
00337                 (msg->sms.sender ? octstr_get_cstr(msg->sms.sender) : ""),
00338                 (msg->sms.receiver ? octstr_get_cstr(msg->sms.receiver) : ""),
00339                 (msg->sms.smsc_id ? octstr_get_cstr(msg->sms.smsc_id) : ""),
00340                 (msg->sms.boxc_id ? octstr_get_cstr(msg->sms.boxc_id) : ""),
00341                 (msg->sms.udhdata ? octstr_get_cstr(msg->sms.udhdata) : ""),
00342                 (msg->sms.msgdata ? octstr_get_cstr(msg->sms.msgdata) : ""));
00343 
00344             if (msg->sms.udhdata)
00345                 octstr_hex_to_binary(msg->sms.udhdata);
00346             if (msg->sms.msgdata &&
00347                 (msg->sms.coding == DC_8BIT || msg->sms.coding == DC_UCS2 ||
00348                 (msg->sms.coding == DC_UNDEF && msg->sms.udhdata)))
00349                 octstr_hex_to_binary(msg->sms.msgdata);
00350         }
00351     }
00352     gwlist_destroy(keys, octstr_destroy_item);
00353 
00354 finish:
00355     /* set the type based footer */
00356     if (status_type == BBSTATUS_HTML) {
00357         octstr_append_cstr(ret,"</table>");
00358     }
00359 
00360     return ret;
00361 }
00362 
00363 
00364 static long store_file_messages(void)
00365 {
00366     return (sms_dict ? dict_key_count(sms_dict) : -1);
00367 }
00368 
00369 
00370 static int store_to_dict(Msg *msg)
00371 {
00372     Msg *copy;
00373     Octstr *uuid_os;
00374     char id[UUID_STR_LEN + 1];
00375     
00376     /* always set msg id and timestamp */
00377     if (msg_type(msg) == sms && uuid_is_null(msg->sms.id))
00378         uuid_generate(msg->sms.id);
00379 
00380     if (msg_type(msg) == sms && msg->sms.time == MSG_PARAM_UNDEFINED)
00381         time(&msg->sms.time);
00382 
00383     if (msg_type(msg) == sms) {
00384         copy = msg_duplicate(msg);
00385         
00386         uuid_unparse(copy->sms.id, id);
00387         uuid_os = octstr_create(id);
00388         
00389         dict_put(sms_dict, uuid_os, copy);
00390         octstr_destroy(uuid_os);
00391         last_dict_mod = time(NULL);
00392     } else if (msg_type(msg) == ack) {
00393         uuid_unparse(msg->ack.id, id);
00394         uuid_os = octstr_create(id);
00395         copy = dict_remove(sms_dict, uuid_os);
00396         octstr_destroy(uuid_os);
00397         if (copy == NULL) {
00398             warning(0, "bb_store: get ACK of message not found "
00399                    "from store, strange?");
00400         } else {
00401             msg_destroy(copy);
00402             last_dict_mod = time(NULL);
00403         }
00404     } else
00405         return -1;
00406     return 0;
00407 }
00408     
00409 static int store_file_save(Msg *msg)
00410 {
00411     if (filename == NULL)
00412         return 0;
00413 
00414     /* block here until store not loaded */
00415     gwlist_consume(loaded);
00416 
00417     /* lock file_mutex in order to have dict and file in sync */
00418     mutex_lock(file_mutex);
00419     if (store_to_dict(msg) == -1) {
00420         mutex_unlock(file_mutex);
00421         return -1;
00422     }
00423     
00424     /* write to file, too */
00425     write_msg(msg);
00426     fflush(file);
00427     mutex_unlock(file_mutex);
00428 
00429     return 0;
00430 }
00431 
00432 
00433 static int store_file_save_ack(Msg *msg, ack_status_t status)
00434 {
00435     Msg *mack;
00436     int ret;
00437 
00438     /* only sms are handled */
00439     if (!msg || msg_type(msg) != sms)
00440         return -1;
00441 
00442     if (filename == NULL)
00443         return 0;
00444 
00445     mack = msg_create(ack);
00446     if (!mack)
00447         return -1;
00448 
00449     mack->ack.time = msg->sms.time;
00450     uuid_copy(mack->ack.id, msg->sms.id);
00451     mack->ack.nack = status;
00452 
00453     ret = store_save(mack);
00454     msg_destroy(mack);
00455 
00456     return ret;
00457 }
00458 
00459 
00460 static int store_file_load(void(*receive_msg)(Msg*))
00461 {
00462     List *keys;
00463     Octstr *store_file, *key;
00464     Msg *msg;
00465     int retval, msgs;
00466     long end, pos;
00467 
00468     if (filename == NULL)
00469         return 0;
00470 
00471     mutex_lock(file_mutex);
00472     if (file != NULL) {
00473         fclose(file);
00474         file = NULL;
00475     }
00476 
00477     store_file = octstr_read_file(octstr_get_cstr(filename));
00478     if (store_file != NULL)
00479         info(0, "Loading store file `%s'", octstr_get_cstr(filename));
00480     else {
00481         store_file = octstr_read_file(octstr_get_cstr(newfile));
00482         if (store_file != NULL)
00483             info(0, "Loading store file `%s'", octstr_get_cstr(newfile));
00484         else {
00485             store_file = octstr_read_file(octstr_get_cstr(bakfile));
00486             if (store_file != NULL)
00487                    info(0, "Loading store file `%s'", octstr_get_cstr(bakfile));
00488             else {
00489                 info(0, "Cannot open any store file, starting a new one");
00490                 retval = open_file(filename);
00491                 goto end;
00492             }
00493         }
00494     }
00495 
00496     info(0, "Store-file size %ld, starting to unpack%s", octstr_len(store_file),
00497         octstr_len(store_file) > 10000 ? " (may take awhile)" : "");
00498 
00499 
00500     pos = 0;
00501     msgs = 0;
00502     end = octstr_len(store_file);
00503     
00504     while (pos < end) {
00505         if (read_msg(&msg, store_file, &pos) == -1) {
00506             error(0, "Garbage at store-file, skipped.");
00507             continue;
00508         }
00509         if (msg_type(msg) == sms) {
00510             store_to_dict(msg);
00511             msgs++;
00512         } else if (msg_type(msg) == ack) {
00513             store_to_dict(msg);
00514         } else {
00515             warning(0, "Strange message in store-file, discarded, "
00516                 "dump follows:");
00517             msg_dump(msg, 0);
00518         }
00519         msg_destroy(msg);
00520     }
00521     octstr_destroy(store_file);
00522 
00523     info(0, "Retrieved %d messages, non-acknowledged messages: %ld",
00524         msgs, dict_key_count(sms_dict));
00525 
00526     /* now create a new sms_store out of messages left */
00527 
00528     keys = dict_keys(sms_dict);
00529     while ((key = gwlist_extract_first(keys)) != NULL) {
00530         msg = dict_remove(sms_dict, key);
00531         if (store_to_dict(msg) != -1) {
00532             receive_msg(msg);
00533         } else {
00534             error(0, "Found unknown message type in store file.");
00535             msg_dump(msg, 0);
00536             msg_destroy(msg);
00537         }
00538         octstr_destroy(key);
00539     }
00540     gwlist_destroy(keys, octstr_destroy_item);
00541 
00542     /* Finally, generate new store file out of left messages */
00543     retval = do_dump();
00544 
00545 end:
00546     mutex_unlock(file_mutex);
00547 
00548     /* allow using of store */
00549     gwlist_remove_producer(loaded);
00550 
00551     /* start dumper thread */
00552     if ((cleanup_thread = gwthread_create(store_dumper, NULL))==-1)
00553         panic(0, "Failed to create a cleanup thread!");
00554 
00555     return retval;
00556 }
00557 
00558 
00559 static int store_file_dump(void)
00560 {
00561     int retval;
00562 
00563     debug("bb.store", 0, "Dumping %ld messages to store",
00564       dict_key_count(sms_dict));
00565     mutex_lock(file_mutex);
00566     if (file != NULL) {
00567         fclose(file);
00568         file = NULL;
00569     }
00570     retval = do_dump();
00571     mutex_unlock(file_mutex);
00572 
00573     return retval;
00574 }
00575 
00576 
00577 static void store_file_shutdown(void)
00578 {
00579     if (filename == NULL)
00580         return;
00581 
00582     active = 0;
00583     gwthread_wakeup(cleanup_thread);
00584     /* wait for cleanup thread */
00585     if (cleanup_thread != -1)
00586         gwthread_join(cleanup_thread);
00587 
00588     gwlist_destroy(loaded, NULL);
00589 }
00590 
00591 
00592 int store_file_init(const Octstr *fname, long dump_freq)
00593 {
00594     /* Initialize function pointers */
00595     store_messages = store_file_messages;
00596     store_save = store_file_save;
00597     store_save_ack = store_file_save_ack;
00598     store_load = store_file_load;
00599     store_dump = store_file_dump;
00600     store_shutdown = store_file_shutdown;
00601     store_status = store_file_status;
00602 
00603     if (fname == NULL)
00604         return 0; /* we are done */
00605 
00606     if (octstr_len(fname) > (FILENAME_MAX-5))
00607         panic(0, "Store file filename too long: `%s', failed to init.",
00608           octstr_get_cstr(fname));
00609 
00610     filename = octstr_duplicate(fname);
00611     newfile = octstr_format("%s.new", octstr_get_cstr(filename));
00612     bakfile = octstr_format("%s.bak", octstr_get_cstr(filename));
00613 
00614     sms_dict = dict_create(1024, msg_destroy_item);
00615 
00616     if (dump_freq > 0)
00617         dump_frequency = dump_freq;
00618     else
00619         dump_frequency = BB_STORE_DEFAULT_DUMP_FREQ;
00620 
00621     file_mutex = mutex_create();
00622     active = 1;
00623 
00624     loaded = gwlist_create();
00625     gwlist_add_producer(loaded);
00626 
00627     return 0;
00628 }
See file LICENSE for details about the license agreement for using, modifying, copying or deriving work from this software.