a/src/upmpdutils.cxx b/src/upmpdutils.cxx
1
/* Copyright (C) 2014 J.F.Dockes
1
/* Copyright (C) 2014 J.F.Dockes
2
 *     This program is free software; you can redistribute it and/or modify
2
 *   This program is free software; you can redistribute it and/or modify
3
 *     it under the terms of the GNU General Public License as published by
3
 *   it under the terms of the GNU General Public License as published by
4
 *     the Free Software Foundation; either version 2 of the License, or
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *     (at your option) any later version.
5
 *   (at your option) any later version.
6
 *
6
 *
7
 *     This program is distributed in the hope that it will be useful,
7
 *   This program is distributed in the hope that it will be useful,
8
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *     GNU General Public License for more details.
10
 *   GNU General Public License for more details.
11
 *
11
 *
12
 *     You should have received a copy of the GNU General Public License
12
 *   You should have received a copy of the GNU General Public License
13
 *     along with this program; if not, write to the
13
 *   along with this program; if not, write to the
14
 *     Free Software Foundation, Inc.,
14
 *   Free Software Foundation, Inc.,
15
 *     59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
16
 */
17
17
18
// 
18
//
19
// This file has a number of mostly uninteresting and badly
19
// This file has a number of mostly uninteresting and badly
20
// implemented small utility functions. This is a bit ugly, but I am
20
// implemented small utility functions. This is a bit ugly, but I am
21
// not linking to Qt or glib just to get path-concatenating
21
// not linking to Qt or glib just to get path-concatenating
22
// functions...
22
// functions...
23
23
...
...
52
52
53
using namespace std;
53
using namespace std;
54
using namespace UPnPP;
54
using namespace UPnPP;
55
using namespace UPnPClient;
55
using namespace UPnPClient;
56
56
57
// Append system error string to input string
58
void catstrerror(string *reason, const char *what, int _errno)
59
{
60
    if (!reason)
61
  return;
62
    if (what)
63
  reason->append(what);
64
65
    reason->append(": errno: ");
66
67
    char nbuf[20];
68
    sprintf(nbuf, "%d", _errno);
69
    reason->append(nbuf);
70
71
    reason->append(" : ");
72
73
#ifdef sun
74
    // Note: sun strerror is noted mt-safe ??
75
    reason->append(strerror(_errno));
76
#else
77
#define ERRBUFSZ 200    
78
    char errbuf[ERRBUFSZ];
79
    // There are 2 versions of strerror_r. 
80
    // - The GNU one returns a pointer to the message (maybe
81
    //   static storage or supplied buffer).
82
    // - The POSIX one always stores in supplied buffer and
83
    //   returns 0 on success. As the possibility of error and
84
    //   error code are not specified, we're basically doomed
85
    //   cause we can't use a test on the 0 value to know if we
86
    //   were returned a pointer... 
87
    // Also couldn't find an easy way to disable the gnu version without
88
    // changing the cxxflags globally, so forget it. Recent gnu lib versions
89
    // normally default to the posix version.
90
    // At worse we get no message at all here.
91
    errbuf[0] = 0;
92
    (void)strerror_r(_errno, errbuf, ERRBUFSZ);
93
    reason->append(errbuf);
94
#endif
95
}
96
97
bool file_to_string(const string &fn, string &data, string *reason)
98
{
99
    const int RDBUFSZ = 4096;
100
    bool ret = false;
101
    int fd = -1;
102
    struct stat st;
103
104
    fd = open(fn.c_str(), O_RDONLY|O_STREAMING);
105
    if (fd < 0 || fstat(fd, &st) < 0) {
106
        catstrerror(reason, "open/stat", errno);
107
        return false;
108
    }
109
110
    data.reserve(st.st_size+1);
111
112
    char buf[RDBUFSZ];
113
    for (;;) {
114
  int n = read(fd, buf, RDBUFSZ);
115
  if (n < 0) {
116
      catstrerror(reason, "read", errno);
117
      goto out;
118
  }
119
  if (n == 0)
120
      break;
121
122
        data.append(buf, n);
123
    }
124
125
    ret = true;
126
out:
127
    if (fd >= 0)
128
  close(fd);
129
    return ret;
130
}
131
132
void path_catslash(string &s) {
133
    if (s.empty() || s[s.length() - 1] != '/')
134
  s += '/';
135
}
136
137
string path_cat(const string &s1, const string &s2) {
138
    string res = s1;
139
    path_catslash(res);
140
    res +=  s2;
141
    return res;
142
}
143
144
bool path_exists(const string& path)
145
{
146
    return access(path.c_str(), 0) == 0;
147
}
148
149
bool path_isabsolute(const string &path)
150
{
151
    if (!path.empty() && (path[0] == '/'
152
#ifdef _WIN32
153
                          || path_isdriveabs(path)
154
#endif
155
            )) {
156
        return true;
157
    } 
158
    return false;
159
}
160
161
string path_home()
162
{
163
    uid_t uid = getuid();
164
165
    struct passwd *entry = getpwuid(uid);
166
    if (entry == 0) {
167
  const char *cp = getenv("HOME");
168
  if (cp)
169
      return cp;
170
  else 
171
  return "/";
172
    }
173
174
    string homedir = entry->pw_dir;
175
    path_catslash(homedir);
176
    return homedir;
177
}
178
179
string path_tildexpand(const string &s) 
180
{
181
    if (s.empty() || s[0] != '~')
182
  return s;
183
    string o = s;
184
    if (s.length() == 1) {
185
  o.replace(0, 1, path_home());
186
    } else if  (s[1] == '/') {
187
  o.replace(0, 2, path_home());
188
    } else {
189
  string::size_type pos = s.find('/');
190
  int l = (pos == string::npos) ? s.length() - 1 : pos - 1;
191
  struct passwd *entry = getpwnam(s.substr(1, l).c_str());
192
  if (entry)
193
      o.replace(0, l+1, entry->pw_dir);
194
    }
195
    return o;
196
}
197
198
bool path_makepath(const string& path, int mode)
199
{
200
    if (path.empty() || path[0] != '/') {
201
        return false;
202
    }
203
    vector<string> vpath;
204
    stringToTokens(path, vpath, "/", true);
205
    string npath;
206
    for (auto it = vpath.begin(); it != vpath.end(); it++) {
207
        npath += string("/") + *it;
208
        if (access(npath.c_str(), 0) < 0) {
209
            if (mkdir(npath.c_str(), mode)) {
210
                return false;
211
            }
212
        }
213
    }
214
    return true;
215
}
216
217
void trimstring(string &s, const char *ws)
218
{
219
    string::size_type pos = s.find_first_not_of(ws);
220
    if (pos == string::npos) {
221
  s.clear();
222
  return;
223
    }
224
    s.replace(0, pos, string());
225
226
    pos = s.find_last_not_of(ws);
227
    if (pos != string::npos && pos != s.length()-1)
228
  s.replace(pos+1, string::npos, string());
229
}
230
231
void stringToTokens(const string& str, vector<string>& tokens,
232
          const string& delims, bool skipinit)
