main.cxx 27.9 KB
Newer Older
1
/* Copyright (C) 2015-2020 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
3
4
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation; either version 2.1 of the License, or
5
6
7
8
9
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public License
13
14
15
16
17
18
 *   along with this program; if not, write to the
 *   Free Software Foundation, Inc.,
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
/////////////////////////////////////////////////////////////////////
// Main program
19
20
#define UPMPDCLI_NEED_PACKAGE_VERSION
#include "config.h"
21
22
23
24
25
26
27
28
29
30

#include <errno.h>             
#include <fcntl.h>             
#include <pwd.h>               
#include <signal.h>            
#include <stdio.h>             
#include <stdlib.h>            
#include <sys/param.h>         
#include <unistd.h>            
#include <grp.h>
31
#include <assert.h>
32
33
34
35
36
37
38
39
40

#include <iostream>            
#include <string>              
#include <unordered_map>       
#include <vector>              

#include "libupnpp/log.hxx"    
#include "libupnpp/upnpplib.hxx"
#include "execmd.h"
41
#include "conftree.h"
42
43
#include "mpdcli.hxx"
#include "upmpd.hxx"
44
45
#include "mediaserver/mediaserver.hxx"
#include "mediaserver/contentdirectory.hxx"
46
#include "upmpdutils.hxx"
47
#include "pathut.h"
48
#include "readfile.h"
49
50
51
52

using namespace std;
using namespace UPnPP;

53
54
55
// Can't remember why it's static in config.h and don't want to try change
const string g_upmpdcli_package_version{UPMPDCLI_PACKAGE_VERSION};

56
57
58
static char *thisprog;

static int op_flags;
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#define OPT_MOINS 0x1   
#define OPT_D     0x2   
#define OPT_O     0x4   
#define OPT_P     0x8   
#define OPT_c     0x10  
#define OPT_d     0x20  
#define OPT_f     0x40  
#define OPT_h     0x80  
#define OPT_i     0x100 
#define OPT_l     0x200 
#define OPT_m     0x400 
#define OPT_p     0x800 
#define OPT_q     0x1000
#define OPT_v     0x2000
73
74
75
76
77
78
79
80
81
82
83
84
85
86

static const char usage[] = 
    "-c configfile \t configuration file to use\n"
    "-h host    \t specify host MPD is running on\n"
    "-p port     \t specify MPD port\n"
    "-d logfilename\t debug messages to\n"
    "-l loglevel\t  log level (0-6)\n"
    "-D    \t run as a daemon\n"
    "-f friendlyname\t define device displayed name\n"
    "-q 0|1\t if set, we own the mpd queue, else avoid clearing it whenever we feel like it\n"
    "-i iface    \t specify network interface name to be used for UPnP\n"
    "-P upport    \t specify port number to be used for UPnP\n"
    "-O 0|1\t decide if we run and export the OpenHome services\n"
    "-v      \tprint version info\n"
87
    "-m <0|1|2|3|4> media server mode "
88
    "(default, multidev|only renderer|only media|embedded|multidev)\n"
89
90
91
    "\n"
    ;

92
93
94
95
// We can implement a Media Server in addition to the Renderer, for
// accessing streaming services. This can happen in several modes. In
// all cases, the Media Server is only created if the configuration
// file does have parameters set for streaming services.
96
97
98
// - -m 0, -m 4 combined/multidev: implement the Media Server and
//   Media Renderers, as enabled by the configuration, as separate
//   root devices.
99
// - -m 1, RdrOnly: for the main instance: be a Renderer, do not start the
100
101
102
//    Media Server even if the configuration indicates it is needed
//    (this is not used in normal situations, just edit the config
//    instead!)
103
104
// - -m 2, MSOnly Media Server only.
// - -m 3, combined/embedded: implement the Media Server
105
106
//    as an embedded device. This works just fine with, for example,
//    upplay, but confuses most of the other Control Points.
107
108

enum MSMode {Default, RdrOnly, MSOnly, CombinedEmbedded, CombinedMultiDev};
109

110
111
112
113
static void
versionInfo(FILE *fp)
{
    fprintf(fp, "Upmpdcli %s %s\n",
114
            UPMPDCLI_PACKAGE_VERSION, LibUPnP::versionString().c_str());
115
116
117
118
119
120
121
122
123
124
125
126
127
}

static void
Usage(FILE *fp = stderr)
{
    fprintf(fp, "%s: usage:\n%s", thisprog, usage);
    versionInfo(fp);
    exit(1);
}


static const string dfltFriendlyName("UpMpd");

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
ohProductDesc_t ohProductDesc = {
    // Manufacturer
    {
        "UpMPDCli heavy industries Co.",            // name
        "Such nice guys and gals",                  // info
        "http://www.lesbonscomptes.com/upmpdcli",   // url
        ""                                          // imageUri
    },
    // Model
    {
        "UpMPDCli UPnP-MPD gateway",                // name
        "",                                         // info
        "http://www.lesbonscomptes.com/upmpdcli",   // url
        ""                                          // imageUri
    },
    // Product
    {
        "Upmpdcli",                                 // name
146
        UPMPDCLI_PACKAGE_VERSION,                   // info
147
148
149
150
151
        "",                                         // url
        ""                                          // imageUri
    }
};

152
// Static for cleanup in sig handler.
153
static vector<UpnpDevice *> devs;
154
static MPDCli *mpdclip{nullptr};
155
156

string g_datadir(DATADIR "/");
157
string g_cachedir("/var/cache/upmpdcli");
158
159

// Global
160
string g_configfilename;
161
ConfSimple *g_config;
162
ConfSimple *g_state;
163
bool g_enableL16 = false;
164
bool g_lumincompat = false;
165
166
167
168

static void onsig(int)
{
    LOGDEB("Got sig" << endl);
169
    for (auto& dev : devs) {
Jean-Francois Dockes's avatar
comment    
Jean-Francois Dockes committed
170
171
172
173
        // delete has a tendancy to crash (it works most of the time
        // though). Anyway, we're exiting, so just call shouldExit()
        // which will send the byebyes.
        //delete dev;
174
175
        dev->shouldExit();
    }
176
177
178
    if (mpdclip) {
        mpdclip->shouldExit();
    }
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
}

static const int catchedSigs[] = {SIGINT, SIGQUIT, SIGTERM};
static void setupsigs()
{
    struct sigaction action;
    action.sa_handler = onsig;
    action.sa_flags = 0;
    sigemptyset(&action.sa_mask);
    for (unsigned int i = 0; i < sizeof(catchedSigs) / sizeof(int); i++)
        if (signal(catchedSigs[i], SIG_IGN) != SIG_IGN) {
            if (sigaction(catchedSigs[i], &action, 0) < 0) {
                perror("Sigaction failed");
            }
        }
}

196
197
198
199
200
201
static UpnpDevice *rootdevice{nullptr};
static MediaServer *mediaserver{nullptr};
static std::string uuidMS;
static std::string fnameMS;
static bool msroot{false};
bool startMediaServer(bool enable)
202
{
203
    if (mediaserver) {
204
205
        return true;
    }
206
207
208
209
210
211
212
213
214
215
    mediaserver = new MediaServer(
        msroot ? nullptr : rootdevice, string("uuid:")+ uuidMS, fnameMS, enable);
    if (nullptr == mediaserver)
        return false;
    devs.push_back(mediaserver);
    LOGDEB("Media server event loop" << endl);
    // msonly && !enableMediaServer is possible if we're just using
    // the "mediaserver" to redirect URLs for ohcredentials/Kazoo
    if (enable) {
        mediaserver->startloop();
216
    }
217
    return true;
218
219
}

220
221
222
223
int main(int argc, char *argv[])
{
    // Path for the sc2mpd command, or empty
    string sc2mpdpath;
224
    string screceiverstatefile;
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

    // Sender mode: path for the command creating the mpd and mpd2sc
    // processes, and port for the auxiliary mpd.
    string senderpath;
    int sendermpdport = 6700;

    // Main MPD parameters
    string mpdhost("localhost");
    int mpdport = 6600;
    string mpdpassword;

    string logfilename;
    int loglevel(Logger::LLINF);
    string friendlyname(dfltFriendlyName);
    bool ownqueue = true;
    bool enableAV = true;
    bool enableOH = true;
Jean-Francois Dockes's avatar
typo    
Jean-Francois Dockes committed
242
    bool enableMediaServer = false;
243
244
245
246
247
248
249
250
    bool ohmetapersist = true;
    string upmpdcliuser("upmpdcli");
    string pidfilename("/var/run/upmpdcli.pid");
    string iconpath(DATADIR "/icon.png");
    string presentationhtml(DATADIR "/presentation.html");
    string iface;
    unsigned short upport = 0;
    string upnpip;
251
    int msm = 0;
252
253
    bool inprocessms{false};
    bool msonly{false};
254
    
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
    const char *cp;
    if ((cp = getenv("UPMPD_HOST")))
        mpdhost = cp;
    if ((cp = getenv("UPMPD_PORT")))
        mpdport = atoi(cp);
    if ((cp = getenv("UPMPD_FRIENDLYNAME")))
        friendlyname = atoi(cp);
    if ((cp = getenv("UPMPD_CONFIG")))
        g_configfilename = cp;
    if ((cp = getenv("UPMPD_UPNPIFACE")))
        iface = cp;
    if ((cp = getenv("UPMPD_UPNPPORT")))
        upport = atoi(cp);

    thisprog = argv[0];
    argc--; argv++;
    while (argc > 0 && **argv == '-') {
        (*argv)++;
        if (!(**argv))
            Usage();
        while (**argv)
            switch (*(*argv)++) {
            case 'c':   op_flags |= OPT_c; if (argc < 2)  Usage();
                g_configfilename = *(++argv); argc--; goto b1;
            case 'D':   op_flags |= OPT_D; break;
            case 'd':   op_flags |= OPT_d; if (argc < 2)  Usage();
                logfilename = *(++argv); argc--; goto b1;
            case 'f':   op_flags |= OPT_f; if (argc < 2)  Usage();
                friendlyname = *(++argv); argc--; goto b1;
            case 'h':   op_flags |= OPT_h; if (argc < 2)  Usage();
                mpdhost = *(++argv); argc--; goto b1;
            case 'i':   op_flags |= OPT_i; if (argc < 2)  Usage();
                iface = *(++argv); argc--; goto b1;
            case 'l':   op_flags |= OPT_l; if (argc < 2)  Usage();
                loglevel = atoi(*(++argv)); argc--; goto b1;
290
291
            case 'm':   op_flags |= OPT_m; if (argc < 2)  Usage();
                msm = atoi(*(++argv)); argc--; goto b1;
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
            case 'O': {
                op_flags |= OPT_O; 
                if (argc < 2)  Usage();
                const char *cp =  *(++argv);
                if (*cp == '1' || *cp == 't' || *cp == 'T' || *cp == 'y' || 
                    *cp == 'Y')
                    enableOH = true;
                argc--; goto b1;
            }
            case 'P':   op_flags |= OPT_P; if (argc < 2)  Usage();
                upport = atoi(*(++argv)); argc--; goto b1;
            case 'p':   op_flags |= OPT_p; if (argc < 2)  Usage();
                mpdport = atoi(*(++argv)); argc--; goto b1;
            case 'q':   op_flags |= OPT_q; if (argc < 2)  Usage();
                ownqueue = atoi(*(++argv)) != 0; argc--; goto b1;
            case 'v': versionInfo(stdout); exit(0); break;
            default: Usage();   break;
            }
    b1: argc--; argv++;
    }

313
    if (argc != 0 || msm < 0 || msm > 4) {
314
        Usage();
315
    }
316
    MSMode arg_msmode = MSMode(msm);
317
    
318
319
320
321
322
323
324
325
326
327
328
329
330
331
    UpMpd::Options opts;

    if (!g_configfilename.empty()) {
        g_config = new ConfSimple(g_configfilename.c_str(), 1, true);
        if (!g_config || !g_config->ok()) {
            cerr << "Could not open config: " << g_configfilename << endl;
            return 1;
        }

        string value;
        if (!(op_flags & OPT_d))
            g_config->get("logfilename", logfilename);
        if (!(op_flags & OPT_f))
            g_config->get("friendlyname", friendlyname);
332
333
        if (!(op_flags & OPT_l)) {
            loglevel = g_config->getInt("loglevel", Logger::LLINF);
334
        }
335
336
        if (!(op_flags & OPT_h)) {
            g_config->get("mpdhost", mpdhost);
337
        }
338
339
        if (!(op_flags & OPT_p)) {
            mpdport =  g_config->getInt("mpdport", 6600);
340
        }
341
342
343
        g_config->get("mpdpassword", mpdpassword);
        if (!(op_flags & OPT_q)) {
            ownqueue = g_config->getBool("ownqueue", true);
344
        }
345
346
        enableOH = g_config->getBool("openhome", true);
        enableAV = g_config->getBool("upnpav", true);
347

348
        if (!g_config->getBool("checkcontentformat", true)) {
349
            // If option is specified and 0, set nocheck flag
350
            opts.options |= UpMpd::upmpdNoContentFormatCheck;
351
        }
352
        ohmetapersist = g_config->getBool("ohmetapersist", true);
353
354
355
        if (g_config->get("pkgdatadir", g_datadir)) {
            path_catslash(g_datadir);
            iconpath = path_cat(g_datadir, "icon.png");
356
357
358
            if (!path_exists(iconpath)) {
                iconpath.clear();
            }
359
360
            presentationhtml = path_cat(g_datadir, "presentation.html");
        }
361
362
        g_config->get("iconpath", iconpath);
        g_config->get("presentationhtml", presentationhtml);
363
        g_config->get("cachedir", opts.cachedir);
364
        g_config->get("pidfile", pidfilename);
365
366
367
368
369
370
        if (!(op_flags & OPT_i)) {
            g_config->get("upnpiface", iface);
            if (iface.empty()) {
                g_config->get("upnpip", upnpip);
            }
        }
371
372
        if (!(op_flags & OPT_P)) {
            upport = g_config->getInt("upnpport", 0);
373
        }
374
        opts.schttpport = g_config->getInt("schttpport", 0);
375
376
        g_config->get("scplaymethod", opts.scplaymethod);
        g_config->get("sc2mpd", sc2mpdpath);
377
        g_config->get("screceiverstatefile", screceiverstatefile);
378
        if (g_config->getBool("scnosongcastsource", false)) {
379
            // If option is specified and 1, set nocheck flag
380
            opts.options |= UpMpd::upmpdNoSongcastSource;
381
        }
382
        opts.ohmetasleep = g_config->getInt("ohmetasleep", 0);
383
384
385
386
387
388
389
        g_config->get("ohmanufacturername", ohProductDesc.manufacturer.name);
        g_config->get("ohmanufacturerinfo", ohProductDesc.manufacturer.info);
        g_config->get("ohmanufacturerurl", ohProductDesc.manufacturer.url);
        g_config->get("ohmanufacturerimageuri", ohProductDesc.manufacturer.imageUri);
        g_config->get("ohmodelname", ohProductDesc.model.name);
        g_config->get("ohmodelinfo", ohProductDesc.model.info);
        g_config->get("ohmodelurl", ohProductDesc.model.url);
390
        // imageUri was mistake, keep compat and override with imageuri if set
391
        g_config->get("ohmodelimageUri", ohProductDesc.model.imageUri);
392
        g_config->get("ohmodelimageuri", ohProductDesc.model.imageUri);
393
394
395
396
397
398
399
        g_config->get("ohproductname", ohProductDesc.product.name);
        g_config->get("ohproductinfo", ohProductDesc.product.info);
        g_config->get("ohproducturl", ohProductDesc.product.url);
        g_config->get("ohproductimageuri", ohProductDesc.product.imageUri);
        g_config->get("ohproductroom", ohProductDesc.room);
        // ProductName is set to ModelName by default
        if (ohProductDesc.product.name.empty()) {
400
            ohProductDesc.product.name = ohProductDesc.model.name;
401
402
403
        }
        // ProductRoom is set to "Main Room" by default
        if (ohProductDesc.room.empty()) {
404
            ohProductDesc.room = "Main Room";
405
        }
406
407
408
409

        g_config->get("scsenderpath", senderpath);
        if (g_config->get("scsendermpdport", value))
            sendermpdport = atoi(value.c_str());
Jean-Francois Dockes's avatar
typo    
Jean-Francois Dockes committed
410

411
        g_lumincompat = g_config->getBool("lumincompat", g_lumincompat);
412
413
414
415
416
417
418
    } else {
        // g_configfilename is empty. Create an empty config anyway
        g_config = new ConfSimple(string(), 1, true);
        if (!g_config || !g_config->ok()) {
            cerr << "Could not create empty config\n";
            return 1;
        }
419
    }
420

421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
    if (Logger::getTheLog(logfilename) == 0) {
        cerr << "Can't initialize log" << endl;
        return 1;
    }
    Logger::getTheLog("")->reopen(logfilename);
    Logger::getTheLog("")->setLogLevel(Logger::LogLevel(loglevel));

    // If a streaming service is enabled, we need a Media
    // Server. We let a static ContentDirectory method decide this
    // for us. The way we then implement it depends on the command
    // line option (see the enum comments near the top of the file):
    enableMediaServer = ContentDirectory::mediaServerNeeded();
    switch (arg_msmode) {
    case MSOnly:
        inprocessms = true;
        msonly = true;
        break;
438
    case CombinedEmbedded:
439
440
        inprocessms = true;
        msonly = false;
441
442
        msroot = false;
        break;
443
444
    case RdrOnly:
        inprocessms = false;
445
        msonly = false;
446
        break;
447
448
    case CombinedMultiDev:
    case Default:
449
    default:
450
        inprocessms = true;
451
        msonly = false;
452
        msroot = true;
453
454
455
        break;
    }

456
    // If neither OH nor AV are enabled, run as pure media server. This
457
458
459
    // is another way to do it besides the -m option
    if (!enableOH && !enableAV) {
        msonly = true;
460
        inprocessms = true;
461
462
    }
    
463
464
    Pidfile pidfile(pidfilename);

465
466
    // If started by root, we use the pidfile and we will change the
    // uid (later). First part follows
467
468
    uid_t runas(0);
    gid_t runasg(0);
469
470
471
472
473
    struct passwd *pass = getpwnam(upmpdcliuser.c_str());
    if (pass) {
        runas = pass->pw_uid;
        runasg = pass->pw_gid;
    }
474
    if (geteuid() == 0) {
475
        if (runas == 0) {
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
            LOGFAT("upmpdcli won't run as root and user " << upmpdcliuser << 
                   " does not exist " << endl);
            return 1;
        }
        runas = pass->pw_uid;
        runasg = pass->pw_gid;

        pid_t pid;
        if ((pid = pidfile.open()) != 0) {
            LOGFAT("Can't open pidfile: " << pidfile.getreason() << 
                   ". Return (other pid?): " << pid << endl);
            return 1;
        }
        if (pidfile.write_pid() != 0) {
            LOGFAT("Can't write pidfile: " << pidfile.getreason() << endl);
            return 1;
        }
493
        if (opts.cachedir.empty())
494
            opts.cachedir = "/var/cache/upmpdcli";
495
496
497
498
499
500
501
502
503
504
505
506
    } else if (runas == geteuid()) {
        // Already running as upmpdcli user. There are actually 2
        // possibilities: either we were initially started as
        // upmpdcli, and we should be using ~upmpdcli/.cache as work
        // directory, or we were exec'd by a master process started as
        // root, and we should be using /var/cache/upmpdcli. We have
        // no way to decide actually, another command line option
        // would be needed. For now, behave as if exec'd by a master
        // process, because this is what happens if the default
        // package was installed. One way to fix this if this if it is
        // wrong is to set cachedir in the configuration file
        // (opts.cachedir will be non-empty then).
507
        if (opts.cachedir.empty())
508
            opts.cachedir = "/var/cache/upmpdcli";
509
    } else {
510
        if (opts.cachedir.empty())
511
            opts.cachedir = path_cat(path_tildexpand("~") , "/.cache/upmpdcli");
512
    }
513

514
    g_cachedir = opts.cachedir;
515
516
517
518
519
520
521
522
    if (!path_makepath(opts.cachedir, 0755)) {
        LOGERR("makepath("<< opts.cachedir << ") : errno : " << errno << endl);
        cerr << "Can't create " << opts.cachedir << endl;
        return 1;
    }

    string statefn = path_cat(opts.cachedir, "/upmstate");
    g_state = new ConfSimple(statefn.c_str());
523
    
524
    opts.cachefn.clear();
525
    if (!msonly && ohmetapersist) {
526
        opts.cachefn = path_cat(opts.cachedir, "/metacache");
527
528
529
        int fd;
        if ((fd = open(opts.cachefn.c_str(), O_CREAT|O_RDWR, 0644)) < 0) {
            LOGERR("creat("<< opts.cachefn << ") : errno : " << errno << endl);
530
        } else {
531
            close(fd);
532
533
534
535
536
537
538
539
540
541
542
        }
    }
    
    if ((op_flags & OPT_D)) {
        if (daemon(1, 0)) {
            LOGFAT("Daemon failed: errno " << errno << endl);
            return 1;
        }
    }

    if (geteuid() == 0) {
543
544
        // Need to rewrite pid, it may have changed with the daemon call. Also
        // adjust file ownership and access.
545
546
        pidfile.write_pid();
        if (!logfilename.empty() && logfilename.compare("stderr")) {
547
            if (chown(logfilename.c_str(), runas, -1) < 0 && errno != ENOENT) {
548
549
550
                LOGERR("chown("<<logfilename<<") : errno : " << errno << endl);
            }
        }
551
552
553
554
555
556
557
558
559
560
561
562
        if (chown(opts.cachedir.c_str(), runas, -1) != 0) {
            LOGERR("chown("<< opts.cachedir << ") : errno : " << errno << endl);
        }
        if (chown(statefn.c_str(), runas, -1) != 0) {
            LOGERR("chown("<< statefn << ") : errno : " << errno << endl);
        }
        if (!opts.cachefn.empty()) {
            if (chown(opts.cachefn.c_str(), runas, -1) != 0) {
                LOGERR("chown("<< opts.cachefn << ") : errno : " <<
                       errno << endl);
            }
        }
563
564
565
566
        if (!g_configfilename.empty()) {
            ensureconfreadable(g_configfilename.c_str(), upmpdcliuser.c_str(),
                               runas, runasg);
        }
567
568
569
570
571
572
573
574
        if (initgroups(upmpdcliuser.c_str(), runasg) < 0) {
            LOGERR("initgroup failed. Errno: " << errno << endl);
        }
        if (setuid(runas) < 0) {
            LOGFAT("Can't set my uid to " << runas << " current: " << geteuid()
                   << endl);
            return 1;
        }
575
#if 0
576
577
578
579
580
581
582
583
584
585
        gid_t list[100];
        int ng = getgroups(100, list);
        cerr << "GROUPS: ";
        for (int i = 0; i < ng; i++) {
            cerr << int(list[i]) << " ";
        }
        cerr << endl;
#endif
    }

586
587

/////////////////////////// Dropped root /////////////////////////////
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630

    if (sc2mpdpath.empty()) {
        // Do we have an sc2mpd command installed (for songcast)?
        if (!ExecCmd::which("sc2mpd", sc2mpdpath))
            sc2mpdpath.clear();
    }
    if (senderpath.empty()) {
        // Do we have an scmakempdsender command installed (for
        // starting the songcast sender and its auxiliary mpd)?
        if (!ExecCmd::which("scmakempdsender", senderpath))
            senderpath.clear();
    }
    
    if (!sc2mpdpath.empty()) {
        // Check if sc2mpd is actually there
        if (access(sc2mpdpath.c_str(), X_OK|R_OK) != 0) {
            LOGERR("Specified path for sc2mpd: " << sc2mpdpath << 
                   " is not executable" << endl);
            sc2mpdpath.clear();
        }
    }

    if (!senderpath.empty()) {
        // Check that both the starter script and the mpd2sc sender
        // command are executable. We'll assume that mpd is ok
        if (access(senderpath.c_str(), X_OK|R_OK) != 0) {
            LOGERR("The specified path for the sender starter script: ["
                   << senderpath <<
                   "] is not executable, disabling the sender mode.\n");
            senderpath.clear();
        } else {
            string path;
            if (!ExecCmd::which("mpd2sc", path)) {
                LOGERR("Sender starter was specified and found but the mpd2sc "
                       "command is not found (or executable). Disabling "
                       "the sender mode.\n");
                senderpath.clear();
            }
        }
    }


    // Initialize MPD client object. Retry until it works or power fail.
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
    if (!msonly) {
        int mpdretrysecs = 2;
        for (;;) {
            mpdclip = new MPDCli(mpdhost, mpdport, mpdpassword);
            if (mpdclip == 0) {
                LOGFAT("Can't allocate MPD client object" << endl);
                return 1;
            }
            if (!mpdclip->ok()) {
                LOGERR("MPD connection failed" << endl);
                delete mpdclip;
                mpdclip = 0;
                sleep(mpdretrysecs);
                mpdretrysecs = MIN(2*mpdretrysecs, 120);
            } else {
                break;
            }
648
        }
649
650
        const MpdStatus& mpdstat = mpdclip->getStatus();
        // Only the "special" upmpdcli 0.19.16 version has patch != 0
651
        g_enableL16 = mpdstat.versmajor >= 1 || mpdstat.versminor >= 20 ||
652
653
654
655
656
657
658
659
660
661
662
            mpdstat.verspatch >= 16;
        // Also L16 is a major source of issues when playing with
        // win10 'cast to device', inciting it to transcode for some
        // reason, with very bad results. So for the future (new in
        // 1.5), only enable it if it's explicitely required by the
        // config.
        bool confl16{false};
        if (g_config) {
            confl16 = g_config->getBool("enablel16", false);
        }
        g_enableL16 = g_enableL16 && confl16;
663
    }
664
    
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    // Initialise lower upnp lib logging. Static so can be done before
    // the rest of init.
    if ((cp = getenv("UPMPDCLI_UPNPLOGFILENAME"))) {
        char *cp1 = getenv("UPMPDCLI_UPNPLOGLEVEL");
        int loglevel = LibUPnP::LogLevelNone;
        if (cp1) {
            loglevel = atoi(cp1);
        }
        loglevel = loglevel < 0 ? 0: loglevel;
        if (loglevel != LibUPnP::LogLevelNone) {
            LibUPnP::setLogFileName(cp, LibUPnP::LogLevel(loglevel));
        }
    }

679
680
681
682
    // Initialize libupnpp, and check health
    LibUPnP *mylib = 0;
    string hwaddr;
    int libretrysecs = 10;
683
684
685
686
    int flags = LibUPnP::UPNPPINIT_FLAG_SERVERONLY;;
    if (!g_config->getBool("useipv6", false)) {
        flags |= LibUPnP::UPNPPINIT_FLAG_NOIPV6;
    }
687
688
689
    for (;;) {
        // Libupnp init fails if we're started at boot and the network
        // is not ready yet. So retry this forever
690
691
692
693
694
        if (LibUPnP::init(flags,
                          LibUPnP::UPNPPINIT_OPTION_IFNAMES, &iface,
                          LibUPnP::UPNPPINIT_OPTION_IPV4, &upnpip,
                          LibUPnP::UPNPPINIT_OPTION_PORT, upport,
                          LibUPnP::UPNPPINIT_OPTION_END)) {
695
696
697
698
699
            break;
        }
        sleep(libretrysecs);
        libretrysecs = MIN(2*libretrysecs, 120);
    }
700
701
    mylib = LibUPnP::getLibUPnP();
    if (!mylib || !mylib->ok()) {
702
703
704
705
        LOGFAT("Lib init failed: " <<
               mylib->errAsString("main", mylib->getInitError()) << endl);
        return 1;
    }
706
707
    hwaddr = mylib->hwaddr();
    
708
    // Create unique IDs for renderer and possible media server
709
710
    if (!g_config || !g_config->get("msfriendlyname", fnameMS)) {
        fnameMS = friendlyname + "-mediaserver";
711
    }
712
    uuidMS = LibUPnP::makeDevUUID(fnameMS, hwaddr);
713

714
715
716
717
718
719
    // If running as mediaserver only, make sure we don't conflict
    // with a possible renderer
    if (msonly) {
        pidfilename = pidfilename + "-ms";
    }

720
721
    opts.iconpath = iconpath;
    opts.presentationhtml = presentationhtml;
722
723
724
725
726
727
728
729
730
731
    if (ownqueue)
        opts.options |= UpMpd::upmpdOwnQueue;
    if (enableOH)
        opts.options |= UpMpd::upmpdDoOH;
    if (ohmetapersist)
        opts.options |= UpMpd::upmpdOhMetaPersist;
    if (!sc2mpdpath.empty()) {
        opts.sc2mpdpath = sc2mpdpath;
        opts.options |= UpMpd::upmpdOhReceiver;
    }
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
    if (!screceiverstatefile.empty()) {
        opts.screceiverstatefile = screceiverstatefile;
        int fd;
        if ((fd = open(opts.screceiverstatefile.c_str(),
                       O_CREAT|O_RDWR, 0644)) < 0) {
            LOGERR("creat(" << opts.screceiverstatefile << ") : errno : "
                   << errno << endl);
        } else {
            close(fd);
            if (geteuid() == 0 && chown(opts.screceiverstatefile.c_str(),
                                        runas, -1) != 0) {
                LOGERR("chown(" << opts.screceiverstatefile << ") : errno : "
                       << errno << endl);
            }
        }
    }
748
749
750
751
752
753
754
755
    if (!senderpath.empty()) {
        opts.options |= UpMpd::upmpdOhSenderReceiver;
        opts.senderpath = senderpath;
        opts.sendermpdport = sendermpdport;
    }

    if (!enableAV)
        opts.options |= UpMpd::upmpdNoAV;
756

757
758
    setupsigs();

759
    UpMpd *mediarenderer{nullptr};
760
    if (!msonly) {
761
        mediarenderer = new UpMpd(hwaddr, friendlyname,
762
                                  ohProductDesc, mpdclip, opts);
763
        UpMpdOpenHome *oh = mediarenderer->getoh();
764
765
766
        // rootdevice is only used if we implement the media server as
        // an embedded device, which is mostly for testing purposes
        // and not done by default
767
        if (nullptr != oh) {
768
            rootdevice = oh;
769
770
771
772
            devs.push_back(oh);
        }
        UpMpdMediaRenderer *av = mediarenderer->getav();
        if (nullptr != av) {
773
            rootdevice = av;
774
775
            devs.push_back(av);
        }
776
    }
777

778
779
780
781
    if (inprocessms && !startMediaServer(enableMediaServer)) {
        LOGERR("Could not start media server\n");
        std::cerr << "Could not start media server\n";
        return 0;
782
    }
783

784
785
786
787
788
    if (!msonly) {
        LOGDEB("Renderer event loop" << endl);
        mediarenderer->startnoloops();
    }

789
    pause();
790
791
792
    LOGDEB("Event loop returned" << endl);
    return 0;
}
793
794
795
796
797
798
799
800
801
802
803
804

// Read file from datadir
bool readLibFile(const std::string& name, std::string& contents)
{
    string path = path_cat(g_datadir, name);
    string reason;
    if (!file_to_string(path, contents, &reason)) {
        LOGERR("readLibFile: error reading " << name << " : " << reason << endl);
        return false;
    }
    return true;
}