main.c 18.1 KB
Newer Older
1 2 3 4 5
/* Icecast
 *
 * This program is distributed under the GNU General Public License, version 2.
 * A copy of this license is included with this source.
 *
6
 * Copyright 2000-2004, Jack Moffitt <jack@xiph.org,
7 8
 *                      Michael Smith <msmith@xiph.org>,
 *                      oddsock <oddsock@xiph.org>,
9
 *                      Karl Heyes <karl@xiph.org>,
10
 *                      and others (see AUTHORS for details).
11
 * Copyright 2011-2018, Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>,
12
 * Copyright 2014,      Thomas B. Ruecker <thomas@ruecker.fi>.
13 14
 */

15
/* -*- c-basic-offset: 4; indent-tabs-mode: nil; -*- */
16 17 18 19
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

Jack Moffitt's avatar
Jack Moffitt committed
20 21
#include <stdio.h>
#include <string.h>
22 23
#include <errno.h>

24
#ifdef HAVE_CURL
25
#include "curl.h"
26 27
#endif

28
#ifdef WIN32
29
#ifndef _WIN32_WINNT
30
#define _WIN32_WINNT 0x0400
31
#endif
32 33 34 35 36 37
/* For getpid() */
#include <process.h>
#include <windows.h>
#define snprintf _snprintf
#define getpid _getpid
#endif
Jack Moffitt's avatar
Jack Moffitt committed
38

39 40 41 42
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif

43 44 45 46
#ifdef HAVE_UNAME
#include <sys/utsname.h>
#endif

47 48
#include <igloo/igloo.h>
#include <igloo/ro.h>
49 50 51 52
#include <igloo/thread.h>
#include <igloo/sock.h>
#include <igloo/resolver.h>
#include <igloo/httpp.h>
Jack Moffitt's avatar
Jack Moffitt committed
53

54
#if HAVE_SYS_TYPES_H
55
#include <sys/types.h>
56 57
#endif
#if HAVE_GRP_H
58
#include <grp.h>
59 60
#endif
#if HAVE_PWD_H
61 62
#include <pwd.h>
#endif
Jack Moffitt's avatar
Jack Moffitt committed
63

64
#include "main.h"
65
#include "cfgfile.h"
66
#include "util.h"
Jack Moffitt's avatar
Jack Moffitt committed
67 68 69
#include "sighandler.h"

#include "global.h"
70
#include "compat.h"
Jack Moffitt's avatar
Jack Moffitt committed
71 72 73
#include "connection.h"
#include "refbuf.h"
#include "client.h"
74
#include "slave.h"
Jack Moffitt's avatar
Jack Moffitt committed
75 76
#include "stats.h"
#include "logging.h"
77
#include "xslt.h"
78
#include "fserve.h"
79
#include "yp.h"
80
#include "auth.h"
81
#include "event.h"
82
#include "listensocket.h"
83
#include "fastevent.h"
Jack Moffitt's avatar
Jack Moffitt committed
84

85 86
#include <libxml/xmlmemory.h>

Jack Moffitt's avatar
Jack Moffitt committed
87 88 89
#undef CATMODULE
#define CATMODULE "main"

90 91 92
static int background;
static char *pidfile = NULL;

93 94
static igloo_ro_t icecast_igloo_instance;

95 96
static void pidfile_update(ice_config_t *config, int always_try);

97
static void _fatal_error(const char *perr)
98
{
99 100 101
#ifdef WIN32_SERVICE
    MessageBox(NULL, perr, "Error", MB_SERVICE_NOTIFICATION);
#elif defined(WIN32)
102 103 104 105 106 107
    MessageBox(NULL, perr, "Error", MB_OK);
#else
    fprintf(stdout, "%s\n", perr);
#endif
}

108
static void _print_usage(void)
Jack Moffitt's avatar
Jack Moffitt committed
109
{
110
    printf("%s\n\n", ICECAST_VERSION_STRING);
111 112
    printf("usage: icecast [-b] -c <file>\n");
    printf("or   : icecast {-v|--version}\n");
113
    printf("options:\n");
114 115 116
    printf("\t-c <file>       Specify configuration file\n");
    printf("\t-v or --version Display version info\n");
    printf("\t-b              Run icecast in the background\n");
117
    printf("\n");
Jack Moffitt's avatar
Jack Moffitt committed
118 119
}

