Vuo 2.4.4
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
107string VuoFileUtilities::getAbsolutePath(const string &path)
108{
109 if (isAbsolutePath(path))
110 return path;
111
112 char *cwd = getcwd(NULL, 0);
113 if (! cwd)
114 {
115 VUserLog("Couldn't get current working directory: %s", strerror(errno));
116 return path;
117 }
118
119 string absolutePath = string(cwd) + "/" + path;
120 free(cwd);
121 return absolutePath;
122}
123
128bool VuoFileUtilities::isAbsolutePath(const string &path)
129{
130 return VuoStringUtilities::beginsWith(path, "/");
131}
132
140string VuoFileUtilities::makeTmpFile(string file, string extension, string directory)
141{
142 if (directory.empty())
143 directory = getTmpDir();
144 if (directory.at(directory.length()-1) != '/')
145 directory += "/";
146 string suffix = (extension.empty() ? "" : ("." + extension));
147 string pathTemplate = directory + file + "-XXXXXX" + suffix;
148 char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
149 strncpy(path, pathTemplate.c_str(), pathTemplate.length());
150 int fd = mkstemps(path, suffix.length());
151 close(fd);
152 string pathStr(path);
153 free(path);
154 return pathStr;
155}
156
166string VuoFileUtilities::makeTmpDir(string prefix)
167{
168 string pathTemplate = getTmpDir() + "/" + prefix + ".XXXXXX";
169 char *path = (char *)calloc(pathTemplate.length() + 1, sizeof(char));
170 strncpy(path, pathTemplate.c_str(), pathTemplate.length());
171 mkdtemp(path);
172 string pathStr(path);
173 free(path);
174 return pathStr;
175}
176
189
202{
203 char *cwd = getcwd(NULL, 0);
204 if (!cwd)
205 VUserLog("Error in getcwd(): %s", strerror(errno));
206
207 if (cwd && strstr(cwd, "/Library/Containers/")) // We're in a sandbox.
208 {
209 // https://b33p.net/kosada/node/16374
210 // In the macOS 10.15 screen saver sandbox, at least,
211 // we're not allowed to use the system or user temporary directories for socket-files,
212 // but we are allowed to use the sandbox container directory.
213 string cwdS(cwd);
214 free(cwd);
215 return VuoStringUtilities::endsWith(cwdS, "/") ? VuoStringUtilities::substrBefore(cwdS, "/") : cwdS;
216 }
217 free(cwd);
218
219 char userTempDir[PATH_MAX];
220 if (confstr(_CS_DARWIN_USER_TEMP_DIR, userTempDir, PATH_MAX) > 0)
221 {
222 string userTempDirS(userTempDir);
223 return VuoStringUtilities::endsWith(userTempDirS, "/") ? VuoStringUtilities::substrBefore(userTempDirS, "/") : userTempDirS;
224 }
225
226 return "/tmp";
227}
228
237{
238 if (path.empty())
239 throw VuoException("Couldn't create directory with empty path");
240
241 if (dirExists(path))
242 return;
243
244 const char FILE_SEPARATOR = '/';
245 size_t lastNonSeparator = path.find_last_not_of(FILE_SEPARATOR);
246 if (lastNonSeparator != string::npos)
247 path.resize(lastNonSeparator + 1);
248
249 string parentDir, file, ext;
250 splitPath(path, parentDir, file, ext);
251 makeDir(parentDir);
252
253 // `mkdir` ANDs the mode with the process's umask,
254 // so by default the created directory's mode will end up being 0755.
255 int ret = mkdir(path.c_str(), 0777);
256 int mkdirErrno = errno;
257 if (ret != 0 && ! dirExists(path))
258 throw VuoException((string("Couldn't create directory \"" + path + "\": ") + strerror(mkdirErrno)).c_str());
259}
260
266void VuoFileUtilities::dylibLoaded(const struct mach_header *mh32, intptr_t vmaddr_slide)
267{
268 if (dylibLoaderInitialMatchCompleted)
269 return;
270
271 const struct mach_header_64 *mh = reinterpret_cast<const mach_header_64 *>(mh32);
272
273 // Ignore system libraries.
274 if (mh->flags & MH_DYLIB_IN_CACHE)
275 return;
276
277 // Get the file path of the current dylib.
278 Dl_info info{"", nullptr, "", nullptr};
279 dladdr((void *)vmaddr_slide, &info);
280
281 // Check whether it's one of the dylibs we're looking for.
282
283 auto getMatchingPath = [info](string fragment) -> string {
284 string qualifiedFragment = "/" + fragment + ".framework/Versions/";
285 const char *found = strstr(info.dli_fname, qualifiedFragment.c_str());
286 if (found)
287 {
288 char *pathC = strndup(info.dli_fname, found - info.dli_fname);
289 string path = string(pathC) + "/" + fragment + ".framework";
290 free(pathC);
291 if (access(path.c_str(), 0) == 0)
292 return path;
293 }
294 return string();
295 };
296
297 string possibleVuoFramework = getMatchingPath("Vuo");
298 if (!possibleVuoFramework.empty())
299 vuoFrameworkPath = possibleVuoFramework;
300
301 string possibleVuoRunnerFramework = getMatchingPath("VuoRunner");
302 if (!possibleVuoRunnerFramework.empty())
303 vuoRunnerFrameworkPath = possibleVuoRunnerFramework;
304}
305
309void VuoFileUtilities::initializeVuoFrameworkPaths(void)
310{
311 static once_flag once;
312 call_once(once, []() {
313 // Check whether Vuo.framework is in the list of loaded dynamic libraries.
314 _dyld_register_func_for_add_image(&dylibLoaded);
315 dylibLoaderInitialMatchCompleted = true;
316 // The above function invokes the callback for each already-loaded dylib, then returns.
317
318 if (vuoRunnerFrameworkPath.empty() && !vuoFrameworkPath.empty())
319 {
320 // Check for VuoRunner.framework alongside Vuo.framework.
321 string pathCandidate = vuoFrameworkPath + "/../VuoRunner.framework";
322 if (dirExists(pathCandidate))
323 vuoRunnerFrameworkPath = pathCandidate;
324 }
325 });
326}
327
336{
337 initializeVuoFrameworkPaths();
338 return vuoFrameworkPath;
339}
340
347{
348 initializeVuoFrameworkPaths();
349 return vuoRunnerFrameworkPath;
350}
351
360string VuoFileUtilities::getCompositionLocalModulesPath(const string &compositionPath)
361{
362 string compositionDir, compositionFile, ext;
363 splitPath(getAbsolutePath(compositionPath), compositionDir, compositionFile, ext);
364 canonicalizePath(compositionDir);
365
366 string parentDir, compositionDirName;
367 splitPath(compositionDir, parentDir, compositionDirName, ext);
368 canonicalizePath(compositionDirName);
369
370 string compositionBaseDir = (compositionDirName == "Modules" ? parentDir : compositionDir);
371
372 string compositionModulesDir = compositionBaseDir + "/Modules";
373 canonicalizePath(compositionModulesDir);
374
375 return compositionModulesDir;
376}
377
387{
388 const char *home = getenv("HOME");
389 if (!home)
390 return string();
391
392 return string(home) + "/Library/Application Support/Vuo/Modules";
393}
394
399{
400 return "/Library/Application Support/Vuo/Modules";
401}
402
412{
413 const char *home = getenv("HOME");
414 if (!home)
415 return string();
416
417 return string(home) + "/Library/Caches/org.vuo/" + VUO_VERSION_AND_BUILD_STRING;
418}
419
424{
425 if (path.empty())
426 return false;
427
428 string compositionLocalModulesPath = getCompositionLocalModulesPath(path);
429
430 string dir, file, ext;
431 VuoFileUtilities::splitPath(path, dir, file, ext);
432
433 return VuoFileUtilities::arePathsEqual(compositionLocalModulesPath, dir);
434}
435
444void VuoFileUtilities::preserveOriginalFileName(string &fileContents, string originalFileName)
445{
446 fileContents.insert(getFirstInsertionIndex(fileContents), "#line 1 \"" + originalFileName + "\"\n");
447}
448
454{
455 string bomUtf8 = "\xEF\xBB\xBF";
456 string bomUtf16Be = "\xFE\xFF";
457 string bomUtf16Le = "\xFF\xFE";
458 string boms[] = { bomUtf8, bomUtf16Be, bomUtf16Le };
459 for (int i = 0; i < 3; ++i)
460 {
461 bool isMatch = true;
462 for (int j = 0; j < boms[i].length(); ++j)
463 if (boms[i][j] != s[j])
464 isMatch = false;
465
466 if (isMatch)
467 return boms[i].length();
468 }
469 return 0;
470}
471
476{
477 // https://stackoverflow.com/questions/201992/how-to-read-until-eof-from-cin-in-c
478
479 string contents;
480 string line;
481 while (getline(cin, line))
482 contents += line;
483
484 return contents;
485}
486
493{
494 // https://stackoverflow.com/questions/2602013/read-whole-ascii-file-into-c-stdstring
495
496 ifstream in(path.c_str(), ios::in | ios::binary);
497 if (! in)
498 throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + path);
499
500 string contents;
501 in.seekg(0, ios::end);
502 contents.resize(in.tellg());
503 in.seekg(0, ios::beg);
504 in.read(&contents[0], contents.size());
505 in.close();
506 return contents;
507}
508
516void VuoFileUtilities::writeRawDataToFile(const char *data, size_t numBytes, string file)
517{
518 FILE *f = fopen(file.c_str(), "wb");
519 if (! f)
520 throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + file);
521
522 size_t numBytesWritten = fwrite(data, sizeof(char), numBytes, f);
523 if (numBytesWritten != numBytes)
524 {
525 fclose(f);
526 throw VuoException(string("Couldn't write all data: ") + strerror(errno) + " — " + file);
527 }
528
529 fclose(f);
530}
531
539void VuoFileUtilities::writeStringToFile(string s, string file)
540{
541 writeRawDataToFile(s.data(), s.length(), file);
542}
543
553{
554 string dir, file, ext;
555 splitPath(path, dir, file, ext);
556 string tmpPath = makeTmpFile("." + file, ext, dir);
557
558 writeStringToFile(s, tmpPath);
559
560 moveFile(tmpPath, path);
561}
562
569{
572
573 // `access()` automatically dereferences symlinks.
574 return access(path.c_str(), 0) == 0;
575}
576
583{
586
587 // `stat()` automatically dereferences symlinks.
588 struct stat st_buf;
589 int status = stat(path.c_str(), &st_buf);
590 return (! status && S_ISDIR(st_buf.st_mode));
591}
592
597{
598 struct stat st_buf;
599 int status = lstat(path.c_str(), &st_buf);
600 return (! status && S_ISLNK(st_buf.st_mode));
601}
602
609{
612
613 // `access()` automatically dereferences symlinks.
614 return access(path.c_str(), R_OK) == 0;
615}
616
623{
626
627 // `fopen()` automatically dereferences symlinks.
628 FILE *f = fopen(path.c_str(), "rb");
629 if (!f)
630 return false;
631
632 fseek(f, 0, SEEK_END);
633 long pos = ftell(f);
634 if (pos <= 0)
635 {
636 fclose(f);
637 return false;
638 }
639
640 // `fopen` and `fseek` both succeed on directories, so also attempt to actually read the data.
641 fseek(f, 0, SEEK_SET);
642 char z;
643 size_t ret = fread(&z, 1, 1, f);
644 fclose(f);
645 return ret > 0;
646}
647
654{
655 FILE *f = fopen(path.c_str(), "a");
656 if (! f)
657 throw VuoException(string("Couldn't open file for writing: ") + strerror(errno) + " — " + path);
658
659 fclose(f);
660}
661
666{
667 int ret = remove(path.c_str());
668 if (ret != 0 && fileExists(path))
669 VUserLog("Couldn't delete file: %s — %s", strerror(errno), path.c_str());
670}
671
676{
677 if (! dirExists(path))
678 return;
679
680 DIR *d = opendir(path.c_str());
681 if (! d)
682 {
683 VUserLog("Couldn't read directory: %s — %s", strerror(errno), path.c_str());
684 return;
685 }
686
687 struct dirent *de;
688 while( (de=readdir(d)) )
689 {
690 string fileName = de->d_name;
691 string filePath = path + "/" + fileName;
692
693 if (fileName == "." || fileName == "..")
694 continue;
695
696 if (dirExists(filePath))
697 deleteDir(filePath);
698 else
699 deleteFile(filePath);
700 }
701
702 closedir(d);
703
704 deleteFile(path);
705}
706
712void VuoFileUtilities::moveFile(string fromPath, string toPath)
713{
714 int ret = rename(fromPath.c_str(), toPath.c_str());
715 if (ret != 0)
716 throw VuoException(string("Couldn't move file: ") + strerror(errno) + " — " + toPath);
717}
718
728
740void VuoFileUtilities::copyFile(string fromPath, string toPath, bool preserveMetadata)
741{
742 int i = open(fromPath.c_str(), O_RDONLY);
743 if (i == -1)
744 throw VuoException(string("Couldn't open copy source: ") + strerror(errno) + " — " + fromPath);
745
746 struct stat s;
747 fstat(i, &s);
748 int o = open(toPath.c_str(), O_WRONLY | O_CREAT, s.st_mode & 0777);
749 if (o == -1)
750 {
751 close(i);
752 throw VuoException(string("Couldn't open copy destination: ") + strerror(errno) + " — " + toPath);
753 }
754
755 int ret = fcopyfile(i, o, NULL, COPYFILE_DATA | (preserveMetadata ? COPYFILE_STAT : 0));
756 char *e = strerror(errno);
757 close(o);
758 close(i);
759
760 if (ret)
761 throw VuoException(string("Couldn't copy ") + fromPath + " to " + toPath + ": " + e);
762
763 VDebugLog("%s -> %s", fromPath.c_str(), toPath.c_str());
764}
765
775void VuoFileUtilities::copyDirectory(string fromPath, string toPath)
776{
777 if (isSymlink(fromPath))
778 {
779 // Preserve the relative symlinks found in framework bundles, instead of flattening them.
780 char linkDestination[PATH_MAX + 1];
781 ssize_t len = readlink(fromPath.c_str(), linkDestination, PATH_MAX);
782 linkDestination[len] = 0; // "readlink does not append a NUL character to buf."
783 if (symlink(linkDestination, toPath.c_str()) == -1)
784 throw VuoException(string("Couldn't copy symlink \"") + fromPath + "\" to \"" + toPath + "\": " + strerror(errno));
785
786 VDebugLog("%s -> %s (symlink)", fromPath.c_str(), toPath.c_str());
787 }
788
789 else if (!dirExists(fromPath))
790 copyFile(fromPath, toPath);
791
792 else
793 {
794 VDebugLog("%s ::", fromPath.c_str());
795 auto files = findAllFilesInDirectory(fromPath);
796 makeDir(toPath);
797 for (auto file : files)
798 {
799 string sourceFile = fromPath + "/" + file->getRelativePath();
800 string targetFile = toPath + "/" + file->getRelativePath();
801 copyDirectory(sourceFile, targetFile);
802 }
803 }
804}
805
812{
813 int fd = open(path.c_str(), O_RDONLY);
814 if (fd < 0)
815 throw VuoException("Error: Couldn't open \"" + path + "\": " + strerror(errno));
816 VuoDefer(^{ close(fd); });
817
818 struct stat stat;
819 if (fstat(fd, &stat) != 0)
820 throw VuoException("Error: Couldn't fstat \"" + path + "\": " + strerror(errno));
821
822 // Instead of reading the file into a string and calling `VuoStringUtilities::calculateSHA256`,
823 // use `mmap` so the OS can efficiently read parts of the file at a time (reducing memory required).
824 void *data = mmap(nullptr, stat.st_size, PROT_READ, MAP_PRIVATE | MAP_NOCACHE, fd, 0);
825 if (data == MAP_FAILED)
826 throw VuoException("Error: Couldn't mmap \"" + path + "\": " + strerror(errno));
827 VuoDefer(^{ munmap(data, stat.st_size); });
828
829 unsigned char hash[CC_SHA256_DIGEST_LENGTH];
830 if (!CC_SHA256(data, stat.st_size, hash))
831 throw VuoException("Error: CC_SHA256 failed on file \"" + path + "\"");
832
833 ostringstream oss;
834 oss << setfill('0') << hex;
835 for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i)
836 oss << setw(2) << (int)hash[i];
837 return oss.str();
838}
839
846{
847 struct stat s;
848 lstat(path.c_str(), &s);
849 return s.st_mtimespec.tv_sec + ((s.st_mtimespec.tv_nsec/1000)/1000000.0);
850}
851
857{
858 // Access times are tricky. Some Unix/Linux filesystems don't track access times at all.
859 // On APFS, the exact behavior depends on whether APFS_FEATURE_STRICTATIME is set on the volume.
860 // Here we check access time in case it's been updated, but use modification time as a backup.
861
862 struct stat s;
863 lstat(path.c_str(), &s);
864 double lastAccessed = s.st_atimespec.tv_sec + ((s.st_atimespec.tv_nsec/1000)/1000000.0);
865
866 double lastModified = getFileLastModifiedInSeconds(path);
867
868 struct timeval t;
869 gettimeofday(&t, NULL);
870
871 return (t.tv_sec + t.tv_usec/1000000.0) - max(lastAccessed, lastModified);
872}
873
888set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInDirectory(string dirPath, set<string> archiveExtensions,
889 bool shouldSearchRecursively)
890{
891 set<File *> files;
892
894 dirPath = VuoFileUtilitiesCocoa_resolveMacAlias(dirPath);
895
896 // `opendir()` automatically dereferences symlinks.
897 DIR *d = opendir(dirPath.c_str());
898 if (! d)
899 {
900 if (access(dirPath.c_str(), F_OK) != -1)
901 throw VuoException(string("Couldn't read directory: ") + strerror(errno) + " — " + dirPath);
902 return files;
903 }
904
905 struct dirent *de;
906 while( (de=readdir(d)) )
907 {
908 string fileName = de->d_name;
909 string relativeFilePath = dirPath + "/" + fileName;
910
911 if (fileName == "." || fileName == "..")
912 continue;
913
914 bool isArchive = false;
915 for (set<string>::iterator archiveExtension = archiveExtensions.begin(); archiveExtension != archiveExtensions.end(); ++archiveExtension)
916 if (VuoStringUtilities::endsWith(fileName, "." + *archiveExtension))
917 isArchive = true;
918
919 if (isArchive)
920 {
921 set<File *> fs = findAllFilesInArchive(relativeFilePath);
922 if (fs.empty())
923 isArchive = false;
924 else
925 files.insert(fs.begin(), fs.end());
926 }
927
928 if (! isArchive)
929 {
930 bool shouldSearchDir = shouldSearchRecursively
931 && dirExists(relativeFilePath)
932 && !isSymlink(relativeFilePath);
933 if (shouldSearchDir)
934 {
935 set<File *> filesInDir = findAllFilesInDirectory(relativeFilePath, archiveExtensions, true);
936 for (set<File *>::iterator i = filesInDir.begin(); i != filesInDir.end(); ++i)
937 {
938 File *f = *i;
939 f->dirPath = dirPath;
940 f->filePath = fileName + "/" + f->filePath;
941 }
942 files.insert(filesInDir.begin(), filesInDir.end());
943 }
944 else
945 {
946 File *f = new File(dirPath, fileName);
947 files.insert(f);
948 }
949 }
950 }
951
952 closedir(d);
953
954 return files;
955}
956
966set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInDirectory(string dirPath, set<string> extensions, set<string> archiveExtensions)
967{
968 set<File *> allFiles = findAllFilesInDirectory(dirPath, archiveExtensions);
969 set<File *> matchingFiles;
970
971 for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
972 {
973 bool endsWithExtension = false;
974 for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
975 if (VuoStringUtilities::endsWith((*i)->getRelativePath(), "." + *extension))
976 endsWithExtension = true;
977
978 if (endsWithExtension && ((*i)->isInArchive() || !VuoFileUtilities::dirExists((*i)->path())))
979 matchingFiles.insert(*i);
980 else
981 delete *i;
982 }
983
984 return matchingFiles;
985}
986
993set<VuoFileUtilities::File *> VuoFileUtilities::findAllFilesInArchive(string archivePath)
994{
995 set<File *> files;
996
997 Archive *archive = new Archive(archivePath);
998 if (! archive->zipArchive)
999 {
1000 delete archive;
1001 return files;
1002 }
1003
1004 mz_uint numFiles = mz_zip_reader_get_num_files(archive->zipArchive);
1005 for (mz_uint i = 0; i < numFiles; ++i)
1006 {
1007 mz_zip_archive_file_stat file_stat;
1008 bool success = mz_zip_reader_file_stat(archive->zipArchive, i, &file_stat);
1009 if (! success)
1010 {
1011 VUserLog("Error: Couldn't read file '%u' in zip archive '%s'.", i, archive->path.c_str());
1012 break;
1013 }
1014
1015 File *f = new File(archive, file_stat.m_filename);
1016 files.insert(f);
1017 }
1018
1019 if (archive->referenceCount == 0)
1020 delete archive;
1021
1022 return files;
1023}
1024
1034set<VuoFileUtilities::File *> VuoFileUtilities::findFilesInArchive(string archivePath, string dirPath, set<string> extensions)
1035{
1036 set<File *> allFiles = findAllFilesInArchive(archivePath);
1037 set<File *> matchingFiles;
1038
1039 for (set<File *>::iterator i = allFiles.begin(); i != allFiles.end(); ++i)
1040 {
1041 File *f = *i;
1042
1043 string dir, file, ext;
1044 splitPath(f->getRelativePath(), dir, file, ext);
1045 bool beginsWithDir = VuoStringUtilities::beginsWith(dir, dirPath);
1046
1047 bool endsWithExtension = false;
1048 for (set<string>::iterator extension = extensions.begin(); extension != extensions.end(); ++extension)
1049 if (VuoStringUtilities::endsWith(f->getRelativePath(), "." + *extension))
1050 endsWithExtension = true;
1051
1052 if (beginsWithDir && endsWithExtension)
1053 matchingFiles.insert(f);
1054 else
1055 delete f;
1056 }
1057
1058 return matchingFiles;
1059}
1060
1070string VuoFileUtilities::getArchiveFileContentsAsString(string archivePath, string filePath)
1071{
1072 string contents;
1073 set<File *> archiveFiles = findAllFilesInArchive(archivePath);
1074 for (set<File *>::iterator i = archiveFiles.begin(); i != archiveFiles.end(); ++i)
1075 {
1076 File *file = *i;
1077 if (filePath == file->getRelativePath())
1078 contents = file->getContentsAsString();
1079 delete file;
1080 }
1081
1082 return contents;
1083}
1084
1096
1100void VuoFileUtilities::adHocCodeSign(string path, vector<string> environment, string entitlementsPath)
1101{
1102 double t0 = VuoLogGetTime();
1103 vector<string> processAndArgs{
1104 getVuoFrameworkPath() + "/Helpers/codesignWrapper.sh",
1105 "--force",
1106 "--sign",
1107 "-", // "-" = ad-hoc
1108 path,
1109 };
1110 if (!entitlementsPath.empty())
1111 {
1112 processAndArgs.push_back("--entitlements");
1113 processAndArgs.push_back(entitlementsPath);
1114 }
1115 executeProcess(processAndArgs, environment);
1116
1117 VDebugLog("\tAd-hoc code-signing took %5.2fs", VuoLogGetTime() - t0);
1118}
1119
1126 path(path)
1127{
1128 this->referenceCount = 0;
1129
1130 // mz_zip_reader_init_file sometimes ends up with a garbage function pointer if the mz_zip_archive isn't initialized to zeroes
1131 this->zipArchive = (mz_zip_archive *)calloc(1, sizeof(mz_zip_archive));
1132
1133 bool success = mz_zip_reader_init_file(zipArchive, path.c_str(), 0);
1134 if (! success)
1135 {
1136 free(zipArchive);
1137 zipArchive = NULL;
1138 }
1139}
1140
1145{
1146 if (zipArchive)
1147 {
1148 mz_zip_reader_end(zipArchive);
1149 free(zipArchive);
1150 }
1151}
1152
1161
1165VuoFileUtilities::File::File(string dirPath, string filePath) :
1166 filePath(filePath),
1167 dirPath(dirPath)
1168{
1169 this->fileDescriptor = -1;
1170 this->archive = NULL;
1171}
1172
1179VuoFileUtilities::File::File(Archive *archive, string filePath) :
1180 filePath(filePath)
1181{
1182 this->fileDescriptor = -1;
1183 this->archive = archive;
1184 ++archive->referenceCount;
1185}
1186
1192{
1193 string newFilePath = basename() + "." + extension;
1194
1195 if (archive)
1196 return File(archive, newFilePath);
1197 else
1198 return File(dirPath, newFilePath);
1199}
1200
1206{
1207 if (archive && --archive->referenceCount == 0)
1208 delete archive;
1209}
1210
1215{
1216 return (archive != NULL);
1217}
1218
1225{
1226 return (archive ? archive->path : "");
1227}
1228
1233{
1234 return filePath;
1235}
1236
1243{
1244 if (archive)
1245 throw VuoException("Can't return a simple absolute path for a file in an archive.");
1246
1247 return dirPath + "/" + filePath;
1248}
1249
1256{
1257 if (archive)
1258 throw VuoException("Can't return a simple absolute path for a file in an archive.");
1259
1260 return dirPath;
1261}
1262
1267{
1268 return filePath.substr(0, filePath.find_last_of('.'));
1269}
1270
1275{
1276 return filePath.substr(filePath.find_last_of('.') + 1);
1277}
1278
1283{
1284 if (archive)
1285 return mz_zip_reader_locate_file(archive->zipArchive, filePath.c_str(), nullptr, MZ_ZIP_FLAG_CASE_SENSITIVE) != -1;
1286 else
1287 return VuoFileUtilities::fileExists((dirPath.empty() ? "" : (dirPath + "/")) + filePath);
1288}
1289
1299{
1300 char * buffer;
1301 numBytes = 0;
1302
1303 if (! archive)
1304 {
1305 // http://www.cplusplus.com/reference/cstdio/fread/
1306
1307 string fullPath = (dirPath.empty() ? "" : (dirPath + "/")) + filePath;
1308
1309 FILE *pFile = fopen ( fullPath.c_str() , "rb" );
1310 if (pFile==NULL)
1311 throw VuoException(string("Couldn't open file: ") + strerror(errno) + " — " + fullPath);
1312
1313 // obtain file size:
1314 fseek (pFile , 0 , SEEK_END);
1315 size_t lSize = ftell (pFile);
1316 rewind (pFile);
1317
1318 // allocate memory to contain the whole file:
1319 buffer = (char*) malloc (sizeof(char)*lSize);
1320
1321 // copy the file into the buffer:
1322 numBytes = fread (buffer,1,lSize,pFile);
1323 fclose(pFile);
1324 if (numBytes != lSize)
1325 {
1326 free(buffer);
1327 throw VuoException(string("Couldn't read file: ") + strerror(errno) + " — " + fullPath);
1328 }
1329 }
1330 else
1331 {
1332 buffer = (char *)mz_zip_reader_extract_file_to_heap(archive->zipArchive, filePath.c_str(), &numBytes, 0);
1333 }
1334
1335 return buffer;
1336}
1337
1344{
1345 size_t numBytes;
1346 char *buffer = getContentsAsRawData(numBytes);
1347 string s(buffer, numBytes);
1348 free(buffer);
1349 return s;
1350}
1351
1362{
1363 if (fileDescriptor < 0)
1364 fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1365 int ret = flock(fileDescriptor, LOCK_SH | (nonBlocking ? LOCK_NB : 0));
1366 return ret == 0;
1367}
1368
1379{
1380 if (fileDescriptor < 0)
1381 fileDescriptor = open((dirPath + "/" + filePath).c_str(), O_RDONLY);
1382 int ret = flock(fileDescriptor, LOCK_EX | (nonBlocking ? LOCK_NB : 0));
1383 return ret == 0;
1384}
1385
1391{
1392 flock(fileDescriptor, LOCK_UN);
1393 close(fileDescriptor);
1394 fileDescriptor = -1;
1395}
1396
1402void VuoFileUtilities::focusProcess(pid_t pid, bool force)
1403{
1405}
1406
1415void VuoFileUtilities::executeProcess(vector<string> processAndArgs, vector<string> environment)
1416{
1417 string binaryDir, binaryFile, binaryExt;
1418 splitPath(processAndArgs[0], binaryDir, binaryFile, binaryExt);
1419
1420 string errorPrefix = "Couldn't execute " + binaryFile + ": ";
1421
1422 // Capture stdout and stderr.
1423 int outputPipe[2];
1424 int ret = pipe(outputPipe);
1425 if (ret)
1426 throw VuoException(errorPrefix + "couldn't open pipe: " + strerror(ret));
1427 int pipeRead = outputPipe[0];
1428 int pipeWrite = outputPipe[1];
1429 VuoDefer(^{ close(pipeRead); });
1430 posix_spawn_file_actions_t fileActions;
1431 posix_spawn_file_actions_init(&fileActions);
1432 posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDOUT_FILENO);
1433 posix_spawn_file_actions_adddup2(&fileActions, pipeWrite, STDERR_FILENO);
1434 posix_spawn_file_actions_addclose(&fileActions, pipeRead);
1435
1436 // Convert args.
1437 // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1438 char **processAndArgsZ = (char **)malloc(sizeof(char *) * (processAndArgs.size() + 1));
1439 int argCount = 0;
1440 string commandLine;
1441 for (auto arg : processAndArgs)
1442 {
1443 processAndArgsZ[argCount++] = strdup(arg.c_str());
1444 if (argCount > 0)
1445 commandLine += ' ';
1446 commandLine += '"' + arg + '"';
1447 }
1448 processAndArgsZ[argCount] = nullptr;
1449
1450
1451 // Convert environment.
1452 // posix_spawn requires a null-terminated array, which `&processAndArgs[0]` doesn't guarantee.
1453 char **environmentZ = (char **)malloc(sizeof(char *) * (environment.size() + 1));
1454 int environmentVarCount = 0;
1455 for (auto environmentVar : environment)
1456 {
1457 environmentZ[environmentVarCount++] = strdup(environmentVar.c_str());
1458 commandLine = environmentVar + " " + commandLine;
1459 }
1460 environmentZ[environmentVarCount] = nullptr;
1461
1462
1463 // Execute.
1464 pid_t pid;
1465 VDebugLog("%s", commandLine.c_str());
1466 ret = posix_spawn(&pid, processAndArgs[0].c_str(), &fileActions, NULL, (char * const *)processAndArgsZ, (char * const *)environmentZ);
1467 close(pipeWrite);
1468 posix_spawn_file_actions_destroy(&fileActions);
1469 for (int i = 0; i < argCount; ++i)
1470 free(processAndArgsZ[i]);
1471
1472 if (ret)
1473 throw VuoException(errorPrefix + strerror(ret));
1474 else
1475 {
1476 int ret;
1477 int status;
1478 while (true)
1479 {
1480 ret = waitpid(pid, &status, 0);
1481 if (ret == -1 && errno == EINTR)
1482 // This process received a signal while waiting for the other process
1483 // (seems to happen when running under lldb); try waiting again.
1484 continue;
1485 else if (ret == -1)
1486 throw VuoException(errorPrefix + "waitpid failed: " + strerror(errno));
1487 else if (status != 0)
1488 {
1489 string output;
1490 char buf[256];
1491 bzero(buf, 256);
1492 while (read(pipeRead, &buf, 255) > 0)
1493 {
1494 output += buf;
1495 bzero(buf, 256);
1496 }
1497
1498 throw VuoException(binaryFile + " failed: " + output);
1499 }
1500 else
1501 // The other process completed without error.
1502 break;
1503 }
1504 }
1505}
1506
1511{
1512 return extension == "vuo";
1513}
1514
1519{
1520 set<string> e;
1521 e.insert("c");
1522 e.insert("cc");
1523 e.insert("C");
1524 e.insert("cpp");
1525 e.insert("cxx");
1526 e.insert("c++");
1527 e.insert("m");
1528 e.insert("mm");
1529 return e;
1530}
1531
1536{
1537 auto extensions = getCFamilySourceExtensions();
1538 return extensions.find(extension) != extensions.end();
1539}
1540
1545{
1546 set<string> e = { "cc", "cpp", "cxx", "c++" };
1547 return e.find(extension) != e.end();
1548}
1549
1554{
1555 return extension == "m";
1556}
1557
1562{
1563 return extension == "mm";
1564}
1565
1570{
1571 set<string> e;
1572 e.insert("fs");
1573 e.insert("vs");
1574 e.insert("vuoshader");
1575 e.insert("vuoobjectfilter");
1576 return e;
1577}
1578
1583{
1584 auto extensions = getIsfSourceExtensions();
1585 return extensions.find(extension) != extensions.end();
1586}