Anklang 0.3.0-460-gc4ef46ba
ASE — Anklang Sound Engine (C++)

« « « Anklang Documentation
Loading...
Searching...
No Matches
path.cc
Go to the documentation of this file.
1 // This Source Code Form is licensed MPL-2.0: http://mozilla.org/MPL/2.0
2#include "path.hh"
3#include "platform.hh"
4#include "strings.hh"
5#include "utils.hh"
6#include "inifile.hh"
7#include "internal.hh"
8#include <unistd.h> // getuid
9#include <sys/stat.h> // lstat
10#include <sys/types.h>
11#include <fcntl.h>
12#include <sys/ioctl.h>
13#include <algorithm>
14#include <cstring> // strchr
15#include <glob.h> // glob
16#include <mutex>
17
18#if __has_include(<linux/fs.h>)
19#include <linux/fs.h>
20#endif
21
22#include <filesystem>
23namespace Fs = std::filesystem;
24
25
26#define IS_DIRSEP(c) ((c) == ASE_DIRSEP || (c) == ASE_DIRSEP2)
27#define IS_SEARCHPATH_SEPARATOR(c) ((c) == ASE_SEARCHPATH_SEPARATOR || (c) == ';') // make ';' work under Windows and Unix
28#define UPPER_ALPHA(L) (L >= 'A' && L <= 'Z')
29#define LOWER_ALPHA(L) (L >= 'a' && L <= 'z')
30#define ISALPHA(L) (LOWER_ALPHA (L) || UPPER_ALPHA (L))
31
32// == CxxPasswd =
33namespace { // Anon
34struct CxxPasswd {
35 String pw_name, pw_passwd, pw_gecos, pw_dir, pw_shell;
36 uid_t pw_uid = -1;
37 gid_t pw_gid = -1;
38 CxxPasswd (std::string username = "");
39};
40} // Anon
41
42namespace Ase {
43
44namespace Path {
45
46[[maybe_unused]] static bool
47startswith_dosdrive (const char *p)
48{
49 return ASE_DOS_PATHS && p && ISALPHA (p[0]) && p[1] == ':';
50}
51
52static bool
53startswith_dosdrive (const String &s)
54{
55 return ASE_DOS_PATHS && s.size() >= 2 && ISALPHA (s[0]) && s[1] == ':';
56}
57
60dirname (const String &path)
61{
62 const String dir = Fs::path (path).parent_path();
63 return dir.empty() ? "." : dir;
64}
65
68basename (const String &path)
69{
70 const char *base = strrchr (path.c_str(), ASE_DIRSEP);
72 {
73 const char *base2 = strrchr (path.c_str(), ASE_DIRSEP2);
74 if (base2 > base || !base)
75 base = base2;
76 }
77 if (base)
78 return base + 1;
79 if (startswith_dosdrive (path))
80 return path.substr (2);
81 return path;
82}
83
86normalize (const String &path)
87{
88 return Fs::path (path).lexically_normal();
89}
90
93realpath (const String &path)
94{
95 char *const cpath = ::realpath (path.c_str(), NULL);
96 if (cpath)
97 {
98 const String result = cpath;
99 free (cpath);
100 errno = 0;
101 return result;
102 }
103 // error case
104 return path;
105}
106
108String
110{
111 if (path.empty() || !IS_DIRSEP (path.back()))
112 return path + ASE_DIRSEP;
113 return path;
114}
115
117String
119{
120 String s = path;
121 while (s.size() > 1 && IS_DIRSEP (s.back()))
122 s.resize (s.size() - 1);
123 return s;
124}
125
133String
134abspath (const String &path, const String &incwd)
135{
136 if (isabs (path))
137 return path;
138 if (!incwd.empty())
139 return abspath (join (incwd, path), "");
140 String pcwd = program_cwd();
141 if (!pcwd.empty())
142 return join (pcwd, path);
143 return join (cwd(), path);
144}
145
147bool
148isabs (const String &path)
149{
150 return_unless (path.size(), false);
151 if (IS_DIRSEP (path[0]))
152 return true;
153 if (startswith_dosdrive (path) && IS_DIRSEP (path[2]))
154 return true;
155 return false;
156}
157
159bool
160isroot (const String &path, bool dos_drives)
161{
162 const char *c = path.data();
163 // skip drive letter
164 if (dos_drives &&
165 ((c[0] >= 'A' && c[0] <= 'Z') ||
166 (c[0] >= 'a' && c[0] <= 'z')) &&
167 c[1] == ':')
168 c += 2; // skip drive letter like "C:"
169 // path MUST begin with a slash
170 if (!IS_DIRSEP (c[0]))
171 return false;
172 // path MAY contain "./" or more slashes
173 while (IS_DIRSEP (c[0]) || (c[1] == '.' && IS_DIRSEP (c[1])))
174 c += 1 + (c[1] == '.'); // skip slash and possibly leading dot
175 // path MUST NOT contain other entries
176 return c[0] == 0;
177}
178
180bool
181isdirname (const String &path)
182{
183 uint l = path.size();
184 if (path == "." || path == "..")
185 return true;
186 if (l >= 1 && IS_DIRSEP (path[l-1]))
187 return true;
188 if (l >= 2 && IS_DIRSEP (path[l-2]) && path[l-1] == '.')
189 return true;
190 if (l >= 3 && IS_DIRSEP (path[l-3]) && path[l-2] == '.' && path[l-1] == '.')
191 return true;
192 return false;
193}
194
196bool
197mkdirs (const String &dirpath, uint mode)
198{
199 Fs::path target = dirpath;
200 if (check (target, "d"))
201 return true; // IS_DIR
202 if (check (target, "e"))
203 {
204 errno = ENOTDIR;
205 return false; // !IS_DIR
206 }
207 if (target.has_relative_path() &&
208 !mkdirs (target.parent_path(), mode))
209 return false; // !IS_DIR
210 if (mkdir (target.native().c_str(), mode) == 0)
211 return true; // IS_DIR
212 const int saved_errno = errno;
213 if (check (target, "d"))
214 return true; // IS_DIR
215 errno = saved_errno;
216 return false; // !IS_DIR
217}
218
220bool
221dircontains (const String &dirpath, const String &descendant, String *relpath)
222{
223 String child = realpath (descendant);
224 String dir = realpath (dirpath) + ASE_DIRSEP;
225 if (0 == child.compare (0, dir.size(), dir))
226 {
227 if (relpath)
228 *relpath = child.substr (dir.size(), String::npos);
229 return true;
230 }
231 return false;
232}
233
235void
236rmrf (const String &dir)
237{
240}
241
243bool
244copy_file (const String &src, const String &dest)
245{
246 unsigned long ficlone = 0;
247#ifdef FICLONE
248 ficlone = FICLONE;
249#endif
250 // try cloning a file, supported on XFS & BTRFS
251 if (ficlone)
252 {
253 const int srcfd = open (src.c_str(), O_RDONLY | O_NOCTTY);
254 if (srcfd >= 0)
255 {
256 const int dstfd = open (dest.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0644);
257 bool cloned = dstfd >= 0 ? 0 == ioctl (dstfd, ficlone, srcfd) : false; // FICLONE
258 close (srcfd);
259 if (dstfd >= 0)
260 {
261 cloned &= 0 == close (dstfd);
262 if (cloned)
263 return true;
264 // cleanup failed cloning attempt
265 unlink (dest.c_str());
266 }
267 }
268 }
269 // attempt regular file copy
270 std::error_code ec = {};
271 std::filesystem::copy_file (src, dest, ec); // [out] ec
272 errno = ec ? ec.value() : 0;
273 return !ec;
274}
275
276bool
277rename (const String &src, const String &dest)
278{
279 std::error_code ec = {};
280 std::filesystem::rename (src, dest, ec); // [out] ec
281 errno = ec ? ec.value() : 0;
282 return !ec;
283}
284
286String
287user_home (const String &username)
288{
289 if (username.empty())
290 {
291 // $HOME gets precedence over getpwnam(3), like '~/' vs '~username/' expansion
292 const char *homedir = getenv ("HOME");
293 if (homedir && isabs (homedir))
294 return homedir;
295 }
296 CxxPasswd pwn (username);
297 return pwn.pw_dir;
298}
299
301String
303{
304 const char *var = getenv ("XDG_DATA_HOME");
305 if (var && isabs (var))
306 return var;
307 return expand_tilde ("~/.local/share");
308}
309
311String
313{
314 const char *var = getenv ("XDG_CONFIG_HOME");
315 if (var && isabs (var))
316 return var;
317 return expand_tilde ("~/.config");
318}
319
321String
323{
324 const char *var = getenv ("XDG_CACHE_HOME");
325 if (var && isabs (var))
326 return var;
327 return expand_tilde ("~/.cache");
328}
329
331String
333{
334 const char *var = getenv ("XDG_RUNTIME_DIR");
335 if (var && isabs (var))
336 return var;
337 return string_format ("/run/user/%u", getuid());
338}
339
340using StringStringM = std::map<String,String>;
341
342static StringStringM
343xdg_user_dirs()
344{
345 StringStringM defs = {
346 { "XDG_DESKTOP_DIR", "$HOME/Desktop" },
347 { "XDG_DOWNLOAD_DIR", "$HOME/Downloads" },
348 { "XDG_TEMPLATES_DIR", "$HOME/Templates" },
349 { "XDG_PUBLICSHARE_DIR", "$HOME/Public" },
350 { "XDG_DOCUMENTS_DIR", "$HOME/Documents" },
351 { "XDG_MUSIC_DIR", "$HOME/Music" },
352 { "XDG_PICTURES_DIR", "$HOME/Pictures" },
353 { "XDG_VIDEOS_DIR", "$HOME/Videos" },
354 };
355 String udirs = join (config_home(), "user-dirs.dirs"); // https://wiki.archlinux.org/title/XDG_user_directories
356 String data = stringread (udirs);
357 if (!data.empty())
358 {
359 IniFile ff { udirs, data };
360 const String global = "";
361 for (String key : ff.attributes (global))
362 {
363 const String v = ff.value_as_string (global + "." + key);
364 if (!key.empty() && !v.empty())
365 defs[key] = v;
366 }
367 }
368 const String uhome = user_home();
369 for (auto &it : defs)
370 if (string_startswith (it.second, "$HOME/"))
371 it.second = uhome + it.second.substr (5);
372 if (0)
373 for (const auto &pair : defs)
374 printerr ("XDG: %s = %s\n", pair.first, pair.second);
375 return defs;
376}
377
378String
379xdg_dir (const String &xdgdir)
380{
381 const String udir = string_toupper (xdgdir);
382 if (udir == "HOME")
383 return user_home();
384 if (udir == "DATA")
385 return data_home();
386 if (udir == "CONFIG")
387 return config_home();
388 if (udir == "CACHE")
389 return cache_home();
390 if (udir == "RUNTIME")
391 return runtime_dir();
392 static const StringStringM defs = xdg_user_dirs();
393 const auto it = defs.find ("XDG_" + string_toupper (xdgdir) + "_DIR");
394 if (it == defs.end())
395 warning ("%s: unknown XDG dir: %s", __func__, xdgdir);
396 return it != defs.end() ? it->second : "";
397}
398
400String
402{
403 const char *var = getenv ("XDG_CONFIG_DIRS");
404 if (var && var[0])
405 return var;
406 else
407 return "/etc/xdg";
408}
409
411String
413{
414 const char *var = getenv ("XDG_DATA_DIRS");
415 if (var && var[0])
416 return var;
417 else
418 return "/usr/local/share:/usr/share";
419}
420
421static String
422access_config_names (const String *newval)
423{
424 static std::mutex mutex;
425 static std::lock_guard<std::mutex> locker (mutex);
426 static String cfg_names;
427 if (newval)
428 cfg_names = *newval;
429 if (cfg_names.empty())
430 {
432 if (program_alias() != names)
433 names = searchpath_join (names, program_alias());
434 return names;
435 }
436 else
437 return cfg_names;
438}
439
441String
443{
444 return access_config_names (NULL);
445}
446
448void
449config_names (const String &names)
450{
451 access_config_names (&names);
452}
453
455split_extension (const std::string &filepath, const bool lastdot)
456{
457 const char *const fullpath = filepath.c_str();
458 const char *const slash1 = strrchr (fullpath, '/'), *const slash2 = strrchr (fullpath, '\\');
459 const char *const slash = slash2 > slash1 ? slash2 : slash1;
460 const char *const dot = lastdot ? strrchr (slash ? slash : fullpath, '.') : strchr (slash ? slash : fullpath, '.');
461 if (dot)
462 return std::make_pair (filepath.substr (0, dot - fullpath), filepath.substr (dot - fullpath));
463 return std::make_pair (filepath, "");
464}
465
467String
468expand_tilde (const String &path)
469{
470 if (path[0] != '~')
471 return path;
472 const size_t dir1 = path.find (ASE_DIRSEP);
473 const size_t dir2 = ASE_DIRSEP == ASE_DIRSEP2 ? String::npos : path.find (ASE_DIRSEP2);
474 const size_t dir = std::min (dir1, dir2);
475 String username;
476 if (dir != String::npos)
477 username = path.substr (1, dir - 1);
478 else
479 username = path.substr (1);
480 const String userhome = user_home (username);
481 if (userhome.empty())
482 return path;
483 return join (userhome, dir == String::npos ? "" : path.substr (dir));
484}
485
486String
487skip_root (const String &path)
488{
489 return_unless (!path.empty(), path);
490#ifdef _WIN32 // strip C:/
491 if (path.size() >= 3 &&
492 ( (path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z') ) &&
493 path[1] == ':' && IS_DIRSEP (path[2]))
494 return path.substr (3);
495#endif
496#ifdef _WIN32 // strip //server/
497 if (path.size() >= 3 && IS_DIRSEP (path[0]) && IS_DIRSEP (path[1]) && !IS_DIRSEP (path[2]))
498 {
499 const char *p = &path[3];
500 while (*p && !IS_DIRSEP (*p))
501 p++;
502 if (IS_DIRSEP (*p))
503 return path.substr (++p - &path[0]);
504 }
505#endif
506 const char *p = &path[0];
507 while (*p && IS_DIRSEP (*p))
508 p++;
509 return path.substr (p - &path[0]);
510}
511
513size_t
514file_size (const String &path)
515{
516 std::error_code ec = {};
517 size_t size = std::filesystem::file_size (path, ec); // [out] ec
518 return ec ? 0 : size;
519}
520
521static int
522errno_check_file (const char *file_name, const char *mode)
523{
524 uint access_mask = 0, nac = 0;
525
526 if (strchr (mode, 'e')) // exists
527 nac++, access_mask |= F_OK;
528 if (strchr (mode, 'r')) // readable
529 nac++, access_mask |= R_OK;
530 if (strchr (mode, 'w')) // writable
531 nac++, access_mask |= W_OK;
532 bool check_exec = strchr (mode, 'x') != NULL;
533 if (check_exec) // executable
534 nac++, access_mask |= X_OK;
535
536 /* on some POSIX systems, X_OK may succeed for root without any
537 * executable bits set, so we also check via stat() below.
538 */
539 if (nac && access (file_name, access_mask) < 0)
540 return -errno;
541
542 const bool check_size0 = strchr (mode, 'z') != NULL; // zero size
543 const bool check_size1 = strchr (mode, 's') != NULL; // non-zero size
544 const bool check_file = strchr (mode, 'f') != NULL; // open as file
545 const bool check_dir = strchr (mode, 'd') != NULL; // open as directory
546 const bool check_link = strchr (mode, 'L') != NULL || strchr (mode, 'h') != NULL; // open as link
547 const bool check_char = strchr (mode, 'c') != NULL; // open as character device
548 const bool check_block = strchr (mode, 'b') != NULL; // open as block device
549 const bool check_pipe = strchr (mode, 'p') != NULL; // open as pipe
550 const bool check_socket = strchr (mode, 'S') != NULL; // open as socket
551
552 if (check_exec || check_size0 || check_size1 || check_file || check_dir ||
553 check_link || check_char || check_block || check_pipe || check_socket)
554 {
555 struct stat st;
556
557 if (check_link)
558 {
559 if (lstat (file_name, &st) < 0)
560 return -errno;
561 }
562 else if (stat (file_name, &st) < 0)
563 return -errno;
564
565 if (0)
566 printerr ("file-check(\"%s\",\"%s\"): %u %s%s%s%s%s%s%s\n",
567 file_name, mode,
568 st.st_size,
569 S_ISREG (st.st_mode) ? "f" : "",
570 S_ISDIR (st.st_mode) ? "d" : "",
571 S_ISLNK (st.st_mode) ? "L" : "",
572 S_ISCHR (st.st_mode) ? "c" : "",
573 S_ISBLK (st.st_mode) ? "b" : "",
574 S_ISFIFO (st.st_mode) ? "p" : "",
575 S_ISSOCK (st.st_mode) ? "S" : "");
576
577 if (check_size0 && st.st_size != 0)
578 return -EFBIG;
579 if (check_size1 && st.st_size == 0)
580 return -ENODATA;
581 if (S_ISDIR (st.st_mode) && (check_file || check_link || check_char || check_block || check_pipe))
582 return -EISDIR;
583 if (check_file && !S_ISREG (st.st_mode))
584 return -EINVAL;
585 if (check_dir && !S_ISDIR (st.st_mode))
586 return -ENOTDIR;
587 if (check_link && !S_ISLNK (st.st_mode))
588 return -EINVAL;
589 if (check_char && !S_ISCHR (st.st_mode))
590 return -ENODEV;
591 if (check_block && !S_ISBLK (st.st_mode))
592 return -ENOTBLK;
593 if (check_pipe && !S_ISFIFO (st.st_mode))
594 return -ENXIO;
595 if (check_socket && !S_ISSOCK (st.st_mode))
596 return -ENOTSOCK;
597 if (check_exec && !(st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)))
598 return -EACCES; // for root executable, any +x bit is good enough
599 }
600
601 return 0;
602}
603
624bool
625check (const String &file, const String &mode)
626{
627 const int err = file.size() && mode.size() ? errno_check_file (file.c_str(), mode.c_str()) : -ENOENT;
628 errno = err < 0 ? -err : 0;
629 return errno == 0;
630}
631
640bool
641equals (const String &file1, const String &file2)
642{
643 if (!file1.size() || !file2.size())
644 return file1.size() == file2.size();
645 struct stat st1 = { 0, }, st2 = { 0, };
646 int err1 = 0, err2 = 0;
647 errno = 0;
648 if (stat (file1.c_str(), &st1) < 0 && stat (file1.c_str(), &st1) < 0)
649 err1 = errno;
650 errno = 0;
651 if (stat (file2.c_str(), &st2) < 0 && stat (file2.c_str(), &st2) < 0)
652 err2 = errno;
653 if (err1 || err2)
654 return false;
655 return (st1.st_dev == st2.st_dev &&
656 st1.st_ino == st2.st_ino &&
657 st1.st_rdev == st2.st_rdev);
658}
659
661String
663{
664#ifdef _GNU_SOURCE
665 {
666 char *dir = get_current_dir_name();
667 if (dir)
668 {
669 const String result = dir;
670 free (dir);
671 return result;
672 }
673 }
674#endif
675 size_t size = 512;
676 do
677 {
678 char *buf = (char*) malloc (size);
679 if (!buf)
680 break;
681 const char *const dir = getcwd (buf, size);
682 if (dir)
683 {
684 const String result = dir;
685 free (buf);
686 return result;
687 }
688 free (buf);
689 size *= 2;
690 }
691 while (errno == ERANGE);
692 // system must be in a bad shape if we get here...
693 return "./";
694}
695
697searchpath_split (const String &searchpath)
698{
699 StringS sv;
700 uint i, l = 0;
701 for (i = 0; i < searchpath.size(); i++)
702 if (IS_SEARCHPATH_SEPARATOR (searchpath[i]))
703 {
704 if (i > l)
705 sv.push_back (searchpath.substr (l, i - l));
706 l = i + 1;
707 }
708 if (i > l)
709 sv.push_back (searchpath.substr (l, i - l));
710 return sv;
711}
712
714bool
715searchpath_contains (const String &searchpath, const String &element)
716{
717 const bool dirsearch = element.size() > 0 && IS_DIRSEP (element[element.size() - 1]);
718 const String needle = dirsearch && element.size() > 1 ? element.substr (0, element.size() - 1) : element; // strip trailing slash
719 size_t pos = searchpath.find (needle);
720 while (pos != String::npos)
721 {
722 size_t end = pos + needle.size();
723 if (pos == 0 || IS_SEARCHPATH_SEPARATOR (searchpath[pos - 1]))
724 {
725 if (dirsearch && IS_DIRSEP (searchpath[end]))
726 end++; // skip trailing slash in searchpath segment
727 if (searchpath[end] == 0 || IS_SEARCHPATH_SEPARATOR (searchpath[end]))
728 return true;
729 }
730 pos = searchpath.find (needle, end);
731 }
732 return false;
733}
734
736String
737searchpath_find (const String &searchpath, const String &file, const String &mode)
738{
739 if (isabs (file))
740 return check (file, mode) ? file : "";
741 StringS sv = searchpath_split (searchpath);
742 for (size_t i = 0; i < sv.size(); i++)
743 if (check (join (sv[i], file), mode))
744 return join (sv[i], file);
745 return "";
746}
747
750searchpath_list (const String &searchpath, const String &mode)
751{
752 StringS v;
753 for (const auto &file : searchpath_split (searchpath))
754 if (check (file, mode))
755 v.push_back (file);
756 return v;
757}
758
760String
761join_with (const String &head, char joiner, const String &tail)
762{
763 return_unless (head.size(), tail);
764 return_unless (tail.size(), head);
765 if (head.back() == joiner)
766 {
767 if (tail[0] == joiner)
768 return head + tail.substr (1);
769 return head + tail;
770 }
771 if (tail[0] == joiner)
772 return head + tail;
773 return head + joiner + tail;
774}
775
777String
778searchpath_multiply (const String &searchpath, const String &postfixes)
779{
780 String newpath;
781 for (const auto &e : searchpath_split (searchpath))
782 for (const auto &p : searchpath_split (postfixes))
783 newpath = join_with (newpath, ASE_SEARCHPATH_SEPARATOR, join (e, p));
784 return newpath;
785}
786
787String
788searchpath_join (const StringS &string_vector)
789{
790 const char searchsep[2] = { ASE_SEARCHPATH_SEPARATOR, 0 };
791 return string_join (searchsep, string_vector);
792}
793
794String
795vpath_find (const String &file, const String &mode)
796{
797 String result = searchpath_find (".", file, mode);
798 if (!result.empty())
799 return result;
800 const char *vpath = getenv ("VPATH");
801 if (vpath)
802 {
803 result = searchpath_find (vpath, file, mode);
804 if (!result.empty())
805 return result;
806 }
807 return file;
808}
809
811void
812glob (const String &pathpattern, StringS &dirs, StringS &files)
813{
814 glob_t iglob = { 0, };
815 const int ir = ::glob (pathpattern.c_str(), GLOB_TILDE | GLOB_MARK, nullptr, &iglob);
816 if (ir != 0)
817 return;
818 for (size_t i = 0; i < iglob.gl_pathc; i++) {
819 const char *const p = iglob.gl_pathv[i];
820 size_t l = strlen (p);
821 if (IS_DIRSEP (p[l-1]))
822 dirs.push_back (p);
823 else
824 files.push_back (p);
825 }
826 globfree (&iglob);
827}
828
830void
831glob (const String &pathpattern, StringS &matches)
832{
833 glob_t iglob = { 0, };
834 const int ir = ::glob (pathpattern.c_str(), GLOB_TILDE, nullptr, &iglob);
835 if (ir != 0)
836 return;
837 for (size_t i = 0; i < iglob.gl_pathc; i++)
838 matches.push_back (iglob.gl_pathv[i]);
839 globfree (&iglob);
840}
841
843void
844rglob (const String &basedir, const String &pattern, StringS &matches)
845{
846 glob_t iglob = { 0, };
847 const int ir = ::glob (basedir.c_str(), GLOB_TILDE_CHECK | GLOB_NOSORT | GLOB_MARK | GLOB_ONLYDIR, nullptr, &iglob);
848 if (ir != 0)
849 return;
850 for (size_t i = 0; i < iglob.gl_pathc; i++)
851 {
852 std::string subdir = iglob.gl_pathv[i];
853 if (subdir[subdir.size()-1] != ASE_DIRSEP && subdir[subdir.size()-1] != ASE_DIRSEP2)
854 continue;
855 rglob (subdir + "*", pattern, matches);
856 glob_t jglob = { 0, };
857 const int jr = ::glob ((subdir + pattern).c_str(), GLOB_NOSORT, nullptr, &jglob);
858 if (jr != 0)
859 continue;
860 for (size_t j = 0; j < jglob.gl_pathc; j++)
861 matches.push_back (jglob.gl_pathv[j]);
862 globfree (&jglob);
863 }
864 globfree (&iglob);
865}
866
868void
870{
871 size_t j = 0;
872 for (ssize_t i = 0; i < pathnames.size(); i++) {
873 char *rpath = ::realpath (pathnames[i].c_str(), nullptr);
874 if (rpath) {
875 pathnames[j++] = rpath;
876 free (rpath);
877 }
878 }
879 pathnames.resize (j);
880 strings_version_sort (&pathnames);
881 pathnames.erase (std::unique (pathnames.begin(), pathnames.end()), pathnames.end());
882}
883
885String
886simplify_abspath (const std::string &abspath_expression)
887{
888 std::vector<std::string> dirs = Ase::string_split (abspath_expression, "/");
889 for (ssize_t i = 0; i < ssize_t (dirs.size()); i++)
890 if (dirs[i].empty() || dirs[i] == ".")
891 dirs.erase (dirs.begin() + i--);
892 else if (dirs[i] == "..")
893 {
894 dirs.erase (dirs.begin() + i--);
895 if (i >= 0)
896 dirs.erase (dirs.begin() + i--);
897 }
898 return "/" + string_join ("/", dirs);
899}
900
901static char* // return malloc()-ed buffer containing a full read of FILE
902file_memread (FILE *stream, size_t *lengthp, ssize_t maxlength)
903{
904 size_t sz = maxlength <= 0 || maxlength > 1048576 ? 1048576 : maxlength;
905 char *buffer = (char*) malloc (sz);
906 if (!buffer)
907 return NULL;
908 char *current = buffer;
909 errno = 0;
910 while (!feof (stream))
911 {
912 ssize_t bytes = fread (current, 1, sz - (current - buffer), stream);
913 if (bytes <= 0 && ferror (stream) && errno != EAGAIN)
914 {
915 current = buffer; // error/0-data
916 break;
917 }
918 current += bytes;
919 if (maxlength >= 0 && current - buffer >= maxlength)
920 {
921 current = buffer + maxlength; // shorten if needed
922 break;
923 }
924 if (current == buffer + sz)
925 {
926 bytes = current - buffer;
927 sz *= 2;
928 char *newstring = (char*) realloc (buffer, sz);
929 if (!newstring)
930 {
931 current = buffer; // error/0-data
932 break;
933 }
934 buffer = newstring;
935 current = buffer + bytes;
936 }
937 }
938 int savederr = errno;
939 *lengthp = current - buffer;
940 if (!*lengthp)
941 {
942 free (buffer);
943 buffer = NULL;
944 }
945 errno = savederr;
946 return buffer;
947}
948
949char*
950memread (const String &filename, size_t *lengthp, ssize_t maxlength)
951{
952 FILE *file = fopen (filename.c_str(), "r");
953 if (!file)
954 {
955 *lengthp = 0;
956 return strdup ("");
957 }
958 char *contents = file_memread (file, lengthp, maxlength);
959 int savederr = errno;
960 fclose (file);
961 contents = (char*) realloc (contents, *lengthp);
962 errno = savederr;
963 return contents;
964}
965
966void
967memfree (char *memread_mem)
968{
969 if (memread_mem)
970 free (memread_mem);
971}
972
973bool
974memwrite (const String &filename, size_t len, const uint8 *bytes, bool append, int perms)
975{
976 FILE *file = fopen (filename.c_str(), append ? "a" : "w");
977 if (!file)
978 return false;
979 if (perms != -1 && fchmod (fileno (file), perms) != 0) {
980 const int err = errno;
981 fclose (file);
982 unlink (filename.c_str());
983 errno = err;
984 return false;
985 }
986 const size_t nbytes = fwrite (bytes, 1, len, file);
987 bool success = ferror (file) == 0 && nbytes == len;
988 success = fclose (file) == 0 && success;
989 if (!success) {
990 const int err = errno;
991 unlink (filename.c_str());
992 errno = err;
993 }
994 return success;
995}
996
997// Read `filename` into a std::string, check `errno` for empty returns.
998String
999stringread (const String &filename, ssize_t maxlength)
1000{
1001 String s;
1002 size_t length = 0;
1003 errno = 0;
1004 char *data = memread (filename, &length, maxlength);
1005 if (data)
1006 {
1007 s = String (data, length);
1008 memfree (data);
1009 errno = 0;
1010 }
1011 return s;
1012}
1013
1014// Write `data` into `filename`, check `errno` for false returns.
1015bool
1016stringwrite (const String &filename, const String &data, bool mkdirs_, int perms)
1017{
1018 if (mkdirs_)
1019 mkdirs (dirname (filename), 0750);
1020 return memwrite (filename, data.size(), (const uint8*) data.data(), false, perms);
1021}
1022
1023bool
1024stringappend (const String &filename, const String &data, bool mkdirs_, int perms)
1025{
1026 if (mkdirs_)
1027 mkdirs (dirname (filename), 0750);
1028 return memwrite (filename, data.size(), (const uint8*) data.data(), true, perms);
1029}
1030
1031} // Path
1032} // Ase
1033
1034#include <pwd.h> // getpwuid
1035
1036// == CxxPasswd =
1037namespace { // Anon
1038CxxPasswd::CxxPasswd (std::string username) :
1039 pw_uid (-1), pw_gid (-1)
1040{
1041 const int strbuf_size = 5 * 1024;
1042 char strbuf[strbuf_size + 256]; // work around Darwin getpwnam_r overwriting buffer boundaries
1043 struct passwd pwnambuf, *p = NULL;
1044 if (username.empty())
1045 {
1046 int ret = 0;
1047 errno = 0;
1048 do
1049 {
1050 if (1) // HAVE_GETPWUID_R
1051 ret = getpwuid_r (getuid(), &pwnambuf, strbuf, strbuf_size, &p);
1052 else // HAVE_GETPWUID
1053 p = getpwuid (getuid());
1054 }
1055 while ((ret != 0 || p == NULL) && errno == EINTR);
1056 if (ret != 0)
1057 p = NULL;
1058 }
1059 else // !username.empty()
1060 {
1061 int ret = 0;
1062 errno = 0;
1063 do
1064 ret = getpwnam_r (username.c_str(), &pwnambuf, strbuf, strbuf_size, &p);
1065 while ((ret != 0 || p == NULL) && errno == EINTR);
1066 if (ret != 0)
1067 p = NULL;
1068 }
1069 if (p)
1070 {
1071 pw_name = p->pw_name;
1072 pw_passwd = p->pw_passwd;
1073 pw_uid = p->pw_uid;
1074 pw_gid = p->pw_gid;
1075 pw_gecos = p->pw_gecos;
1076 pw_dir = p->pw_dir;
1077 pw_shell = p->pw_shell;
1078 }
1079}
1080} // Anon
1081
1082// == Testing ==
1083#include "testing.hh"
1084#include "internal.hh"
1085
1086namespace { // Anon
1087using namespace Ase;
1088
1089TEST_INTEGRITY (path_tests);
1090static void
1091path_tests()
1092{
1093 String p, s;
1094 // Path::join
1095 s = Path::join ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f");
1096#if ASE_DIRSEP == '/'
1097 p = "0/1/2/3/4/5/6/7/8/9/a/b/c/d/e/f";
1098#else
1099 p = "0\\1\\2\\3\\4\\5\\6\\7\\8\\9\\a\\b\\c\\d\\e\\f";
1100#endif
1101 TCMP (s, ==, p);
1102 // Path::searchpath_join
1103 s = Path::searchpath_join ("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f");
1104#if ASE_SEARCHPATH_SEPARATOR == ';'
1105 p = "0;1;2;3;4;5;6;7;8;9;a;b;c;d;e;f";
1106#else
1107 p = "0:1:2:3:4:5:6:7:8:9:a:b:c:d:e:f";
1108#endif
1109 TCMP (s, ==, p);
1110 // Path
1111 bool b = Path::isabs (p);
1112 TCMP (b, ==, false);
1113 const char dirsep[2] = { ASE_DIRSEP, 0 };
1114#if ASE_DIRSEP == '/'
1115 s = Path::join (dirsep, s);
1116#else
1117 s = Path::join ("C:\\", s);
1118#endif
1119 b = Path::isabs (s);
1120 TCMP (b, ==, true);
1121 s = Path::skip_root (s);
1122 TCMP (s, ==, p);
1123 // TASSERT (Path::dir_separator == "/" || Path::dir_separator == "\\");
1125 TCMP (Path::basename ("simple"), ==, "simple");
1126 TCMP (Path::basename ("skipthis" + String (dirsep) + "file"), ==, "file");
1127 TCMP (Path::basename (String (dirsep) + "skipthis" + String (dirsep) + "file"), ==, "file");
1128 TCMP (Path::dirname ("file"), ==, ".");
1129 TCMP (Path::dirname ("dir" + String (dirsep)), ==, "dir");
1130 TCMP (Path::dirname ("dir" + String (dirsep) + "file"), ==, "dir");
1131 TCMP (Path::cwd(), !=, "");
1132 TCMP (Path::check (Path::join (Path::cwd(), "..", Path::basename (Path::cwd())), "rd"), ==, true); // ../. should be a readable directory
1133 TASSERT (Path::isroot ("/") == true);
1134 TASSERT (Path::isroot ("//") == true);
1135 TASSERT (Path::isroot ("//////////") == true);
1136 TASSERT (Path::isroot ("/.") == true);
1137 TASSERT (Path::isroot ("./") == false);
1138 TASSERT (Path::isroot (".////") == false);
1139 TASSERT (Path::isroot ("/./") == true);
1140 TASSERT (Path::isroot ("/./././././") == true);
1141 TASSERT (Path::isroot ("/././././.") == true);
1142 TASSERT (Path::isroot ("///././././//.///") == true);
1143 TASSERT (Path::isroot ("///././././//.///.") == true);
1144 TASSERT (Path::isroot ("abc") == false);
1145 TASSERT (Path::isroot ("C:/", true) == true);
1146 TASSERT (Path::isroot ("C:/.", true) == true);
1147 TASSERT (Path::isroot ("8:/", true) == false);
1148 TASSERT (Path::isroot ("8:/..", true) == false);
1149 TASSERT (Path::isroot ("C:/D", true) == false);
1150 TCMP (Path::isdirname (""), ==, false);
1151 TCMP (Path::isdirname ("foo"), ==, false);
1152 TCMP (Path::isdirname ("foo/"), ==, true);
1153 TCMP (Path::isdirname ("/foo"), ==, false);
1154 TCMP (Path::isdirname ("foo/."), ==, true);
1155 TCMP (Path::isdirname ("foo/.."), ==, true);
1156 TCMP (Path::isdirname ("foo/..."), ==, false);
1157 TCMP (Path::isdirname ("foo/..../"), ==, true);
1158 TCMP (Path::isdirname ("/."), ==, true);
1159 TCMP (Path::isdirname ("/.."), ==, true);
1160 TCMP (Path::isdirname ("/"), ==, true);
1161 TCMP (Path::isdirname ("."), ==, true);
1162 TCMP (Path::isdirname (".."), ==, true);
1163 TCMP (Path::expand_tilde (""), ==, "");
1164 const char *env_home = getenv ("HOME");
1165 if (env_home)
1166 TCMP (Path::expand_tilde ("~"), ==, env_home);
1167 const char *env_logname = getenv ("LOGNAME");
1168 if (env_home && env_logname)
1169 TCMP (Path::expand_tilde ("~" + String (env_logname)), ==, env_home);
1170 TCMP (Path::expand_tilde ("~:unknown/"), ==, "~:unknown/");
1171 TCMP (Path::searchpath_multiply ("/:/tmp", "foo:bar"), ==, "/foo:/bar:/tmp/foo:/tmp/bar");
1172 const String abs_basedir = Path::abspath (anklang_runpath (RPath::PREFIXDIR));
1173 TCMP (Path::searchpath_list ("/:" + abs_basedir, "e"), ==, StringS ({ "/", abs_basedir }));
1174 TCMP (Path::searchpath_contains ("/foo/:/bar", "/"), ==, false);
1175 TCMP (Path::searchpath_contains ("/foo/:/bar", "/foo"), ==, false); // false because "/foo" is file search
1176 TCMP (Path::searchpath_contains ("/foo/:/bar", "/foo/"), ==, true); // true because "/foo/" is dir search
1177 TCMP (Path::searchpath_contains ("/foo/:/bar", "/bar"), ==, true); // file search matches /bar
1178 TCMP (Path::searchpath_contains ("/foo/:/bar", "/bar/"), ==, true); // dir search matches /bar
1179 TCMP (Path::skip_root ("foo/"), ==, "foo/");
1180 TCMP (Path::skip_root ("/foo/"), ==, "foo/");
1181 TCMP (Path::skip_root ("///foo/"), ==, "foo/");
1182#if 0
1183 TCMP (Path::check ("/tmp/empty", "z"), ==, true);
1184 TCMP (Path::check ("/tmp/empty", "s"), ==, false);
1185#endif
1186 TCMP (Path::check ("/etc/os-release", "s"), ==, true);
1187 TCMP (Path::check ("/etc/os-release", "z"), ==, false);
1188#ifdef _WIN32
1189 TCMP (Path::skip_root ("//foo/."), ==, ".");
1190 TCMP (Path::skip_root ("C:/foo/."), ==, "foo/.");
1191 TCMP (Path::skip_root ("\\\\foo\\."), ==, ".");
1192 TCMP (Path::skip_root ("C:\\foo\\."), ==, "foo\\.");
1193#else
1194 TCMP (Path::skip_root ("//foo/."), ==, "foo/.");
1195 TCMP (Path::skip_root ("C:/foo/."), ==, "C:/foo/.");
1196#endif
1197}
1198
1199} // Anon
#define ENOTDIR
T back(T... args)
basename
T begin(T... args)
T c_str(T... args)
close
T compare(T... args)
T copy_file(T... args)
T data(T... args)
dirname
T empty(T... args)
T end(T... args)
T erase(T... args)
errno
fclose
ferror
T file_size(T... args)
T find(T... args)
fopen
fread
free
stat
fwrite
getcwd
getenv
getpwnam_r
getpwuid_r
getuid
glob
T has_relative_path(T... args)
#define return_unless(cond,...)
Return silently if cond does not evaluate to true with return value ...
Definition internal.hh:71
#define TEST_INTEGRITY(FUNC)
Register func as an integrity test.
Definition internal.hh:77
ioctl
T lexically_normal(T... args)
T make_pair(T... args)
malloc
T min(T... args)
mkdir
String user_home(const String &username)
Get a user's home directory, uses $HOME if no username is given.
Definition path.cc:287
String cwd()
Return the current working directoy, including symlinks used in $PWD if available.
Definition path.cc:662
bool searchpath_contains(const String &searchpath, const String &element)
Check if searchpath contains element, a trailing slash searches for directories.
Definition path.cc:715
String config_dirs()
Get the $XDG_CONFIG_DIRS directory list, see: https://specifications.freedesktop.org/basedir-spec/lat...
Definition path.cc:401
void unique_realpaths(StringS &pathnames)
Convert all pathnames via realpath() and eliminate duplicates.
Definition path.cc:869
String config_names()
Get config names as set with config_names(), if unset defaults to program_alias().
Definition path.cc:442
String basename(const String &path)
Strips all directory components from path and returns the resulting file name.
Definition path.cc:68
String config_home()
Get the $XDG_CONFIG_HOME directory, see: https://specifications.freedesktop.org/basedir-spec/latest.
Definition path.cc:312
String searchpath_multiply(const String &searchpath, const String &postfixes)
Yield a new searchpath by combining each element of searchpath with each element of postfixes.
Definition path.cc:778
bool mkdirs(const String &dirpath, uint mode)
Create the directories in dirpath with mode, check errno on false returns.
Definition path.cc:197
bool check(const String &file, const String &mode)
Definition path.cc:625
String expand_tilde(const String &path)
Expand a "~/" or "~user/" path which refers to user home directories.
Definition path.cc:468
bool equals(const String &file1, const String &file2)
Definition path.cc:641
String dir_terminate(const String &path)
Append trailing slash to path, unless it's present.
Definition path.cc:109
String simplify_abspath(const std::string &abspath_expression)
Remove extra slashes, './' and '../' from abspath_expression.
Definition path.cc:886
bool isdirname(const String &path)
Return wether path is pointing to a directory component.
Definition path.cc:181
String strip_slashes(const String &path)
Strip trailing directory terminators.
Definition path.cc:118
String data_home()
Get the $XDG_DATA_HOME directory, see: https://specifications.freedesktop.org/basedir-spec/latest.
Definition path.cc:302
String cache_home()
Get the $XDG_CACHE_HOME directory, see: https://specifications.freedesktop.org/basedir-spec/latest.
Definition path.cc:322
String join_with(const String &head, char joiner, const String &tail)
Construct head + joiner + tail avoiding duplicates of joiner.
Definition path.cc:761
String runtime_dir()
Get the $XDG_RUNTIME_DIR directory, see: https://specifications.freedesktop.org/basedir-spec/latest.
Definition path.cc:332
String searchpath_find(const String &searchpath, const String &file, const String &mode)
Find the first file in searchpath which matches mode (see check()).
Definition path.cc:737
String abspath(const String &path, const String &incwd)
Definition path.cc:134
void rmrf(const String &dir)
Recursively delete directory tree.
Definition path.cc:236
size_t file_size(const String &path)
Retrieve the on-disk size in bytes of path.
Definition path.cc:514
bool copy_file(const String &src, const String &dest)
Copy a file to a new non-existing location, sets errno and returns false on error.
Definition path.cc:244
bool isabs(const String &path)
Return wether path is an absolute pathname.
Definition path.cc:148
bool isroot(const String &path, bool dos_drives)
Return wether path is an absolute pathname which identifies the root directory.
Definition path.cc:160
String data_dirs()
Get the $XDG_DATA_DIRS directory list, see: https://specifications.freedesktop.org/basedir-spec/lates...
Definition path.cc:412
bool dircontains(const String &dirpath, const String &descendant, String *relpath)
Check if descendant belongs to the directory hierarchy under dirpath.
Definition path.cc:221
void rglob(const String &basedir, const String &pattern, StringS &matches)
Recursively match files with glob pattern under basedir.
Definition path.cc:844
StringS searchpath_list(const String &searchpath, const String &mode)
Find all searchpath entries matching mode (see check()).
Definition path.cc:750
String normalize(const String &path)
Convert path to normal form.
Definition path.cc:86
The Anklang C++ API namespace.
Definition api.hh:9
std::string string_format(const char *format, const Args &...args) __attribute__((__format__(__printf__
Format a string similar to sprintf(3) with support for std::string and std::ostringstream convertible...
String string_join(const String &junctor, const StringS &strvec)
Definition strings.cc:452
StringS string_split(const String &string, const String &splitter, size_t maxn)
Definition strings.cc:343
uint8_t uint8
An 8-bit unsigned integer.
Definition cxxaux.hh:22
String string_toupper(const String &str)
Convert all string characters into Unicode upper case characters.
Definition strings.cc:156
String program_cwd()
The current working directory during startup.
Definition platform.cc:877
String program_alias()
Retrieve the program name as used for logging or debug messages.
Definition platform.cc:849
std::string String
Convenience alias for std::string.
Definition cxxaux.hh:35
uint32_t uint
Provide 'uint' as convenience type.
Definition cxxaux.hh:18
bool string_startswith(const String &string, const String &fragment)
Returns whether string starts with fragment.
Definition strings.cc:846
open
T parent_path(T... args)
#define ASE_DOS_PATHS
Equals 1 on _WIN32 and _WIN64 and 0 on Unix.
Definition path.hh:17
#define ASE_DIRSEP
Platform directory separator character, '/' on Unix-like systems, a '\' on _WIN32.
Definition path.hh:18
#define ASE_DIRSEP2
Secondary directory separator character, '/' on Unix-like systems.
Definition path.hh:19
#define ASE_SEARCHPATH_SEPARATOR
Platform searchpath separator, ':' on Unix-like systems, ';' on _WIN32.
Definition path.hh:21
T push_back(T... args)
realpath
T remove_all(T... args)
T rename(T... args)
rename
T resize(T... args)
T size(T... args)
strchr
strdup
strlen
strrchr
T substr(T... args)
typedef uid_t
#define TASSERT(cond)
Unconditional test assertion, enters breakpoint if not fullfilled.
Definition testing.hh:24
#define TCMP(a, cmp, b)
Compare a and b according to operator cmp, verbose on failiure.
Definition testing.hh:23
T unique(T... args)
unlink
T value(T... args)