120 121
static void _stop_logging(void)
{
122 123 124
    igloo_log_close(errorlog);
    igloo_log_close(accesslog);
    igloo_log_close(playlistlog);
125 126
}

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
#ifndef FASTEVENT_ENABLED
static void __fastevent_cb(const void *userdata, fastevent_type_t type, fastevent_flag_t flags, fastevent_datatype_t datatype, va_list ap)
{
    event_t *event;

    if (datatype != FASTEVENT_DATATYPE_EVENT)
        return;

    event = va_arg(ap, event_t*);

    if (event == NULL) {
        ICECAST_LOG_DEBUG("event=%p", event);
    } else {
        ICECAST_LOG_DEBUG("event=%p{.trigger='%s', ...}", event, event->trigger);
    }
}

static refobject_t fastevent_reg;
#endif

147
static void initialize_subsystems(void)
Jack Moffitt's avatar
Jack Moffitt committed
148
{
149 150 151
    icecast_igloo_instance = igloo_initialize();
    /* FIXME: Check for igloo_RO_IS_NULL(icecast_igloo_instance) */

152
    global_initialize();
153
#ifndef FASTEVENT_ENABLED
154
    fastevent_initialize();
155 156
    fastevent_reg = fastevent_register(FASTEVENT_TYPE_SLOWEVENT, __fastevent_cb, NULL, NULL);
#endif
157
    config_initialize();
158
    tls_initialize();
159 160
    connection_initialize();
    refbuf_initialize();
161

162
    xslt_initialize();
163 164
#ifdef HAVE_CURL
    icecast_curl_initialize();
165
#endif
Jack Moffitt's avatar
Jack Moffitt committed
166 167
}

168
static void shutdown_subsystems(void)
Jack Moffitt's avatar
Jack Moffitt committed
169
{
170
    event_shutdown();
171
    fserve_shutdown();
172 173
    refbuf_shutdown();
    slave_shutdown();
174
    auth_shutdown();
175
    yp_shutdown();
176
    stats_shutdown();
177

178
    connection_shutdown();
179
    tls_shutdown();
180
    config_shutdown();
181 182
#ifndef FASTEVENT_ENABLED
    refobject_unref(fastevent_reg);
183
    fastevent_shutdown();
184
#endif
185
    global_shutdown();
186

187
#ifdef HAVE_CURL
188
    icecast_curl_shutdown();
189 190
#endif

191 192
    /* Now that these are done, we can stop the loggers. */
    _stop_logging();
193
    xslt_shutdown();
194 195

    igloo_ro_unref(icecast_igloo_instance);
Jack Moffitt's avatar
Jack Moffitt committed
196 197
}

198 199 200 201 202 203
void main_config_reload(ice_config_t *config)
{
    ICECAST_LOG_DEBUG("Reloading configuration.");
    pidfile_update(config, 0);
}

Philipp Schafft's avatar
Philipp Schafft committed
204
static int _parse_config_opts(int argc, char **argv, char *filename, size_t size)
Jack Moffitt's avatar
Jack Moffitt committed
205
{
206
    int i = 1;
207 208
    int config_ok = 0;

209
    background = 0;
210 211
    if (argc < 2) {
        if (filename[0] != 0) {
212
            /* We have a default filename, so we can work with no options. */
213 214
            return 1;
        } else {
215
            /* We need at least a config filename. */
216 217 218
            return -1;
        }
    }
Jack Moffitt's avatar
Jack Moffitt committed
219

220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
    while (i < argc) {
        if (strcmp(argv[i], "-b") == 0) {
#ifndef WIN32
            pid_t pid;
            fprintf(stdout, "Starting icecast2\nDetaching from the console\n");

            pid = fork();

            if (pid > 0) {
                /* exit the parent */
                exit(0);
            }
            else if(pid < 0) {
                fprintf(stderr, "FATAL: Unable to fork child!");
                exit(1);
            }
            background = 1;
#endif
        }
239
        if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--version") == 0) {
240
            fprintf(stdout, "%s\n", ICECAST_VERSION_STRING);
241 242 243
            exit(0);
        }

244 245 246
        if (strcmp(argv[i], "-c") == 0) {
            if (i + 1 < argc) {
                strncpy(filename, argv[i + 1], size-1);
247
                filename[size-1] = 0;
248
                config_ok = 1;
249 250 251 252
            } else {
                return -1;
            }
        }
253
        i++;
254 255
    }

