Vuo 2.4.2
Loading...
Searching...
No Matches
VuoFileUtilities.cc
Go to the documentation of this file.
1
10#include <CommonCrypto/CommonDigest.h>
11#include <dirent.h>
12#include <spawn.h>
13#include <sstream>
14#include <sys/mman.h>
15#include <sys/stat.h>
16#include <copyfile.h>
17#include <fstream>
18#include <iomanip>
19#include <iostream>
20#include <mach-o/dyld.h>
21#include <sys/time.h>
22#include "VuoException.hh"
23#include "VuoFileUtilities.hh"
25#include "VuoStringUtilities.hh"
26
27bool VuoFileUtilities::dylibLoaderInitialMatchCompleted = false;
28string VuoFileUtilities::vuoFrameworkPath;
29string VuoFileUtilities::vuoRunnerFrameworkPath;
30
41void VuoFileUtilities::splitPath(string path, string &dir, string &file, string &extension)
42{
43 dir = "";
44 file = "";
45 extension = "";
46 const char FILE_SEPARATOR = '/';
47
48 size_t separatorIndex = path.rfind(FILE_SEPARATOR);
49 string fileAndExtension;
50 if (separatorIndex != string::npos)
51 {
52 dir = path.substr(0, separatorIndex + 1);
53 if (separatorIndex < path.length())
54 fileAndExtension = path.substr(separatorIndex + 1, path.length());
55 }
56 else
57 {
58 fileAndExtension = path;
59 }
60
61 size_t dotIndex = fileAndExtension.rfind(".");
62 if (dotIndex != string::npos)
63 {
64 file = fileAndExtension.substr(0, dotIndex);
65 extension = fileAndExtension.substr(dotIndex + 1, fileAndExtension.length());
66 }
67 else
68 {
69 file = fileAndExtension;
70 }
71}
72
78{
79 // Remove repeated file separators.
80 for (int i = 0; i < path.length(); ++i)
81 if (path[i] == '/')
82 while (i+1 < path.length() && path[i+1] == '/')
83 path.erase(i+1, 1);
84
85 // Remove trailing file separator.
86 if (path.length() > 1 && VuoStringUtilities::endsWith(path, "/"))
87 path.erase(path.length()-1);
88}
89
96bool VuoFileUtilities::arePathsEqual(string path1, string path2)
97{
98 canonicalizePath(path1);
99 canonicalizePath(path2);
100 return path1 == path2;
101}
102
106string VuoFileUtilities::getAbsolutePath(const string &path)
107{
108 if (VuoStringUtilities::beginsWith(path, "/"))
109 return path;
110
111 char *cwd = getcwd(NULL, 0);
112 if (! cwd)
113 {
114 VUserLog("Couldn't get current working directory: %s", strerror(errno));
115 return path;
116 }
117
118 string absolutePath = string(cwd) + "/" + path;
119 free(cwd);
120 return absolutePath;
121}
122
130string VuoFileUtilities::makeTmpFile(string file, string extension, string directory)
131{
132 if (directory.empty())
133 directory = getTmpDir();
134 if (directory.at(directory.length()-1) != '/')
135 directory += "/";
136 string suffix = (extension.empty() ? "" : ("." + extension));
137 string pathTemplate = directory + file + "-XXXXXX" + suffix;
138 char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
139 strncpy(path, pathTemplate.c_str(), pathTemplate.length());
140 int fd = mkstemps(path, suffix.length());
141 close(fd);
142 string pathStr(path);
143 free(path);
144 return pathStr;
145}
146
156string VuoFileUtilities::makeTmpDir(string prefix)
157{
158 string pathTemplate = getTmpDir() + "/" + prefix + ".XXXXXX";
159 char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
160 strncpy(path, pathTemplate.c_str(), pathTemplate.length());
161 mkdtemp(path);
162 string pathStr(path);
163 free(path);
164 return pathStr;
165}
166
176{
178}
179
192{
193 char *cwd = getcwd(NULL, 0);
194 if (!cwd)
195 VUserLog("Error in getcwd(): %s", strerror(errno));
196
197 if (cwd && strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
198 {
199 // https://b33p.net/kosada/node/16374
200 // In the macOS 10.15 screen saver sandbox, at least,
201 // we're not allowed to use the system or user temporary directories for socket-files,
202 // but we are allowed to use the sandbox container directory.
203 string cwdS(cwd);
204 free(cwd);
205 return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
206 }
207 free(cwd);
208
209 char userTempDir[PATH_MAX];
210 if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
211 {
212 string userTempDirS(userTempDir);
213 return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
214 }
215
216 return "/tmp";
217}
218
227{
228 if (path.empty())
229 throw VuoException("Couldn't create directory with empty path");
230
231 if (dirExists(path))
232 return;
233
234 const char FILE_SEPARATOR = '/';
235 size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
236 if (lastNonSeparator != string::npos)
237 path.resize(lastNonSeparator + 1);
238
239 string parentDir, file, ext;
240 splitPath(path, parentDir, file, ext);
241 makeDir(parentDir);
242
243 // `mkdir` ANDs the mode with the process's umask,
244 // so by default the created directory's mode will end up being 0755.
245 int ret = mkdir(path.c_str(), 0777);
246 int mkdirErrno = errno;
247 if (ret != 0 && ! dirExists(path))
248 throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(mkdirErrno)).c_str());
249}
250
256void VuoFileUtilities::dylibLoaded(const struct mach_header *mh32, intptr_t vmaddr_slide)
257{
258 if (dylibLoaderInitialMatchCompleted)
259 return;
260
261 const struct mach_header_64 *mh = reinterpret_cast<const mach_header_64 *>(mh32);
262
263 // Ignore system libraries.
264 if (mh->flags & MH_DYLIB_IN_CACHE)
265 return;
266
267 // Get the file path of the current dylib.
268 Dl_info info{"", nullptr, "", nullptr};
269 dladdr((void *)vmaddr_slide, &info);
270
271 // Check whether it's one of the dylibs we're looking for.
272
273 auto getMatchingPath = [info](string fragment) -> string {
274 string qualifiedFragment = "/" + fragment + ".framework/Versions/";
275 const char *found = strstr(info.dli_fname, qualifiedFragment.c_str());
276 if (found)
277 {
278 char *pathC = strndup(info.dli_fname, found - info.dli_fname);
279 string path = string(pathC) + "/" + fragment + ".framework";
280 free(pathC);
281 if (access(path.c_str(), 0) == 0)
282 return path;
283 }
284 return string();
285 };
286
287 string possibleVuoFramework = getMatchingPath("Vuo");
288 if (!possibleVuoFramework.empty())
289 vuoFrameworkPath = possibleVuoFramework;
290
291 string possibleVuoRunnerFramework = getMatchingPath("VuoRunner");
292 if (!possibleVuoRunnerFramework.empty())
293 vuoRunnerFrameworkPath = possibleVuoRunnerFramework;
294}
295
299void VuoFileUtilities::initializeVuoFrameworkPaths(void)
300{
301 static once_flag once;
302 call_once(once, []() {
303 // Check whether Vuo.framework is in the list of loaded dynamic libraries.
304 _dyld_register_func_for_add_image(&dylibLoaded);
305 dylibLoaderInitialMatchCompleted = true;
306 // The above function invokes the callback for each already-loaded dylib, then returns.
307
308 if (vuoRunnerFrameworkPath.empty() && !vuoFrameworkPath.empty())
309 {
310 // Check for VuoRunner.framework alongside Vuo.framework.
311 string pathCandidate = vuoFrameworkPath + "/../VuoRunner.framework";
312 if (dirExists(pathCandidate))
313 vuoRunnerFrameworkPath = pathCandidate;
314 }
315 });
316}
317
326{
327 initializeVuoFrameworkPaths();
328 return vuoFrameworkPath;
329}
330
337{
338 initializeVuoFrameworkPaths();
339 return vuoRunnerFrameworkPath;
340}
341
350string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
351{
352 string compositionDir, compositionFile, ext;
353 splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
354 canonicalizePath(compositionDir);
355
356 string parentDir, compositionDirName;
357 splitPath(compositionDir, parentDir, compositionDirName, ext);
358 canonicalizePath(compositionDirName);
359
360 string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
361
362 string compositionModulesDir = compositionBaseDir + "/Modules";
363 canonicalizePath(compositionModulesDir);
364
365 return compositionModulesDir;
366}
367
377{
378 const char *home = getenv("HOME");
379 if (!home)
380 return string();
381
382 return string(home) + "/Library/Application Support/Vuo/Modules";
383}
384
389{
390 return "/Library/Application Support/Vuo/Modules";
391}
392
402{
403 const char *home = getenv("HOME");
404 if (!home)
405 return string();
406
407 return string(home) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
408}
409
414{
415 if (path.empty())
416 return false;
417
418 string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
419
420 string dir, file, ext;
421 VuoFileUtilities::splitPath(path, dir, file, ext);
422
423 return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
424}
425
434void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
435{
436 fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
437}
438
444{
445 string bomUtf8 = "\xEF\xBB\xBF";
446 string bomUtf16Be = "\xFE\xFF";
447 string bomUtf16Le = "\xFF\xFE";
448 string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
449 for (int i = 0; i < 3; ++i)
450 {
451 bool isMatch = true;
452 for (int j = 0; j < boms[i].length(); ++j)
453 if (boms[i][j] != s[j])
454 isMatch = false;
455
456 if (isMatch)
457 return boms[i].length();
458 }
459 return 0;
460}
461
466{
467 // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
468
469 string contents;
470 string line;
471 while (getline(cin, line))
472 contents += line;
473
474 return contents;
475}
476
483{
484 // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
485
486 ifstream in(path.c_str(), ios::in | ios::binary);
487 if (! in)
488 throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
489
490 string contents;
491 in.seekg(0, ios::end);
492 contents.resize(in.tellg());
493 in.seekg(0, ios::beg);
494 in.read(&contents[0], contents.size());
495 in.close();
496 return contents;
497}
498
506void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
507{
508 FILE *f = fopen(file.c_str(), "wb");
509 if (! f)
510 throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
511
512 size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
513 if (numBytesWritten != numBytes)
514 {
515 fclose(f);
516 throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
517 }
518
519 fclose(f);
520}
521
529void VuoFileUtilities::writeStringToFile(string s, string file)
530{
531 writeRawDataToFile(s.data(), s.length(), file);
532}
533
543{
544 string dir, file, ext;
545 splitPath(path, dir, file, ext);
546 string tmpPath = makeTmpFile("." + file, ext, dir);
547
548 writeStringToFile(s, tmpPath);
549
550 moveFile(tmpPath, path);
551}
552
559{
562
563 // `access()` automatically dereferences symlinks.
564 return access(path.c_str(), 0) == 0;
565}
566
573{
576
577 // `stat()` automatically dereferences symlinks.
578 struct stat st_buf;
579 int status = stat(path.c_str(), &st_buf);
580 return (! status && S_ISDIR(st_buf.st_mode));
581}
582
587{
588 struct stat st_buf;
589 int status = lstat(path.c_str(), &st_buf);
590 return (! status && S_ISLNK(st_buf.st_mode));
591}
592
599{
602
603 // `access()` automatically dereferences symlinks.
604 return access(path.c_str(), R_OK) == 0;
605}
606
613{
616
617 // `fopen()` automatically dereferences symlinks.
618 FILE *f = fopen(path.c_str(), "rb");
619 if (!f)
620 return false;
621
622 fseek(f, 0, SEEK_END);
623 long pos = ftell(f);
624 if (pos <= 0)
625 {
626 fclose(f);
627 return false;
628 }
629
630 // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
631 fseek(f, 0, SEEK_SET);
632 char z;
633 size_t ret = fread(&z, 1, 1, f);
634 fclose(f);
635 return ret > 0;
636}
637
642{
643 FILE *f = fopen(path.c_str(), "a");
644 fclose(f);
645}
646
651{
652 int ret = remove(path.c_str());
653 if (ret != 0 && fileExists(path))
654 VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
655}
656
661{
662 if (! dirExists(path))
663 return;
664
665 DIR *d = opendir(path.c_str());
666 if (! d)
667 {
668 VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
669 return;
670 }
671
672 struct dirent *de;
673 while( (de=readdir(d)) )
674 {
675 string fileName = de->d_name;
676 string filePath = path + "/" + fileName;
677
678 if (fileName == "." || fileName == "..")
679 continue;
680
681 if (dirExists(filePath))
682 deleteDir(filePath);
683 else
684 deleteFile(filePath);
685 }
686
687 closedir(d);
688
689 deleteFile(path);
690}
691
697void VuoFileUtilities::moveFile(string fromPath, string toPath)
698{
699 int ret = rename(fromPath.c_str(), toPath.c_str());
700 if (ret != 0)
701 throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
702}
703
710{
712}
713
725void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
726{
727 int i = open(fromPath.c_str(), O_RDONLY);
728 if (i == -1)
729 throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
730
731 struct stat s;
732 fstat(i, &s);
733 int o = open(toPath.c_str(), O_WRONLY | O_CREAT, s.st_mode & 0777);
734 if (o == -1)
735 {
736 close(i);
737 throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
738 }
739
740 int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
741 char *e = strerror(errno);
742 close(o);
743 close(i);
744
745 if (ret)
746 throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
747
748 VDebugLog("%s -> %s", fromPath.c_str(), toPath.c_str());
749}
750
760void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
761{
762 if (isSymlink(fromPath))
763 {
764 // Preserve the relative symlinks found in framework bundles, instead of flattening them.
765 char linkDestination[PATH_MAX + 1];
766 ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
767 linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
768 if (symlink(linkDestination, toPath.c_str()) == -1)
769 throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
770
771 VDebugLog("%s -> %s (symlink)", fromPath.c_str(), toPath.c_str());
772 }
773
774 else if (!dirExists(fromPath))
775 copyFile(fromPath, toPath);
776
777 else
778 {
779 VDebugLog("%s ::", fromPath.c_str());
780 auto files = findAllFilesInDirectory(fromPath);
781 makeDir(toPath);
782 for (auto file : files)
783 {
784 string sourceFile = fromPath + "/" + file->getRelativePath();
785 string targetFile = toPath + "/" + file->getRelativePath();
786 copyDirectory(sourceFile, targetFile);
787 }
788 }
789}
790
797{
798 int fd = open(path.c_str(), O_RDONLY);
799 if (fd < 0)
800 throw VuoException("Error: Couldn't open \"" + path + "\": " + strerror(errno));
801 VuoDefer(^{ close(fd); });
802
803 struct stat stat;
804 if (fstat(fd, &stat) != 0)
805 throw VuoException("Error: Couldn't fstat \"" + path + "\": " + strerror(errno));
806
807 // Instead of reading the file into a string and calling `VuoStringUtilities::calculateSHA256`,
808 // use `mmap` so the OS can efficiently read parts of the file at a time (reducing memory required).
809 void *data = mmap(nullptr, stat.st_size, PROT_READ, MAP_PRIVATE | MAP_NOCACHE, fd, 0);
810 if (data == MAP_FAILED)
811 throw VuoException("Error: Couldn't mmap \"" + path + "\": " + strerror(errno));
812 VuoDefer(^{ munmap(data, stat.st_size); });
813
814 unsigned char hash[CC_SHA256_DIGEST_LENGTH];
815 if (!CC_SHA256(data, stat.st_size, hash))
816 throw VuoException("Error: CC_SHA256 failed on file \"" + path + "\"");
817
818 ostringstream oss;
819 oss << setfill('0') << hex;
820 for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i)
821 oss << setw(2) << (int)hash[i];
822 return oss.str();
823}
824
830{
831 struct stat s;
832 lstat(path.c_str(), &s);
833 return s.st_mtimespec.tv_sec; // s.st_mtimespec.tv_nsec is always 0 on some OSes, hence the resolution of 1 second
834}
835
840{
841 struct stat s;
842 lstat(path.c_str(), &s);
843
844 struct timeval t;
845 gettimeofday(&t, NULL);
846
847 return t.tv_sec - s.st_atimespec.tv_sec;
848}
849
864set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
865 bool shouldSearchRecursively)
866{
867 set<File *> files;
868
870 dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
871
872 // `opendir()` automatically dereferences symlinks.
873 DIR *d = opendir(dirPath.c_str());
874 if (! d)
875 {
876 if (access(dirPath.c_str(), F_OK) != -1)
877 throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
878 return files;
879 }
880
881 struct dirent *de;
882 while( (de=readdir(d)) )
883 {
884 string fileName = de->d_name;
885 string relativeFilePath = dirPath + "/" + fileName;
886
887 if (fileName == "." || fileName == "..")
888 continue;
889
890 bool isArchive = false;
891 for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
892 if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
893 isArchive = true;
894
895 if (isArchive)
896 {
897 set<File *> fs = findAllFilesInArchive(relativeFilePath);
898 if (fs.empty())
899 isArchive = false;
900 else
901 files.insert(fs.begin(), fs.end());
902 }
903
904 if (! isArchive)
905 {
906 bool shouldSearchDir = shouldSearchRecursively
907 && dirExists(relativeFilePath)
908 && !isSymlink(relativeFilePath);
909 if (shouldSearchDir)
910 {
911 set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
912 for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
913 {
914 File *f = *i;
915 f->dirPath = dirPath;
916 f->filePath = fileName + "/" + f->filePath;
917 }
918 files.insert(filesInDir.begin(), filesInDir.end());
919 }
920 else
921 {
922 File *f = new File(dirPath, fileName);
923 files.insert(f);
924 }
925 }
926 }
927
928 closedir(d);
929
930 return files;
931}
932
942set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
943{
944 set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
945 set<File *> matchingFiles;
946
947 for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
948 {
949 bool endsWithExtension = false;
950 for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
951 if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
952 endsWithExtension = true;
953
954 if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
955 matchingFiles.insert(*i);
956 else
957 delete *i;
958 }
959
960 return matchingFiles;
961}
962
969set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
970{
971 set<File *> files;
972
973 Archive *archive = new Archive(archivePath);
974 if (! archive->zipArchive)
975 {
976 delete archive;
977 return files;
978 }
979
980 mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
981 for (mz_uint i = 0; i < numFiles; ++i)
982 {
983 mz_zip_archive_file_stat file_stat;
984 bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
985 if (! success)
986 {
987 VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
988 break;
989 }
990
991 File *f = new File(archive, file_stat.m_filename);
992 files.insert(f);
993 }
994
995 if (archive->referenceCount == 0)
996 delete archive;
997
998 return files;
999}
1000
1010set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
1011{
1012 set<File *> allFiles = findAllFilesInArchive(archivePath);
1013 set<File *> matchingFiles;
1014
1015 for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
1016 {
1017 File *f = *i;
1018
1019 string dir, file, ext;
1020 splitPath(f->getRelativePath(), dir, file, ext);
1021 bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
1022
1023 bool endsWithExtension = false;
1024 for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
1025 if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
1026 endsWithExtension = true;
1027
1028 if (beginsWithDir && endsWithExtension)
1029 matchingFiles.insert(f);
1030 else
1031 delete f;
1032 }
1033
1034 return matchingFiles;
1035}
1036
1046string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1047{
1048 string contents;
1049 set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1050 for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1051 {
1052 File *file = *i;
1053 if (filePath == file->getRelativePath())
1054 contents = file->getContentsAsString();
1055 delete file;
1056 }
1057
1058 return contents;
1059}
1060
1069{
1071}
1072
1076void VuoFileUtilities::adHocCodeSign(string path, vector<string> environment, string entitlementsPath)
1077{
1078 double t0 = VuoLogGetTime();
1079 vector<string> processAndArgs{
1080 getVuoFrameworkPath() + "/Helpers/codesignWrapper.sh",
1081 "--force",
1082 "--sign",
1083 "-", // "-" = ad-hoc
1084 path,
1085 };
1086 if (!entitlementsPath.empty())
1087 {
1088 processAndArgs.push_back("--entitlements");
1089 processAndArgs.push_back(entitlementsPath);
1090 }
1091 executeProcess(processAndArgs, environment);
1092
1093 VDebugLog("\tAd-hoc code-signing took %5.2fs", VuoLogGetTime() - t0);
1094}
1095
1102 path(path)
1103{
1104 this->referenceCount = 0;
1105
1106 // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1107 this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1108
1109 bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1110 if (! success)
1111 {
1112 free(zipArchive);
1113 zipArchive = NULL;
1114 }
1115}
1116
1121{
1122 if (zipArchive)
1123 {
1124 mz_zip_reader_end(zipArchive);
1125 free(zipArchive);
1126 }
1127}
1128
1135{
1136}
1137
1141VuoFileUtilities::File::File(string dirPath, string filePath) :
1142 filePath(filePath),
1143 dirPath(dirPath)
1144{
1145 this->fileDescriptor = -1;
1146 this->archive = NULL;
1147}
1148
1155VuoFileUtilities::File::File(Archive *archive, string filePath) :
1156 filePath(filePath)
1157{
1158 this->fileDescriptor = -1;
1159 this->archive = archive;
1160 ++archive->referenceCount;
1161}
1162
1168{
1169 string newFilePath = basename() + "." + extension;
1170
1171 if (archive)
1172 return File(archive, newFilePath);
1173 else
1174 return File(dirPath, newFilePath);
1175}
1176
1182{
1183 if (archive && --archive->referenceCount == 0)
1184 delete archive;
1185}
1186
1191{
1192 return (archive != NULL);
1193}
1194
1201{
1202 return (archive ? archive->path : "");
1203}
1204
1209{
1210 return filePath;
1211}
1212
1219{
1220 if (archive)
1221 throw VuoException("Can't return a simple absolute path for a file in an archive.");
1222
1223 return dirPath + "/" + filePath;
1224}
1225
1232{
1233 if (archive)
1234 throw VuoException("Can't return a simple absolute path for a file in an archive.");
1235
1236 return dirPath;
1237}
1238
1243{
1244 return filePath.substr(0, filePath.find_last_of('.'));
1245}
1246
1251{
1252 return filePath.substr(filePath.find_last_of('.') + 1);
1253}
1254
1259{
1260 if (archive)
1261 return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1262 else
1263 return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1264}
1265
1275{
1276 char * buffer;
1277 numBytes = 0;
1278
1279 if (! archive)
1280 {
1281 // http://www.cplusplus.com/reference/cstdio/fread/
1282
1283 string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1284
1285 FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1286 if (pFile==NULL)
1287 throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1288
1289 // obtain file size:
1290 fseek (pFile , 0 , SEEK_END);
1291 size_t lSize = ftell (pFile);
1292 rewind (pFile);
1293
1294 // allocate memory to contain the whole file:
1295 buffer = (char*) malloc (sizeof(char)*lSize);
1296
1297 // copy the file into the buffer:
1298 numBytes = fread (buffer,1,lSize,pFile);
1299 fclose(pFile);
1300 if (numBytes != lSize)
1301 {
1302 free(buffer);
1303 throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1304 }
1305 }
1306 else
1307 {
1308 buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1309 }
1310
1311 return buffer;
1312}
1313
1320{
1321 size_t numBytes;
1322 char *buffer = getContentsAsRawData(numBytes);
1323 string s(buffer, numBytes);
1324 free(buffer);
1325 return s;
1326}
1327
1338{
1339 if (fileDescriptor < 0)
1340 fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1341 int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1342 return ret == 0;
1343}
1344
1355{
1356 if (fileDescriptor < 0)
1357 fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1358 int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1359 return ret == 0;
1360}
1361
1367{
1368 flock(fileDescriptor, LOCK_UN);
1369 close(fileDescriptor);
1370 fileDescriptor = -1;
1371}
1372
1378void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1379{
1381}
1382
1391void VuoFileUtilities::executeProcess(vector<string> processAndArgs, vector<string> environment)
1392{
1393 string binaryDir, binaryFile, binaryExt;
1394 splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1395
1396 string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1397
1398 // Capture stdout and stderr.
1399 int outputPipe[2];
1400 int ret = pipe(outputPipe);
1401 if (ret)
1402 throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1403 int pipeRead = outputPipe[0];
1404 int pipeWrite = outputPipe[1];
1405 VuoDefer(^{ close(pipeRead); });
1406 posix_spawn_file_actions_t fileActions;
1407 posix_spawn_file_actions_init(&fileActions);
1408 posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1409 posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1410 posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1411
1412 // Convert args.
1413 // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1414 char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1415 int argCount = 0;
1416 string commandLine;
1417 for (auto arg : processAndArgs)
1418 {
1419 processAndArgsZ[argCount++] = strdup(arg.c_str());
1420 if (argCount > 0)
1421 commandLine += ' ';
1422 commandLine += '"' + arg + '"';
1423 }
1424 processAndArgsZ[argCount] = nullptr;
1425
1426
1427 // Convert environment.
1428 // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1429 char **environmentZ = (char **)malloc(sizeof(char *) * (environment.size() + 1));
1430 int environmentVarCount = 0;
1431 for (auto environmentVar : environment)
1432 {
1433 environmentZ[environmentVarCount++] = strdup(environmentVar.c_str());
1434 commandLine = environmentVar + " " + commandLine;
1435 }
1436 environmentZ[environmentVarCount] = nullptr;
1437
1438
1439 // Execute.
1440 pid_t pid;
1441 VDebugLog("%s", commandLine.c_str());
1442 ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, (char * const *)environmentZ);
1443 close(pipeWrite);
1444 posix_spawn_file_actions_destroy(&fileActions);
1445 for (int i = 0; i < argCount; ++i)
1446 free(processAndArgsZ[i]);
1447
1448 if (ret)
1449 throw VuoException(errorPrefix + strerror(ret));
1450 else
1451 {
1452 int ret;
1453 int status;
1454 while (true)
1455 {
1456 ret = waitpid(pid, &status, 0);
1457 if (ret == -1 && errno == EINTR)
1458 // This process received a signal while waiting for the other process
1459 // (seems to happen when running under lldb); try waiting again.
1460 continue;
1461 else if (ret == -1)
1462 throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1463 else if (status != 0)
1464 {
1465 string output;
1466 char buf[256];
1467 bzero(buf, 256);
1468 while (read(pipeRead, &buf, 255) > 0)
1469 {
1470 output += buf;
1471 bzero(buf, 256);
1472 }
1473
1474 throw VuoException(binaryFile + " failed: " + output);
1475 }
1476 else
1477 // The other process completed without error.
1478 break;
1479 }
1480 }
1481}
1482
1487{
1488 return extension == "vuo";
1489}
1490
1495{
1496 set<string> e;
1497 e.insert("c");
1498 e.insert("cc");
1499 e.insert("C");
1500 e.insert("cpp");
1501 e.insert("cxx");
1502 e.insert("c++");
1503 e.insert("m");
1504 e.insert("mm");
1505 return e;
1506}
1507
1512{
1513 auto extensions = getCSourceExtensions();
1514 return extensions.find(extension) != extensions.end();
1515}
1516
1521{
1522 set<string> e;
1523 e.insert("fs");
1524 e.insert("vs");
1525 e.insert("vuoshader");
1526 e.insert("vuoobjectfilter");
1527 return e.find(extension) != e.end();
1528}
1529
1533string VuoFileUtilities::buildModuleCacheDescription(const string &moduleCachePath, bool generated)
1534{
1535 return string() + "the cache of " + (generated ? "generated" : "installed") + " modules at '" + moduleCachePath + "'";
1536}
1537
1541string VuoFileUtilities::buildModuleCacheIndexPath(const string &moduleCachePath, bool builtIn, bool generated)
1542{
1543 string moduleCachePathCanonical = moduleCachePath;
1544 canonicalizePath(moduleCachePathCanonical);
1545
1546 return moduleCachePathCanonical + "/libVuoModuleCache-" + (generated ? "generated" : "installed") + ".txt";
1547}
1548
1554string VuoFileUtilities::buildModuleCacheDylibPath(const string &moduleCachePath, bool builtIn, bool generated)
1555{
1556 string moduleCachePathCanonical = moduleCachePath;
1557 canonicalizePath(moduleCachePathCanonical);
1558
1559 string partialPath = moduleCachePathCanonical + "/libVuoModuleCache-" + (generated ? "generated" : "installed");
1560 if (builtIn)
1561 return partialPath + ".dylib";
1562
1563 string uniquePath;
1564 do {
1565 uniquePath = partialPath + "-" + VuoStringUtilities::makeRandomHash(4) + ".dylib";
1566 } while (fileExists(uniquePath));
1567 return uniquePath;
1568}
1569
1573string VuoFileUtilities::findLatestRevisionOfModuleCacheDylib(const string &moduleCachePath, bool builtIn, bool generated,
1574 unsigned long &lastModified)
1575{
1576 string path = buildModuleCacheDylibPath(moduleCachePath, builtIn, generated);
1577
1578 if (builtIn)
1579 {
1580 if (fileExists(path))
1581 {
1582 lastModified = getFileLastModifiedInSeconds(path);
1583 return path;
1584 }
1585 else
1586 {
1587 lastModified = 0;
1588 return "";
1589 }
1590 }
1591
1592 string dir, file, ext;
1593 splitPath(path, dir, file, ext);
1594 string fileName = file + "." + ext;
1595
1596 vector< pair<string, unsigned long> > existingRevisions;
1597 for (File *existingFile : findFilesInDirectory(moduleCachePath, {"dylib"}))
1598 {
1599 if (areDifferentRevisionsOfSameModuleCacheDylib(existingFile->getRelativePath(), fileName))
1600 existingRevisions.push_back({existingFile->path(), getFileLastModifiedInSeconds(existingFile->path())});
1601
1602 delete existingFile;
1603 }
1604
1605 if (existingRevisions.empty())
1606 {
1607 lastModified = 0;
1608 return "";
1609 }
1610
1611 auto latest = std::max_element(existingRevisions.begin(), existingRevisions.end(),
1612 [](pair<string, unsigned long> p1, pair<string, unsigned long> p2) { return p1.second < p2.second; });
1613
1614 lastModified = latest->second;
1615 return latest->first;
1616}
1617
1622{
1623 string moduleCachePath, file, ext;
1624 splitPath(dylibPath, moduleCachePath, file, ext);
1625 string dylibFileName = file + "." + ext;
1626
1627 for (File *otherFile : findFilesInDirectory(moduleCachePath, {"dylib"}))
1628 {
1629 if (areDifferentRevisionsOfSameModuleCacheDylib(otherFile->getRelativePath(), dylibFileName))
1630 deleteFile(otherFile->path());
1631
1632 delete otherFile;
1633 }
1634}
1635
1639bool VuoFileUtilities::areDifferentRevisionsOfSameModuleCacheDylib(const string &dylibPath1, const string &dylibPath2)
1640{
1641 string dir1, dir2, file1, file2, ext;
1642 splitPath(dylibPath1, dir1, file1, ext);
1643 splitPath(dylibPath2, dir2, file2, ext);
1644
1645 if (arePathsEqual(dir1, dir2))
1646 {
1647 vector<string> parts1 = VuoStringUtilities::split(file1, '-');
1648 vector<string> parts2 = VuoStringUtilities::split(file2, '-');
1649
1650 if (parts1.size() == 3 && parts2.size() == 3 && parts1.back() != parts2.back())
1651 {
1652 parts1.pop_back();
1653 parts2.pop_back();
1654 return parts1 == parts2;
1655 }
1656 }
1657
1658 return false;
1659}