233
{
234
    string::size_type startPos = 0, pos;
235
236
    // Skip initial delims, return empty if this eats all.
237
    if (skipinit && 
238
  (startPos = str.find_first_not_of(delims, 0)) == string::npos) {
239
  return;
240
    }
241
    while (startPos < str.size()) { 
242
        // Find next delimiter or end of string (end of token)
243
        pos = str.find_first_of(delims, startPos);
244
245
        // Add token to the vector and adjust start
246
  if (pos == string::npos) {
247
      tokens.push_back(str.substr(startPos));
248
      break;
249
  } else if (pos == startPos) {
250
      // Dont' push empty tokens after first
251
      if (tokens.empty())
252
      tokens.push_back(string());
253
      startPos = ++pos;
254
  } else {
255
      tokens.push_back(str.substr(startPos, pos - startPos));
256
      startPos = ++pos;
257
  }
258
    }
259
}
260
261
262
template <class T> bool stringToStrings(const string &s, T &tokens, 
263
                                        const string& addseps)
264
{
265
    string current;
266
    tokens.clear();
267
    enum states {SPACE, TOKEN, INQUOTE, ESCAPE};
268
    states state = SPACE;
269
    for (unsigned int i = 0; i < s.length(); i++) {
270
  switch (s[i]) {
271
        case '"': 
272
      switch(state) {
273
            case SPACE: 
274
      state=INQUOTE; continue;
275
            case TOKEN: 
276
          current += '"';
277
      continue;
278
            case INQUOTE: 
279
                tokens.insert(tokens.end(), current);
280
      current.clear();
281
      state = SPACE;
282
      continue;
283
            case ESCAPE:
284
          current += '"';
285
          state = INQUOTE;
286
                continue;
287
      }
288
      break;
289
        case '\\': 
290
      switch(state) {
291
            case SPACE: 
292
            case TOKEN: 
293
                current += '\\';
294
                state=TOKEN; 
295
                continue;
296
            case INQUOTE: 
297
                state = ESCAPE;
298
                continue;
299
            case ESCAPE:
300
                current += '\\';
301
                state = INQUOTE;
302
                continue;
303
      }
304
      break;
305
306
        case ' ': 
307
        case '\t': 
308
        case '\n': 
309
        case '\r': 
310
      switch(state) {
311
            case SPACE: 
312
                continue;
313
            case TOKEN: 
314
      tokens.insert(tokens.end(), current);
315
      current.clear();
316
      state = SPACE;
317
      continue;
318
            case INQUOTE: 
319
            case ESCAPE:
320
                current += s[i];
321
                continue;
322
      }
323
      break;
324
325
        default:
326
            if (!addseps.empty() && addseps.find(s[i]) != string::npos) {
327
                switch(state) {
328
                case ESCAPE:
329
                    state = INQUOTE;
330
                    break;
331
                case INQUOTE: 
332
                    break;
333
                case SPACE: 
334
                    tokens.insert(tokens.end(), string(1, s[i]));
335
                    continue;
336
                case TOKEN: 
337
                    tokens.insert(tokens.end(), current);
338
                    current.erase();
339
                    tokens.insert(tokens.end(), string(1, s[i]));
340
                    state = SPACE;
341
                    continue;
342
                }
343
            } else switch(state) {
344
                case ESCAPE:
345
                    state = INQUOTE;
346
                    break;
347
                case SPACE: 
348
                    state = TOKEN;
349
                    break;
350
                case TOKEN: 
351
                case INQUOTE: 
352
                    break;
353
                }
354
      current += s[i];
355
  }
356
    }
357
    switch(state) {
358
    case SPACE: 
359
  break;
360
    case TOKEN: 
361
  tokens.insert(tokens.end(), current);
362
  break;
363
    case INQUOTE: 
364
    case ESCAPE:
365
  return false;
366
    }
367
    return true;
368
}
369
370
template bool stringToStrings<vector<string> >(const string &, 
371
                         vector<string> &,const string&);