256 257 258 259
    if(config_ok)
        return 1;
    else
        return -1;
Jack Moffitt's avatar
Jack Moffitt committed
260 261
}

262
static int _start_logging_stdout(void) {
263
    errorlog = igloo_log_open_file(stderr);
264 265 266
    if ( errorlog < 0 )
        return 0;

267
    igloo_log_set_level(errorlog, ICECAST_LOGLEVEL_WARN);
268 269 270 271

    return 1;
}

272
static int _start_logging(void)
Jack Moffitt's avatar
Jack Moffitt committed
273
{
274 275
    char fn_error[FILENAME_MAX];
    char fn_access[FILENAME_MAX];
276
    char fn_playlist[FILENAME_MAX];
277
    char buf[1024];
278
    int log_to_stderr;
279

280
    ice_config_t *config = config_get_config_unlocked();
Jack Moffitt's avatar
Jack Moffitt committed
281

282 283 284
    if(strcmp(config->error_log, "-") == 0) {
        /* this is already in place because of _start_logging_stdout() */
    } else {
285
        snprintf(fn_error, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->error_log);
286
        errorlog = igloo_log_open(fn_error);
287
        log_to_stderr = 0;
288
        if (config->logsize)
289 290
            igloo_log_set_trigger (errorlog, config->logsize);
        igloo_log_set_archive_timestamp(errorlog, config->logarchive);
291
    }
292 293 294

    if (errorlog < 0) {
        buf[sizeof(buf)-1] = 0;
295
        snprintf(buf, sizeof(buf)-1,
296 297
                "FATAL: could not open error logging (%s): %s",
                log_to_stderr?"standard error":fn_error,
298 299 300
                strerror(errno));
        _fatal_error(buf);
    }
301
    igloo_log_set_level(errorlog, config->loglevel);
302

303
    if(strcmp(config->access_log, "-") == 0) {
304
        accesslog = igloo_log_open_file(stderr);
305 306
        log_to_stderr = 1;
    } else {
307
        snprintf(fn_access, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->access_log);
308
        accesslog = igloo_log_open(fn_access);
309
        log_to_stderr = 0;
310
        if (config->logsize)
311 312
            igloo_log_set_trigger (accesslog, config->logsize);
        igloo_log_set_archive_timestamp(accesslog, config->logarchive);
313
    }
314 315 316

    if (accesslog < 0) {
        buf[sizeof(buf)-1] = 0;
317 318 319 320
        snprintf(buf, sizeof(buf) - 1,
            "FATAL: could not open access logging (%s): %s",
            log_to_stderr ? "standard error" : fn_access,
            strerror(errno));
321 322
        _fatal_error(buf);
    }
323 324 325

    if(config->playlist_log) {
        snprintf(fn_playlist, FILENAME_MAX, "%s%s%s", config->log_dir, PATH_SEPARATOR, config->playlist_log);
326
        playlistlog = igloo_log_open(fn_playlist);
327 328
        if (playlistlog < 0) {
            buf[sizeof(buf)-1] = 0;
329
            snprintf(buf, sizeof(buf)-1,
330 331 332 333 334 335
                "FATAL: could not open playlist logging (%s): %s",
                log_to_stderr?"standard error":fn_playlist,
                strerror(errno));
            _fatal_error(buf);
        }
        log_to_stderr = 0;
336
        if (config->logsize)
337 338
            igloo_log_set_trigger (playlistlog, config->logsize);
        igloo_log_set_archive_timestamp(playlistlog, config->logarchive);
339 340 341
    } else {
        playlistlog = -1;
    }
342

343 344 345
    igloo_log_set_level(errorlog, config->loglevel);
    igloo_log_set_level(accesslog, 4);
    igloo_log_set_level(playlistlog, 4);
346 347

    if (errorlog >= 0 && accesslog >= 0) return 1;
348

349
    return 0;
Jack Moffitt's avatar
Jack Moffitt committed
350 351
}

