mirror of https://github.com/KLayout/klayout.git
1196 lines
28 KiB
C++
1196 lines
28 KiB
C++
|
|
/*
|
|
|
|
KLayout Layout Viewer
|
|
Copyright (C) 2006-2025 Matthias Koefferlein
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
*/
|
|
|
|
#include "tlFileUtils.h"
|
|
#include "tlStream.h"
|
|
#include "tlLog.h"
|
|
#include "tlInternational.h"
|
|
#include "tlEnv.h"
|
|
#include "tlGlobPattern.h"
|
|
|
|
#include <cctype>
|
|
#include <fstream>
|
|
|
|
// Use this define to print debug output
|
|
// #define FILE_UTILS_VERBOSE
|
|
|
|
#if defined(_MSC_VER)
|
|
|
|
# include <sys/types.h>
|
|
# include <sys/stat.h>
|
|
# include <io.h>
|
|
# include <Windows.h>
|
|
|
|
#elif defined(_WIN32)
|
|
|
|
# include <sys/stat.h>
|
|
# include <unistd.h>
|
|
# include <dirent.h>
|
|
# include <dir.h>
|
|
# include <Windows.h>
|
|
|
|
#elif defined(__APPLE__)
|
|
|
|
# include <sys/stat.h>
|
|
# include <unistd.h>
|
|
# include <dirent.h>
|
|
# include <libproc.h>
|
|
# include <dlfcn.h>
|
|
# include <pwd.h>
|
|
|
|
#else
|
|
|
|
# include <sys/stat.h>
|
|
# include <unistd.h>
|
|
# include <dirent.h>
|
|
# include <dlfcn.h>
|
|
# include <pwd.h>
|
|
|
|
#endif
|
|
|
|
#if defined(__FreeBSD__)
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/sysctl.h>
|
|
|
|
#endif
|
|
|
|
namespace tl
|
|
{
|
|
|
|
enum { OS_Auto, OS_Windows, OS_Linux } s_mode = OS_Auto;
|
|
|
|
static bool is_win ()
|
|
{
|
|
if (s_mode == OS_Windows) {
|
|
return true;
|
|
} else if (s_mode == OS_Linux) {
|
|
return false;
|
|
} else {
|
|
#if defined(_WIN32)
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
}
|
|
|
|
// Secret mode switchers for testing
|
|
TL_PUBLIC void file_utils_force_windows () { s_mode = OS_Windows; }
|
|
TL_PUBLIC void file_utils_force_linux () { s_mode = OS_Linux; }
|
|
TL_PUBLIC void file_utils_force_reset () { s_mode = OS_Auto; }
|
|
|
|
const char *line_separator ()
|
|
{
|
|
return is_win () ? "\r\n" : "\n";
|
|
}
|
|
|
|
static bool is_drive (const std::string &part)
|
|
{
|
|
return is_win () && (part.size () == 2 && isalpha (part[0]) && part[1] == ':');
|
|
}
|
|
|
|
static std::string normalized_part (const std::string &part)
|
|
{
|
|
if (! is_win ()) {
|
|
return part;
|
|
}
|
|
|
|
std::string p;
|
|
p.reserve (part.size ());
|
|
const char *cp = part.c_str ();
|
|
while (*cp == '\\' || *cp == '/') {
|
|
p += '\\';
|
|
++cp;
|
|
}
|
|
p += cp;
|
|
return p;
|
|
}
|
|
|
|
static std::string trimmed_part (const std::string &part)
|
|
{
|
|
const char *cp = part.c_str ();
|
|
|
|
if (is_win ()) {
|
|
while (*cp == '\\' || *cp == '/') {
|
|
++cp;
|
|
}
|
|
} else {
|
|
while (*cp == '/') {
|
|
++cp;
|
|
}
|
|
}
|
|
|
|
return std::string (cp);
|
|
}
|
|
|
|
static bool is_part_with_separator (const std::string &part)
|
|
{
|
|
const char *cp = part.c_str ();
|
|
if (is_win ()) {
|
|
return (*cp == '\\' || *cp == '/');
|
|
} else {
|
|
return (*cp == '/');
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> split_path (const std::string &p, bool keep_last)
|
|
{
|
|
std::vector<std::string> parts;
|
|
|
|
bool first = true;
|
|
|
|
if (is_win ()) {
|
|
|
|
const char *cp = p.c_str ();
|
|
if (*cp && isalpha (*cp) && cp[1] == ':') {
|
|
|
|
// drive name
|
|
parts.push_back (std::string ());
|
|
parts.back () += toupper (*cp);
|
|
parts.back () += ":";
|
|
|
|
cp += 2;
|
|
|
|
} else if ((*cp == '\\' && cp[1] == '\\') || (*cp == '/' && cp[1] == '/')) {
|
|
|
|
// UNC server name
|
|
const char *cp0 = cp;
|
|
cp += 2;
|
|
while (*cp && *cp != '\\' && *cp != '/') {
|
|
++cp;
|
|
}
|
|
parts.push_back (tl::normalized_part (std::string (cp0, 0, cp - cp0)));
|
|
|
|
} else if ((*cp == '\\' || *cp == '/') && cp[1] && isalpha (cp[1]) && cp[2] == ':') {
|
|
|
|
// drive name in the form "/c:" or "\c:"
|
|
parts.push_back (std::string ());
|
|
parts.back () += toupper (cp[1]);
|
|
parts.back () += ":";
|
|
|
|
cp += 3;
|
|
|
|
}
|
|
|
|
while (*cp) {
|
|
|
|
const char *cp0 = cp;
|
|
bool any = false;
|
|
while (*cp && (!any || (*cp != '\\' && *cp != '/'))) {
|
|
if (*cp != '\\' && *cp != '/') {
|
|
any = true;
|
|
} else {
|
|
cp0 = cp;
|
|
}
|
|
++cp;
|
|
}
|
|
|
|
if (any || first || keep_last) {
|
|
first = false;
|
|
parts.push_back (tl::normalized_part (std::string (cp0, 0, cp - cp0)));
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const char *cp = p.c_str ();
|
|
while (*cp) {
|
|
|
|
const char *cp0 = cp;
|
|
bool any = false;
|
|
while (*cp && (!any || *cp != '/')) {
|
|
if (*cp != '/') {
|
|
any = true;
|
|
} else {
|
|
cp0 = cp;
|
|
}
|
|
// backslash escape
|
|
if (*cp == '\\' && cp[1]) {
|
|
++cp;
|
|
}
|
|
++cp;
|
|
}
|
|
|
|
if (any || first || keep_last) {
|
|
first = false;
|
|
parts.push_back (std::string (cp0, 0, cp - cp0));
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
static std::vector<std::string> split_filename (const std::string &fn)
|
|
{
|
|
std::vector<std::string> parts;
|
|
|
|
const char *cp = fn.c_str ();
|
|
|
|
while (*cp) {
|
|
|
|
const char *cp0 = cp;
|
|
++cp;
|
|
while (*cp && *cp != '.') {
|
|
if (*cp == '\\' && cp[1]) {
|
|
++cp;
|
|
}
|
|
++cp;
|
|
}
|
|
|
|
parts.push_back (std::string (cp0, 0, cp - cp0));
|
|
if (*cp) {
|
|
++cp;
|
|
}
|
|
|
|
}
|
|
|
|
return parts;
|
|
}
|
|
|
|
std::string normalize_path (const std::string &s)
|
|
{
|
|
return tl::join (tl::split_path (s), "");
|
|
}
|
|
|
|
std::string combine_path (const std::string &p1, const std::string &p2, bool always_join)
|
|
{
|
|
if (! always_join && p2.empty ()) {
|
|
return p1;
|
|
} else if (is_win ()) {
|
|
return p1 + "\\" + p2;
|
|
} else {
|
|
return p1 + "/" + p2;
|
|
}
|
|
}
|
|
|
|
std::string dirname (const std::string &s)
|
|
{
|
|
std::vector<std::string> parts = split_path (s, true /*keep last part*/);
|
|
if (parts.size () > 0) {
|
|
parts.pop_back ();
|
|
}
|
|
|
|
if (parts.empty ()) {
|
|
return is_part_with_separator (s) ? "" : ".";
|
|
} else {
|
|
return tl::join (parts, "");
|
|
}
|
|
}
|
|
|
|
std::string filename (const std::string &s)
|
|
{
|
|
std::vector<std::string> parts = split_path (s, true /*keep last part*/);
|
|
if (parts.size () > 0) {
|
|
return trimmed_part (parts.back ());
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
std::string basename (const std::string &s)
|
|
{
|
|
std::vector<std::string> fnp = split_filename (filename (s));
|
|
if (fnp.size () > 0) {
|
|
return fnp.front ();
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
std::string complete_basename (const std::string &s)
|
|
{
|
|
std::vector<std::string> fnp = split_filename (filename (s));
|
|
if (fnp.size () > 0) {
|
|
fnp.pop_back ();
|
|
return tl::join (fnp, ".");
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
std::string extension (const std::string &s)
|
|
{
|
|
std::vector<std::string> fnp = split_filename (filename (s));
|
|
if (fnp.size () > 0) {
|
|
fnp.erase (fnp.begin ());
|
|
}
|
|
return tl::join (fnp, ".");
|
|
}
|
|
|
|
std::string extension_last (const std::string &s)
|
|
{
|
|
std::vector<std::string> fnp = split_filename (filename (s));
|
|
if (fnp.size () > 1) {
|
|
return fnp.back ();
|
|
} else {
|
|
return std::string ();
|
|
}
|
|
}
|
|
|
|
bool
|
|
is_parent_path (const std::string &parent, const std::string &path)
|
|
{
|
|
if (! file_exists (parent)) {
|
|
// If the parent path does not exist, we always return false. This cannot be a parent.
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::string> parts = split_path (absolute_file_path (path));
|
|
|
|
while (! parts.empty () && ! (parts.size () == 1 && is_drive (parts[0]))) {
|
|
if (is_same_file (parent, tl::join (parts, ""))) {
|
|
return true;
|
|
} else {
|
|
parts.pop_back ();
|
|
}
|
|
}
|
|
|
|
// We did not find a match - now maybe the parent is root
|
|
return (is_same_file (parent, tl::combine_path (tl::join (parts, ""), "", true /*always add slash*/)));
|
|
}
|
|
|
|
std::vector<std::string> dir_entries (const std::string &s, bool with_files, bool with_dirs, bool without_dotfiles)
|
|
{
|
|
std::vector<std::string> ee;
|
|
|
|
#if defined(_WIN32)
|
|
|
|
struct _wfinddata_t fileinfo;
|
|
|
|
intptr_t h = _wfindfirst (tl::to_wstring (s + "\\*").c_str (), &fileinfo);
|
|
if (h != -1) {
|
|
|
|
do {
|
|
|
|
std::string e = tl::to_string (std::wstring (fileinfo.name));
|
|
if (e.empty () || e == "." || e == "..") {
|
|
continue;
|
|
}
|
|
|
|
bool is_dir = ((fileinfo.attrib & _A_SUBDIR) != 0);
|
|
if ((e[0] != '.' || !without_dotfiles) && ((is_dir && with_dirs) || (!is_dir && with_files))) {
|
|
ee.push_back (e);
|
|
}
|
|
|
|
} while (_wfindnext (h, &fileinfo) == 0);
|
|
|
|
}
|
|
|
|
_findclose (h);
|
|
|
|
#else
|
|
|
|
DIR *h = opendir (tl::to_local (s).c_str ());
|
|
if (h) {
|
|
|
|
struct dirent *d;
|
|
while ((d = readdir (h)) != NULL) {
|
|
|
|
std::string e = tl::to_string_from_local (d->d_name);
|
|
if (e.empty () || e == "." || e == "..") {
|
|
continue;
|
|
}
|
|
|
|
bool is_dir = (d->d_type == DT_DIR);
|
|
if ((e[0] != '.' || !without_dotfiles) && ((is_dir && with_dirs) || (!is_dir && with_files))) {
|
|
ee.push_back (e);
|
|
}
|
|
|
|
}
|
|
|
|
closedir (h);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
return ee;
|
|
}
|
|
|
|
static void glob_partial (const std::string &where, std::vector<std::string>::const_iterator pfrom, std::vector<std::string>::const_iterator pto, std::vector<std::string> &res)
|
|
{
|
|
if (pfrom == pto) {
|
|
if (! is_dir (where)) {
|
|
res.push_back (where);
|
|
}
|
|
return;
|
|
}
|
|
|
|
auto p = where + *pfrom;
|
|
if (file_exists (p)) {
|
|
glob_partial (p, pfrom + 1, pto, res);
|
|
return;
|
|
}
|
|
|
|
if (tl::trimmed_part (*pfrom) == "**") {
|
|
if (pfrom + 1 == pto) {
|
|
// a glob pattern can't be "**" without anything after that
|
|
return;
|
|
}
|
|
auto subdirs = dir_entries (where, false, true, true);
|
|
for (auto s = subdirs.begin (); s != subdirs.end (); ++s) {
|
|
glob_partial (combine_path (where, *s), pfrom, pto, res);
|
|
}
|
|
++pfrom;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
if (where.empty ()) {
|
|
// On Windows, we cannot iterate the drives
|
|
std::string root = *pfrom;
|
|
++pfrom;
|
|
glob_partial (root, pfrom, pto, res);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
tl::GlobPattern glob (tl::trimmed_part (*pfrom));
|
|
++pfrom;
|
|
auto entries = dir_entries (where, true, true, true);
|
|
for (auto e = entries.begin (); e != entries.end (); ++e) {
|
|
if (glob.match (*e)) {
|
|
glob_partial (combine_path (where, *e), pfrom, pto, res);
|
|
}
|
|
}
|
|
}
|
|
|
|
std::vector<std::string> glob_expand (const std::string &path)
|
|
{
|
|
auto apath = absolute_file_path (path);
|
|
auto parts = split_path (apath);
|
|
|
|
std::vector<std::string> res;
|
|
glob_partial (std::string (), parts.begin (), parts.end (), res);
|
|
return res;
|
|
}
|
|
|
|
bool mkdir (const std::string &path)
|
|
{
|
|
#if defined(_WIN32)
|
|
return _wmkdir (tl::to_wstring (path).c_str ()) == 0;
|
|
#else
|
|
return ::mkdir (tl::to_local (path).c_str (), 0777) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool mkpath (const std::string &p)
|
|
{
|
|
std::vector<std::string> parts = split_path (absolute_file_path (p));
|
|
|
|
size_t i = 0;
|
|
std::string front;
|
|
if (! parts.empty () && is_drive (parts.front ())) {
|
|
front = parts.front ();
|
|
++i;
|
|
}
|
|
|
|
while (i < parts.size ()) {
|
|
front += parts[i++];
|
|
if (! file_exists (front)) {
|
|
if (! mkdir (front)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to create directory: ") << front;
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool rename_file (const std::string &path, const std::string &new_name)
|
|
{
|
|
// resolve relative names in new_name
|
|
std::string new_path = new_name;
|
|
if (! tl::is_absolute (new_path)) {
|
|
new_path = tl::combine_path (tl::dirname (path), new_name);
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
return _wrename (tl::to_wstring (path).c_str (), tl::to_wstring (new_path).c_str ()) == 0;
|
|
#else
|
|
return rename (tl::to_local (path).c_str (), tl::to_local (new_path).c_str ()) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool rm_file (const std::string &path)
|
|
{
|
|
#if defined(_WIN32)
|
|
std::wstring wpath = tl::to_wstring (path);
|
|
_wchmod (wpath.c_str (), _S_IREAD | _S_IWRITE);
|
|
return _wunlink (wpath.c_str ()) == 0;
|
|
#else
|
|
return unlink (tl::to_local (path).c_str ()) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool rm_dir (const std::string &path)
|
|
{
|
|
#if defined(_WIN32)
|
|
return _wrmdir (tl::to_wstring (path).c_str ()) == 0;
|
|
#else
|
|
return rmdir (tl::to_local (path).c_str ()) == 0;
|
|
#endif
|
|
}
|
|
|
|
bool rm_dir_recursive (const std::string &p)
|
|
{
|
|
std::vector<std::string> entries;
|
|
std::string path = tl::absolute_file_path (p);
|
|
|
|
if (! tl::file_exists (path)) {
|
|
// already gone.
|
|
return true;
|
|
}
|
|
|
|
entries = dir_entries (path, false /*without_files*/, true /*with_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
if (! rm_dir_recursive (tl::combine_path (path, *e))) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
entries = dir_entries (path, true /*with_files*/, false /*without_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
std::string tc = tl::combine_path (path, *e);
|
|
if (! rm_file (tc)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to remove file: ") << tc;
|
|
#endif
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (! rm_dir (path)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to remove directory: ") << path;
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
cp_dir_recursive (const std::string &source, const std::string &target)
|
|
{
|
|
std::vector<std::string> entries;
|
|
std::string path = tl::absolute_file_path (source);
|
|
std::string path_to = tl::absolute_file_path (target);
|
|
|
|
entries = dir_entries (path, false /*without_files*/, true /*with_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
std::string tc = tl::combine_path (path_to, *e);
|
|
if (! mkpath (tc)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to create target directory: ") << tc;
|
|
#endif
|
|
return false;
|
|
}
|
|
if (! cp_dir_recursive (tl::combine_path (path, *e), tc)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
entries = dir_entries (path, true /*with_files*/, false /*without_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
|
|
// TODO: leave symlinks symlinks? How to copy symlinks with Qt?
|
|
|
|
// copy the files
|
|
try {
|
|
|
|
tl::OutputFile os_file (tl::combine_path (path_to, *e));
|
|
tl::OutputStream os (os_file);
|
|
tl::InputFile is_file (tl::combine_path (path, *e));
|
|
tl::InputStream is (is_file);
|
|
is.copy_to (os);
|
|
|
|
} catch (tl::Exception &ex) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to copy file ") << tl::combine_path (path_to, *e) << tr (" to ") << tl::combine_path (path, *e)
|
|
<< tr ("(Error ") << ex.msg () << ")";
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
mv_dir_recursive (const std::string &source, const std::string &target)
|
|
{
|
|
std::vector<std::string> entries;
|
|
std::string path = tl::absolute_file_path (source);
|
|
std::string path_to = tl::absolute_file_path (target);
|
|
|
|
bool error = false;
|
|
|
|
entries = dir_entries (path, false /*without_files*/, true /*with_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
std::string tc = tl::combine_path (path_to, *e);
|
|
if (! mkpath (tc)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to create target directory: ") << tc;
|
|
#endif
|
|
error = true;
|
|
} else if (! mv_dir_recursive (tl::combine_path (path, *e), tc)) {
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
entries = dir_entries (path, true /*with_files*/, false /*without_dirs*/);
|
|
for (std::vector<std::string>::const_iterator e = entries.begin (); e != entries.end (); ++e) {
|
|
if (! tl::rename_file (tl::combine_path (path, *e), tl::combine_path (path_to, *e))) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to move file from ") << tl::combine_path (path, *e) << tr (" to ") << tl::combine_path (path_to, *e);
|
|
#endif
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
if (! tl::rm_dir (path)) {
|
|
#if defined(FILE_UTILS_VERBOSE)
|
|
tl::error << tr ("Unable to remove folder ") << path;
|
|
#endif
|
|
error = true;
|
|
}
|
|
|
|
return ! error;
|
|
}
|
|
|
|
std::string absolute_path (const std::string &s)
|
|
{
|
|
std::vector<std::string> parts = split_path (absolute_file_path (s));
|
|
if (parts.size () > 0) {
|
|
parts.pop_back ();
|
|
}
|
|
|
|
return tl::join (parts, "");
|
|
}
|
|
|
|
std::string current_dir ()
|
|
{
|
|
#if defined(_WIN32)
|
|
|
|
wchar_t *cwd = _wgetcwd (NULL, 0);
|
|
if (cwd == NULL) {
|
|
return std::string ();
|
|
} else {
|
|
std::string cwds (tl::to_string (std::wstring (cwd)));
|
|
free (cwd);
|
|
return cwds;
|
|
}
|
|
|
|
#else
|
|
|
|
char *cwd = getcwd (NULL, 0);
|
|
if (cwd == NULL) {
|
|
return std::string ();
|
|
} else {
|
|
std::string cwds (tl::to_string_from_local (cwd));
|
|
free (cwd);
|
|
return cwds;
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
bool chdir (const std::string &path)
|
|
{
|
|
#if defined(_WIN32)
|
|
return _wchdir (tl::to_wstring (path).c_str ()) == 0;
|
|
#else
|
|
return ::chdir (tl::to_local (path).c_str ()) == 0;
|
|
#endif
|
|
}
|
|
|
|
static std::pair<std::string, bool> absolute_path_of_existing (const std::string &s)
|
|
{
|
|
#if defined(_WIN32)
|
|
|
|
wchar_t *fp = _wfullpath (NULL, tl::to_wstring (s).c_str (), 0);
|
|
if (fp == NULL) {
|
|
return std::make_pair (std::string (), false);
|
|
} else {
|
|
std::string fps (tl::to_string (std::wstring (fp)));
|
|
free (fp);
|
|
return std::make_pair (fps, true);
|
|
}
|
|
|
|
#else
|
|
|
|
char *fp;
|
|
fp = realpath (tl::to_local (s).c_str (), NULL);
|
|
if (fp == NULL) {
|
|
return std::make_pair (std::string (), false);
|
|
} else {
|
|
std::string fps (tl::to_string_from_local (fp));
|
|
free (fp);
|
|
return std::make_pair (fps, true);
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
bool is_absolute (const std::string &s)
|
|
{
|
|
// ~ paths are always absolute, because the home directory is
|
|
if (s.size () > 0 && s[0] == '~') {
|
|
return true;
|
|
}
|
|
|
|
std::vector<std::string> parts = split_path (s);
|
|
if (parts.size () > 1 && is_drive (parts [0])) {
|
|
return is_part_with_separator (parts [1]);
|
|
} else if (! parts.empty ()) {
|
|
return is_part_with_separator (parts.front ());
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::string absolute_file_path (const std::string &s)
|
|
{
|
|
// ~ paths are always absolute, because the home directory is
|
|
if (s.size () > 0 && s[0] == '~') {
|
|
return get_home_path () + std::string (s, 1);
|
|
}
|
|
|
|
std::vector<std::string> parts = split_path (s);
|
|
if (parts.empty ()) {
|
|
return current_dir ();
|
|
}
|
|
|
|
std::pair<std::string, bool> known_part;
|
|
std::vector<std::string> unknown_parts;
|
|
|
|
while (! parts.empty () && ! (parts.size () == 1 && is_drive (parts[0]))) {
|
|
known_part = absolute_path_of_existing (tl::join (parts, ""));
|
|
if (! known_part.second) {
|
|
unknown_parts.push_back (parts.back ());
|
|
parts.pop_back ();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
std::reverse (unknown_parts.begin (), unknown_parts.end ());
|
|
|
|
if (! known_part.second) {
|
|
|
|
// the top-level component is unknown. This can mean:
|
|
// 1.) the path is already absolute, but the top-level entry does not exist
|
|
// 2.) the path is relative, but the entry does not exist
|
|
|
|
tl_assert (! unknown_parts.empty ());
|
|
if (is_part_with_separator (unknown_parts.front ())) {
|
|
|
|
// case 1: return the full path as absolute
|
|
return s;
|
|
|
|
} else if (parts.size () == 1 && is_drive (parts[0])) {
|
|
|
|
// case 2 (for Windows): try to root on drive's working dir
|
|
known_part = absolute_path_of_existing (parts[0]);
|
|
if (! known_part.second) {
|
|
// drive is not known ... return the original path as fallback
|
|
return s;
|
|
} else {
|
|
return combine_path (known_part.first, tl::join (unknown_parts, ""));
|
|
}
|
|
|
|
} else {
|
|
|
|
// case 2 (for *nix): try to root on current working dir
|
|
return combine_path (current_dir (), tl::join (unknown_parts, ""));
|
|
|
|
}
|
|
|
|
} else {
|
|
return combine_path (known_part.first, tl::join (unknown_parts, ""));
|
|
}
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
|
|
typedef struct _stat stat_struct;
|
|
|
|
static int stat_func (const std::string &s, stat_struct &st)
|
|
{
|
|
return _wstat (tl::to_wstring (s).c_str (), &st);
|
|
}
|
|
|
|
#else
|
|
|
|
typedef struct stat stat_struct;
|
|
|
|
static int stat_func (const std::string &s, stat_struct &st)
|
|
{
|
|
return stat (tl::to_local (s).c_str (), &st);
|
|
}
|
|
|
|
#endif
|
|
|
|
bool file_exists (const std::string &p)
|
|
{
|
|
stat_struct st;
|
|
return stat_func (p, st) == 0;
|
|
}
|
|
|
|
bool is_writable (const std::string &p)
|
|
{
|
|
stat_struct st;
|
|
#if defined(_MSC_VER)
|
|
return stat_func (p, st) == 0 && (st.st_mode & _S_IWRITE) != 0;
|
|
#else
|
|
return stat_func (p, st) == 0 && (st.st_mode & S_IWUSR) != 0;
|
|
#endif
|
|
}
|
|
|
|
bool is_readable (const std::string &p)
|
|
{
|
|
stat_struct st;
|
|
#if defined(_MSC_VER)
|
|
return stat_func (p, st) == 0 && (st.st_mode & _S_IREAD) != 0;
|
|
#else
|
|
return stat_func (p, st) == 0 && (st.st_mode & S_IRUSR) != 0;
|
|
#endif
|
|
}
|
|
|
|
bool is_dir (const std::string &p)
|
|
{
|
|
stat_struct st;
|
|
if (stat_func (p, st) != 0) {
|
|
return false;
|
|
} else {
|
|
#if defined(_MSC_VER)
|
|
return !(st.st_mode & _S_IFREG);
|
|
#else
|
|
return !S_ISREG (st.st_mode);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
std::string relative_path (const std::string &base, const std::string &p)
|
|
{
|
|
std::vector<std::string> rem;
|
|
std::vector<std::string> parts = split_path (p);
|
|
|
|
while (! parts.empty ()) {
|
|
|
|
if (is_same_file (base, tl::join (parts, ""))) {
|
|
|
|
// combine the remaining path
|
|
std::reverse (rem.begin (), rem.end ());
|
|
if (! rem.empty ()) {
|
|
rem[0] = tl::trimmed_part (rem.front ());
|
|
}
|
|
return tl::join (rem, "");
|
|
|
|
}
|
|
|
|
rem.push_back (parts.back ());
|
|
parts.pop_back ();
|
|
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
bool is_same_file (const std::string &a, const std::string &b)
|
|
{
|
|
if (tl::normalize_path (a) == tl::normalize_path (b)) {
|
|
return true;
|
|
}
|
|
|
|
#if defined(_WIN32)
|
|
|
|
HANDLE h1 = ::CreateFileW (tl::to_wstring (a).c_str (), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
HANDLE h2 = ::CreateFileW (tl::to_wstring (b).c_str (), 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
|
|
bool result = false;
|
|
|
|
if (h1 != INVALID_HANDLE_VALUE && h2 != INVALID_HANDLE_VALUE) {
|
|
BY_HANDLE_FILE_INFORMATION fi1, fi2;
|
|
if (::GetFileInformationByHandle(h1, &fi1) && ::GetFileInformationByHandle(h2, &fi2)) {
|
|
result = fi1.dwVolumeSerialNumber == fi2.dwVolumeSerialNumber &&
|
|
fi1.nFileIndexHigh == fi2.nFileIndexHigh &&
|
|
fi1.nFileIndexLow == fi2.nFileIndexLow;
|
|
}
|
|
}
|
|
|
|
if (h1 != INVALID_HANDLE_VALUE) {
|
|
::CloseHandle(h1);
|
|
}
|
|
|
|
if (h2 != INVALID_HANDLE_VALUE) {
|
|
::CloseHandle(h2);
|
|
}
|
|
|
|
return result;
|
|
|
|
#else
|
|
|
|
stat_struct sta, stb;
|
|
if (stat_func (a, sta) != 0 || stat_func (b, stb) != 0) {
|
|
return false;
|
|
}
|
|
|
|
return sta.st_dev == stb.st_dev && sta.st_ino == stb.st_ino;
|
|
|
|
#endif
|
|
}
|
|
|
|
std::string
|
|
get_home_path ()
|
|
{
|
|
#if !defined(_WIN32)
|
|
if (tl::has_env ("HOME")) {
|
|
return tl::get_env ("HOME");
|
|
} else {
|
|
struct passwd *pwd = getpwuid (getuid ());
|
|
if (pwd) {
|
|
return std::string (pwd->pw_dir);
|
|
}
|
|
}
|
|
tl::warn << tl::to_string (tr ("Unable to get home directory (set HOME environment variable)"));
|
|
#else
|
|
if (tl::has_env ("HOMEDRIVE") && tl::has_env ("HOMEPATH")) {
|
|
return tl::get_env ("HOMEDRIVE") + tl::get_env ("HOMEPATH");
|
|
} else if (tl::has_env ("HOMESHARE") && tl::has_env ("HOMEPATH")) {
|
|
return tl::get_env ("HOMESHARE") + tl::get_env ("HOMEPATH");
|
|
} else if (tl::has_env ("USERPROFILE")) {
|
|
return tl::get_env ("USERPROFILE");
|
|
}
|
|
tl::warn << tl::to_string (tr ("Unable to get home directory (no HOMEDRIVE/HOMEPATH, HOMESHARE/HOMEPATH or USERPROFILE environment variables)"));
|
|
#endif
|
|
return std::string (".");
|
|
}
|
|
|
|
static std::string
|
|
get_app_path_internal ()
|
|
{
|
|
#if defined(_WIN32)
|
|
|
|
wchar_t buffer[MAX_PATH];
|
|
int len;
|
|
if ((len = GetModuleFileNameW (NULL, buffer, MAX_PATH)) > 0) {
|
|
return tl::to_string (std::wstring (buffer));
|
|
}
|
|
|
|
#elif __APPLE__
|
|
|
|
char buffer[PROC_PIDPATHINFO_MAXSIZE];
|
|
int ret = proc_pidpath (getpid (), buffer, sizeof (buffer));
|
|
if (ret > 0) {
|
|
// TODO: does this correctly translate paths? (MacOS uses UTF-8 encoding with D-like normalization)
|
|
return std::string (buffer);
|
|
}
|
|
|
|
#elif defined (__FreeBSD__)
|
|
|
|
char path[PATH_MAX];
|
|
size_t len = PATH_MAX;
|
|
const int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1};
|
|
if (sysctl(&mib[0], 4, &path, &len, NULL, 0) == 0) {
|
|
return path;
|
|
}
|
|
return "";
|
|
|
|
#else
|
|
|
|
std::string pf = tl::sprintf ("/proc/%d/exe", getpid ());
|
|
if (tl::file_exists (pf)) {
|
|
return pf;
|
|
}
|
|
|
|
#endif
|
|
tl_assert (false);
|
|
}
|
|
|
|
std::string
|
|
get_inst_path ()
|
|
{
|
|
static std::string s_inst_path;
|
|
if (s_inst_path.empty ()) {
|
|
s_inst_path = tl::absolute_path (get_app_path_internal ());
|
|
}
|
|
return s_inst_path;
|
|
}
|
|
|
|
std::string
|
|
get_app_path ()
|
|
{
|
|
static std::string s_app_path;
|
|
if (s_app_path.empty ()) {
|
|
s_app_path = get_app_path_internal ();
|
|
}
|
|
return s_app_path;
|
|
}
|
|
|
|
std::string
|
|
get_module_path (void *addr)
|
|
{
|
|
#if defined(_WIN32)
|
|
|
|
HMODULE h_module = NULL;
|
|
if (GetModuleHandleEx (GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR) addr, &h_module)) {
|
|
|
|
wchar_t buffer[MAX_PATH];
|
|
int len;
|
|
if ((len = GetModuleFileNameW (h_module, buffer, MAX_PATH)) > 0) {
|
|
return tl::absolute_file_path (tl::to_string (std::wstring (buffer, 0, len)));
|
|
}
|
|
|
|
}
|
|
|
|
// no way to get module file path
|
|
return std::string ();
|
|
|
|
#else
|
|
|
|
Dl_info info = { };
|
|
if (dladdr (addr, &info)) {
|
|
return tl::absolute_file_path (tl::to_string_from_local (info.dli_fname));
|
|
} else {
|
|
tl::warn << tl::to_string (tr ("Unable to get path of db library (as basis for loading db_plugins)"));
|
|
return std::string ();
|
|
}
|
|
|
|
#endif
|
|
}
|
|
|
|
std::string
|
|
tmpfile (const std::string &domain)
|
|
{
|
|
std::string tmp = tl::get_env ("TMPDIR");
|
|
if (tmp.empty ()) {
|
|
tmp = tl::get_env ("TMP");
|
|
}
|
|
if (tmp.empty ()) {
|
|
#if defined(_WIN32)
|
|
throw tl::Exception (tl::to_string (tr ("TMP and TMPDIR not set - cannot create temporary file")));
|
|
#else
|
|
tmp = "/tmp";
|
|
#endif
|
|
}
|
|
|
|
std::string templ = tl::combine_path (tmp, domain + "XXXXXX");
|
|
char *tmpstr = strdup (templ.c_str ());
|
|
|
|
#if defined(_WIN32)
|
|
if (_mktemp_s (tmpstr, templ.size () + 1) != 0) {
|
|
free (tmpstr);
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder name in %s")), tmp);
|
|
}
|
|
|
|
// for compatibility with Linux, create the file as an empty one
|
|
std::ofstream os (tmpstr);
|
|
if (os.bad ()) {
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder in %s")), tmp);
|
|
}
|
|
os.close ();
|
|
#else
|
|
int fd = mkstemp (tmpstr);
|
|
if (fd < 0) {
|
|
free (tmpstr);
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder in %s")), tmp);
|
|
}
|
|
close (fd);
|
|
#endif
|
|
|
|
std::string res = tmpstr;
|
|
free (tmpstr);
|
|
return res;
|
|
}
|
|
|
|
TemporaryFile::TemporaryFile (const std::string &domain)
|
|
{
|
|
m_path = tmpfile (domain);
|
|
}
|
|
|
|
TemporaryFile::~TemporaryFile ()
|
|
{
|
|
tl::rm_file (m_path);
|
|
}
|
|
|
|
std::string
|
|
tmpdir (const std::string &domain)
|
|
{
|
|
std::string tmp = tl::get_env ("TMPDIR");
|
|
if (tmp.empty ()) {
|
|
tmp = tl::get_env ("TMP");
|
|
}
|
|
if (tmp.empty ()) {
|
|
#if defined(_WIN32)
|
|
throw tl::Exception (tl::to_string (tr ("TMP and TMPDIR not set - cannot create temporary file")));
|
|
#else
|
|
tmp = "/tmp";
|
|
#endif
|
|
}
|
|
|
|
std::string templ = tl::combine_path (tmp, domain + "XXXXXX");
|
|
char *tmpstr = strdup (templ.c_str ());
|
|
|
|
#if defined(_WIN32)
|
|
if (_mktemp_s (tmpstr, templ.size () + 1) != 0) {
|
|
free (tmpstr);
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder name in %s")), tmp);
|
|
}
|
|
if (! tl::mkdir (tmpstr)) {
|
|
free (tmpstr);
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder in %s")), tmp);
|
|
}
|
|
#else
|
|
if (mkdtemp (tmpstr) == NULL) {
|
|
free (tmpstr);
|
|
throw tl::Exception (tl::to_string (tr ("Unable to create temporary folder in %s")), tmp);
|
|
}
|
|
#endif
|
|
|
|
std::string res = tmpstr;
|
|
free (tmpstr);
|
|
return res;
|
|
}
|
|
|
|
TemporaryDirectory::TemporaryDirectory (const std::string &domain)
|
|
{
|
|
m_path = tmpdir (domain);
|
|
}
|
|
|
|
TemporaryDirectory::~TemporaryDirectory ()
|
|
{
|
|
tl::rm_dir_recursive (m_path);
|
|
}
|
|
|
|
|
|
}
|