main.c

Go to the documentation of this file.
00001 /* Services -- main source file.
00002  *
00003  * (C) 2003-2013 Anope Team
00004  * Contact us at team@anope.org
00005  *
00006  * Please read COPYING and README for further details.
00007  *
00008  * Based on the original code of Epona by Lara.
00009  * Based on the original code of Services by Andy Church. 
00010  * 
00011  * This program is free software; you can redistribute it and/or modify
00012  * it under the terms of the GNU General Public License as published by
00013  * the Free Software Foundation; either version 2 of the License, or
00014  * (at your option) any later version.
00015  *
00016  * This program is distributed in the hope that it will be useful,
00017  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00018  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00019  * GNU General Public License for more details.
00020  *
00021  * You should have received a copy of the GNU General Public License
00022  * along with this program (see the file COPYING); if not, write to the
00023  * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
00024  *
00025  *
00026  */
00027 
00028 #include "services.h"
00029 #include "timeout.h"
00030 #include "version.h"
00031 #include "datafiles.h"
00032 
00033 /******** Global variables! ********/
00034 
00035 /* Command-line options: (note that configuration variables are in config.c) */
00036 char *services_dir = SERVICES_DIR;      /* -dir dirname */
00037 char *log_filename = LOG_FILENAME;      /* -log filename */
00038 int debug = 0;                  /* -debug */
00039 int readonly = 0;               /* -readonly */
00040 int logchan = 0;                /* -logchan */
00041 int skeleton = 0;               /* -skeleton */
00042 int nofork = 0;                 /* -nofork */
00043 int forceload = 0;              /* -forceload */
00044 int nothird = 0;                /* -nothrid */
00045 int noexpire = 0;               /* -noexpire */
00046 int protocoldebug = 0;          /* -protocoldebug */
00047 
00048 #ifdef _WIN32
00049 char *binary_dir;               /* Used to store base path for win32 restart */
00050 #endif
00051 
00052 #ifdef USE_RDB
00053 int do_mysql = 0;               /* use mysql ? */
00054 #endif
00055 
00056 /* Set to 1 if we are to quit */
00057 int quitting = 0;
00058 
00059 /* Set to 1 if we are to quit after saving databases */
00060 int delayed_quit = 0;
00061 
00062 /* Contains a message as to why services is terminating */
00063 char *quitmsg = NULL;
00064 
00065 /* Input buffer - global, so we can dump it if something goes wrong */
00066 char inbuf[BUFSIZE];
00067 
00068 /* Socket for talking to server */
00069 int servsock = -1;
00070 
00071 /* Should we update the databases now? */
00072 int save_data = 0;
00073 
00074 /* At what time were we started? */
00075 time_t start_time;
00076 
00077 /* Parameters and environment */
00078 char **my_av, **my_envp;
00079 
00080 /* Moved here from version.h */
00081 const char version_number[] = VERSION_STRING;
00082 const char version_number_dotted[] = VERSION_STRING_DOTTED;
00083 const char version_build[] =
00084     "build #" BUILD ", compiled " __DATE__ " " __TIME__;
00085 /* the space is needed cause if you build with nothing it will complain */
00086 const char version_flags[] = " " VER_DEBUG VER_OS VER_MYSQL VER_MODULE;
00087 
00088 extern char *mod_current_buffer;
00089 
00090 /******** Local variables! ********/
00091 
00092 /* Set to 1 if we are waiting for input */
00093 static int waiting = 0;
00094 
00095 /* Set to 1 after we've set everything up */
00096 static int started = 0;
00097 
00098 /*************************************************************************/
00099 
00100 /* Run expiration routines */
00101 
00102 extern void expire_all(void)
00103 {
00104     if (noexpire || readonly)
00105     {
00106         /* don't allow expiry here or bad things will happen. */
00107         return;
00108     }
00109 
00110     waiting = -30;
00111     send_event(EVENT_DB_EXPIRE, 1, EVENT_START);
00112     waiting = -3;
00113     if (debug)
00114         alog("debug: Running expire routines");
00115     if (!skeleton) {
00116         waiting = -21;
00117         expire_nicks();
00118         waiting = -22;
00119         expire_chans();
00120         waiting = -23;
00121         expire_requests();
00122     }
00123     waiting = -25;
00124     expire_akills();
00125     if (ircd->sgline) {
00126         waiting = -26;
00127         expire_sglines();
00128     }
00129     if (ircd->sqline) {
00130         waiting = -28;
00131         expire_sqlines();
00132     }
00133     if (ircd->szline) {
00134         waiting = -27;
00135         expire_szlines();
00136     }
00137     waiting = -29;
00138     expire_exceptions();
00139     waiting = -31;
00140     send_event(EVENT_DB_EXPIRE, 1, EVENT_STOP);
00141 }
00142 
00143 /*************************************************************************/
00144 
00145 void save_databases(void)
00146 {
00147     if (readonly)
00148         return;
00149     waiting = -19;
00150     send_event(EVENT_DB_SAVING, 1, EVENT_START);
00151     waiting = -2;
00152     if (debug)
00153         alog("debug: Saving FFF databases");
00154     waiting = -10;
00155     backup_databases();
00156     if (!skeleton) {
00157         waiting = -11;
00158         save_ns_dbase();
00159         waiting = -12;
00160         if (PreNickDBName) {
00161             save_ns_req_dbase();
00162             waiting = -13;
00163         }
00164         save_cs_dbase();
00165         if (s_BotServ) {
00166             waiting = -14;
00167             save_bs_dbase();
00168         }
00169         if (s_HostServ) {
00170             waiting = -15;
00171             save_hs_dbase();
00172         }
00173     }
00174     waiting = -16;
00175     save_os_dbase();
00176     waiting = -17;
00177     save_news();
00178     waiting = -18;
00179     save_exceptions();
00180 
00181 #ifdef USE_RDB
00182     if (do_mysql) {
00183         if (debug)
00184             alog("debug: Saving RDB databases");
00185         waiting = -10;
00186         if (!skeleton) {
00187             waiting = -11;
00188             save_ns_rdb_dbase();
00189             /* We send these PONG's when we're not syncing to avoid timeouts.
00190              * If we send them during the sync, we fuck something up there and
00191              * break the syncing process, resulting in lost (literally lost)
00192              * data. -GD
00193              * This used is_sync(serv_uplink) to check for sync states. There's
00194              * only a minor error with this: serv_uplink doesn't exist during
00195              * the first save. So now we check for serv_uplink only; if it
00196              * exists we're safe. -GD
00197              */
00198             if (serv_uplink)
00199                 anope_cmd_pong(ServerName, ServerName);
00200             waiting = -12;
00201             save_cs_rdb_dbase();
00202             if (serv_uplink)
00203                 anope_cmd_pong(ServerName, ServerName);
00204             if (PreNickDBName) {
00205                 save_ns_req_rdb_dbase();
00206                 if (serv_uplink)
00207                     anope_cmd_pong(ServerName, ServerName);
00208                 waiting = -13;
00209             }
00210             if (s_BotServ) {
00211                 waiting = -14;
00212                 save_bs_rdb_dbase();
00213                 if (serv_uplink)
00214                     anope_cmd_pong(ServerName, ServerName);
00215             }
00216             if (s_HostServ) {
00217                 waiting = -15;
00218                 save_hs_rdb_dbase();
00219                 if (serv_uplink)
00220                     anope_cmd_pong(ServerName, ServerName);
00221             }
00222             waiting = -16;
00223             save_os_rdb_dbase();
00224             if (serv_uplink)
00225                 anope_cmd_pong(ServerName, ServerName);
00226             waiting = -17;
00227             save_rdb_news();
00228             if (serv_uplink)
00229                 anope_cmd_pong(ServerName, ServerName);
00230             waiting = -18;
00231             save_rdb_exceptions();
00232             if (serv_uplink)
00233                 anope_cmd_pong(ServerName, ServerName);
00234 
00235         } else {
00236             alog("!WARNING! Both MySQL and SKELETON are enabled, however SKELETON mode overrides MySQL dumping so databases will NOT be saved to MySQL. Keep this in mind next time you start Anope with UseRDB!");
00237         }
00238     }
00239 #endif
00240     waiting = -20;
00241     send_event(EVENT_DB_SAVING, 1, EVENT_STOP);
00242 }
00243 
00244 /*************************************************************************/
00245 
00246 /* Restarts services */
00247 
00248 static void services_restart(void)
00249 {
00250     alog("Restarting");
00251     send_event(EVENT_RESTART, 1, EVENT_START);
00252     if (!quitmsg)
00253         quitmsg = "Restarting";
00254     anope_cmd_squit(ServerName, quitmsg);
00255     disconn(servsock);
00256     close_log();
00257     /* First don't unload protocol module, then do so */
00258     modules_unload_all(true, false);
00259     modules_unload_all(true, true);
00260 #ifdef _WIN32
00261     /* This fixes bug #589 - change to binary directory for restart */
00262     /*  -- heinz */
00263     if (binary_dir)
00264         chdir(binary_dir);
00265 #endif
00266     execve(SERVICES_BIN, my_av, my_envp);
00267     if (!readonly) {
00268         open_log();
00269         log_perror("Restart failed");
00270         close_log();
00271     }
00272 }
00273 
00274 /*************************************************************************/
00279 void do_restart_services(void)
00280 {
00281     if (!readonly) {
00282         expire_all();
00283         save_databases();
00284     }
00285     services_restart();
00286     exit(1);
00287 }
00288 
00289 /*************************************************************************/
00290 
00291 /* Terminates services */
00292 
00293 static void services_shutdown(void)
00294 {
00295     User *u, *next;
00296 
00297     send_event(EVENT_SHUTDOWN, 1, EVENT_START);
00298 
00299     if (!quitmsg)
00300         quitmsg = "Terminating, reason unknown";
00301     alog("%s", quitmsg);
00302     if (started) {
00303         anope_cmd_squit(ServerName, quitmsg);
00304         Anope_Free(uplink);
00305         Anope_Free(mod_current_buffer);
00306         if (ircd->chanmodes) {
00307             Anope_Free(ircd->chanmodes);
00308         }
00309         u = firstuser();
00310         while (u) {
00311             next = nextuser();
00312             delete_user(u);
00313             u = next;
00314         }
00315     }
00316     send_event(EVENT_SHUTDOWN, 1, EVENT_STOP);
00317     disconn(servsock);
00318     /* First don't unload protocol module, then do so */
00319     modules_unload_all(true, false);
00320     modules_unload_all(true, true);
00321     /* just in case they weren't all removed at least run once */
00322     ModuleRunTimeDirCleanUp();
00323 }
00324 
00325 /*************************************************************************/
00326 
00327 /* If we get a weird signal, come here. */
00328 
00329 void sighandler(int signum)
00330 {
00331     /* We set the quit message to something default, just to be sure it is
00332      * always set when we need it. It seems some signals slip through to the
00333      * QUIT code without having a valid quitmsg. -GD
00334      */
00335     quitmsg = "Signal Received";
00336     if (started) {
00337 #ifndef _WIN32
00338         if (signum == SIGHUP) { /* SIGHUP = save databases and restart */
00339             signal(SIGHUP, SIG_IGN);
00340             signal(SIGUSR2, SIG_IGN);
00341             alog("Received SIGHUP, restarting.");
00342 
00343             expire_all();
00344             save_databases();
00345 
00346             if (!quitmsg)
00347                 quitmsg = "Restarting on SIGHUP";
00348 
00349 #ifdef SERVICES_BIN
00350             services_restart();
00351             exit(1);
00352 #else
00353             quitmsg =
00354                 "Restart attempt failed--SERVICES_BIN not defined (rerun configure)";
00355 #endif
00356         } else if (signum == SIGQUIT) {
00357             /* had to move it to here to make win32 happy */
00358         } else if (signum == SIGUSR2) {
00359 
00360             alog("Received SIGUSR2: Saving Databases & Rehash Configuration");
00361 
00362             expire_all();
00363             save_databases();
00364 
00365             if (!read_config(1)) {
00366                 static char *buf = "Error Reading Configuration File (Received SIGUSR2)";
00367                 quitmsg = buf;
00368                 quitting = 1;
00369             }
00370             send_event(EVENT_RELOAD, 1, EVENT_START);
00371             return;
00372 
00373         } else
00374 #endif
00375         if (signum == SIGTERM) {
00376             signal(SIGTERM, SIG_IGN);
00377 #ifndef _WIN32
00378             signal(SIGHUP, SIG_IGN);
00379 #endif
00380 
00381             alog("Received SIGTERM, exiting.");
00382 
00383             expire_all();
00384             save_databases();
00385             quitmsg = "Shutting down on SIGTERM";
00386             services_shutdown();
00387             exit(0);
00388         } else if (signum == SIGINT) {
00389             if (nofork) {
00390                 signal(SIGINT, SIG_IGN);
00391                 alog("Received SIGINT, exiting.");
00392                 expire_all();
00393                 save_databases();
00394                 quitmsg = "Shutting down on SIGINT";
00395                 services_shutdown();
00396                 exit(0);
00397             }
00398         } else if (!waiting) {
00399             alog("PANIC! buffer = %s", inbuf);
00400             /* Cut off if this would make IRC command >510 characters. */
00401             if (strlen(inbuf) > 448) {
00402                 inbuf[446] = '>';
00403                 inbuf[447] = '>';
00404                 inbuf[448] = 0;
00405             }
00406             wallops(NULL, "PANIC! buffer = %s\r\n", inbuf);
00407             modules_unload_all(false, true);
00408         } else if (waiting < 0) {
00409             /* This is static on the off-chance we run low on stack */
00410             static char buf[BUFSIZE];
00411             switch (waiting) {
00412             case -1:
00413                 snprintf(buf, sizeof(buf), "in timed_update");
00414                 break;
00415             case -10:
00416                 snprintf(buf, sizeof(buf), "backing up databases");
00417                 break;
00418             case -11:
00419                 snprintf(buf, sizeof(buf), "saving %s", NickDBName);
00420                 break;
00421             case -12:
00422                 snprintf(buf, sizeof(buf), "saving %s", ChanDBName);
00423                 break;
00424             case -13:
00425                 snprintf(buf, sizeof(buf), "saving %s", PreNickDBName);
00426                 break;
00427             case -14:
00428                 snprintf(buf, sizeof(buf), "saving %s", BotDBName);
00429                 break;
00430             case -15:
00431                 snprintf(buf, sizeof(buf), "saving %s", HostDBName);
00432                 break;
00433             case -16:
00434                 snprintf(buf, sizeof(buf), "saving %s", OperDBName);
00435                 break;
00436             case -17:
00437                 snprintf(buf, sizeof(buf), "saving %s", NewsDBName);
00438                 break;
00439             case -18:
00440                 snprintf(buf, sizeof(buf), "saving %s", ExceptionDBName);
00441                 break;
00442             case -19:
00443                 snprintf(buf, sizeof(buf), "Sending event %s %s",
00444                          EVENT_DB_SAVING, EVENT_START);
00445                 break;
00446             case -20:
00447                 snprintf(buf, sizeof(buf), "Sending event %s %s",
00448                          EVENT_DB_SAVING, EVENT_STOP);
00449                 break;
00450             case -21:
00451                 snprintf(buf, sizeof(buf), "expiring nicknames");
00452                 break;
00453             case -22:
00454                 snprintf(buf, sizeof(buf), "expiring channels");
00455                 break;
00456             case -25:
00457                 snprintf(buf, sizeof(buf), "expiring autokills");
00458                 break;
00459             case -26:
00460                 snprintf(buf, sizeof(buf), "expiring SGLINEs");
00461                 break;
00462             case -27:
00463                 snprintf(buf, sizeof(buf), "expiring SZLINEs");
00464                 break;
00465             case -28:
00466                 snprintf(buf, sizeof(buf), "expiring SQLINEs");
00467                 break;
00468             case -29:
00469                 snprintf(buf, sizeof(buf), "expiring Exceptions");
00470                 break;
00471             case -30:
00472                 snprintf(buf, sizeof(buf), "Sending event %s %s",
00473                          EVENT_DB_EXPIRE, EVENT_START);
00474                 break;
00475             case -31:
00476                 snprintf(buf, sizeof(buf), "Sending event %s %s",
00477                          EVENT_DB_EXPIRE, EVENT_STOP);
00478                 break;
00479             default:
00480                 snprintf(buf, sizeof(buf), "waiting=%d", waiting);
00481             }
00482             wallops(NULL, "PANIC! %s (%s)", buf, strsignal(signum));
00483             alog("PANIC! %s (%s)", buf, strsignal(signum));
00484             modules_unload_all(false, true);
00485         }
00486     }
00487 
00488     if (
00489 #ifndef _WIN32
00490            signum == SIGUSR1 ||
00491 #endif
00492            !(quitmsg = calloc(BUFSIZE, 1))) {
00493         quitmsg = "Out of memory!";
00494     } else {
00495 #if HAVE_STRSIGNAL
00496         snprintf(quitmsg, BUFSIZE, "Services terminating: %s",
00497                  strsignal(signum));
00498 #else
00499         snprintf(quitmsg, BUFSIZE, "Services terminating on signal %d",
00500                  signum);
00501 #endif
00502     }
00503 
00504     if (signum == SIGSEGV) {
00505         do_backtrace(1);
00506         modules_unload_all(false, true);        /* probably cant do this, but might as well try, we have nothing left to loose */
00507     }
00508     /* Should we send the signum here as well? -GD */
00509     send_event(EVENT_SIGNAL, 1, quitmsg);
00510 
00511     if (started) {
00512         services_shutdown();
00513         exit(0);
00514     } else {
00515         if (isatty(2)) {
00516             fprintf(stderr, "%s\n", quitmsg);
00517         } else {
00518             alog("%s", quitmsg);
00519         }
00520         exit(1);
00521     }
00522 }
00523 
00524 /*************************************************************************/
00525 
00526 /* Main routine.  (What does it look like? :-) ) */
00527 
00528 int main(int ac, char **av, char **envp)
00529 {
00530     volatile time_t last_update;        /* When did we last update the databases? */
00531     volatile time_t last_expire;        /* When did we last expire nicks/channels? */
00532     volatile time_t last_check; /* When did we last check timeouts? */
00533     volatile time_t last_DefCon;        /* When was DefCon last checked? */
00534 
00535     int i;
00536     char *progname;
00537 
00538     my_av = av;
00539     my_envp = envp;
00540 
00541 #ifndef _WIN32
00542     /* If we're root, issue a warning now */
00543     if ((getuid() == 0) && (getgid() == 0)) {
00544         fprintf(stderr,
00545                 "WARNING: You are currently running Anope as the root superuser. Anope does not\n");
00546         fprintf(stderr,
00547                 "    require root privileges to run, and it is discouraged that you run Anope\n");
00548         fprintf(stderr, "    as the root superuser.\n");
00549     }
00550 #else
00551     /*
00552      * We need to know which directory we're in for when restart is called.
00553      * This only affects Windows as a full path is not specified in services_dir.
00554      * This fixes bug #589.
00555      * -- heinz
00556      */
00557     binary_dir = smalloc(MAX_PATH);
00558     if (!getcwd(binary_dir, MAX_PATH)) {
00559         fprintf(stderr, "error: getcwd() error\n");
00560         return -1;
00561     }
00562 #endif
00563 
00564     /* Clean out the module runtime directory prior to running, just in case files were left behind on a previous run */
00565     ModuleRunTimeDirCleanUp();
00566 
00567     /* General initialization first */
00568     if ((i = init_primary(ac, av)) != 0)
00569         return i;
00570 
00571     /* Find program name. */
00572     if ((progname = strrchr(av[0], '/')) != NULL)
00573         progname++;
00574     else
00575         progname = av[0];
00576 
00577 #ifdef _WIN32
00578     if (strcmp(progname, "listnicks.exe") == 0)
00579 #else
00580     if (strcmp(progname, "listnicks") == 0)
00581 #endif
00582     {
00583         do_listnicks(ac, av);
00584         modules_unload_all(1, 0);
00585         modules_unload_all(1, 1);
00586         ModuleRunTimeDirCleanUp();
00587         return 0;
00588     }
00589 #ifdef _WIN32
00590     else if (strcmp(progname, "listchans.exe") == 0)
00591 #else
00592     else if (strcmp(progname, "listchans") == 0)
00593 #endif
00594     {
00595         do_listchans(ac, av);
00596         modules_unload_all(1, 0);
00597         modules_unload_all(1, 1);
00598         ModuleRunTimeDirCleanUp();
00599         return 0;
00600     }
00601 
00602     /* Initialization stuff. */
00603     if ((i = init_secondary(ac, av)) != 0)
00604         return i;
00605 
00606 
00607     /* We have a line left over from earlier, so process it first. */
00608     process();
00609 
00610     /* Set up timers. */
00611     last_update = time(NULL);
00612     last_expire = time(NULL);
00613     last_check = time(NULL);
00614     last_DefCon = time(NULL);
00615 
00616     started = 1;
00617 
00618     /*** Main loop. ***/
00619 
00620     while (!quitting) {
00621         time_t t = time(NULL);
00622 
00623         if (debug >= 2)
00624             alog("debug: Top of main loop");
00625 
00626         if (save_data || t - last_expire >= ExpireTimeout) {
00627             expire_all();
00628             last_expire = t;
00629         }
00630 
00631         if (!readonly && (save_data || t - last_update >= UpdateTimeout)) {
00632             if (delayed_quit)
00633                 anope_cmd_global(NULL,
00634                                  "Updating databases on shutdown, please wait.");
00635 
00636             save_databases();
00637 
00638             if (save_data < 0)
00639                 break;          /* out of main loop */
00640 
00641             save_data = 0;
00642             last_update = t;
00643         }
00644 
00645         if ((DefConTimeOut) && (t - last_DefCon >= dotime(DefConTimeOut))) {
00646             resetDefCon(5);
00647             last_DefCon = t;
00648         }
00649 
00650         if (delayed_quit)
00651             break;
00652 
00653         moduleCallBackRun();
00654 
00655         waiting = -1;
00656         if (t - last_check >= TimeoutCheck) {
00657             check_timeouts();
00658             last_check = t;
00659         }
00660 
00661         waiting = 1;
00662         /* this is a nasty nasty typecast. we need to rewrite the
00663            socket stuff -Certus */
00664         i = (int) (long) sgets2(inbuf, sizeof(inbuf), servsock);
00665         waiting = 0;
00666         if ((i > 0) || (i < (-1))) {
00667             process();
00668         } else if (i == 0) {
00669             int errno_save = errno;
00670             quitmsg = scalloc(BUFSIZE, 1);
00671             if (quitmsg) {
00672                 snprintf(quitmsg, BUFSIZE,
00673                          "Read error from server: %s (error num: %d)",
00674                          strerror(errno_save), errno_save);
00675             } else {
00676                 quitmsg = "Read error from server";
00677             }
00678             quitting = 1;
00679 
00680             /* Save the databases */
00681             if (!readonly)
00682                 save_databases();
00683         }
00684         waiting = -4;
00685     }
00686 
00687 
00688     /* Check for restart instead of exit */
00689     if (save_data == -2) {
00690 #ifdef SERVICES_BIN
00691         alog("Restarting");
00692         if (!quitmsg)
00693             quitmsg = "Restarting";
00694         anope_cmd_squit(ServerName, quitmsg);
00695         disconn(servsock);
00696         close_log();
00697 #ifdef _WIN32
00698         /* This fixes bug #589 - change to binary directory for restart */
00699         /*  -- heinz */
00700         if (binary_dir)
00701             chdir(binary_dir);
00702 #endif
00703         execve(SERVICES_BIN, av, envp);
00704         if (!readonly) {
00705             open_log();
00706             log_perror("Restart failed");
00707             close_log();
00708         }
00709         return 1;
00710 #else
00711         quitmsg =
00712             "Restart attempt failed--SERVICES_BIN not defined (rerun configure)";
00713 #endif
00714     }
00715 
00716     /* Disconnect and exit */
00717     services_shutdown();
00718 
00719 #ifdef _WIN32
00720     if (binary_dir)
00721         free(binary_dir);
00722 #endif
00723 
00724     return 0;
00725 }
00726 
00727 /*************************************************************************/
00728 
00729 void do_backtrace(int show_segheader)
00730 {
00731 #ifndef _WIN32
00732 #ifdef HAVE_BACKTRACE
00733     void *array[50];
00734     size_t size;
00735     char **strings;
00736     int i;
00737 
00738     if (show_segheader) {
00739         alog("Backtrace: Segmentation fault detected");
00740         alog("Backtrace: report the following lines");
00741     }
00742     alog("Backtrace: Anope version %s %s %s", version_number,
00743          version_build, version_flags);
00744     size = backtrace(array, 10);
00745     strings = backtrace_symbols(array, size);
00746     for (i = 0; i < size; i++) {
00747         alog("Backtrace(%d): %s", i, strings[i]);
00748     }
00749     free(strings);
00750     alog("Backtrace: complete");
00751 #else
00752     alog("Backtrace: not available on this platform");
00753 #endif
00754 #else
00755     char *winver;
00756     winver = GetWindowsVersion();
00757     alog("Backtrace: not available on Windows");
00758     alog("Running %S", winver);
00759     free(winver);
00760 #endif
00761 }