352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
static void pidfile_update(ice_config_t *config, int always_try)
{
    char *newpidfile = NULL;

    if (config->pidfile) {
        FILE *f;

        /* check if the file actually changed */
        if (pidfile && strcmp(pidfile, config->pidfile) == 0)
            return;

        ICECAST_LOG_DEBUG("New pidfile on %H", config->pidfile);

        if (!always_try) {
            if (config->chuid) {
                ICECAST_LOG_ERROR("Can not write new pidfile, changeowner in effect.");
                return;
            }

            if (config->chroot) {
                ICECAST_LOG_ERROR("Can not write new pidfile, chroot in effect.");
                return;
            }
        }

        newpidfile = strdup(config->pidfile);
        if (!newpidfile) {
            ICECAST_LOG_ERROR("Can not allocate memory for pidfile filename. BAD.");
            return;
        }

        f = fopen(newpidfile, "w");
        if (!f) {
            free(newpidfile);
            ICECAST_LOG_ERROR("Can not open new pidfile for writing.");
            return;
        }

        fprintf(f, "%lld\n", (long long int)getpid());
        fclose(f);

393
        ICECAST_LOG_INFO("pidfile %H updated.", pidfile);
394 395 396 397 398 399 400 401 402 403
    }

    if (newpidfile != pidfile) {
        if (pidfile)
            remove(pidfile);
        free(pidfile);
        pidfile = newpidfile;
    }
}

404
/* bind the socket and start listening */
405
static int _server_proc_init(void)
Jack Moffitt's avatar
Jack Moffitt committed
406
{
407
    ice_config_t *config = config_get_config_unlocked();
408

409
    connection_setup_sockets(config);
410

411 412 413 414 415
    if (listensocket_container_sockcount(global.listensockets) < 1) {
        ICECAST_LOG_ERROR("Can not listen on any sockets.");
        return 0;
    }

416
    pidfile_update(config, 1);
417

418
    return 1;
419
}
Jack Moffitt's avatar
Jack Moffitt committed
420

421 422 423
/* this is the heart of the beast */
static void _server_proc(void)
{
424 425 426 427 428 429
    if (background)
    {
        fclose (stdin);
        fclose (stdout);
        fclose (stderr);
    }
430
    connection_accept_loop();
Jack Moffitt's avatar
Jack Moffitt committed
431

432
    connection_setup_sockets (NULL);
Jack Moffitt's avatar
Jack Moffitt committed
433 434
}

435
/* chroot the process. Watch out - we need to do this before starting other
436
 * threads. Change uid as well, after figuring out uid _first_ */
