this repo has no description
1/* -*- mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*- */
2
3/*
4 * Main authors:
5 * Guido Tack <guido.tack@monash.edu>
6 */
7
8/* This Source Code Form is subject to the terms of the Mozilla Public
9 * License, v. 2.0. If a copy of the MPL was not distributed with this
10 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11
12#ifdef _MSC_VER
13#define _CRT_SECURE_NO_WARNINGS
14#endif
15
16#include <minizinc/_thirdparty/b64/decode.h>
17#include <minizinc/_thirdparty/b64/encode.h>
18#include <minizinc/_thirdparty/miniz.h>
19#include <minizinc/config.hh>
20#include <minizinc/exception.hh>
21#include <minizinc/file_utils.hh>
22
23#include <cstring>
24#include <sstream>
25#include <string>
26
27#ifdef HAS_PIDPATH
28#include <cstdio>
29#include <cstdlib>
30#include <cstring>
31#include <libproc.h>
32#include <unistd.h>
33#elif defined(HAS_GETMODULEFILENAME) || defined(HAS_GETFILEATTRIBUTES)
34#define NOMINMAX // Ensure the words min/max remain available
35#include <windows.h>
36#undef ERROR
37#else
38#include <unistd.h>
39#endif
40#include <sys/stat.h>
41#include <sys/types.h>
42
43#ifdef _MSC_VER
44#include "Shlwapi.h"
45#pragma comment(lib, "Shlwapi.lib")
46#include "Shlobj.h"
47
48#include <direct.h>
49#else
50#include <dirent.h>
51#include <ftw.h>
52#include <libgen.h>
53#endif
54
55namespace MiniZinc {
56namespace FileUtils {
57
58#ifdef HAS_PIDPATH
59std::string progpath() {
60 pid_t pid = getpid();
61 char path[PROC_PIDPATHINFO_MAXSIZE];
62 int ret = proc_pidpath(pid, path, sizeof(path));
63 if (ret <= 0) {
64 return "";
65 }
66 std::string p(path);
67 size_t slash = p.find_last_of('/');
68 if (slash != std::string::npos) {
69 p = p.substr(0, slash);
70 }
71 return p;
72}
73#elif defined(HAS_GETMODULEFILENAME)
74std::string progpath() {
75 wchar_t path[MAX_PATH];
76 int ret = GetModuleFileNameW(nullptr, path, MAX_PATH);
77 if (ret <= 0) {
78 return "";
79 }
80 std::string p = wide_to_utf8(path);
81 size_t slash = p.find_last_of("/\\");
82 if (slash != std::string::npos) {
83 p = p.substr(0, slash);
84 }
85 return p;
86}
87#else
88std::string progpath() {
89 const int bufsz = 2000;
90 char path[bufsz + 1];
91 ssize_t sz = readlink("/proc/self/exe", path, bufsz);
92 if (sz < 0) {
93 return "";
94 }
95 path[sz] = '\0';
96 std::string p(path);
97 size_t slash = p.find_last_of('/');
98 if (slash != std::string::npos) {
99 p = p.substr(0, slash);
100 }
101 return p;
102}
103#endif
104
105bool file_exists(const std::string& filename) {
106#if defined(HAS_GETFILEATTRIBUTES)
107 DWORD dwAttrib = GetFileAttributesW(utf8_to_wide(filename).c_str());
108
109 return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) == 0;
110#else
111 struct stat info;
112 return stat(filename.c_str(), &info) == 0 && ((info.st_mode & S_IFREG) != 0);
113#endif
114}
115
116bool directory_exists(const std::string& dirname) {
117#if defined(HAS_GETFILEATTRIBUTES)
118 DWORD dwAttrib = GetFileAttributesW(utf8_to_wide(dirname).c_str());
119
120 return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY) != 0;
121#else
122 struct stat info;
123 return stat(dirname.c_str(), &info) == 0 && ((info.st_mode & S_IFDIR) != 0);
124#endif
125}
126
127std::string file_path(const std::string& filename, const std::string& basePath) {
128#ifdef _MSC_VER
129 LPWSTR lpFilePart;
130 DWORD nBufferLength = GetFullPathNameW(utf8_to_wide(filename).c_str(), 0, nullptr, &lpFilePart);
131 auto lpBuffer = static_cast<LPWSTR>(LocalAlloc(LMEM_FIXED, sizeof(WCHAR) * nBufferLength));
132 if (lpBuffer == nullptr) {
133 return "";
134 }
135
136 std::string ret;
137 DWORD error =
138 GetFullPathNameW(utf8_to_wide(filename).c_str(), nBufferLength, lpBuffer, &lpFilePart);
139 DWORD fileAttr = GetFileAttributesW(lpBuffer);
140 DWORD lastError = GetLastError();
141
142 if (error == 0 || (fileAttr == INVALID_FILE_ATTRIBUTES && lastError != NO_ERROR)) {
143 ret = basePath.empty() ? filename : file_path(basePath + "/" + filename);
144 } else {
145 ret = wide_to_utf8(lpBuffer);
146 }
147 LocalFree(lpBuffer);
148 return ret;
149#else
150 char* rp = realpath(filename.c_str(), nullptr);
151 if (rp == nullptr) {
152 if (basePath.empty()) {
153 return filename;
154 }
155 return file_path(basePath + "/" + filename);
156 }
157 std::string rp_s(rp);
158 free(rp);
159 return rp_s;
160#endif
161}
162
163std::string dir_name(const std::string& filename) {
164#ifdef _MSC_VER
165 size_t pos = filename.find_last_of("\\/");
166 return (pos == std::string::npos) ? "" : filename.substr(0, pos);
167#else
168 char* fn = strdup(filename.c_str());
169 char* dn = dirname(fn);
170 std::string ret(dn);
171 free(fn);
172 return ret;
173#endif
174}
175
176std::string base_name(const std::string& filename) {
177#ifdef _MSC_VER
178 size_t pos = filename.find_last_of("\\/");
179 return (pos == std::string::npos) ? filename : filename.substr(pos + 1);
180#else
181 char* fn = strdup(filename.c_str());
182 char* dn = basename(fn);
183 std::string ret(dn);
184 free(fn);
185 return ret;
186#endif
187}
188
189bool is_absolute(const std::string& path) {
190#ifdef _MSC_VER
191 if (path.size() > 2 &&
192 ((path[0] == '\\' && path[1] == '\\') || (path[0] == '/' && path[1] == '/'))) {
193 return true;
194 }
195 return PathIsRelativeW(utf8_to_wide(path).c_str()) == FALSE;
196#else
197 return path.empty() ? false : (path[0] == '/');
198#endif
199}
200
201std::string find_executable(const std::string& filename) {
202 if (is_absolute(filename)) {
203 if (file_exists(filename)) {
204 return filename;
205 }
206#ifdef _MSC_VER
207 if (FileUtils::file_exists(filename + ".exe")) {
208 return filename + ".exe";
209 }
210 if (FileUtils::file_exists(filename + ".bat")) {
211 return filename + ".bat";
212 }
213#endif
214 return "";
215 }
216 char* path_c = getenv("PATH");
217#ifdef _MSC_VER
218 char pathsep = ';';
219#else
220 char pathsep = ':';
221#endif
222 std::string path;
223 if (path_c != nullptr) {
224 path = path_c;
225 if (!path.empty()) {
226 path += pathsep;
227 }
228 }
229 path += progpath();
230 std::string pathItem;
231 std::stringstream pathStream(path);
232 while (std::getline(pathStream, pathItem, pathsep)) {
233 std::string fileWithPath = pathItem.append("/").append(filename);
234 if (file_exists(fileWithPath)) {
235 return fileWithPath;
236 }
237#ifdef _MSC_VER
238 if (FileUtils::file_exists(fileWithPath + ".exe")) {
239 return fileWithPath + ".exe";
240 }
241 if (FileUtils::file_exists(fileWithPath + ".bat")) {
242 return fileWithPath + ".bat";
243 }
244#endif
245 }
246 return "";
247}
248
249std::vector<std::string> directory_list(const std::string& dir, const std::string& ext) {
250 std::vector<std::string> entries;
251#ifdef _MSC_VER
252 WIN32_FIND_DATAW findData;
253 HANDLE hFind = ::FindFirstFileW(utf8_to_wide(dir + "/*." + ext).c_str(), &findData);
254 if (hFind != INVALID_HANDLE_VALUE) {
255 do {
256 if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
257 entries.push_back(wide_to_utf8(findData.cFileName));
258 }
259 } while (::FindNextFileW(hFind, &findData) == TRUE);
260 ::FindClose(hFind);
261 }
262#else
263 DIR* dirp = opendir(dir.c_str());
264 if (dirp != nullptr) {
265 struct dirent* dp;
266 while ((dp = readdir(dirp)) != nullptr) {
267 std::string fileName(dp->d_name);
268 struct stat info;
269 if (stat(((dir + "/").append(fileName)).c_str(), &info) == 0 &&
270 ((info.st_mode & S_IFREG) != 0)) {
271 if (ext == "*") {
272 entries.push_back(fileName);
273 } else {
274 if (fileName.size() > ext.size() + 2 &&
275 fileName.substr(fileName.size() - ext.size() - 1) == "." + ext) {
276 entries.push_back(fileName);
277 }
278 }
279 }
280 }
281 closedir(dirp);
282 }
283#endif
284 return entries;
285}
286
287std::string working_directory() {
288#ifdef _MSC_VER
289 wchar_t wd[FILENAME_MAX];
290 if (_wgetcwd(wd, FILENAME_MAX) == FALSE) {
291 return "";
292 }
293 return wide_to_utf8(wd);
294#else
295 char wd[FILENAME_MAX];
296 if (getcwd(wd, sizeof(wd)) == nullptr) {
297 return "";
298 }
299 return wd;
300#endif
301}
302
303std::string share_directory() {
304#ifdef _WIN32
305 if (wchar_t* MZNSTDLIBDIR = _wgetenv(L"MZN_STDLIB_DIR")) {
306 return wide_to_utf8(MZNSTDLIBDIR);
307 }
308#else
309 if (char* MZNSTDLIBDIR = getenv("MZN_STDLIB_DIR")) {
310 return std::string(MZNSTDLIBDIR);
311 }
312#endif
313 // NOLINTNEXTLINE(readability-redundant-string-init)
314 std::string static_stdlib_dir(MZN_STATIC_STDLIB_DIR);
315 if (FileUtils::file_exists(static_stdlib_dir + "/std/stdlib.mzn")) {
316 return static_stdlib_dir;
317 }
318 std::string mypath = FileUtils::progpath();
319 int depth = 0;
320 for (char i : mypath) {
321 if (i == '/' || i == '\\') {
322 depth++;
323 }
324 }
325 for (int i = 0; i <= depth; i++) {
326 if (FileUtils::file_exists(mypath + "/share/minizinc/std/stdlib.mzn")) {
327 return mypath + "/share/minizinc";
328 }
329 mypath += "/..";
330 }
331 return "";
332}
333
334std::string user_config_dir() {
335#ifdef _MSC_VER
336 HRESULT hr;
337 PWSTR pszPath = nullptr;
338
339 hr = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pszPath);
340 if (SUCCEEDED(hr)) {
341 auto configPath = wide_to_utf8(pszPath);
342 CoTaskMemFree(pszPath);
343 if (configPath.empty()) {
344 return "";
345 }
346 return configPath + "/MiniZinc";
347 }
348 return "";
349#else
350 if (const char* hd = getenv("HOME")) {
351 return std::string(hd) + "/.minizinc";
352 }
353 return "";
354#endif
355}
356
357std::string global_config_file() {
358 std::string sd = share_directory();
359 if (sd.empty()) {
360 return "";
361 }
362 return sd + "/Preferences.json";
363}
364
365std::string user_config_file() { return user_config_dir() + "/Preferences.json"; }
366
367TmpFile::TmpFile(const std::string& ext) {
368#ifdef _WIN32
369 WCHAR szTempFileName[MAX_PATH];
370 WCHAR lpTempPathBuffer[MAX_PATH];
371
372 bool didCopy;
373 do {
374 GetTempPathW(MAX_PATH, lpTempPathBuffer);
375 GetTempFileNameW(lpTempPathBuffer, L"tmp_mzn_", 0, szTempFileName);
376
377 _name = wide_to_utf8(szTempFileName);
378 _tmpNames.push_back(_name);
379 didCopy = CopyFileW(szTempFileName, utf8_to_wide(_name + ext).c_str(), TRUE) == TRUE;
380 } while (!didCopy);
381 _name += ext;
382#else
383 _tmpfileDesc = -1;
384 _name = "/tmp/mznfileXXXXXX" + ext;
385 char* tmpfile = strndup(_name.c_str(), _name.size());
386 _tmpfileDesc = mkstemps(tmpfile, ext.size());
387 if (_tmpfileDesc == -1) {
388 ::free(tmpfile);
389 throw InternalError("Error occurred when creating temporary file");
390 }
391 _name = std::string(tmpfile);
392 ::free(tmpfile);
393#endif
394}
395
396TmpFile::~TmpFile() {
397#ifdef _WIN32
398 _wremove(utf8_to_wide(_name).c_str()); // TODO: Is this necessary?
399 for (auto& n : _tmpNames) {
400 _wremove(utf8_to_wide(n).c_str());
401 }
402#else
403 remove(_name.c_str());
404 if (_tmpfileDesc != -1) {
405 close(_tmpfileDesc);
406 }
407#endif
408}
409
410TmpDir::TmpDir() {
411#ifdef _WIN32
412 WCHAR szTempFileName[MAX_PATH];
413 WCHAR lpTempPathBuffer[MAX_PATH];
414
415 GetTempPathW(MAX_PATH, lpTempPathBuffer);
416 GetTempFileNameW(lpTempPathBuffer, L"tmp_mzn_", 0, szTempFileName);
417
418 _name = wide_to_utf8(szTempFileName);
419 DeleteFileW(szTempFileName);
420 CreateDirectoryW(szTempFileName, nullptr);
421#else
422 _name = "/tmp/mzndirXXXXXX";
423 char* tmpfile = strndup(_name.c_str(), _name.size());
424
425 if (mkdtemp(tmpfile) == nullptr) {
426 ::free(tmpfile);
427 throw InternalError("Error occurred when creating temporary directory");
428 }
429 _name = std::string(tmpfile);
430 ::free(tmpfile);
431#endif
432}
433
434#ifdef _WIN32
435namespace {
436void remove_dir(const std::string& d) {
437 HANDLE dh;
438 WIN32_FIND_DATAW info;
439 auto dw = utf8_to_wide(d);
440 auto pattern = dw + L"\\*.*";
441 dh = ::FindFirstFileW(pattern.c_str(), &info);
442 if (dh != INVALID_HANDLE_VALUE) {
443 do {
444 if (info.cFileName[0] != L'.') {
445 auto fp = dw + L"\\" + info.cFileName;
446 if ((info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
447 remove_dir(wide_to_utf8(fp));
448 } else {
449 ::SetFileAttributesW(fp.c_str(), FILE_ATTRIBUTE_NORMAL);
450 ::DeleteFileW(fp.c_str());
451 }
452 }
453 } while (::FindNextFileW(dh, &info) == TRUE);
454 }
455 ::FindClose(dh);
456 ::SetFileAttributesW(dw.c_str(), FILE_ATTRIBUTE_NORMAL);
457 ::RemoveDirectoryW(dw.c_str());
458}
459} // namespace
460#else
461namespace {
462int remove_file(const char* fpath, const struct stat* /*s*/, int /*i*/, struct FTW* /*ftw*/) {
463 return unlink(fpath);
464}
465} // namespace
466#endif
467
468TmpDir::~TmpDir() {
469#ifdef _WIN32
470 remove_dir(_name);
471#else
472 nftw(_name.c_str(), remove_file, 64, FTW_DEPTH | FTW_PHYS);
473 rmdir(_name.c_str());
474#endif
475}
476
477std::vector<std::string> parse_cmd_line(const std::string& s) {
478 // Break the string up at whitespace, except inside quotes, but ignore escaped quotes
479 std::vector<std::string> c;
480 size_t cur = 0;
481 size_t l = s.length();
482 std::ostringstream oss;
483 bool inside_quote = false;
484 bool had_escape = false;
485 for (; cur < l; cur++) {
486 if (inside_quote) {
487 if (s[cur] == '"') {
488 if (had_escape) {
489 oss << "\"";
490 had_escape = false;
491 } else {
492 inside_quote = false;
493 }
494 } else if (s[cur] == '\\') {
495 had_escape = true;
496 } else {
497 if (had_escape) {
498 oss << "\\";
499 had_escape = false;
500 }
501 oss << s[cur];
502 }
503 } else {
504 if (s[cur] == ' ') {
505 if (had_escape) {
506 oss << " ";
507 had_escape = false;
508 } else {
509 c.push_back(oss.str());
510 oss.str(std::string());
511 }
512 } else if (s[cur] == '\\') {
513 if (had_escape) {
514 oss << "\\";
515 had_escape = false;
516 } else {
517 had_escape = true;
518 }
519 } else if (s[cur] == '"') {
520 if (had_escape) {
521 oss << "\"";
522 had_escape = false;
523 } else {
524 inside_quote = true;
525 }
526 } else {
527 if (had_escape) {
528 switch (s[cur]) {
529 case 'a':
530 oss << "\a";
531 break;
532 case 'b':
533 oss << "\b";
534 break;
535 case 'f':
536 oss << "\f";
537 break;
538 case 'n':
539 oss << "\n";
540 break;
541 case 'r':
542 oss << "\r";
543 break;
544 case 't':
545 oss << "\t";
546 break;
547 case 'v':
548 oss << "\v";
549 break;
550 default:
551 oss << "\\" << s[cur];
552 break;
553 }
554 had_escape = false;
555 } else {
556 oss << s[cur];
557 }
558 }
559 }
560 }
561 c.push_back(oss.str());
562 return c;
563}
564
565std::string combine_cmd_line(const std::vector<std::string>& cmd) {
566 std::ostringstream ret;
567 for (unsigned int i = 0; i < cmd.size(); i++) {
568 const auto& c = cmd[i];
569 ret << "\"";
570 for (char i : c) {
571 switch (i) {
572 case '\a':
573 ret << "\\a";
574 break;
575 case '\b':
576 ret << "\\b";
577 break;
578 case '\f':
579 ret << "\\f";
580 break;
581 case '\n':
582 ret << "\\n";
583 break;
584 case '\r':
585 ret << "\\r";
586 break;
587 case '\t':
588 ret << "\\t";
589 break;
590 case '\v':
591 ret << "\\v";
592 break;
593 case '"':
594 ret << "\\\"";
595 break;
596 case '\\':
597 ret << "\\\\";
598 break;
599 default:
600 ret << i;
601 break;
602 }
603 }
604 ret << "\"";
605 if (i < cmd.size() - 1) {
606 ret << " ";
607 }
608 }
609 return ret.str();
610}
611
612void inflate_string(std::string& s) {
613 auto* cc = reinterpret_cast<unsigned char*>(&s[0]);
614 // autodetect compressed string
615 if (s.size() >= 2 && ((cc[0] == 0x1F && cc[1] == 0x8B) // gzip
616 || (cc[0] == 0x78 && (cc[1] == 0x01 // zlib
617 || cc[1] == 0x9C || cc[1] == 0xDA)))) {
618 const int BUF_SIZE = 1024;
619 unsigned char s_outbuf[BUF_SIZE];
620 z_stream stream;
621 std::memset(&stream, 0, sizeof(stream));
622
623 unsigned char* dataStart;
624 int windowBits;
625 size_t dataLen;
626 if (cc[0] == 0x1F && cc[1] == 0x8B) {
627 dataStart = cc + 10;
628 windowBits = -Z_DEFAULT_WINDOW_BITS;
629 if ((cc[3] & 0x4) != 0) {
630 dataStart += 2;
631 if (dataStart >= cc + s.size()) {
632 throw(-1);
633 }
634 }
635 if ((cc[3] & 0x8) != 0) {
636 while (*dataStart != '\0') {
637 dataStart++;
638 if (dataStart >= cc + s.size()) {
639 throw(-1);
640 }
641 }
642 dataStart++;
643 if (dataStart >= cc + s.size()) {
644 throw(-1);
645 }
646 }
647 if ((cc[3] & 0x10) != 0) {
648 while (*dataStart != '\0') {
649 dataStart++;
650 if (dataStart >= cc + s.size()) {
651 throw(-1);
652 }
653 }
654 dataStart++;
655 if (dataStart >= cc + s.size()) {
656 throw(-1);
657 }
658 }
659 if ((cc[3] & 0x2) != 0) {
660 dataStart += 2;
661 if (dataStart >= cc + s.size()) {
662 throw(-1);
663 }
664 }
665 dataLen = s.size() - (dataStart - cc);
666 } else {
667 dataStart = cc;
668 windowBits = Z_DEFAULT_WINDOW_BITS;
669 dataLen = s.size();
670 }
671
672 stream.next_in = dataStart;
673 stream.avail_in = static_cast<unsigned int>(dataLen);
674 stream.next_out = &s_outbuf[0];
675 stream.avail_out = BUF_SIZE;
676 int status = inflateInit2(&stream, windowBits);
677 if (status != Z_OK) {
678 throw(status);
679 }
680 std::ostringstream oss;
681 while (true) {
682 status = inflate(&stream, Z_NO_FLUSH);
683 if (status == Z_STREAM_END || (stream.avail_out == 0U)) {
684 // output buffer full or compression finished
685 oss << std::string(reinterpret_cast<char*>(s_outbuf), BUF_SIZE - stream.avail_out);
686 stream.next_out = &s_outbuf[0];
687 stream.avail_out = BUF_SIZE;
688 }
689 if (status == Z_STREAM_END) {
690 break;
691 }
692 if (status != Z_OK) {
693 throw(status);
694 }
695 }
696 status = inflateEnd(&stream);
697 if (status != Z_OK) {
698 throw(status);
699 }
700 s = oss.str();
701 }
702}
703
704std::string deflate_string(const std::string& s) {
705 mz_ulong compressedLength = compressBound(static_cast<mz_ulong>(s.size()));
706 auto* cmpr = static_cast<unsigned char*>(::malloc(compressedLength * sizeof(unsigned char)));
707 int status = compress(cmpr, &compressedLength, reinterpret_cast<const unsigned char*>(&s[0]),
708 static_cast<mz_ulong>(s.size()));
709 if (status != Z_OK) {
710 ::free(cmpr);
711 throw(status);
712 }
713 std::string ret(reinterpret_cast<const char*>(cmpr), compressedLength);
714 ::free(cmpr);
715 return ret;
716}
717
718std::string encode_base64(const std::string& s) {
719 base64::encoder E;
720 std::ostringstream oss;
721 oss << "@"; // add leading "@" to distinguish from valid MiniZinc code
722 std::istringstream iss(s);
723 E.encode(iss, oss);
724 return oss.str();
725}
726
727std::string decode_base64(const std::string& s) {
728 if (s.empty() || s[0] != '@') {
729 throw InternalError("string is not base64 encoded");
730 }
731 base64::decoder D;
732 std::ostringstream oss;
733 std::istringstream iss(s);
734 (void)iss.get(); // remove leading "@"
735 D.decode(iss, oss);
736 return oss.str();
737}
738
739#ifdef _WIN32
740std::string wide_to_utf8(const wchar_t* str, int size) {
741 int buffer_size = WideCharToMultiByte(CP_UTF8, 0, str, size, nullptr, 0, nullptr, nullptr);
742 if (buffer_size == 0) {
743 return "";
744 }
745 std::string result(buffer_size - 1, '\0');
746 WideCharToMultiByte(CP_UTF8, 0, str, size, &result[0], buffer_size, nullptr, nullptr);
747 return result;
748}
749
750std::string wide_to_utf8(const std::wstring& str) { return wide_to_utf8(str.c_str(), -1); }
751
752std::wstring utf8_to_wide(const std::string& str) {
753 int buffer_size = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, nullptr, 0);
754 if (buffer_size == 0) {
755 return L"";
756 }
757 std::wstring result(buffer_size - 1, '\0');
758 MultiByteToWideChar(CP_UTF8, 0, str.c_str(), -1, &result[0], buffer_size);
759 return result;
760}
761#endif
762} // namespace FileUtils
763} // namespace MiniZinc