372
373
// Translate 0-100% MPD volume to UPnP VolumeDB: we do db upnp-encoded
57
// Translate 0-100% MPD volume to UPnP VolumeDB: we do db upnp-encoded
374
// values from -10240 (0%) to 0 (100%)
58
// values from -10240 (0%) to 0 (100%)
375
int percentodbvalue(int value)
59
int percentodbvalue(int value)
376
{
60
{
377
    int dbvalue;
61
    int dbvalue;
378
    if (value == 0) {
62
    if (value == 0) {
379
        dbvalue = -10240;
63
        dbvalue = -10240;
380
    } else {
64
    } else {
381
        float ratio = float(value)*value / 10000.0;
65
        float ratio = float(value) * value / 10000.0;
382
        float db = 10 * log10(ratio);
66
        float db = 10 * log10(ratio);
383
        dbvalue = int(256 * db);
67
        dbvalue = int(256 * db);
384
    }
68
    }
385
    return dbvalue;
69
    return dbvalue;
386
}
70
}
...
...
395
79
396
// Translate VolumeDB to MPD 0-100
80
// Translate VolumeDB to MPD 0-100
397
int dbvaluetopercent(int dbvalue)
81
int dbvaluetopercent(int dbvalue)
398
{
82
{
399
    float db = float(dbvalue) / 256.0;
83
    float db = float(dbvalue) / 256.0;
400
    float vol = exp10(db/10);
84
    float vol = exp10(db / 10);
401
    int percent = floor(sqrt(vol * 10000.0));
85
    int percent = floor(sqrt(vol * 10000.0));
402
    if (percent < 0)  percent = 0;
86
    if (percent < 0) {
403
    if (percent > 100)    percent = 100;
87
        percent = 0;
88
    }
89
    if (percent > 100) {
90
        percent = 100;
91
    }
404
    return percent;
92
    return percent;
405
}
93
}
406
94
407
// Get from ssl unordered_map, return empty string for non-existing
95
// Get from ssl unordered_map, return empty string for non-existing
408
// key (so this only works for data where this behaviour makes sense).
96
// key (so this only works for data where this behaviour makes sense).
409
const string& mapget(const unordered_map<string, string>& im, const string& k)
97
const string& mapget(const unordered_map<string, string>& im, const string& k)
410
{
98
{
411
    static string ns; // null string
99
    static string ns; // null string
412
    unordered_map<string, string>::const_iterator it = im.find(k);
100
    unordered_map<string, string>::const_iterator it = im.find(k);
413
    if (it == im.end())
101
    if (it == im.end()) {
414
        return ns;
102
        return ns;
415
    else
103
    } else {
416
        return it->second;
104
        return it->second;
105
    }
417
}
106
}
418
107
419
unordered_map<string, string> 
108
unordered_map<string, string>
420
diffmaps(const unordered_map<string, string>& old,
109
diffmaps(const unordered_map<string, string>& old,
421
         const unordered_map<string, string>& newer)
110
         const unordered_map<string, string>& newer)