437
#if defined(HAVE_SETUID) || defined(HAVE_CHROOT) || defined(HAVE_SETUID)
Michael Smith's avatar
Michael Smith committed
438
static void _ch_root_uid_setup(void)
439
{
Michael Smith's avatar
Michael Smith committed
440
   ice_config_t *conf = config_get_config_unlocked();
441
#ifdef HAVE_SETUID
442 443 444 445 446 447 448
   struct passwd *user;
   struct group *group;
   uid_t uid=-1;
   gid_t gid=-1;

   if(conf->chuid)
   {
449 450 451 452 453 454 455 456 457
       if(conf->user) {
           user = getpwnam(conf->user);
           if(user)
               uid = user->pw_uid;
           else
               fprintf(stderr, "Couldn't find user \"%s\" in password file\n", conf->user);
       }
       if(conf->group) {
           group = getgrnam(conf->group);
458

459 460 461 462 463
           if(group)
               gid = group->gr_gid;
           else
               fprintf(stderr, "Couldn't find group \"%s\" in groups file\n", conf->group);
       }
464 465 466
   }
#endif

467
#if HAVE_CHROOT
468 469 470 471 472 473
   if (conf->chroot)
   {
       if(getuid()) /* root check */
       {
           fprintf(stderr, "WARNING: Cannot change server root unless running as root.\n");
       }
474
       if(chroot(conf->base_dir) == -1 || chdir("/") == -1)
475 476 477 478 479 480 481
       {
           fprintf(stderr,"WARNING: Couldn't change server root: %s\n", strerror(errno));
           return;
       }
       else
           fprintf(stdout, "Changed root successfully to \"%s\".\n", conf->base_dir);

482
   }
483
#endif
484

485
#if HAVE_SETUID
486 487 488 489 490 491 492 493
   if(conf->chuid)
   {
       if(getuid()) /* root check */
       {
           fprintf(stderr, "WARNING: Can't change user id unless you are root.\n");
           return;
       }

494
       if(uid != (uid_t)-1 && gid != (gid_t)-1) {
495 496 497 498 499
#ifdef HAVE_SETRESGID
           if(!setresgid(gid, gid, gid)) {
#else
           if(!setgid(gid)) {
#endif
500
               fprintf(stdout, "Changed groupid to %i.\n", (int)gid);
501
           } else {
502
               fprintf(stdout, "Error changing groupid: %s.\n", strerror(errno));
503
           }
504 505
           if(!initgroups(conf->user, gid))
               fprintf(stdout, "Changed supplementary groups based on user: %s.\n", conf->user);
506
           else
507
               fprintf(stdout, "Error changing supplementary groups: %s.\n", strerror(errno));
508 509 510 511 512
#ifdef HAVE_SETRESUID
           if(!setresuid(uid, uid, uid)) {
#else
           if(!setuid(uid)) {
#endif
513
               fprintf(stdout, "Changed userid to %i.\n", (int)uid);
514
           } else {
515
               fprintf(stdout, "Error changing userid: %s.\n", strerror(errno));
516
           }
517
       }
518 519
   }
#endif
520
}
521
#endif
Philipp Schafft's avatar
Philipp Schafft committed
522

523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562
static inline void __log_system_name(void) {
    char hostname[80] = "(unknown)";
    char system[128] = "(unknown)";
    int have_hostname = 0;
#ifdef HAVE_UNAME
    struct utsname utsname;
#endif
    ice_config_t *config;

#ifdef HAVE_GETHOSTNAME
    if (gethostname(hostname, sizeof(hostname)) != 0) {
        strncpy(hostname, "(unknown)", sizeof(hostname));
    } else {
        have_hostname = 1;
    }
#endif
#ifdef HAVE_UNAME
    if(uname(&utsname) == 0) {
        snprintf(system, sizeof(system), "%s %s, %s, %s, %s",
                 utsname.sysname, utsname.release, utsname.nodename, utsname.version, utsname.machine);
        if (!have_hostname) {
            strncpy(hostname, utsname.nodename, sizeof(hostname));
            have_hostname = 1;
        }
    }
#elif defined(WIN32)
    strncpy(system, "MS Windows", sizeof(system));
#endif

   ICECAST_LOG_INFO("Running on %s; OS: %s; Address Bits: %i", hostname, system, sizeof(void*)*8);

   if (have_hostname) {
       config = config_get_config();
       if (!config->sane_hostname && util_hostcheck(hostname) == HOSTCHECK_SANE) {
           ICECAST_LOG_WARN("Hostname is not set to anything useful in <hostname>, Consider setting it to the system's name \"%s\".", hostname);
       }
       config_release_config();
   }
}

563
#ifdef WIN32_SERVICE
Philipp Schafft's avatar
Philipp Schafft committed
564 565 566 567
int mainService(int argc, char **argv)
#else
int main(int argc, char **argv)
#endif
Jack Moffitt's avatar
Jack Moffitt committed
568
{
569
    int res, ret;
570 571 572 573 574
#ifdef ICECAST_DEFAULT_CONFIG
    char filename[512] = ICECAST_DEFAULT_CONFIG;
#else
    char filename[512] = "";
#endif
575
    char pbuf[1024];
576 577 578 579

    /* parse the '-c icecast.xml' option
    ** only, so that we can read a configfile
    */
580
    res = _parse_config_opts(argc, argv, filename, sizeof(filename));
581
    if (res == 1) {
582
#if !defined(_WIN32) || defined(_CONSOLE) || defined(__MINGW32__) || defined(__MINGW64__)
583
        /* startup all the modules */
584
        initialize_subsystems();
585 586 587 588 589
        if (!_start_logging_stdout()) {
            _fatal_error("FATAL: Could not start logging on stderr.");
            shutdown_subsystems();
            return 1;
        }
590
#endif
591
        /* parse the config file */
Michael Smith's avatar
Michael Smith committed
592
        config_get_config();
593
        ret = config_initial_parse_file(filename);
Michael Smith's avatar
Michael Smith committed
594
        config_release_config();
595
        if (ret < 0) {
596
            memset(pbuf, '\000', sizeof(pbuf));
597
            snprintf(pbuf, sizeof(pbuf)-1,
598 599
                "FATAL: error parsing config file (%s)", filename);
            _fatal_error(pbuf);
600 601
            switch (ret) {
            case CONFIG_EINSANE:
602
                _fatal_error("filename was null or blank");
603 604
                break;
            case CONFIG_ENOROOT:
605
                _fatal_error("no root element found");
606 607
                break;
            case CONFIG_EBADROOT:
608
                _fatal_error("root element is not <icecast>");
609 610
                break;
            default:
611
                _fatal_error("XML config parsing error");
612 613
                break;
            }
614
#if !defined(_WIN32) || defined(_CONSOLE) || defined(__MINGW32__) || defined(__MINGW64__)
615 616
            shutdown_subsystems();
#endif
617
            return 1;
618 619 620 621 622
        }
    } else if (res == -1) {
        _print_usage();
        return 1;
    }
623

624 625
    /* override config file options with commandline options */
    config_parse_cmdline(argc, argv);
Jack Moffitt's avatar
Jack Moffitt committed
626

627 628
    /* Bind socket, before we change userid */
    if(!_server_proc_init()) {
629
        _fatal_error("Server startup failed. Exiting");
630
        shutdown_subsystems();
631 632
        return 1;
    }
633

634
#if defined(HAVE_SETUID) || defined(HAVE_CHROOT) || defined(HAVE_SETUID)
Michael Smith's avatar
Michael Smith committed
635
    _ch_root_uid_setup(); /* Change user id and root if requested/possible */
636
#endif
637 638

    stats_initialize(); /* We have to do this later on because of threading */
639
    fserve_initialize(); /* This too */
640

641
#ifdef HAVE_SETUID
642 643
    /* We'll only have getuid() if we also have setuid(), it's reasonable to
     * assume */
644
    if(!getuid() && getpid() != 1) /* Running as root! Don't allow this */
645
    {
646
        fprintf(stderr, "ERROR: You should not run icecast2 as root\n");
647
        fprintf(stderr, "Use the changeowner directive in the config file\n");
648
        shutdown_subsystems();
649 650 651 652 653 654 655
        return 1;
    }
#endif

    /* setup default signal handlers */
    sighandler_initialize();

656
    if (!_start_logging()) {
657
        _fatal_error("FATAL: Could not start logging");
658
        shutdown_subsystems();
659 660
        return 1;
    }
Jack Moffitt's avatar
Jack Moffitt committed
661

662
    ICECAST_LOG_INFO("%s server started", ICECAST_VERSION_STRING);
663
    ICECAST_LOG_INFO("Server's PID is %lli", (long long int)getpid());
664
    __log_system_name();
Jack Moffitt's avatar
Jack Moffitt committed
665

666
    /* REM 3D Graphics */
Jack Moffitt's avatar
Jack Moffitt committed
667

668
    /* let her rip */
669
    global.running = ICECAST_RUNNING;
670 671 672 673

    /* Startup yp thread */
    yp_initialize();

674 675
    /* Do this after logging init */
    slave_initialize();
676
    auth_initialise ();
677
    event_initialise();
678

679
    event_emit_global("icecast-start");
680
    _server_proc();
681
    event_emit_global("icecast-stop");
Jack Moffitt's avatar
Jack Moffitt committed
682

683
    ICECAST_LOG_INFO("Shutting down");
684
#if !defined(_WIN32) || defined(_CONSOLE) || defined(__MINGW32__) || defined(__MINGW64__)
685 686
    shutdown_subsystems();
#endif
687 688 689 690 691 692
    if (pidfile)
    {
        remove (pidfile);
        free (pidfile);
    }

693
    return 0;
Jack Moffitt's avatar
Jack Moffitt committed
694 695 696
}