1 /* Copyright (C) 2004-2019 J.F.Dockes
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU Lesser General Public License as published by
4  *   the Free Software Foundation; either version 2.1 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   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  *   along with this program; if not, write to the
14  *   Free Software Foundation, Inc.,
15  *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
16  *
17  * flock emulation:
18  *   Emulate flock on platforms that lack it, primarily Windows and MinGW.
19  *
20  *   This is derived from sqlite3 sources.
21  *   https://www.sqlite.org/src/finfo?name=src/os_win.c
22  *   https://www.sqlite.org/copyright.html
23  *
24  *   Written by Richard W.M. Jones <rjones.at.redhat.com>
25  *
26  *   Copyright (C) 2008-2019 Free Software Foundation, Inc.
27  *
28  *   This library is free software; you can redistribute it and/or
29  *   modify it under the terms of the GNU Lesser General Public
30  *   License as published by the Free Software Foundation; either
31  *   version 2.1 of the License, or (at your option) any later version.
32  *
33  *   This library is distributed in the hope that it will be useful,
34  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
35  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
36  *   Lesser General Public License for more details.
37  *
38  *   You should have received a copy of the GNU Lesser General Public License
39  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
40  */
41 
42 #ifdef BUILDING_RECOLL
43 #include "autoconfig.h"
44 #else
45 #include "config.h"
46 #endif
47 
48 #include "pathut.h"
49 
50 #include "smallut.h"
51 #ifdef MDU_INCLUDE_LOG
52 #include MDU_INCLUDE_LOG
53 #else
54 #include "log.h"
55 #endif
56 
57 #include <cstdlib>
58 #include <cstring>
59 #include <errno.h>
60 #include <fstream>
61 #include <iostream>
62 #include <math.h>
63 #include <regex>
64 #include <set>
65 #include <sstream>
66 #include <stack>
67 #include <stdio.h>
68 #include <vector>
69 #include <fcntl.h>
70 
71 // Listing directories: we include the normal dirent.h on Unix-derived
72 // systems, and on MinGW, where it comes with a supplemental wide char
73 // interface. When building with MSVC, we use our bundled msvc_dirent.h,
74 // which is equivalent to the one in MinGW
75 #ifdef _MSC_VER
76 #include "msvc_dirent.h"
77 #else // !_MSC_VER
78 #include <dirent.h>
79 #endif // _MSC_VER
80 
81 
82 #ifdef _WIN32
83 
84 #ifndef _MSC_VER
85 #undef WINVER
86 #define WINVER 0x0601
87 #undef _WIN32_WINNT
88 #define _WIN32_WINNT 0x0601
89 #define LOGFONTW void
90 #endif
91 
92 #ifndef NOMINMAX
93 #define NOMINMAX
94 #endif
95 #define WIN32_LEAN_AND_MEAN
96 #define NOGDI
97 #include <windows.h>
98 #include <io.h>
99 #include <sys/stat.h>
100 #include <direct.h>
101 #include <Shlobj.h>
102 #include <Stringapiset.h>
103 
104 #if !defined(S_IFLNK)
105 #define S_IFLNK 0
106 #endif
107 #ifndef S_ISDIR
108 # define S_ISDIR(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFDIR)
109 #endif
110 #ifndef S_ISREG
111 # define S_ISREG(ST_MODE) (((ST_MODE) & _S_IFMT) == _S_IFREG)
112 #endif
113 #define MAXPATHLEN PATH_MAX
114 #ifndef PATH_MAX
115 #define PATH_MAX MAX_PATH
116 #endif
117 #ifndef R_OK
118 #define R_OK 4
119 #endif
120 
121 #define STAT _wstati64
122 #define LSTAT _wstati64
123 #define STATBUF _stati64
124 #define ACCESS _waccess
125 #define OPENDIR ::_wopendir
126 #define DIRHDL _WDIR
127 #define CLOSEDIR _wclosedir
128 #define READDIR ::_wreaddir
129 #define REWINDDIR ::_wrewinddir
130 #define DIRENT _wdirent
131 #define DIRHDL _WDIR
132 #define MKDIR(a,b) _wmkdir(a)
133 #define OPEN ::_wopen
134 #define UNLINK _wunlink
135 #define RMDIR _wrmdir
136 #define CHDIR _wchdir
137 
138 #define SYSPATH(PATH, SPATH) wchar_t PATH ## _buf[2048];      \
139     utf8towchar(PATH, PATH ## _buf, 2048);                    \
140     wchar_t *SPATH = PATH ## _buf;
141 
142 #define ftruncate _chsize_s
143 
144 #ifdef _MSC_VER
145 // For getpid
146 #include <process.h>
147 #define getpid _getpid
148 
149 #define PATHUT_SSIZE_T int
150 #endif // _MSC_VER
151 
152 #else /* !_WIN32 -> */
153 
154 #include <unistd.h>
155 #include <sys/param.h>
156 #include <pwd.h>
157 #include <sys/file.h>
158 #include <sys/stat.h>
159 #include <sys/statvfs.h>
160 #include <sys/types.h>
161 
162 #define STAT stat
163 #define LSTAT lstat
164 #define STATBUF stat
165 #define ACCESS access
166 #define OPENDIR ::opendir
167 #define DIRHDL DIR
168 #define CLOSEDIR closedir
169 #define READDIR ::readdir
170 #define REWINDDIR ::rewinddir
171 #define DIRENT dirent
172 #define DIRHDL DIR
173 #define MKDIR(a,b) mkdir(a,b)
174 #define O_BINARY 0
175 #define OPEN ::open
176 #define UNLINK ::unlink
177 #define RMDIR ::rmdir
178 #define CHDIR ::chdir
179 
180 #define SYSPATH(PATH, SPATH) const char *SPATH = PATH.c_str()
181 
182 
183 #endif /* !_WIN32 */
184 
185 using namespace std;
186 
187 #ifndef PATHUT_SSIZE_T
188 #define PATHUT_SSIZE_T ssize_t
189 #endif
190 
191 
192 #ifdef _WIN32
193 
wchartoutf8(const wchar_t * in,size_t len)194 std::string wchartoutf8(const wchar_t *in, size_t len)
195 {
196     std::string out;
197     wchartoutf8(in, out, len);
198     return out;
199 }
200 
wchartoutf8(const wchar_t * in,std::string & out,size_t wlen)201 bool wchartoutf8(const wchar_t *in, std::string& out, size_t wlen)
202 {
203     LOGDEB1("WCHARTOUTF8: in [" << in << "]\n");
204     out.clear();
205     if (nullptr == in) {
206         return true;
207     }
208     if (wlen == 0) {
209         wlen = wcslen(in);
210     }
211     int flags = WC_ERR_INVALID_CHARS;
212     int bytes = ::WideCharToMultiByte(
213         CP_UTF8, flags, in, wlen, nullptr, 0, nullptr, nullptr);
214     if (bytes <= 0) {
215         LOGERR("wchartoutf8: conversion error1\n");
216         fwprintf(stderr, L"wchartoutf8: conversion error1 for [%s]\n", in);
217         return false;
218     }
219     char *cp = (char *)malloc(bytes+1);
220     if (nullptr == cp) {
221         LOGERR("wchartoutf8: malloc failed\n");
222         return false;
223     }
224     bytes = ::WideCharToMultiByte(
225         CP_UTF8, flags, in, wlen, cp, bytes, nullptr, nullptr);
226     if (bytes <= 0) {
227         LOGERR("wchartoutf8: CONVERSION ERROR2\n");
228         free(cp);
229         return false;
230     }
231     cp[bytes] = 0;
232     out = cp;
233     free(cp);
234     //fwprintf(stderr, L"wchartoutf8: in: [%s]\n", in);
235     //fprintf(stderr, "wchartoutf8: out:  [%s]\n", out.c_str());
236     return true;
237 }
238 
utf8towchar(const std::string & in,wchar_t * out,size_t obytescap)239 bool utf8towchar(const std::string& in, wchar_t *out, size_t obytescap)
240 {
241     size_t wcharsavail = obytescap / sizeof(wchar_t);
242     if (nullptr == out || wcharsavail < 1) {
243         return false;
244     }
245     out[0] = 0;
246 
247     int wcharcnt = MultiByteToWideChar(
248         CP_UTF8, MB_ERR_INVALID_CHARS, in.c_str(), in.size(), nullptr, 0);
249     if (wcharcnt <= 0) {
250         LOGERR("utf8towchar: conversion error for [" << in << "]\n");
251         return false;
252     }
253     if (wcharcnt + 1 >  int(wcharsavail)) {
254         LOGERR("utf8towchar: not enough space\n");
255         return false;
256     }
257     wcharcnt = MultiByteToWideChar(
258         CP_UTF8, MB_ERR_INVALID_CHARS, in.c_str(), in.size(), out, wcharsavail);
259     if (wcharcnt <= 0) {
260         LOGERR("utf8towchar: conversion error for [" << in << "]\n");
261         return false;
262     }
263     out[wcharcnt] = 0;
264     return true;
265 }
266 
utf8towchar(const std::string & in)267 std::unique_ptr<wchar_t[]> utf8towchar(const std::string& in)
268 {
269     // Note that as we supply in.size(), mbtowch computes the size
270     // without a terminating 0 (and won't write in the second call of
271     // course). We take this into account by allocating one more and
272     // terminating the output.
273     int wcharcnt = MultiByteToWideChar(
274         CP_UTF8, MB_ERR_INVALID_CHARS, in.c_str(), in.size(), nullptr, 0);
275     if (wcharcnt <= 0) {
276         LOGERR("utf8towchar: conversion error for [" << in << "]\n");
277         return std::unique_ptr<wchar_t[]>();
278     }
279     auto buf = unique_ptr<wchar_t[]>(new wchar_t[wcharcnt+1]);
280 
281     wcharcnt = MultiByteToWideChar(
282         CP_UTF8, MB_ERR_INVALID_CHARS, in.c_str(), in.size(),
283         buf.get(), wcharcnt);
284     if (wcharcnt <= 0) {
285         LOGERR("utf8towchar: conversion error for [" << in << "]\n");
286         return std::unique_ptr<wchar_t[]>();
287     }
288     buf.get()[wcharcnt] = 0;
289     return buf;
290 }
291 
292 /// Convert \ separators to /
path_slashize(string & s)293 void path_slashize(string& s)
294 {
295     for (string::size_type i = 0; i < s.size(); i++) {
296         if (s[i] == '\\') {
297             s[i] = '/';
298         }
299     }
300 }
path_backslashize(string & s)301 void path_backslashize(string& s)
302 {
303     for (string::size_type i = 0; i < s.size(); i++) {
304         if (s[i] == '/') {
305             s[i] = '\\';
306         }
307     }
308 }
path_strlookslikedrive(const string & s)309 static bool path_strlookslikedrive(const string& s)
310 {
311     return s.size() == 2 && isalpha(s[0]) && s[1] == ':';
312 }
313 
path_hasdrive(const string & s)314 static bool path_hasdrive(const string& s)
315 {
316     if (s.size() >= 2 && isalpha(s[0]) && s[1] == ':') {
317         return true;
318     }
319     return false;
320 }
path_isdriveabs(const string & s)321 static bool path_isdriveabs(const string& s)
322 {
323     if (s.size() >= 3 && isalpha(s[0]) && s[1] == ':' && s[2] == '/') {
324         return true;
325     }
326     return false;
327 }
328 
329 /* Operations for the 'flock' call (same as Linux kernel constants).  */
330 # define LOCK_SH 1       /* Shared lock.  */
331 # define LOCK_EX 2       /* Exclusive lock.  */
332 # define LOCK_UN 8       /* Unlock.  */
333 
334 /* Can be OR'd in to one of the above.  */
335 # define LOCK_NB 4       /* Don't block when locking.  */
336 
337 #include <io.h>
338 
339 /* Determine the current size of a file.  Because the other braindead
340  * APIs we'll call need lower/upper 32 bit pairs, keep the file size
341  * like that too.
342  */
343 static BOOL
file_size(HANDLE h,DWORD * lower,DWORD * upper)344 file_size (HANDLE h, DWORD * lower, DWORD * upper)
345 {
346     *lower = GetFileSize (h, upper);
347     /* It appears that we can't lock an empty file, a lock is always
348        over a data section. But we seem to be able to set a lock
349        beyond the current file size, which is enough to get Pidfile
350        working */
351     if (*lower == 0 && *upper == 0) {
352         *lower = 100;
353     }
354     return 1;
355 }
356 
357 /* LOCKFILE_FAIL_IMMEDIATELY is undefined on some Windows systems. */
358 # ifndef LOCKFILE_FAIL_IMMEDIATELY
359 #  define LOCKFILE_FAIL_IMMEDIATELY 1
360 # endif
361 
362 /* Acquire a lock. */
363 static BOOL
do_lock(HANDLE h,int non_blocking,int exclusive)364 do_lock (HANDLE h, int non_blocking, int exclusive)
365 {
366     BOOL res;
367     DWORD size_lower, size_upper;
368     OVERLAPPED ovlp;
369     int flags = 0;
370 
371     /* We're going to lock the whole file, so get the file size. */
372     res = file_size (h, &size_lower, &size_upper);
373     if (!res)
374         return 0;
375 
376     /* Start offset is 0, and also zero the remaining members of this struct. */
377     memset (&ovlp, 0, sizeof ovlp);
378 
379     if (non_blocking)
380         flags |= LOCKFILE_FAIL_IMMEDIATELY;
381     if (exclusive)
382         flags |= LOCKFILE_EXCLUSIVE_LOCK;
383 
384     return LockFileEx (h, flags, 0, size_lower, size_upper, &ovlp);
385 }
386 
387 /* Unlock reader or exclusive lock. */
388 static BOOL
do_unlock(HANDLE h)389 do_unlock (HANDLE h)
390 {
391     int res;
392     DWORD size_lower, size_upper;
393 
394     res = file_size (h, &size_lower, &size_upper);
395     if (!res)
396         return 0;
397 
398     return UnlockFile (h, 0, 0, size_lower, size_upper);
399 }
400 
401 /* Now our BSD-like flock operation. */
402 int
flock(int fd,int operation)403 flock (int fd, int operation)
404 {
405     HANDLE h = (HANDLE) _get_osfhandle (fd);
406     DWORD res;
407     int non_blocking;
408 
409     if (h == INVALID_HANDLE_VALUE) {
410         errno = EBADF;
411         return -1;
412     }
413 
414     non_blocking = operation & LOCK_NB;
415     operation &= ~LOCK_NB;
416 
417     switch (operation) {
418     case LOCK_SH:
419         res = do_lock (h, non_blocking, 0);
420         break;
421     case LOCK_EX:
422         res = do_lock (h, non_blocking, 1);
423         break;
424     case LOCK_UN:
425         res = do_unlock (h);
426         break;
427     default:
428         errno = EINVAL;
429         return -1;
430     }
431 
432     /* Map Windows errors into Unix errnos.  As usual MSDN fails to
433      * document the permissible error codes.
434      */
435     if (!res) {
436         DWORD err = GetLastError ();
437         switch (err){
438             /* This means someone else is holding a lock. */
439         case ERROR_LOCK_VIOLATION:
440             errno = EAGAIN;
441             break;
442 
443             /* Out of memory. */
444         case ERROR_NOT_ENOUGH_MEMORY:
445             errno = ENOMEM;
446             break;
447 
448         case ERROR_BAD_COMMAND:
449             errno = EINVAL;
450             break;
451 
452             /* Unlikely to be other errors, but at least don't lose the
453              * error code.
454              */
455         default:
456             errno = err;
457         }
458 
459         return -1;
460     }
461 
462     return 0;
463 }
464 
path_shortpath(const std::string & path)465 std::string path_shortpath(const std::string& path)
466 {
467     SYSPATH(path, syspath);
468     wchar_t wspath[MAX_PATH];
469     int ret = GetShortPathNameW(syspath, wspath, MAX_PATH);
470     if (ret == 0) {
471         LOGERR("GetShortPathNameW failed for [" << path << "]\n");
472         return path;
473     } else if (ret >= MAX_PATH) {
474         LOGERR("GetShortPathNameW [" << path << "] too long " <<
475                path.size() << " MAX_PATH " << MAX_PATH << "\n");
476         return path;
477     }
478     string shortpath;
479     wchartoutf8(wspath, shortpath);
480     return shortpath;
481 }
482 
483 #endif /* _WIN32 */
484 
fsocc(const string & path,int * pc,long long * avmbs)485 bool fsocc(const string& path, int *pc, long long *avmbs)
486 {
487     static const int FSOCC_MB = 1024 * 1024;
488 #ifdef _WIN32
489     ULARGE_INTEGER freebytesavail;
490     ULARGE_INTEGER totalbytes;
491     SYSPATH(path, syspath);
492     if (!GetDiskFreeSpaceExW(syspath, &freebytesavail, &totalbytes, NULL)) {
493         return false;
494     }
495     if (pc) {
496         *pc = int((100 * freebytesavail.QuadPart) / totalbytes.QuadPart);
497     }
498     if (avmbs) {
499         *avmbs = int(totalbytes.QuadPart / FSOCC_MB);
500     }
501     return true;
502 #else /* !_WIN32 */
503 
504     struct statvfs buf;
505     if (statvfs(path.c_str(), &buf) != 0) {
506         return false;
507     }
508 
509     if (pc) {
510         double fsocc_used = double(buf.f_blocks - buf.f_bfree);
511         double fsocc_totavail = fsocc_used + double(buf.f_bavail);
512         double fpc = 100.0;
513         if (fsocc_totavail > 0) {
514             fpc = 100.0 * fsocc_used / fsocc_totavail;
515         }
516         *pc = int(fpc);
517     }
518     if (avmbs) {
519         *avmbs = 0;
520         if (buf.f_bsize > 0) {
521             int ratio = buf.f_frsize > FSOCC_MB ? buf.f_frsize / FSOCC_MB :
522                 FSOCC_MB / buf.f_frsize;
523 
524             *avmbs = buf.f_frsize > FSOCC_MB ?
525                 ((long long)buf.f_bavail) * ratio :
526                 ((long long)buf.f_bavail) / ratio;
527         }
528     }
529     return true;
530 #endif /* !_WIN32 */
531 }
532 
533 
path_PATHsep()534 string path_PATHsep()
535 {
536     static const string w(";");
537     static const string u(":");
538 #ifdef _WIN32
539     return w;
540 #else
541     return u;
542 #endif
543 }
544 
path_catslash(string & s)545 void path_catslash(string& s)
546 {
547 #ifdef _WIN32
548     path_slashize(s);
549 #endif
550     if (s.empty() || s[s.length() - 1] != '/') {
551         s += '/';
552     }
553 }
554 
path_cat(const string & s1,const string & s2)555 string path_cat(const string& s1, const string& s2)
556 {
557     string res = s1;
558     path_catslash(res);
559     res +=  s2;
560     return res;
561 }
562 
path_getfather(const string & s)563 string path_getfather(const string& s)
564 {
565     string father = s;
566 #ifdef _WIN32
567     path_slashize(father);
568 #endif
569 
570     // ??
571     if (father.empty()) {
572         return "./";
573     }
574 
575     if (path_isroot(father)) {
576         return father;
577     }
578 
579     if (father[father.length() - 1] == '/') {
580         // Input ends with /. Strip it, root special case was tested above
581         father.erase(father.length() - 1);
582     }
583 
584     string::size_type slp = father.rfind('/');
585     if (slp == string::npos) {
586         return "./";
587     }
588 
589     father.erase(slp);
590     path_catslash(father);
591     return father;
592 }
593 
path_getsimple(const string & s)594 string path_getsimple(const string& s)
595 {
596     string simple = s;
597 #ifdef _WIN32
598     path_slashize(simple);
599 #endif
600 
601     if (simple.empty()) {
602         return simple;
603     }
604 
605     string::size_type slp = simple.rfind('/');
606     if (slp == string::npos) {
607         return simple;
608     }
609 
610     simple.erase(0, slp + 1);
611     return simple;
612 }
613 
path_basename(const string & s,const string & suff)614 string path_basename(const string& s, const string& suff)
615 {
616     string simple = path_getsimple(s);
617     string::size_type pos = string::npos;
618     if (suff.length() && simple.length() > suff.length()) {
619         pos = simple.rfind(suff);
620         if (pos != string::npos && pos + suff.length() == simple.length()) {
621             return simple.substr(0, pos);
622         }
623     }
624     return simple;
625 }
626 
path_suffix(const string & s)627 string path_suffix(const string& s)
628 {
629     string::size_type dotp = s.rfind('.');
630     if (dotp == string::npos) {
631         return string();
632     }
633     return s.substr(dotp + 1);
634 }
635 
path_home()636 string path_home()
637 {
638 #ifdef _WIN32
639     string dir;
640     // Using wgetenv does not work well, depending on the
641     // environment I get wrong values for the accented chars (works
642     // with recollindex started from msys command window, does not
643     // work when started from recoll. SHGet... fixes this
644     //const wchar_t *cp = _wgetenv(L"USERPROFILE");
645     wchar_t *cp;
646     SHGetKnownFolderPath(FOLDERID_Profile, 0, nullptr, &cp);
647     if (cp != 0) {
648         wchartoutf8(cp, dir);
649     }
650     if (dir.empty()) {
651         cp = _wgetenv(L"HOMEDRIVE");
652         wchartoutf8(cp, dir);
653         if (cp != 0) {
654             string dir1;
655             const wchar_t *cp1 = _wgetenv(L"HOMEPATH");
656             wchartoutf8(cp1, dir1);
657             if (cp1 != 0) {
658                 dir = path_cat(dir, dir1);
659             }
660         }
661     }
662     if (dir.empty()) {
663         dir = "C:/";
664     }
665     dir = path_canon(dir);
666     path_catslash(dir);
667     return dir;
668 #else
669     uid_t uid = getuid();
670 
671     struct passwd *entry = getpwuid(uid);
672     if (entry == 0) {
673         const char *cp = getenv("HOME");
674         if (cp) {
675             return cp;
676         } else {
677             return "/";
678         }
679     }
680 
681     string homedir = entry->pw_dir;
682     path_catslash(homedir);
683     return homedir;
684 #endif
685 }
686 
path_tildexpand(const string & s)687 string path_tildexpand(const string& s)
688 {
689     if (s.empty() || s[0] != '~') {
690         return s;
691     }
692     string o = s;
693 #ifdef _WIN32
694     path_slashize(o);
695 #endif
696 
697     if (s.length() == 1) {
698         o.replace(0, 1, path_home());
699     } else if (s[1] == '/') {
700         o.replace(0, 2, path_home());
701     } else {
702         string::size_type pos = s.find('/');
703         string::size_type l = (pos == string::npos) ? s.length() - 1 : pos - 1;
704 #ifdef _WIN32
705         // Dont know what this means. Just replace with HOME
706         o.replace(0, l + 1, path_home());
707 #else
708         struct passwd *entry = getpwnam(s.substr(1, l).c_str());
709         if (entry) {
710             o.replace(0, l + 1, entry->pw_dir);
711         }
712 #endif
713     }
714     return o;
715 }
716 
path_isroot(const string & path)717 bool path_isroot(const string& path)
718 {
719     if (path.size() == 1 && path[0] == '/') {
720         return true;
721     }
722 #ifdef _WIN32
723     if (path.size() == 3 && isalpha(path[0]) && path[1] == ':' &&
724         (path[2] == '/' || path[2] == '\\')) {
725         return true;
726     }
727 #endif
728     return false;
729 }
730 
path_isdesc(const string & _top,const string & _sub)731 bool path_isdesc(const string& _top, const string& _sub)
732 {
733     string top = path_canon(_top);
734     string sub = path_canon(_sub);
735     path_catslash(top);
736     path_catslash(sub);
737     for (;;) {
738         if (sub == top) {
739             return true;
740         }
741         string::size_type l = sub.size();
742         sub = path_getfather(sub);
743         if (sub.size() == l || sub.size() < top.size()) {
744             // At root or sub shorter than top: done
745             if (sub == top) {
746                 return true;
747             } else {
748                 return false;
749             }
750         }
751     }
752 }
753 
path_isabsolute(const string & path)754 bool path_isabsolute(const string& path)
755 {
756     if (!path.empty() && (path[0] == '/'
757 #ifdef _WIN32
758                           || path_isdriveabs(path)
759 #endif
760             )) {
761         return true;
762     }
763     return false;
764 }
765 
path_absolute(const string & is)766 string path_absolute(const string& is)
767 {
768     if (is.length() == 0) {
769         return is;
770     }
771     string s = is;
772 #ifdef _WIN32
773     path_slashize(s);
774 #endif
775     if (!path_isabsolute(s)) {
776         s = path_cat(path_cwd(), s);
777 #ifdef _WIN32
778         path_slashize(s);
779 #endif
780     }
781     return s;
782 }
783 
path_canon(const string & is,const string * cwd)784 string path_canon(const string& is, const string* cwd)
785 {
786     if (is.length() == 0) {
787         return is;
788     }
789     string s = is;
790 #ifdef _WIN32
791     path_slashize(s);
792     // fix possible path from file: absolute url
793     if (s.size() && s[0] == '/' && path_hasdrive(s.substr(1))) {
794         s = s.substr(1);
795     }
796 #endif
797 
798     if (!path_isabsolute(s)) {
799         if (cwd) {
800             s = path_cat(*cwd, s);
801         } else {
802             s = path_cat(path_cwd(), s);
803         }
804     }
805     vector<string> elems;
806     stringToTokens(s, elems, "/");
807     vector<string> cleaned;
808     for (vector<string>::const_iterator it = elems.begin();
809          it != elems.end(); it++) {
810         if (*it == "..") {
811             if (!cleaned.empty()) {
812                 cleaned.pop_back();
813             }
814         } else if (it->empty() || *it == ".") {
815         } else {
816             cleaned.push_back(*it);
817         }
818     }
819     string ret;
820     if (!cleaned.empty()) {
821         for (vector<string>::const_iterator it = cleaned.begin();
822              it != cleaned.end(); it++) {
823             ret += "/";
824 #ifdef _WIN32
825             if (it == cleaned.begin() && path_strlookslikedrive(*it)) {
826                 // Get rid of just added initial "/"
827                 ret.clear();
828             }
829 #endif
830             ret += *it;
831         }
832     } else {
833         ret = "/";
834     }
835 
836 #ifdef _WIN32
837     // Raw drive needs a final /
838     if (path_strlookslikedrive(ret)) {
839         path_catslash(ret);
840     }
841 #endif
842 
843     return ret;
844 }
845 
path_makepath(const string & ipath,int mode)846 bool path_makepath(const string& ipath, int mode)
847 {
848     string path = path_canon(ipath);
849     vector<string> elems;
850     stringToTokens(path, elems, "/");
851     path = "/";
852     for (const auto& elem : elems) {
853 #ifdef _WIN32
854         PRETEND_USE(mode);
855         if (path == "/" && path_strlookslikedrive(elem)) {
856             path = "";
857         }
858 #endif
859         path += elem;
860         // Not using path_isdir() here, because this cant grok symlinks
861         // If we hit an existing file, no worry, mkdir will just fail.
862         LOGDEB1("path_makepath: testing existence: ["  << path << "]\n");
863         if (!path_exists(path)) {
864             LOGDEB1("path_makepath: creating directory ["  << path << "]\n");
865             SYSPATH(path, syspath);
866             if (MKDIR(syspath, mode) != 0)  {
867                 //cerr << "mkdir " << path << " failed, errno " << errno << endl;
868                 return false;
869             }
870         }
871         path += "/";
872     }
873     return true;
874 }
875 
path_chdir(const std::string & path)876 bool path_chdir(const std::string& path)
877 {
878     SYSPATH(path, syspath);
879     return CHDIR(syspath) == 0;
880 }
881 
path_cwd()882 std::string path_cwd()
883 {
884 #ifdef _WIN32
885     wchar_t *wd = _wgetcwd(nullptr, 0);
886     if (nullptr == wd) {
887         return std::string();
888     }
889     string sdname;
890     wchartoutf8(wd, sdname);
891     free(wd);
892     path_slashize(sdname);
893     return sdname;
894 #else
895     char wd[MAXPATHLEN+1];
896     if (nullptr == getcwd(wd, MAXPATHLEN+1)) {
897         return string();
898     }
899     return wd;
900 #endif
901 }
902 
path_unlink(const std::string & path)903 bool path_unlink(const std::string& path)
904 {
905     SYSPATH(path, syspath);
906     return UNLINK(syspath) == 0;
907 }
908 
path_rmdir(const std::string & path)909 bool path_rmdir(const std::string& path)
910 {
911     SYSPATH(path, syspath);
912     return RMDIR(syspath) == 0;
913 }
914 
path_streamopen(const std::string & path,int mode,std::fstream & outstream)915 bool path_streamopen(const std::string& path, int mode, std::fstream& outstream)
916 {
917 #if defined(_WIN32) && defined (_MSC_VER)
918     // MSC STL has support for using wide chars in fstream
919     // constructor. We need this if, e.g. the user name/home directory
920     // is not ASCII. Actually don't know how to do this with gcc
921     wchar_t wpath[MAX_PATH + 1];
922     utf8towchar(path, wpath, MAX_PATH);
923     outstream.open(wpath, std::ios_base::openmode(mode));
924 #else
925     outstream.open(path, std::ios_base::openmode(mode));
926 #endif
927     if (!outstream.is_open()) {
928         return false;
929     }
930     return true;
931 }
932 
path_isdir(const string & path,bool follow)933 bool path_isdir(const string& path, bool follow)
934 {
935     struct STATBUF st;
936     SYSPATH(path, syspath);
937     int ret = follow ? STAT(syspath, &st) : LSTAT(syspath, &st);
938     if (ret < 0) {
939         return false;
940     }
941     if (S_ISDIR(st.st_mode)) {
942         return true;
943     }
944     return false;
945 }
946 
path_isfile(const string & path,bool follow)947 bool path_isfile(const string& path, bool follow)
948 {
949     struct STATBUF st;
950     SYSPATH(path, syspath);
951     int ret = follow ? STAT(syspath, &st) : LSTAT(syspath, &st);
952     if (ret < 0) {
953         return false;
954     }
955     if (S_ISREG(st.st_mode)) {
956         return true;
957     }
958     return false;
959 }
960 
path_filesize(const string & path)961 long long path_filesize(const string& path)
962 {
963     struct STATBUF st;
964     SYSPATH(path, syspath);
965     if (STAT(syspath, &st) < 0) {
966         return -1;
967     }
968     return (long long)st.st_size;
969 }
970 
path_samefile(const std::string & p1,const std::string & p2)971 bool path_samefile(const std::string& p1, const std::string& p2)
972 {
973 #ifdef _WIN32
974     std::string cp1, cp2;
975     cp1 = path_canon(p1);
976     cp2 = path_canon(p2);
977     return !cp1.compare(cp2);
978 #else
979     struct stat st1, st2;
980     if (stat(p1.c_str(), &st1))
981         return false;
982     if (stat(p2.c_str(), &st2))
983         return false;
984     if (st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino) {
985         return true;
986     }
987     return false;
988 #endif
989 }
990 
path_fileprops(const std::string path,struct PathStat * stp,bool follow)991 int path_fileprops(const std::string path, struct PathStat *stp, bool follow)
992 {
993     if (nullptr == stp) {
994         return -1;
995     }
996     memset(stp, 0, sizeof(struct PathStat));
997     struct STATBUF mst;
998     SYSPATH(path, syspath);
999     int ret = follow ? STAT(syspath, &mst) : LSTAT(syspath, &mst);
1000     if (ret != 0) {
1001         return ret;
1002     }
1003     stp->pst_size = mst.st_size;
1004     stp->pst_mode = mst.st_mode;
1005     stp->pst_mtime = mst.st_mtime;
1006 #ifdef _WIN32
1007     stp->pst_ctime = mst.st_mtime;
1008 #else
1009     stp->pst_ino = mst.st_ino;
1010     stp->pst_dev = mst.st_dev;
1011     stp->pst_ctime = mst.st_ctime;
1012     stp->pst_blocks = mst.st_blocks;
1013     stp->pst_blksize = mst.st_blksize;
1014 #endif
1015     switch (mst.st_mode & S_IFMT) {
1016     case S_IFDIR: stp->pst_type = PathStat::PST_DIR;break;
1017     case S_IFLNK:  stp->pst_type = PathStat::PST_SYMLINK;break;
1018     case S_IFREG: stp->pst_type = PathStat::PST_REGULAR;break;
1019     default: stp->pst_type = PathStat::PST_OTHER;break;
1020     }
1021     return 0;
1022 }
1023 
path_exists(const string & path)1024 bool path_exists(const string& path)
1025 {
1026     SYSPATH(path, syspath);
1027     return ACCESS(syspath, 0) == 0;
1028 }
path_readable(const string & path)1029 bool path_readable(const string& path)
1030 {
1031     SYSPATH(path, syspath);
1032     return ACCESS(syspath, R_OK) == 0;
1033 }
path_access(const std::string & path,int mode)1034 bool path_access(const std::string& path, int mode)
1035 {
1036     SYSPATH(path, syspath);
1037     return ACCESS(syspath, mode) == 0;
1038 }
1039 
1040 /* There is a lot of vagueness about what should be percent-encoded or
1041  * not in a file:// url. The constraint that we have is that we may use
1042  * the encoded URL to compute (MD5) a thumbnail path according to the
1043  * freedesktop.org thumbnail spec, which itself does not define what
1044  * should be escaped. We choose to exactly escape what gio does, as
1045  * implemented in glib/gconvert.c:g_escape_uri_string(uri, UNSAFE_PATH).
1046  * Hopefully, the other desktops have the same set of escaped chars.
1047  * Note that $ is not encoded, so the value is not shell-safe.
1048  */
url_encode(const string & url,string::size_type offs)1049 string url_encode(const string& url, string::size_type offs)
1050 {
1051     string out = url.substr(0, offs);
1052     const char *cp = url.c_str();
1053     for (string::size_type i = offs; i < url.size(); i++) {
1054         unsigned int c;
1055         const char *h = "0123456789ABCDEF";
1056         c = cp[i];
1057         if (c <= 0x20 ||
1058             c >= 0x7f ||
1059             c == '"' ||
1060             c == '#' ||
1061             c == '%' ||
1062             c == ';' ||
1063             c == '<' ||
1064             c == '>' ||
1065             c == '?' ||
1066             c == '[' ||
1067             c == '\\' ||
1068             c == ']' ||
1069             c == '^' ||
1070             c == '`' ||
1071             c == '{' ||
1072             c == '|' ||
1073             c == '}') {
1074             out += '%';
1075             out += h[(c >> 4) & 0xf];
1076             out += h[c & 0xf];
1077         } else {
1078             out += char(c);
1079         }
1080     }
1081     return out;
1082 }
1083 
h2d(int c)1084 static inline int h2d(int c) {
1085     if ('0' <= c && c <= '9')
1086         return c - '0';
1087     else if ('A' <= c && c <= 'F')
1088         return 10 + c - 'A';
1089     else if ('a' <= c && c <= 'f')
1090         return 10 + c - 'a';
1091     else
1092         return -1;
1093 }
1094 
url_decode(const string & in)1095 string url_decode(const string &in)
1096 {
1097     if (in.size() <= 2)
1098         return in;
1099     string out;
1100     out.reserve(in.size());
1101     const char *cp = in.c_str();
1102     string::size_type i = 0;
1103     for (; i < in.size() - 2; i++) {
1104         if (cp[i] == '%') {
1105             int d1 = h2d(cp[i+1]);
1106             int d2 = h2d(cp[i+2]);
1107             if (d1 != -1 && d2 != -1) {
1108                 out += (d1 << 4) + d2;
1109             } else {
1110                 out += '%';
1111                 out += cp[i+1];
1112                 out += cp[i+2];
1113             }
1114             i += 2;
1115         } else {
1116             out += cp[i];
1117         }
1118     }
1119     while (i < in.size()) {
1120         out += cp[i++];
1121     }
1122     return out;
1123 }
1124 
url_gpath(const string & url)1125 string url_gpath(const string& url)
1126 {
1127     // Remove the access schema part (or whatever it's called)
1128     string::size_type colon = url.find_first_of(":");
1129     if (colon == string::npos || colon == url.size() - 1) {
1130         return url;
1131     }
1132     // If there are non-alphanum chars before the ':', then there
1133     // probably is no scheme. Whatever...
1134     for (string::size_type i = 0; i < colon; i++) {
1135         if (!isalnum(url.at(i))) {
1136             return url;
1137         }
1138     }
1139 
1140     // In addition we canonize the path to remove empty host parts
1141     // (for compatibility with older versions of recoll where file://
1142     // was hardcoded, but the local path was used for doc
1143     // identification.
1144     return path_canon(url.substr(colon + 1));
1145 }
1146 
url_parentfolder(const string & url)1147 string url_parentfolder(const string& url)
1148 {
1149     // In general, the parent is the directory above the full path
1150     string parenturl = path_getfather(url_gpath(url));
1151     // But if this is http, make sure to keep the host part. Recoll
1152     // only has file or http urls for now.
1153     bool isfileurl = urlisfileurl(url);
1154     if (!isfileurl && parenturl == "/") {
1155         parenturl = url_gpath(url);
1156     }
1157     return isfileurl ? string("file://") + parenturl :
1158         string("http://") + parenturl;
1159 }
1160 
1161 
1162 // Convert to file path if url is like file:
1163 // Note: this only works with our internal pseudo-urls which are not
1164 // encoded/escaped
fileurltolocalpath(string url)1165 string fileurltolocalpath(string url)
1166 {
1167     if (url.find("file://") == 0) {
1168         url = url.substr(7, string::npos);
1169     } else {
1170         return string();
1171     }
1172 
1173 #ifdef _WIN32
1174     // Absolute file urls are like: file:///c:/mydir/...
1175     // Get rid of the initial '/'
1176     if (url.size() >= 3 && url[0] == '/' && isalpha(url[1]) && url[2] == ':') {
1177         url = url.substr(1);
1178     }
1179 #endif
1180 
1181     // Removing the fragment part. This is exclusively used when
1182     // executing a viewer for the recoll manual, and we only strip the
1183     // part after # if it is preceded by .html
1184     string::size_type pos;
1185     if ((pos = url.rfind(".html#")) != string::npos) {
1186         url.erase(pos + 5);
1187     } else if ((pos = url.rfind(".htm#")) != string::npos) {
1188         url.erase(pos + 4);
1189     }
1190 
1191     return url;
1192 }
1193 
1194 static const string cstr_fileu("file://");
1195 
path_pathtofileurl(const string & path)1196 string path_pathtofileurl(const string& path)
1197 {
1198     // We're supposed to receive a canonic absolute path, but on windows we
1199     // may need to add a '/' in front of the drive spec
1200     string url(cstr_fileu);
1201     if (path.empty() || path[0] != '/') {
1202         url.push_back('/');
1203     }
1204     url += path;
1205     return url;
1206 }
1207 
urlisfileurl(const string & url)1208 bool urlisfileurl(const string& url)
1209 {
1210     return url.find("file://") == 0;
1211 }
1212 
1213 #ifndef NO_STD_REGEX
1214 static std::regex
1215 re_uriparse("^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\\?([^#]*))?(#(.*))?",
1216             std::regex::extended);
1217 
ParsedUri(std::string uri)1218 ParsedUri::ParsedUri(std::string uri)
1219 {
1220     std::smatch mr;
1221     parsed = regex_match(uri, mr, re_uriparse);
1222     if (!parsed)
1223         return;
1224     // cf http://www.ietf.org/rfc/rfc2396.txt
1225     // scheme    = $2
1226     // authority = $4
1227     // path      = $5
1228     // query     = $7
1229     // fragment  = $9
1230     if (mr[2].matched) {
1231         scheme = mr[2].str();
1232     }
1233     if (mr[4].matched) {
1234         string auth = mr[4].str();
1235         // user:pass@host, user@host
1236         string::size_type at = auth.find_first_of('@');
1237         if (at != string::npos) {
1238             host = auth.substr(at+1);
1239             string::size_type colon = auth.find_first_of(':');
1240             if (colon != string::npos && colon < at) {
1241                 user = auth.substr(0, colon);
1242                 pass = auth.substr(colon+1, at-colon-1);
1243             } else {
1244                 user = auth.substr(0, at);
1245             }
1246         } else {
1247             host.swap(auth);
1248         }
1249         string::size_type pc = host.find_first_of(':');
1250         if (pc != string::npos) {
1251             port = host.substr(pc+1);
1252             host = host.substr(0, pc);
1253         }
1254     }
1255     if (mr[5].matched) {
1256         path = mr[5].str();
1257     }
1258     if (mr[7].matched) {
1259         query = mr[7].str();
1260         string::size_type pos=0, amp, eq;
1261         string nm, val;
1262         for (;;) {
1263             nm.clear();
1264             val.clear();
1265             amp = query.find_first_of('&', pos);
1266             //cerr << "pos " << pos << " amp " << amp << endl;
1267             if (amp > pos && amp != string::npos) {
1268                 eq = query.find_first_of('=', pos);
1269                 if (eq > amp || eq == string::npos) {
1270                     nm = query.substr(pos, amp-pos);
1271                 } else {
1272                     nm = query.substr(pos, eq-pos);
1273                     val = query.substr(eq+1, amp-eq-1);
1274                 }
1275                 pos = amp + 1;
1276             } else if (amp == string::npos) {
1277                 if (pos < query.size()-1) {
1278                     eq = query.find_first_of('=', pos);
1279                     if (eq == string::npos) {
1280                         nm = query.substr(pos);
1281                     } else {
1282                         nm = query.substr(pos, eq-pos);
1283                         val = query.substr(eq+1);
1284                     }
1285                 }
1286                 pos = query.size()-1;
1287             } else {
1288                 pos++;
1289             }
1290             if (!nm.empty()) {
1291                 parsedquery.push_back(pair<string,string>(nm, val));
1292             }
1293             if (pos >= query.size()-1) {
1294                 break;
1295             }
1296         }
1297 
1298     }
1299     if (mr[9].matched) {
1300         fragment = mr[9].str();
1301     }
1302 }
1303 #endif // NO_STD_REGEX
1304 
1305 /// Directory reading interface. UTF-8 on Windows.
1306 class PathDirContents::Internal {
1307 public:
~Internal()1308     ~Internal() {
1309         if (dirhdl) {
1310             CLOSEDIR(dirhdl);
1311         }
1312     }
1313 
1314     DIRHDL *dirhdl{nullptr};
1315     PathDirContents::Entry entry;
1316     std::string dirpath;
1317 };
1318 
PathDirContents(const std::string & dirpath)1319 PathDirContents::PathDirContents(const std::string& dirpath)
1320 {
1321     m = new Internal;
1322     m->dirpath = dirpath;
1323 }
1324 
~PathDirContents()1325 PathDirContents::~PathDirContents()
1326 {
1327     delete m;
1328 }
1329 
opendir()1330 bool PathDirContents::opendir()
1331 {
1332     if (m->dirhdl) {
1333         CLOSEDIR(m->dirhdl);
1334         m->dirhdl = nullptr;
1335     }
1336     const std::string& dp{m->dirpath};
1337     SYSPATH(dp, sysdir);
1338     m->dirhdl = OPENDIR(sysdir);
1339 #ifdef _WIN32
1340     if (nullptr == m->dirhdl) {
1341         int rc = GetLastError();
1342         LOGERR("opendir failed: LastError " << rc << endl);
1343         if (rc == ERROR_NETNAME_DELETED) {
1344             // 64: share disconnected.
1345             // Not too sure of the errno in this case.
1346             // Make sure it's not one of the permissible ones
1347             errno = ENODEV;
1348         }
1349     }
1350 #endif
1351     return nullptr != m->dirhdl;
1352 }
1353 
rewinddir()1354 void PathDirContents::rewinddir()
1355 {
1356     REWINDDIR(m->dirhdl);
1357 }
1358 
readdir()1359 const struct PathDirContents::Entry* PathDirContents::readdir()
1360 {
1361     struct DIRENT *ent = READDIR(m->dirhdl);
1362     if (nullptr == ent) {
1363         return nullptr;
1364     }
1365 #ifdef _WIN32
1366     string sdname;
1367     if (!wchartoutf8(ent->d_name, sdname)) {
1368         LOGERR("wchartoutf8 failed for " << ent->d_name << endl);
1369         return nullptr;
1370     }
1371     const char *dname = sdname.c_str();
1372 #else
1373     const char *dname = ent->d_name;
1374 #endif
1375     m->entry.d_name = dname;
1376     return &m->entry;
1377 }
1378 
1379 
listdir(const string & dir,string & reason,set<string> & entries)1380 bool listdir(const string& dir, string& reason, set<string>& entries)
1381 {
1382     ostringstream msg;
1383     PathDirContents dc(dir);
1384 
1385     if (!path_isdir(dir)) {
1386         msg << "listdir: " << dir <<  " not a directory";
1387         goto out;
1388     }
1389     if (!path_access(dir, R_OK)) {
1390         msg << "listdir: no read access to " << dir;
1391         goto out;
1392     }
1393 
1394     if (!dc.opendir()) {
1395         msg << "listdir: cant opendir " << dir << ", errno " << errno;
1396         goto out;
1397     }
1398     const struct PathDirContents::Entry *ent;
1399     while ((ent = dc.readdir()) != 0) {
1400         if (ent->d_name == "." || ent->d_name == "..") {
1401             continue;
1402         }
1403         entries.insert(ent->d_name);
1404     }
1405 
1406 out:
1407     reason = msg.str();
1408     if (reason.empty()) {
1409         return true;
1410     }
1411     return false;
1412 }
1413 
1414 // We do not want to mess with the pidfile content in the destructor:
1415 // the lock might still be in use in a child process. In fact as much
1416 // as we'd like to reset the pid inside the file when we're done, it
1417 // would be very difficult to do it right and it's probably best left
1418 // alone.
~Pidfile()1419 Pidfile::~Pidfile()
1420 {
1421     this->close();
1422 }
1423 
read_pid()1424 int Pidfile::read_pid()
1425 {
1426     SYSPATH(m_path, syspath);
1427     int fd = OPEN(syspath, O_RDONLY);
1428     if (fd == -1) {
1429         return -1;
1430     }
1431 
1432     char buf[16];
1433     int i = read(fd, buf, sizeof(buf) - 1);
1434     ::close(fd);
1435     if (i <= 0) {
1436         return -1;
1437     }
1438     buf[i] = '\0';
1439     char *endptr;
1440     int pid = strtol(buf, &endptr, 10);
1441     if (endptr != &buf[i]) {
1442         return - 1;
1443     }
1444     return pid;
1445 }
1446 
flopen()1447 int Pidfile::flopen()
1448 {
1449     SYSPATH(m_path, syspath);
1450     if ((m_fd = OPEN(syspath, O_RDWR | O_CREAT, 0644)) == -1) {
1451         m_reason = "Open failed: [" + m_path + "]: " + strerror(errno);
1452         return -1;
1453     }
1454 
1455 #ifdef sun
1456     struct flock lockdata;
1457     lockdata.l_start = 0;
1458     lockdata.l_len = 0;
1459     lockdata.l_type = F_WRLCK;
1460     lockdata.l_whence = SEEK_SET;
1461     if (fcntl(m_fd, F_SETLK,  &lockdata) != 0) {
1462         int serrno = errno;
1463         this->close()
1464             errno = serrno;
1465         m_reason = "fcntl lock failed";
1466         return -1;
1467     }
1468 #else
1469     int operation = LOCK_EX | LOCK_NB;
1470     if (flock(m_fd, operation) == -1) {
1471         int serrno = errno;
1472         this->close();
1473         errno = serrno;
1474         m_reason = "flock failed";
1475         return -1;
1476     }
1477 #endif // ! sun
1478 
1479     if (ftruncate(m_fd, 0) != 0) {
1480         /* can't happen [tm] */
1481         int serrno = errno;
1482         this->close();
1483         errno = serrno;
1484         m_reason = "ftruncate failed";
1485         return -1;
1486     }
1487     return 0;
1488 }
1489 
open()1490 int Pidfile::open()
1491 {
1492     if (flopen() < 0) {
1493         return read_pid();
1494     }
1495     return 0;
1496 }
1497 
write_pid()1498 int Pidfile::write_pid()
1499 {
1500     /* truncate to allow multiple calls */
1501     if (ftruncate(m_fd, 0) == -1) {
1502         m_reason = "ftruncate failed";
1503         return -1;
1504     }
1505     char pidstr[20];
1506     sprintf(pidstr, "%u", int(getpid()));
1507     lseek(m_fd, 0, 0);
1508     if (::write(m_fd, pidstr, strlen(pidstr)) != (PATHUT_SSIZE_T)strlen(pidstr)) {
1509         m_reason = "write failed";
1510         return -1;
1511     }
1512     return 0;
1513 }
1514 
close()1515 int Pidfile::close()
1516 {
1517     int ret = -1;
1518     if (m_fd >= 0) {
1519         ret = ::close(m_fd);
1520         m_fd = -1;
1521     }
1522     return ret;
1523 }
1524 
remove()1525 int Pidfile::remove()
1526 {
1527     SYSPATH(m_path, syspath);
1528     return UNLINK(syspath);
1529 }
1530 
1531 // Call funcs that need static init (not initially reentrant)
pathut_init_mt()1532 void pathut_init_mt()
1533 {
1534     path_home();
1535 }
1536