422
{
111
{
423
    unordered_map<string, string>  out;
112
    unordered_map<string, string>  out;
424
    
113
425
    for (unordered_map<string, string>::const_iterator it = newer.begin();
114
    for (unordered_map<string, string>::const_iterator it = newer.begin();
426
         it != newer.end(); it++) {
115
            it != newer.end(); it++) {
427
        unordered_map<string, string>::const_iterator ito = old.find(it->first);
116
        unordered_map<string, string>::const_iterator ito = old.find(it->first);
428
        if (ito == old.end() || ito->second.compare(it->second))
117
        if (ito == old.end() || ito->second.compare(it->second)) {
429
            out[it->first] = it->second;
118
            out[it->first] = it->second;
119
        }
430
    }
120
    }
431
    return out;
121
    return out;
432
}
122
}
433
123
434
// Bogus didl fragment maker. We probably don't need a full-blown XML
124
// Bogus didl fragment maker. We probably don't need a full-blown XML
435
// helper here
125
// helper here
436
string didlmake(const UpSong& song)
126
string didlmake(const UpSong& song)
437
{
127
{
438
    ostringstream ss;
128
    ostringstream ss;
439
    ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
129
    ss << "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
440
        "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
130
       "<DIDL-Lite xmlns:dc=\"http://purl.org/dc/elements/1.1/\" "
441
        "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
131
       "xmlns:upnp=\"urn:schemas-upnp-org:metadata-1-0/upnp/\" "
442
        "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
132
       "xmlns=\"urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/\" "
443
        "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
133
       "xmlns:dlna=\"urn:schemas-dlna-org:metadata-1-0/\">"
444
       << "<item restricted=\"1\">";
134
       << "<item restricted=\"1\">";
445
    ss << "<orig>mpd</orig>";
135
    ss << "<orig>mpd</orig>";
136
    {
446
    {   const string& val = song.title;
137
        const string& val = song.title;
447
        ss << "<dc:title>" << SoapHelp::xmlQuote(val) << "</dc:title>";
138
        ss << "<dc:title>" << SoapHelp::xmlQuote(val) << "</dc:title>";
448
    }
139
    }
449
  
140
450
    // TBD Playlists etc?
141
    // TBD Playlists etc?
451
    ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
142
    ss << "<upnp:class>object.item.audioItem.musicTrack</upnp:class>";
452
143
144
    {
453
    {   const string& val = song.artist;
145
        const string& val = song.artist;
454
        if (!val.empty()) {
146
        if (!val.empty()) {
455
            string a = SoapHelp::xmlQuote(val);
147
            string a = SoapHelp::xmlQuote(val);
456
            ss << "<dc:creator>" << a << "</dc:creator>" << 
148
            ss << "<dc:creator>" << a << "</dc:creator>" <<
457
                "<upnp:artist>" << a << "</upnp:artist>";
149
               "<upnp:artist>" << a << "</upnp:artist>";
150
        }
458
        }
151
    }
152
459
    }
153
    {
460
461
    {   const string& val = song.album;
154
        const string& val = song.album;
462
        if (!val.empty()) {
155
        if (!val.empty()) {
463
            ss << "<upnp:album>" << SoapHelp::xmlQuote(val) << "</upnp:album>";
156
            ss << "<upnp:album>" << SoapHelp::xmlQuote(val) << "</upnp:album>";
464
        }
157
        }
465
    }
158
    }
466
159
160
    {
467
    {   const string& val = song.genre;
161
        const string& val = song.genre;
468
        if (!val.empty()) {
162
        if (!val.empty()) {
469
            ss << "<upnp:genre>" << SoapHelp::xmlQuote(val) << "</upnp:genre>";
163
            ss << "<upnp:genre>" << SoapHelp::xmlQuote(val) << "</upnp:genre>";
470
        }
164
        }
471
    }
165
    }
472
166
167
    {
473
    {string val = song.tracknum;
168
        string val = song.tracknum;
474
        // MPD may return something like xx/yy
169
        // MPD may return something like xx/yy
475
        string::size_type spos = val.find("/");
170
        string::size_type spos = val.find("/");
476
        if (spos != string::npos)
171
        if (spos != string::npos) {
477
            val = val.substr(0, spos);
172
            val = val.substr(0, spos);
173
        }
478
        if (!val.empty()) {
174
        if (!val.empty()) {
479
            ss << "<upnp:originalTrackNumber>" << val << 
175
            ss << "<upnp:originalTrackNumber>" << val <<
480
                "</upnp:originalTrackNumber>";
176
               "</upnp:originalTrackNumber>";
177
        }
481
        }
178
    }
179
482
    }
180
    {
483
484
    {const string& val = song.artUri;
181
        const string& val = song.artUri;
485
        if (!val.empty()) {
182
        if (!val.empty()) {
486
            ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) << 
183
            ss << "<upnp:albumArtURI>" << SoapHelp::xmlQuote(val) <<
487
                "</upnp:albumArtURI>";
184
               "</upnp:albumArtURI>";
488
        }
185
        }
489
    }
186
    }
490
187
491
    // TBD: the res element normally has size, sampleFrequency,
188
    // TBD: the res element normally has size, sampleFrequency,
492
    // nrAudioChannels and protocolInfo attributes, which are bogus
189
    // nrAudioChannels and protocolInfo attributes, which are bogus
493
    // for the moment. partly because MPD does not supply them.  And
190
    // for the moment. partly because MPD does not supply them.  And
494
    // mostly everything is bogus if next is set...  
191
    // mostly everything is bogus if next is set...
495
192
496
    ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000) 
