1
0
This repository has been archived on 2025-03-06. You can view files and clone it, but cannot push or open issues or pull requests.

684 lines
17 KiB
C++

/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
/*
* Main authors:
* Guido Tack <guido.tack@monash.edu>
*/
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS
#endif
#include <minizinc/config.hh>
#include <minizinc/exception.hh>
#include <minizinc/file_utils.hh>
#include <minizinc/thirdparty/b64/decode.h>
#include <minizinc/thirdparty/b64/encode.h>
#include <minizinc/thirdparty/miniz.h>
#include <cstring>
#include <sstream>
#include <string>
#ifdef HAS_PIDPATH
#include <libproc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#elif defined(HAS_GETMODULEFILENAME) || defined(HAS_GETFILEATTRIBUTES)
#include <windows.h>
#else
#include <unistd.h>
#endif
#include <sys/stat.h>
#include <sys/types.h>
#ifdef _MSC_VER
#include "Shlwapi.h"
#pragma comment(lib, "Shlwapi.lib")
#include "Shlobj.h"
#include <direct.h>
#else
#include <dirent.h>
#include <ftw.h>
#include <libgen.h>
#endif
namespace MiniZinc {
namespace FileUtils {
#ifdef HAS_PIDPATH
std::string progpath(void) {
pid_t pid = getpid();
char path[PROC_PIDPATHINFO_MAXSIZE];
int ret = proc_pidpath(pid, path, sizeof(path));
if (ret <= 0) {
return "";
} else {
std::string p(path);
size_t slash = p.find_last_of("/");
if (slash != std::string::npos) {
p = p.substr(0, slash);
}
return p;
}
}
#elif defined(HAS_GETMODULEFILENAME)
std::string progpath(void) {
char path[MAX_PATH];
int ret = GetModuleFileName(NULL, path, MAX_PATH);
if (ret <= 0) {
return "";
} else {
std::string p(path);
size_t slash = p.find_last_of("/\\");
if (slash != std::string::npos) {
p = p.substr(0, slash);
}
return p;
}
}
#else
std::string progpath(void) {
const int bufsz = 2000;
char path[bufsz + 1];
ssize_t sz = readlink("/proc/self/exe", path, bufsz);
if (sz < 0) {
return "";
} else {
path[sz] = '\0';
std::string p(path);
size_t slash = p.find_last_of("/");
if (slash != std::string::npos) {
p = p.substr(0, slash);
}
return p;
}
}
#endif
bool file_exists(const std::string& filename) {
#if defined(HAS_GETFILEATTRIBUTES)
DWORD dwAttrib = GetFileAttributes(filename.c_str());
return (dwAttrib != INVALID_FILE_ATTRIBUTES && !(dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
#else
struct stat info;
return stat(filename.c_str(), &info) == 0 && (info.st_mode & S_IFREG);
#endif
}
bool directory_exists(const std::string& dirname) {
#if defined(HAS_GETFILEATTRIBUTES)
DWORD dwAttrib = GetFileAttributes(dirname.c_str());
return (dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY));
#else
struct stat info;
return stat(dirname.c_str(), &info) == 0 && (info.st_mode & S_IFDIR);
#endif
}
std::string file_path(const std::string& filename, const std::string& basePath) {
#ifdef _MSC_VER
LPSTR lpBuffer, lpFilePart;
DWORD nBufferLength = GetFullPathName(filename.c_str(), 0, 0, &lpFilePart);
if (!(lpBuffer = (LPTSTR)LocalAlloc(LMEM_FIXED, sizeof(TCHAR) * nBufferLength))) return 0;
std::string ret;
DWORD error = GetFullPathName(filename.c_str(), nBufferLength, lpBuffer, &lpFilePart);
DWORD fileAttr = GetFileAttributes(lpBuffer);
DWORD lastError = GetLastError();
if (error == 0 || (fileAttr == INVALID_FILE_ATTRIBUTES && lastError != NO_ERROR)) {
if (basePath.empty())
ret = filename;
else
ret = file_path(basePath + "/" + filename);
} else {
ret = std::string(lpBuffer);
}
LocalFree(lpBuffer);
return ret;
#else
char* rp = realpath(filename.c_str(), NULL);
if (rp == NULL) {
if (basePath.empty())
return filename;
else
return file_path(basePath + "/" + filename);
}
std::string rp_s(rp);
free(rp);
return rp_s;
#endif
}
std::string dir_name(const std::string& filename) {
#ifdef _MSC_VER
size_t pos = filename.find_last_of("\\/");
return (pos == std::string::npos) ? "" : filename.substr(0, pos);
#else
char* fn = strdup(filename.c_str());
char* dn = dirname(fn);
std::string ret(dn);
free(fn);
return ret;
#endif
}
std::string base_name(const std::string& filename) {
#ifdef _MSC_VER
size_t pos = filename.find_last_of("\\/");
return (pos == std::string::npos) ? filename : filename.substr(pos + 1);
#else
char* fn = strdup(filename.c_str());
char* dn = basename(fn);
std::string ret(dn);
free(fn);
return ret;
#endif
}
bool is_absolute(const std::string& path) {
#ifdef _MSC_VER
return !PathIsRelative(path.c_str());
#else
return path.empty() ? false : (path[0] == '/');
#endif
}
std::string find_executable(const std::string& filename) {
if (is_absolute(filename)) {
if (file_exists(filename)) {
return filename;
}
#ifdef _MSC_VER
if (FileUtils::file_exists(filename + ".exe")) {
return filename + ".exe";
} else if (FileUtils::file_exists(filename + ".bat")) {
return filename + ".bat";
}
#endif
return "";
}
char* path_c = getenv("PATH");
#ifdef _MSC_VER
char pathsep = ';';
#else
char pathsep = ':';
#endif
std::string path;
if (path_c) {
path = path_c;
if (path.size()) {
path += pathsep;
}
}
path += progpath();
std::string pathItem;
std::stringstream pathStream(path);
while (std::getline(pathStream, pathItem, pathsep)) {
std::string fileWithPath = pathItem + "/" + filename;
if (file_exists(fileWithPath)) return fileWithPath;
#ifdef _MSC_VER
if (FileUtils::file_exists(fileWithPath + ".exe")) {
return fileWithPath + ".exe";
} else if (FileUtils::file_exists(fileWithPath + ".bat")) {
return fileWithPath + ".bat";
}
#endif
}
return "";
}
std::vector<std::string> directory_list(const std::string& dir, const std::string& ext) {
std::vector<std::string> entries;
#ifdef _MSC_VER
WIN32_FIND_DATA findData;
HANDLE hFind = ::FindFirstFile((dir + "/*." + ext).c_str(), &findData);
if (hFind != INVALID_HANDLE_VALUE) {
do {
if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
entries.push_back(findData.cFileName);
}
} while (::FindNextFile(hFind, &findData));
::FindClose(hFind);
}
#else
DIR* dirp = opendir(dir.c_str());
if (dirp) {
struct dirent* dp;
while ((dp = readdir(dirp)) != NULL) {
std::string fileName(dp->d_name);
struct stat info;
if (stat((dir + "/" + fileName).c_str(), &info) == 0 && (info.st_mode & S_IFREG)) {
if (ext == "*") {
entries.push_back(fileName);
} else {
if (fileName.size() > ext.size() + 2 &&
fileName.substr(fileName.size() - ext.size() - 1) == "." + ext) {
entries.push_back(fileName);
}
}
}
}
closedir(dirp);
}
#endif
return entries;
}
std::string working_directory(void) {
char wd[FILENAME_MAX];
#ifdef _MSC_VER
if (!_getcwd(wd, sizeof(wd))) return "";
#else
if (!getcwd(wd, sizeof(wd))) return "";
#endif
return wd;
}
std::string share_directory(void) {
if (char* MZNSTDLIBDIR = getenv("MZN_STDLIB_DIR")) {
return std::string(MZNSTDLIBDIR);
}
std::string mypath = FileUtils::progpath();
int depth = 0;
for (unsigned int i = 0; i < mypath.size(); i++)
if (mypath[i] == '/' || mypath[i] == '\\') depth++;
for (int i = 0; i <= depth; i++) {
if (FileUtils::file_exists(mypath + "/share/minizinc/std/builtins.mzn"))
return mypath + "/share/minizinc";
mypath += "/..";
}
return "";
}
std::string user_config_dir(void) {
#ifdef _MSC_VER
HRESULT hr;
PWSTR pszPath = NULL;
hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pszPath);
if (SUCCEEDED(hr)) {
int charsRequired = WideCharToMultiByte(CP_ACP, 0, pszPath, -1, 0, 0, 0, 0);
std::string configPath;
if (charsRequired > 0) {
char* tmp = new char[charsRequired];
if (WideCharToMultiByte(CP_ACP, 0, pszPath, -1, tmp, charsRequired, 0, 0) != 0) {
tmp[charsRequired - 1] = 0;
configPath = tmp;
}
delete[] tmp;
}
CoTaskMemFree(pszPath);
if (configPath.empty()) {
return "";
} else {
return configPath + "/MiniZinc";
}
}
return "";
#else
if (const char* hd = getenv("HOME")) {
return std::string(hd) + "/.minizinc";
}
return "";
#endif
}
std::string global_config_file(void) {
std::string sd = share_directory();
if (sd.empty()) return "";
return sd + "/Preferences.json";
}
std::string user_config_file(void) { return user_config_dir() + "/Preferences.json"; }
TmpFile::TmpFile(const std::string& ext) {
#ifdef _WIN32
TCHAR szTempFileName[MAX_PATH];
TCHAR lpTempPathBuffer[MAX_PATH];
GetTempPath(MAX_PATH, lpTempPathBuffer);
GetTempFileName(lpTempPathBuffer, "tmp_mzn_", 0, szTempFileName);
_name = szTempFileName;
MoveFile(_name.c_str(), (_name + ext).c_str());
_name += ext;
#else
_tmpfile_desc = -1;
_name = "/tmp/mznfileXXXXXX" + ext;
char* tmpfile = strndup(_name.c_str(), _name.size());
_tmpfile_desc = mkstemps(tmpfile, ext.size());
if (_tmpfile_desc == -1) {
::free(tmpfile);
throw InternalError("Error occurred when creating temporary file");
}
_name = std::string(tmpfile);
::free(tmpfile);
#endif
}
TmpFile::~TmpFile(void) {
remove(_name.c_str());
#ifndef _WIN32
if (_tmpfile_desc != -1) close(_tmpfile_desc);
#endif
}
TmpDir::TmpDir(void) {
#ifdef _WIN32
TCHAR szTempFileName[MAX_PATH];
TCHAR lpTempPathBuffer[MAX_PATH];
GetTempPath(MAX_PATH, lpTempPathBuffer);
GetTempFileName(lpTempPathBuffer, "tmp_mzn_", 0, szTempFileName);
_name = szTempFileName;
DeleteFile(_name.c_str());
CreateDirectory(_name.c_str(), NULL);
#else
_name = "/tmp/mzndirXXXXXX";
char* tmpfile = strndup(_name.c_str(), _name.size());
if (mkdtemp(tmpfile) == NULL) {
::free(tmpfile);
throw InternalError("Error occurred when creating temporary directory");
}
_name = std::string(tmpfile);
::free(tmpfile);
#endif
}
#ifdef _WIN32
namespace {
void remove_dir(const std::string& d) {
HANDLE dh;
WIN32_FIND_DATA info;
std::string pattern = d + "\\*.*";
dh = ::FindFirstFile(pattern.c_str(), &info);
if (dh != INVALID_HANDLE_VALUE) {
do {
if (info.cFileName[0] != '.') {
std::string fp = d + "\\" + info.cFileName;
if (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
remove_dir(fp);
} else {
::SetFileAttributes(fp.c_str(), FILE_ATTRIBUTE_NORMAL);
::DeleteFile(fp.c_str());
}
}
} while (::FindNextFile(dh, &info) == TRUE);
}
::FindClose(dh);
::SetFileAttributes(d.c_str(), FILE_ATTRIBUTE_NORMAL);
::RemoveDirectory(d.c_str());
}
} // namespace
#else
namespace {
int remove_file(const char* fpath, const struct stat*, int, struct FTW*) { return unlink(fpath); }
} // namespace
#endif
TmpDir::~TmpDir(void) {
#ifdef _WIN32
remove_dir(_name);
#else
nftw(_name.c_str(), remove_file, 64, FTW_DEPTH | FTW_PHYS);
rmdir(_name.c_str());
#endif
}
std::vector<std::string> parseCmdLine(const std::string& s) {
// Break the string up at whitespace, except inside quotes, but ignore escaped quotes
std::vector<std::string> c;
size_t cur = 0;
size_t l = s.length();
std::ostringstream oss;
bool inside_quote = false;
bool had_escape = false;
for (; cur < l; cur++) {
if (inside_quote) {
if (s[cur] == '"') {
if (had_escape) {
oss << "\"";
had_escape = false;
} else {
inside_quote = false;
}
} else if (s[cur] == '\\') {
had_escape = true;
} else {
if (had_escape) {
oss << "\\";
had_escape = false;
}
oss << s[cur];
}
} else {
if (s[cur] == ' ') {
if (had_escape) {
oss << " ";
had_escape = false;
} else {
c.push_back(oss.str());
oss.str(std::string());
}
} else if (s[cur] == '\\') {
if (had_escape) {
oss << "\\";
had_escape = false;
} else {
had_escape = true;
}
} else if (s[cur] == '"') {
if (had_escape) {
oss << "\"";
had_escape = false;
} else {
inside_quote = true;
}
} else {
if (had_escape) {
switch (s[cur]) {
case 'a':
oss << "\a";
break;
case 'b':
oss << "\b";
break;
case 'f':
oss << "\f";
break;
case 'n':
oss << "\n";
break;
case 'r':
oss << "\r";
break;
case 't':
oss << "\t";
break;
case 'v':
oss << "\v";
break;
default:
oss << "\\" << s[cur];
break;
}
had_escape = false;
} else {
oss << s[cur];
}
}
}
}
c.push_back(oss.str());
return c;
}
std::string combineCmdLine(const std::vector<std::string>& cmd) {
std::ostringstream ret;
for (unsigned int i = 0; i < cmd.size(); i++) {
auto& c = cmd[i];
ret << "\"";
for (size_t i = 0; i < c.size(); i++) {
switch (c[i]) {
case '\a':
ret << "\\a";
break;
case '\b':
ret << "\\b";
break;
case '\f':
ret << "\\f";
break;
case '\n':
ret << "\\n";
break;
case '\r':
ret << "\\r";
break;
case '\t':
ret << "\\t";
break;
case '\v':
ret << "\\v";
break;
case '"':
ret << "\\\"";
break;
case '\\':
ret << "\\\\";
break;
default:
ret << c[i];
break;
}
}
ret << "\"";
if (i < cmd.size() - 1) {
ret << " ";
}
}
return ret.str();
}
void inflateString(std::string& s) {
unsigned char* cc = reinterpret_cast<unsigned char*>(&s[0]);
// autodetect compressed string
if (s.size() >= 2 && ((cc[0] == 0x1F && cc[1] == 0x8B) // gzip
|| (cc[0] == 0x78 && (cc[1] == 0x01 // zlib
|| cc[1] == 0x9C || cc[1] == 0xDA)))) {
const int BUF_SIZE = 1024;
unsigned char s_outbuf[BUF_SIZE];
z_stream stream;
std::memset(&stream, 0, sizeof(stream));
unsigned char* dataStart;
int windowBits;
size_t dataLen;
if (cc[0] == 0x1F && cc[1] == 0x8B) {
dataStart = cc + 10;
windowBits = -Z_DEFAULT_WINDOW_BITS;
if (cc[3] & 0x4) {
dataStart += 2;
if (dataStart >= cc + s.size()) throw(-1);
}
if (cc[3] & 0x8) {
while (*dataStart != '\0') {
dataStart++;
if (dataStart >= cc + s.size()) throw(-1);
}
dataStart++;
if (dataStart >= cc + s.size()) throw(-1);
}
if (cc[3] & 0x10) {
while (*dataStart != '\0') {
dataStart++;
if (dataStart >= cc + s.size()) throw(-1);
}
dataStart++;
if (dataStart >= cc + s.size()) throw(-1);
}
if (cc[3] & 0x2) {
dataStart += 2;
if (dataStart >= cc + s.size()) throw(-1);
}
dataLen = s.size() - (dataStart - cc);
} else {
dataStart = cc;
windowBits = Z_DEFAULT_WINDOW_BITS;
dataLen = s.size();
}
stream.next_in = dataStart;
stream.avail_in = static_cast<unsigned int>(dataLen);
stream.next_out = &s_outbuf[0];
stream.avail_out = BUF_SIZE;
int status = inflateInit2(&stream, windowBits);
if (status != Z_OK) throw(status);
std::ostringstream oss;
while (true) {
status = inflate(&stream, Z_NO_FLUSH);
if (status == Z_STREAM_END || !stream.avail_out) {
// output buffer full or compression finished
oss << std::string(reinterpret_cast<char*>(s_outbuf), BUF_SIZE - stream.avail_out);
stream.next_out = &s_outbuf[0];
stream.avail_out = BUF_SIZE;
}
if (status == Z_STREAM_END) break;
if (status != Z_OK) throw(status);
}
status = inflateEnd(&stream);
if (status != Z_OK) throw(status);
s = oss.str();
}
}
std::string deflateString(const std::string& s) {
mz_ulong compressedLength = compressBound(static_cast<mz_ulong>(s.size()));
unsigned char* cmpr =
static_cast<unsigned char*>(::malloc(compressedLength * sizeof(unsigned char)));
int status = compress(cmpr, &compressedLength, reinterpret_cast<const unsigned char*>(&s[0]),
static_cast<mz_ulong>(s.size()));
if (status != Z_OK) {
::free(cmpr);
throw(status);
}
std::string ret(reinterpret_cast<const char*>(cmpr), compressedLength);
::free(cmpr);
return ret;
}
std::string encodeBase64(const std::string& s) {
base64::encoder E;
std::ostringstream oss;
oss << "@"; // add leading "@" to distinguish from valid MiniZinc code
std::istringstream iss(s);
E.encode(iss, oss);
return oss.str();
}
std::string decodeBase64(const std::string& s) {
if (s.size() == 0 || s[0] != '@') throw InternalError("string is not base64 encoded");
base64::decoder D;
std::ostringstream oss;
std::istringstream iss(s);
(void)iss.get(); // remove leading "@"
D.decode(iss, oss);
return oss.str();
}
} // namespace FileUtils
} // namespace MiniZinc