193
    ss << "<res " << "duration=\"" << upnpduration(song.duration_secs * 1000)
497
       << "\" "
194
       << "\" "
498
  // Bitrate keeps changing for VBRs and forces events. Keeping
195
       // Bitrate keeps changing for VBRs and forces events. Keeping
499
  // it out for now.
196
       // it out for now.
500
  //       << "bitrate=\"" << mpds.kbrate << "\" "
197
       //       << "bitrate=\"" << mpds.kbrate << "\" "
501
       << "sampleFrequency=\"44100\" audioChannels=\"2\" "
198
       << "sampleFrequency=\"44100\" audioChannels=\"2\" "
502
       << "protocolInfo=\"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000\""
199
       << "protocolInfo=\"http-get:*:audio/mpeg:DLNA.ORG_PN=MP3;DLNA.ORG_OP=01;DLNA.ORG_CI=0;DLNA.ORG_FLAGS=01700000000000000000000000000000\""
503
       << ">"
200
       << ">"
504
       << SoapHelp::xmlQuote(song.uri) 
201
       << SoapHelp::xmlQuote(song.uri)
505
       << "</res>"
202
       << "</res>"
506
       << "</item></DIDL-Lite>";
203
       << "</item></DIDL-Lite>";
507
    return ss.str();
204
    return ss.str();
508
}
205
}
509
206
510
bool uMetaToUpSong(const string& metadata, UpSong *ups)
207
bool uMetaToUpSong(const string& metadata, UpSong *ups)
511
{
208
{
512
    if (ups == 0)
209
    if (ups == 0) {
513
        return false;
210
        return false;
211
    }
514
212
515
    UPnPDirContent dirc;
213
    UPnPDirContent dirc;
516
    if (!dirc.parse(metadata) || dirc.m_items.size() == 0) {
214
    if (!dirc.parse(metadata) || dirc.m_items.size() == 0) {
517
        return false;
215
        return false;
518
    }
216
    }
519
    UPnPDirObject& dobj = *dirc.m_items.begin();
217
    UPnPDirObject& dobj = *dirc.m_items.begin();
520
218
521
    ups->artist = dobj.f2s("upnp:artist", false);
219
    ups->artist = dobj.f2s("upnp:artist", false);
522
    ups->album = dobj.f2s("upnp:album", false);
220
    ups->album = dobj.f2s("upnp:album", false);
523
    ups->title = dobj.m_title;
221
    ups->title = dobj.m_title;
524
    string stmp = dobj.f2s("duration", true); 
222
    string stmp = dobj.f2s("duration", true);
525
    if (!stmp.empty()) {
223
    if (!stmp.empty()) {
526
        ups->duration_secs = upnpdurationtos(stmp);
224
        ups->duration_secs = upnpdurationtos(stmp);
527
    } else {
225
    } else {
528
        ups->duration_secs = 0;
226
        ups->duration_secs = 0;
529
    }
227
    }
...
...
538
string regsub1(const string& sexp, const string& input, const string& repl)
236
string regsub1(const string& sexp, const string& input, const string& repl)
539
{
237
{
540
    regex_t expr;
238
    regex_t expr;
541
    int err;
239
    int err;
542
    const int ERRSIZE = 200;
240
    const int ERRSIZE = 200;
543
    char errbuf[ERRSIZE+1];
241
    char errbuf[ERRSIZE + 1];
544
    regmatch_t pmatch[10];
242
    regmatch_t pmatch[10];
545
243
546
    if ((err = regcomp(&expr, sexp.c_str(), REG_EXTENDED))) {
244
    if ((err = regcomp(&expr, sexp.c_str(), REG_EXTENDED))) {
547
        regerror(err, &expr, errbuf, ERRSIZE);
245
        regerror(err, &expr, errbuf, ERRSIZE);
548
        LOGERR("upmpd: regsub1: regcomp() failed: " << errbuf << endl);
246
        LOGERR("upmpd: regsub1: regcomp() failed: " << errbuf << endl);
549
        return string();
247
        return string();
550
    }
248
    }
551
    
249
552
    if ((err = regexec(&expr, input.c_str(), 10, pmatch, 0))) {
250
    if ((err = regexec(&expr, input.c_str(), 10, pmatch, 0))) {
553
        regerror(err, &expr, errbuf, ERRSIZE);
251
        regerror(err, &expr, errbuf, ERRSIZE);
554
        //LOGDEB("upmpd: regsub1: regexec(" << sexp << ") failed: "
252
        //LOGDEB("upmpd: regsub1: regexec(" << sexp << ") failed: "
555
        //    <<  errbuf << endl);
253
        //    <<  errbuf << endl);
556
        regfree(&expr);
254
        regfree(&expr);
...
...
565
    out += repl;
263
    out += repl;
566
    out += input.substr(pmatch[0].rm_eo);
264
    out += input.substr(pmatch[0].rm_eo);
567
    regfree(&expr);
265
    regfree(&expr);
568
    return out;
266
    return out;
569
}
267
}
570
571
// We do not want to mess with the pidfile content in the destructor:
572
// the lock might still be in use in a child process. In fact as much
573
// as we'd like to reset the pid inside the file when we're done, it
574
// would be very difficult to do it right and it's probably best left
575
// alone.
576
Pidfile::~Pidfile()
577
{
578
    if (m_fd >= 0)
579
  ::close(m_fd);
580
    m_fd = -1;
581
}
582
583
pid_t Pidfile::read_pid()
584
{
585
    int fd = ::open(m_path.c_str(), O_RDONLY);
586
    if (fd == -1)
587
  return (pid_t)-1;
588
589
    char buf[16];
590
    int i = read(fd, buf, sizeof(buf) - 1);
591
    ::close(fd);
592
    if (i <= 0)
593
  return (pid_t)-1;
594
    buf[i] = '\0';
595
    char *endptr;
596
    pid_t pid = strtol(buf, &endptr, 10);
597
    if (endptr != &buf[i])
598
  return (pid_t)-1;
599
    return pid;
600
}
601
602
int Pidfile::flopen()
603
{
604
    const char *path = m_path.c_str();
605
    if ((m_fd = ::open(path, O_RDWR|O_CREAT, 0644)) == -1) {
606
  m_reason = "Open failed: [" + m_path + "]: " + strerror(errno);
607
  return -1;
608
    }
609
610
#ifdef sun
611
    struct flock lockdata;
612
    lockdata.l_start = 0;
613
    lockdata.l_len = 0;
614
    lockdata.l_type = F_WRLCK;
615
    lockdata.l_whence = SEEK_SET;
616
    if (fcntl(m_fd, F_SETLK,  &lockdata) != 0) {
617
  int serrno = errno;
618
  (void)::close(m_fd);
619
  errno = serrno;
620
  m_reason = "fcntl lock failed";
621
  return -1;
622
    }
623
#else
624
    int operation = LOCK_EX | LOCK_NB;
625
    if (flock(m_fd, operation) == -1) {
626
  int serrno = errno;
627
  (void)::close(m_fd);
628
  errno = serrno;
629
  m_reason = "flock failed";
630
  return -1;
631
    }
632
#endif // ! sun
633
634
    if (ftruncate(m_fd, 0) != 0) {
635
  /* can't happen [tm] */
636
  int serrno = errno;
637
  (void)::close(m_fd);
638
  errno = serrno;
639
  m_reason = "ftruncate failed";
640
  return -1;
641
    }
642
    return 0;
643
}
644
645
pid_t Pidfile::open()
646
{
647
    if (flopen() < 0) {
648
  return read_pid();
649
    }
650
    return (pid_t)0;
651
}
652
653
int Pidfile::write_pid()
654
{
655
    /* truncate to allow multiple calls */
656
    if (ftruncate(m_fd, 0) == -1) {
657
  m_reason = "ftruncate failed";
658
  return -1;
659
    }
660
    char pidstr[20];
661
    sprintf(pidstr, "%u", int(getpid()));
662
    lseek(m_fd, 0, 0);
663
    if (::write(m_fd, pidstr, strlen(pidstr)) != (ssize_t)strlen(pidstr)) {
664
  m_reason = "write failed";
665
  return -1;
666
    }
667
    return 0;
668
}
669
670
int Pidfile::close()
671
{
672
    return ::close(m_fd);
673
}
674
675
int Pidfile::remove()
676
{
677
    return unlink(m_path.c_str());
678
}