1 /*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2020 - Raw Material Software Limited
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21 */
22
23 namespace juce
24 {
25
CriticalSection()26 CriticalSection::CriticalSection() noexcept
27 {
28 pthread_mutexattr_t atts;
29 pthread_mutexattr_init (&atts);
30 pthread_mutexattr_settype (&atts, PTHREAD_MUTEX_RECURSIVE);
31 #if ! JUCE_ANDROID
32 pthread_mutexattr_setprotocol (&atts, PTHREAD_PRIO_INHERIT);
33 #endif
34 pthread_mutex_init (&lock, &atts);
35 pthread_mutexattr_destroy (&atts);
36 }
37
~CriticalSection()38 CriticalSection::~CriticalSection() noexcept { pthread_mutex_destroy (&lock); }
enter()39 void CriticalSection::enter() const noexcept { pthread_mutex_lock (&lock); }
tryEnter()40 bool CriticalSection::tryEnter() const noexcept { return pthread_mutex_trylock (&lock) == 0; }
exit()41 void CriticalSection::exit() const noexcept { pthread_mutex_unlock (&lock); }
42
43 //==============================================================================
sleep(int millisecs)44 void JUCE_CALLTYPE Thread::sleep (int millisecs)
45 {
46 struct timespec time;
47 time.tv_sec = millisecs / 1000;
48 time.tv_nsec = (millisecs % 1000) * 1000000;
49 nanosleep (&time, nullptr);
50 }
51
terminate()52 void JUCE_CALLTYPE Process::terminate()
53 {
54 #if JUCE_ANDROID
55 _exit (EXIT_FAILURE);
56 #else
57 std::_Exit (EXIT_FAILURE);
58 #endif
59 }
60
61
62 #if JUCE_MAC || JUCE_LINUX || JUCE_BSD
setMaxNumberOfFileHandles(int newMaxNumber)63 bool Process::setMaxNumberOfFileHandles (int newMaxNumber) noexcept
64 {
65 rlimit lim;
66
67 if (getrlimit (RLIMIT_NOFILE, &lim) == 0)
68 {
69 if (newMaxNumber <= 0 && lim.rlim_cur == RLIM_INFINITY && lim.rlim_max == RLIM_INFINITY)
70 return true;
71
72 if (newMaxNumber > 0 && lim.rlim_cur >= (rlim_t) newMaxNumber)
73 return true;
74 }
75
76 lim.rlim_cur = lim.rlim_max = newMaxNumber <= 0 ? RLIM_INFINITY : (rlim_t) newMaxNumber;
77 return setrlimit (RLIMIT_NOFILE, &lim) == 0;
78 }
79
80 struct MaxNumFileHandlesInitialiser
81 {
MaxNumFileHandlesInitialiserMaxNumFileHandlesInitialiser82 MaxNumFileHandlesInitialiser() noexcept
83 {
84 #ifndef JUCE_PREFERRED_MAX_FILE_HANDLES
85 enum { JUCE_PREFERRED_MAX_FILE_HANDLES = 8192 };
86 #endif
87
88 // Try to give our app a decent number of file handles by default
89 if (! Process::setMaxNumberOfFileHandles (0))
90 {
91 for (int num = JUCE_PREFERRED_MAX_FILE_HANDLES; num > 256; num -= 1024)
92 if (Process::setMaxNumberOfFileHandles (num))
93 break;
94 }
95 }
96 };
97
98 static MaxNumFileHandlesInitialiser maxNumFileHandlesInitialiser;
99 #endif
100
101 //==============================================================================
102 JUCE_DECLARE_DEPRECATED_STATIC (const juce_wchar File::separator = '/';)
103 JUCE_DECLARE_DEPRECATED_STATIC (const StringRef File::separatorString ("/");)
104
getSeparatorChar()105 juce_wchar File::getSeparatorChar() { return '/'; }
getSeparatorString()106 StringRef File::getSeparatorString() { return "/"; }
107
108
109 //==============================================================================
getCurrentWorkingDirectory()110 File File::getCurrentWorkingDirectory()
111 {
112 HeapBlock<char> heapBuffer;
113
114 char localBuffer[1024];
115 auto cwd = getcwd (localBuffer, sizeof (localBuffer) - 1);
116 size_t bufferSize = 4096;
117
118 while (cwd == nullptr && errno == ERANGE)
119 {
120 heapBuffer.malloc (bufferSize);
121 cwd = getcwd (heapBuffer, bufferSize - 1);
122 bufferSize += 1024;
123 }
124
125 return File (CharPointer_UTF8 (cwd));
126 }
127
setAsCurrentWorkingDirectory()128 bool File::setAsCurrentWorkingDirectory() const
129 {
130 return chdir (getFullPathName().toUTF8()) == 0;
131 }
132
133 //==============================================================================
134 // The unix siginterrupt function is deprecated - this does the same job.
juce_siginterrupt(int sig,int flag)135 int juce_siginterrupt (int sig, int flag)
136 {
137 #if JUCE_WASM
138 ignoreUnused (sig, flag);
139 return 0;
140 #else
141 #if JUCE_ANDROID
142 using juce_sigactionflags_type = unsigned long;
143 #else
144 using juce_sigactionflags_type = int;
145 #endif
146
147 struct ::sigaction act;
148 (void) ::sigaction (sig, nullptr, &act);
149
150 if (flag != 0)
151 act.sa_flags &= static_cast<juce_sigactionflags_type> (~SA_RESTART);
152 else
153 act.sa_flags |= static_cast<juce_sigactionflags_type> (SA_RESTART);
154
155 return ::sigaction (sig, &act, nullptr);
156 #endif
157 }
158
159 //==============================================================================
160 namespace
161 {
162 #if JUCE_LINUX || (JUCE_IOS && ! __DARWIN_ONLY_64_BIT_INO_T) // (this iOS stuff is to avoid a simulator bug)
163 using juce_statStruct = struct stat64;
164 #define JUCE_STAT stat64
165 #elif JUCE_BSD
166 using juce_statStruct = struct stat;
167 #define JUCE_STAT stat
168 #else
169 using juce_statStruct = struct stat;
170 #define JUCE_STAT stat
171 #endif
172
juce_stat(const String & fileName,juce_statStruct & info)173 bool juce_stat (const String& fileName, juce_statStruct& info)
174 {
175 return fileName.isNotEmpty()
176 && JUCE_STAT (fileName.toUTF8(), &info) == 0;
177 }
178
179 #if ! JUCE_WASM
180 // if this file doesn't exist, find a parent of it that does..
juce_doStatFS(File f,struct statfs & result)181 bool juce_doStatFS (File f, struct statfs& result)
182 {
183 for (int i = 5; --i >= 0;)
184 {
185 if (f.exists())
186 break;
187
188 f = f.getParentDirectory();
189 }
190
191 return statfs (f.getFullPathName().toUTF8(), &result) == 0;
192 }
193
194 #if JUCE_MAC || JUCE_IOS
getCreationTime(const juce_statStruct & s)195 static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_birthtime; }
196 #else
getCreationTime(const juce_statStruct & s)197 static int64 getCreationTime (const juce_statStruct& s) noexcept { return (int64) s.st_ctime; }
198 #endif
199
updateStatInfoForFile(const String & path,bool * isDir,int64 * fileSize,Time * modTime,Time * creationTime,bool * isReadOnly)200 void updateStatInfoForFile (const String& path, bool* isDir, int64* fileSize,
201 Time* modTime, Time* creationTime, bool* isReadOnly)
202 {
203 if (isDir != nullptr || fileSize != nullptr || modTime != nullptr || creationTime != nullptr)
204 {
205 juce_statStruct info;
206 const bool statOk = juce_stat (path, info);
207
208 if (isDir != nullptr) *isDir = statOk && ((info.st_mode & S_IFDIR) != 0);
209 if (fileSize != nullptr) *fileSize = statOk ? (int64) info.st_size : 0;
210 if (modTime != nullptr) *modTime = Time (statOk ? (int64) info.st_mtime * 1000 : 0);
211 if (creationTime != nullptr) *creationTime = Time (statOk ? getCreationTime (info) * 1000 : 0);
212 }
213
214 if (isReadOnly != nullptr)
215 *isReadOnly = access (path.toUTF8(), W_OK) != 0;
216 }
217 #endif
218
getResultForErrno()219 Result getResultForErrno()
220 {
221 return Result::fail (String (strerror (errno)));
222 }
223
getResultForReturnValue(int value)224 Result getResultForReturnValue (int value)
225 {
226 return value == -1 ? getResultForErrno() : Result::ok();
227 }
228
getFD(void * handle)229 int getFD (void* handle) noexcept { return (int) (pointer_sized_int) handle; }
fdToVoidPointer(int fd)230 void* fdToVoidPointer (int fd) noexcept { return (void*) (pointer_sized_int) fd; }
231 }
232
isDirectory()233 bool File::isDirectory() const
234 {
235 juce_statStruct info;
236
237 return fullPath.isNotEmpty()
238 && (juce_stat (fullPath, info) && ((info.st_mode & S_IFDIR) != 0));
239 }
240
exists()241 bool File::exists() const
242 {
243 return fullPath.isNotEmpty()
244 && access (fullPath.toUTF8(), F_OK) == 0;
245 }
246
existsAsFile()247 bool File::existsAsFile() const
248 {
249 return exists() && ! isDirectory();
250 }
251
getSize()252 int64 File::getSize() const
253 {
254 juce_statStruct info;
255 return juce_stat (fullPath, info) ? info.st_size : 0;
256 }
257
getFileIdentifier()258 uint64 File::getFileIdentifier() const
259 {
260 juce_statStruct info;
261 return juce_stat (fullPath, info) ? (uint64) info.st_ino : 0;
262 }
263
hasEffectiveRootFilePermissions()264 static bool hasEffectiveRootFilePermissions()
265 {
266 #if JUCE_LINUX || JUCE_BSD
267 return geteuid() == 0;
268 #else
269 return false;
270 #endif
271 }
272
273 //==============================================================================
hasWriteAccess()274 bool File::hasWriteAccess() const
275 {
276 if (exists())
277 return (hasEffectiveRootFilePermissions()
278 || access (fullPath.toUTF8(), W_OK) == 0);
279
280 if ((! isDirectory()) && fullPath.containsChar (getSeparatorChar()))
281 return getParentDirectory().hasWriteAccess();
282
283 return false;
284 }
285
setFileModeFlags(const String & fullPath,mode_t flags,bool shouldSet)286 static bool setFileModeFlags (const String& fullPath, mode_t flags, bool shouldSet) noexcept
287 {
288 juce_statStruct info;
289
290 if (! juce_stat (fullPath, info))
291 return false;
292
293 info.st_mode &= 0777;
294
295 if (shouldSet)
296 info.st_mode |= flags;
297 else
298 info.st_mode &= ~flags;
299
300 return chmod (fullPath.toUTF8(), (mode_t) info.st_mode) == 0;
301 }
302
setFileReadOnlyInternal(bool shouldBeReadOnly)303 bool File::setFileReadOnlyInternal (bool shouldBeReadOnly) const
304 {
305 // Hmm.. should we give global write permission or just the current user?
306 return setFileModeFlags (fullPath, S_IWUSR | S_IWGRP | S_IWOTH, ! shouldBeReadOnly);
307 }
308
setFileExecutableInternal(bool shouldBeExecutable)309 bool File::setFileExecutableInternal (bool shouldBeExecutable) const
310 {
311 return setFileModeFlags (fullPath, S_IXUSR | S_IXGRP | S_IXOTH, shouldBeExecutable);
312 }
313
getFileTimesInternal(int64 & modificationTime,int64 & accessTime,int64 & creationTime)314 void File::getFileTimesInternal (int64& modificationTime, int64& accessTime, int64& creationTime) const
315 {
316 modificationTime = 0;
317 accessTime = 0;
318 creationTime = 0;
319
320 juce_statStruct info;
321
322 if (juce_stat (fullPath, info))
323 {
324 #if JUCE_MAC || (JUCE_IOS && __DARWIN_ONLY_64_BIT_INO_T)
325 modificationTime = (int64) info.st_mtimespec.tv_sec * 1000 + info.st_mtimespec.tv_nsec / 1000000;
326 accessTime = (int64) info.st_atimespec.tv_sec * 1000 + info.st_atimespec.tv_nsec / 1000000;
327 creationTime = (int64) info.st_birthtimespec.tv_sec * 1000 + info.st_birthtimespec.tv_nsec / 1000000;
328 #else
329 modificationTime = (int64) info.st_mtime * 1000;
330 accessTime = (int64) info.st_atime * 1000;
331 #if JUCE_IOS
332 creationTime = (int64) info.st_birthtime * 1000;
333 #else
334 creationTime = (int64) info.st_ctime * 1000;
335 #endif
336 #endif
337 }
338 }
339
setFileTimesInternal(int64 modificationTime,int64 accessTime,int64)340 bool File::setFileTimesInternal (int64 modificationTime, int64 accessTime, int64 /*creationTime*/) const
341 {
342 #if ! JUCE_WASM
343 juce_statStruct info;
344
345 if ((modificationTime != 0 || accessTime != 0) && juce_stat (fullPath, info))
346 {
347 #if JUCE_MAC || (JUCE_IOS && __DARWIN_ONLY_64_BIT_INO_T)
348 struct timeval times[2];
349
350 bool setModificationTime = (modificationTime != 0);
351 bool setAccessTime = (accessTime != 0);
352
353 times[0].tv_sec = setAccessTime ? static_cast<__darwin_time_t> (accessTime / 1000)
354 : info.st_atimespec.tv_sec;
355
356 times[0].tv_usec = setAccessTime ? static_cast<__darwin_suseconds_t> ((accessTime % 1000) * 1000)
357 : static_cast<__darwin_suseconds_t> (info.st_atimespec.tv_nsec / 1000);
358
359 times[1].tv_sec = setModificationTime ? static_cast<__darwin_time_t> (modificationTime / 1000)
360 : info.st_mtimespec.tv_sec;
361
362 times[1].tv_usec = setModificationTime ? static_cast<__darwin_suseconds_t> ((modificationTime % 1000) * 1000)
363 : static_cast<__darwin_suseconds_t> (info.st_mtimespec.tv_nsec / 1000);
364
365 return utimes (fullPath.toUTF8(), times) == 0;
366 #else
367 struct utimbuf times;
368 times.actime = accessTime != 0 ? static_cast<time_t> (accessTime / 1000) : static_cast<time_t> (info.st_atime);
369 times.modtime = modificationTime != 0 ? static_cast<time_t> (modificationTime / 1000) : static_cast<time_t> (info.st_mtime);
370
371 return utime (fullPath.toUTF8(), ×) == 0;
372 #endif
373 }
374 #endif
375
376 return false;
377 }
378
deleteFile()379 bool File::deleteFile() const
380 {
381 if (! isSymbolicLink())
382 {
383 if (! exists())
384 return true;
385
386 if (isDirectory())
387 return rmdir (fullPath.toUTF8()) == 0;
388 }
389
390 return remove (fullPath.toUTF8()) == 0;
391 }
392
moveInternal(const File & dest)393 bool File::moveInternal (const File& dest) const
394 {
395 if (rename (fullPath.toUTF8(), dest.getFullPathName().toUTF8()) == 0)
396 return true;
397
398 if (hasWriteAccess() && copyInternal (dest))
399 {
400 if (deleteFile())
401 return true;
402
403 dest.deleteFile();
404 }
405
406 return false;
407 }
408
replaceInternal(const File & dest)409 bool File::replaceInternal (const File& dest) const
410 {
411 return moveInternal (dest);
412 }
413
createDirectoryInternal(const String & fileName)414 Result File::createDirectoryInternal (const String& fileName) const
415 {
416 return getResultForReturnValue (mkdir (fileName.toUTF8(), 0777));
417 }
418
419 //==============================================================================
juce_fileSetPosition(void * handle,int64 pos)420 int64 juce_fileSetPosition (void* handle, int64 pos)
421 {
422 if (handle != nullptr && lseek (getFD (handle), (off_t) pos, SEEK_SET) == pos)
423 return pos;
424
425 return -1;
426 }
427
openHandle()428 void FileInputStream::openHandle()
429 {
430 auto f = open (file.getFullPathName().toUTF8(), O_RDONLY);
431
432 if (f != -1)
433 fileHandle = fdToVoidPointer (f);
434 else
435 status = getResultForErrno();
436 }
437
~FileInputStream()438 FileInputStream::~FileInputStream()
439 {
440 if (fileHandle != nullptr)
441 close (getFD (fileHandle));
442 }
443
readInternal(void * buffer,size_t numBytes)444 size_t FileInputStream::readInternal (void* buffer, size_t numBytes)
445 {
446 ssize_t result = 0;
447
448 if (fileHandle != nullptr)
449 {
450 result = ::read (getFD (fileHandle), buffer, numBytes);
451
452 if (result < 0)
453 {
454 status = getResultForErrno();
455 result = 0;
456 }
457 }
458
459 return (size_t) result;
460 }
461
462 //==============================================================================
openHandle()463 void FileOutputStream::openHandle()
464 {
465 if (file.exists())
466 {
467 auto f = open (file.getFullPathName().toUTF8(), O_RDWR);
468
469 if (f != -1)
470 {
471 currentPosition = lseek (f, 0, SEEK_END);
472
473 if (currentPosition >= 0)
474 {
475 fileHandle = fdToVoidPointer (f);
476 }
477 else
478 {
479 status = getResultForErrno();
480 close (f);
481 }
482 }
483 else
484 {
485 status = getResultForErrno();
486 }
487 }
488 else
489 {
490 auto f = open (file.getFullPathName().toUTF8(), O_RDWR | O_CREAT, 00644);
491
492 if (f != -1)
493 fileHandle = fdToVoidPointer (f);
494 else
495 status = getResultForErrno();
496 }
497 }
498
closeHandle()499 void FileOutputStream::closeHandle()
500 {
501 if (fileHandle != nullptr)
502 {
503 close (getFD (fileHandle));
504 fileHandle = nullptr;
505 }
506 }
507
writeInternal(const void * data,size_t numBytes)508 ssize_t FileOutputStream::writeInternal (const void* data, size_t numBytes)
509 {
510 if (fileHandle == nullptr)
511 return 0;
512
513 auto result = ::write (getFD (fileHandle), data, numBytes);
514
515 if (result == -1)
516 status = getResultForErrno();
517
518 return (ssize_t) result;
519 }
520
521 #ifndef JUCE_ANDROID
flushInternal()522 void FileOutputStream::flushInternal()
523 {
524 if (fileHandle != nullptr && fsync (getFD (fileHandle)) == -1)
525 status = getResultForErrno();
526 }
527 #endif
528
truncate()529 Result FileOutputStream::truncate()
530 {
531 if (fileHandle == nullptr)
532 return status;
533
534 flush();
535 return getResultForReturnValue (ftruncate (getFD (fileHandle), (off_t) currentPosition));
536 }
537
538 //==============================================================================
getEnvironmentVariable(const String & name,const String & defaultValue)539 String SystemStats::getEnvironmentVariable (const String& name, const String& defaultValue)
540 {
541 if (auto s = ::getenv (name.toUTF8()))
542 return String::fromUTF8 (s);
543
544 return defaultValue;
545 }
546
547 //==============================================================================
548 #if ! JUCE_WASM
openInternal(const File & file,AccessMode mode,bool exclusive)549 void MemoryMappedFile::openInternal (const File& file, AccessMode mode, bool exclusive)
550 {
551 jassert (mode == readOnly || mode == readWrite);
552
553 if (range.getStart() > 0)
554 {
555 auto pageSize = sysconf (_SC_PAGE_SIZE);
556 range.setStart (range.getStart() - (range.getStart() % pageSize));
557 }
558
559 auto filename = file.getFullPathName().toUTF8();
560
561 if (mode == readWrite)
562 fileHandle = open (filename, O_CREAT | O_RDWR, 00644);
563 else
564 fileHandle = open (filename, O_RDONLY);
565
566 if (fileHandle != -1)
567 {
568 auto m = mmap (nullptr, (size_t) range.getLength(),
569 mode == readWrite ? (PROT_READ | PROT_WRITE) : PROT_READ,
570 exclusive ? MAP_PRIVATE : MAP_SHARED, fileHandle,
571 (off_t) range.getStart());
572
573 if (m != MAP_FAILED)
574 {
575 address = m;
576 madvise (m, (size_t) range.getLength(), MADV_SEQUENTIAL);
577 }
578 else
579 {
580 range = Range<int64>();
581 }
582 }
583 }
584
~MemoryMappedFile()585 MemoryMappedFile::~MemoryMappedFile()
586 {
587 if (address != nullptr)
588 munmap (address, (size_t) range.getLength());
589
590 if (fileHandle != 0)
591 close (fileHandle);
592 }
593
594 //==============================================================================
595 File juce_getExecutableFile();
juce_getExecutableFile()596 File juce_getExecutableFile()
597 {
598 struct DLAddrReader
599 {
600 static String getFilename()
601 {
602 Dl_info exeInfo;
603
604 auto localSymbol = (void*) juce_getExecutableFile;
605 dladdr (localSymbol, &exeInfo);
606 return CharPointer_UTF8 (exeInfo.dli_fname);
607 }
608 };
609
610 static String filename = DLAddrReader::getFilename();
611 return File::getCurrentWorkingDirectory().getChildFile (filename);
612 }
613
614 //==============================================================================
getBytesFreeOnVolume()615 int64 File::getBytesFreeOnVolume() const
616 {
617 struct statfs buf;
618
619 if (juce_doStatFS (*this, buf))
620 return (int64) buf.f_bsize * (int64) buf.f_bavail; // Note: this returns space available to non-super user
621
622 return 0;
623 }
624
getVolumeTotalSize()625 int64 File::getVolumeTotalSize() const
626 {
627 struct statfs buf;
628
629 if (juce_doStatFS (*this, buf))
630 return (int64) buf.f_bsize * (int64) buf.f_blocks;
631
632 return 0;
633 }
634
getVolumeLabel()635 String File::getVolumeLabel() const
636 {
637 #if JUCE_MAC
638 struct VolAttrBuf
639 {
640 u_int32_t length;
641 attrreference_t mountPointRef;
642 char mountPointSpace[MAXPATHLEN];
643 } attrBuf;
644
645 struct attrlist attrList;
646 zerostruct (attrList); // (can't use "= {}" on this object because it's a C struct)
647 attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
648 attrList.volattr = ATTR_VOL_INFO | ATTR_VOL_NAME;
649
650 File f (*this);
651
652 for (;;)
653 {
654 if (getattrlist (f.getFullPathName().toUTF8(), &attrList, &attrBuf, sizeof (attrBuf), 0) == 0)
655 return String::fromUTF8 (((const char*) &attrBuf.mountPointRef) + attrBuf.mountPointRef.attr_dataoffset,
656 (int) attrBuf.mountPointRef.attr_length);
657
658 auto parent = f.getParentDirectory();
659
660 if (f == parent)
661 break;
662
663 f = parent;
664 }
665 #endif
666
667 return {};
668 }
669
getVolumeSerialNumber()670 int File::getVolumeSerialNumber() const
671 {
672 return 0;
673 }
674
675 #endif
676
677 //==============================================================================
678 #if ! JUCE_IOS
679 void juce_runSystemCommand (const String&);
juce_runSystemCommand(const String & command)680 void juce_runSystemCommand (const String& command)
681 {
682 int result = system (command.toUTF8());
683 ignoreUnused (result);
684 }
685
686 String juce_getOutputFromCommand (const String&);
juce_getOutputFromCommand(const String & command)687 String juce_getOutputFromCommand (const String& command)
688 {
689 // slight bodge here, as we just pipe the output into a temp file and read it...
690 auto tempFile = File::getSpecialLocation (File::tempDirectory)
691 .getNonexistentChildFile (String::toHexString (Random::getSystemRandom().nextInt()), ".tmp", false);
692
693 juce_runSystemCommand (command + " > " + tempFile.getFullPathName());
694
695 auto result = tempFile.loadFileAsString();
696 tempFile.deleteFile();
697 return result;
698 }
699 #endif
700
701 //==============================================================================
702 #if JUCE_IOS
703 class InterProcessLock::Pimpl
704 {
705 public:
Pimpl(const String &,int)706 Pimpl (const String&, int) {}
707
708 int handle = 1, refCount = 1; // On iOS just fake success..
709 };
710
711 #else
712
713 class InterProcessLock::Pimpl
714 {
715 public:
Pimpl(const String & lockName,int timeOutMillisecs)716 Pimpl (const String& lockName, int timeOutMillisecs)
717 {
718 #if JUCE_MAC
719 if (! createLockFile (File ("~/Library/Caches/com.juce.locks").getChildFile (lockName), timeOutMillisecs))
720 // Fallback if the user's home folder is on a network drive with no ability to lock..
721 createLockFile (File ("/tmp/com.juce.locks").getChildFile (lockName), timeOutMillisecs);
722
723 #else
724 File tempFolder ("/var/tmp");
725
726 if (! tempFolder.isDirectory())
727 tempFolder = "/tmp";
728
729 createLockFile (tempFolder.getChildFile (lockName), timeOutMillisecs);
730 #endif
731 }
732
~Pimpl()733 ~Pimpl()
734 {
735 closeFile();
736 }
737
createLockFile(const File & file,int timeOutMillisecs)738 bool createLockFile (const File& file, int timeOutMillisecs)
739 {
740 file.create();
741 handle = open (file.getFullPathName().toUTF8(), O_RDWR);
742
743 if (handle != 0)
744 {
745 struct flock fl;
746 zerostruct (fl);
747
748 fl.l_whence = SEEK_SET;
749 fl.l_type = F_WRLCK;
750
751 auto endTime = Time::currentTimeMillis() + timeOutMillisecs;
752
753 for (;;)
754 {
755 auto result = fcntl (handle, F_SETLK, &fl);
756
757 if (result >= 0)
758 return true;
759
760 auto error = errno;
761
762 if (error != EINTR)
763 {
764 if (error == EBADF || error == ENOTSUP)
765 return false;
766
767 if (timeOutMillisecs == 0
768 || (timeOutMillisecs > 0 && Time::currentTimeMillis() >= endTime))
769 break;
770
771 Thread::sleep (10);
772 }
773 }
774 }
775
776 closeFile();
777 return true; // only false if there's a file system error. Failure to lock still returns true.
778 }
779
closeFile()780 void closeFile()
781 {
782 if (handle != 0)
783 {
784 struct flock fl;
785 zerostruct (fl);
786
787 fl.l_whence = SEEK_SET;
788 fl.l_type = F_UNLCK;
789
790 while (! (fcntl (handle, F_SETLKW, &fl) >= 0 || errno != EINTR))
791 {}
792
793 close (handle);
794 handle = 0;
795 }
796 }
797
798 int handle = 0, refCount = 1;
799 };
800 #endif
801
InterProcessLock(const String & nm)802 InterProcessLock::InterProcessLock (const String& nm) : name (nm)
803 {
804 }
805
~InterProcessLock()806 InterProcessLock::~InterProcessLock()
807 {
808 }
809
enter(int timeOutMillisecs)810 bool InterProcessLock::enter (int timeOutMillisecs)
811 {
812 const ScopedLock sl (lock);
813
814 if (pimpl == nullptr)
815 {
816 pimpl.reset (new Pimpl (name, timeOutMillisecs));
817
818 if (pimpl->handle == 0)
819 pimpl.reset();
820 }
821 else
822 {
823 pimpl->refCount++;
824 }
825
826 return pimpl != nullptr;
827 }
828
exit()829 void InterProcessLock::exit()
830 {
831 const ScopedLock sl (lock);
832
833 // Trying to release the lock too many times!
834 jassert (pimpl != nullptr);
835
836 if (pimpl != nullptr && --(pimpl->refCount) == 0)
837 pimpl.reset();
838 }
839
840 //==============================================================================
841 void JUCE_API juce_threadEntryPoint (void*);
842
843 #if JUCE_ANDROID
844 extern JavaVM* androidJNIJavaVM;
845 #endif
846
threadEntryProc(void * userData)847 static void* threadEntryProc (void* userData)
848 {
849 auto* myself = static_cast<Thread*> (userData);
850
851 JUCE_AUTORELEASEPOOL
852 {
853 juce_threadEntryPoint (myself);
854 }
855
856 #if JUCE_ANDROID
857 if (androidJNIJavaVM != nullptr)
858 {
859 void* env = nullptr;
860 androidJNIJavaVM->GetEnv(&env, JNI_VERSION_1_2);
861
862 // only detach if we have actually been attached
863 if (env != nullptr)
864 androidJNIJavaVM->DetachCurrentThread();
865 }
866 #endif
867
868 return nullptr;
869 }
870
871 #if JUCE_ANDROID && JUCE_MODULE_AVAILABLE_juce_audio_devices && \
872 ((JUCE_USE_ANDROID_OPENSLES || (! defined(JUCE_USE_ANDROID_OPENSLES) && JUCE_ANDROID_API_VERSION > 8)) \
873 || (JUCE_USE_ANDROID_OBOE || (! defined(JUCE_USE_ANDROID_OBOE) && JUCE_ANDROID_API_VERSION > 15)))
874
875 #define JUCE_ANDROID_REALTIME_THREAD_AVAILABLE 1
876 #endif
877
878 #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
879 extern pthread_t juce_createRealtimeAudioThread (void* (*entry) (void*), void* userPtr);
880 #endif
881
launchThread()882 void Thread::launchThread()
883 {
884 #if JUCE_ANDROID
885 if (isAndroidRealtimeThread)
886 {
887 #if JUCE_ANDROID_REALTIME_THREAD_AVAILABLE
888 threadHandle = (void*) juce_createRealtimeAudioThread (threadEntryProc, this);
889 threadId = (ThreadID) threadHandle.get();
890
891 return;
892 #else
893 jassertfalse;
894 #endif
895 }
896 #endif
897
898 threadHandle = {};
899 pthread_t handle = {};
900 pthread_attr_t attr;
901 pthread_attr_t* attrPtr = nullptr;
902
903 if (pthread_attr_init (&attr) == 0)
904 {
905 attrPtr = &attr;
906 pthread_attr_setstacksize (attrPtr, threadStackSize);
907 }
908
909
910 if (pthread_create (&handle, attrPtr, threadEntryProc, this) == 0)
911 {
912 pthread_detach (handle);
913 threadHandle = (void*) handle;
914 threadId = (ThreadID) threadHandle.get();
915 }
916
917 if (attrPtr != nullptr)
918 pthread_attr_destroy (attrPtr);
919 }
920
closeThreadHandle()921 void Thread::closeThreadHandle()
922 {
923 threadId = {};
924 threadHandle = {};
925 }
926
killThread()927 void Thread::killThread()
928 {
929 if (threadHandle.get() != nullptr)
930 {
931 #if JUCE_ANDROID
932 jassertfalse; // pthread_cancel not available!
933 #else
934 pthread_cancel ((pthread_t) threadHandle.get());
935 #endif
936 }
937 }
938
setCurrentThreadName(const String & name)939 void JUCE_CALLTYPE Thread::setCurrentThreadName (const String& name)
940 {
941 #if JUCE_IOS || JUCE_MAC
942 JUCE_AUTORELEASEPOOL
943 {
944 [[NSThread currentThread] setName: juceStringToNS (name)];
945 }
946 #elif JUCE_LINUX || JUCE_BSD || JUCE_ANDROID
947 #if ((JUCE_LINUX && (__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2012) \
948 || JUCE_ANDROID && __ANDROID_API__ >= 9) || JUCE_BSD
949 pthread_setname_np (pthread_self(), name.toRawUTF8());
950 #else
951 prctl (PR_SET_NAME, name.toRawUTF8(), 0, 0, 0);
952 #endif
953 #endif
954 }
955
setThreadPriority(void * handle,int priority)956 bool Thread::setThreadPriority (void* handle, int priority)
957 {
958 struct sched_param param;
959 int policy;
960 priority = jlimit (0, 10, priority);
961
962 if (handle == nullptr)
963 handle = (void*) pthread_self();
964
965 if (pthread_getschedparam ((pthread_t) handle, &policy, ¶m) != 0)
966 return false;
967
968 policy = priority == 0 ? SCHED_OTHER : SCHED_RR;
969
970 const int minPriority = sched_get_priority_min (policy);
971 const int maxPriority = sched_get_priority_max (policy);
972
973 param.sched_priority = ((maxPriority - minPriority) * priority) / 10 + minPriority;
974 return pthread_setschedparam ((pthread_t) handle, policy, ¶m) == 0;
975 }
976
getCurrentThreadId()977 Thread::ThreadID JUCE_CALLTYPE Thread::getCurrentThreadId()
978 {
979 return (ThreadID) pthread_self();
980 }
981
yield()982 void JUCE_CALLTYPE Thread::yield()
983 {
984 sched_yield();
985 }
986
987 //==============================================================================
988 /* Remove this macro if you're having problems compiling the cpu affinity
989 calls (the API for these has changed about quite a bit in various Linux
990 versions, and a lot of distros seem to ship with obsolete versions)
991 */
992 #if defined (CPU_ISSET) && ! defined (SUPPORT_AFFINITIES)
993 #define SUPPORT_AFFINITIES 1
994 #endif
995
setCurrentThreadAffinityMask(uint32 affinityMask)996 void JUCE_CALLTYPE Thread::setCurrentThreadAffinityMask (uint32 affinityMask)
997 {
998 #if SUPPORT_AFFINITIES
999 cpu_set_t affinity;
1000 CPU_ZERO (&affinity);
1001
1002 for (int i = 0; i < 32; ++i)
1003 if ((affinityMask & (uint32) (1 << i)) != 0)
1004 CPU_SET ((size_t) i, &affinity);
1005
1006 #if (! JUCE_ANDROID) && ((! (JUCE_LINUX || JUCE_BSD)) || ((__GLIBC__ * 1000 + __GLIBC_MINOR__) >= 2004))
1007 pthread_setaffinity_np (pthread_self(), sizeof (cpu_set_t), &affinity);
1008 #elif JUCE_ANDROID
1009 sched_setaffinity (gettid(), sizeof (cpu_set_t), &affinity);
1010 #else
1011 // NB: this call isn't really correct because it sets the affinity of the process,
1012 // (getpid) not the thread (not gettid). But it's included here as a fallback for
1013 // people who are using ridiculously old versions of glibc
1014 sched_setaffinity (getpid(), sizeof (cpu_set_t), &affinity);
1015 #endif
1016
1017 sched_yield();
1018
1019 #else
1020 // affinities aren't supported because either the appropriate header files weren't found,
1021 // or the SUPPORT_AFFINITIES macro was turned off
1022 jassertfalse;
1023 ignoreUnused (affinityMask);
1024 #endif
1025 }
1026
1027 //==============================================================================
1028 #if ! JUCE_WASM
open(const String & name)1029 bool DynamicLibrary::open (const String& name)
1030 {
1031 close();
1032 handle = dlopen (name.isEmpty() ? nullptr : name.toUTF8().getAddress(), RTLD_LOCAL | RTLD_NOW);
1033 return handle != nullptr;
1034 }
1035
close()1036 void DynamicLibrary::close()
1037 {
1038 if (handle != nullptr)
1039 {
1040 dlclose (handle);
1041 handle = nullptr;
1042 }
1043 }
1044
getFunction(const String & functionName)1045 void* DynamicLibrary::getFunction (const String& functionName) noexcept
1046 {
1047 return handle != nullptr ? dlsym (handle, functionName.toUTF8()) : nullptr;
1048 }
1049
1050 //==============================================================================
1051 #if JUCE_LINUX || JUCE_BSD || JUCE_ANDROID
readPosixConfigFileValue(const char * file,const char * key)1052 static String readPosixConfigFileValue (const char* file, const char* key)
1053 {
1054 StringArray lines;
1055 File (file).readLines (lines);
1056
1057 for (int i = lines.size(); --i >= 0;) // (NB - it's important that this runs in reverse order)
1058 if (lines[i].upToFirstOccurrenceOf (":", false, false).trim().equalsIgnoreCase (key))
1059 return lines[i].fromFirstOccurrenceOf (":", false, false).trim();
1060
1061 return {};
1062 }
1063 #endif
1064
1065
1066 //==============================================================================
1067 class ChildProcess::ActiveProcess
1068 {
1069 public:
ActiveProcess(const StringArray & arguments,int streamFlags)1070 ActiveProcess (const StringArray& arguments, int streamFlags)
1071 {
1072 auto exe = arguments[0].unquoted();
1073
1074 // Looks like you're trying to launch a non-existent exe or a folder (perhaps on OSX
1075 // you're trying to launch the .app folder rather than the actual binary inside it?)
1076 jassert (File::getCurrentWorkingDirectory().getChildFile (exe).existsAsFile()
1077 || ! exe.containsChar (File::getSeparatorChar()));
1078
1079 int pipeHandles[2] = {};
1080
1081 if (pipe (pipeHandles) == 0)
1082 {
1083 auto result = fork();
1084
1085 if (result < 0)
1086 {
1087 close (pipeHandles[0]);
1088 close (pipeHandles[1]);
1089 }
1090 else if (result == 0)
1091 {
1092 // we're the child process..
1093 close (pipeHandles[0]); // close the read handle
1094
1095 if ((streamFlags & wantStdOut) != 0)
1096 dup2 (pipeHandles[1], STDOUT_FILENO); // turns the pipe into stdout
1097 else
1098 dup2 (open ("/dev/null", O_WRONLY), STDOUT_FILENO);
1099
1100 if ((streamFlags & wantStdErr) != 0)
1101 dup2 (pipeHandles[1], STDERR_FILENO);
1102 else
1103 dup2 (open ("/dev/null", O_WRONLY), STDERR_FILENO);
1104
1105 close (pipeHandles[1]);
1106
1107 Array<char*> argv;
1108
1109 for (auto& arg : arguments)
1110 if (arg.isNotEmpty())
1111 argv.add (const_cast<char*> (arg.toRawUTF8()));
1112
1113 argv.add (nullptr);
1114
1115 execvp (exe.toRawUTF8(), argv.getRawDataPointer());
1116 _exit (-1);
1117 }
1118 else
1119 {
1120 // we're the parent process..
1121 childPID = result;
1122 pipeHandle = pipeHandles[0];
1123 close (pipeHandles[1]); // close the write handle
1124 }
1125 }
1126 }
1127
~ActiveProcess()1128 ~ActiveProcess()
1129 {
1130 if (readHandle != nullptr)
1131 fclose (readHandle);
1132
1133 if (pipeHandle != 0)
1134 close (pipeHandle);
1135 }
1136
isRunning()1137 bool isRunning() noexcept
1138 {
1139 if (childPID == 0)
1140 return false;
1141
1142 int childState = 0;
1143 auto pid = waitpid (childPID, &childState, WNOHANG);
1144
1145 if (pid == 0)
1146 return true;
1147
1148 if (WIFEXITED (childState))
1149 {
1150 exitCode = WEXITSTATUS (childState);
1151 return false;
1152 }
1153
1154 return ! WIFSIGNALED (childState);
1155 }
1156
read(void * dest,int numBytes)1157 int read (void* dest, int numBytes) noexcept
1158 {
1159 jassert (dest != nullptr && numBytes > 0);
1160
1161 #ifdef fdopen
1162 #error // some crazy 3rd party headers (e.g. zlib) define this function as NULL!
1163 #endif
1164
1165 if (readHandle == nullptr && childPID != 0)
1166 readHandle = fdopen (pipeHandle, "r");
1167
1168 if (readHandle != nullptr)
1169 {
1170 for (;;)
1171 {
1172 auto numBytesRead = (int) fread (dest, 1, (size_t) numBytes, readHandle);
1173
1174 if (numBytesRead > 0 || feof (readHandle))
1175 return numBytesRead;
1176
1177 // signal occurred during fread() so try again
1178 if (ferror (readHandle) && errno == EINTR)
1179 continue;
1180
1181 break;
1182 }
1183 }
1184
1185 return 0;
1186 }
1187
killProcess()1188 bool killProcess() const noexcept
1189 {
1190 return ::kill (childPID, SIGKILL) == 0;
1191 }
1192
getExitCode()1193 uint32 getExitCode() noexcept
1194 {
1195 if (exitCode >= 0)
1196 return (uint32) exitCode;
1197
1198 if (childPID != 0)
1199 {
1200 int childState = 0;
1201 auto pid = waitpid (childPID, &childState, WNOHANG);
1202
1203 if (pid >= 0 && WIFEXITED (childState))
1204 {
1205 exitCode = WEXITSTATUS (childState);
1206 return (uint32) exitCode;
1207 }
1208 }
1209
1210 return 0;
1211 }
1212
1213 int childPID = 0;
1214 int pipeHandle = 0;
1215 int exitCode = -1;
1216 FILE* readHandle = {};
1217
1218 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveProcess)
1219 };
1220
start(const String & command,int streamFlags)1221 bool ChildProcess::start (const String& command, int streamFlags)
1222 {
1223 return start (StringArray::fromTokens (command, true), streamFlags);
1224 }
1225
start(const StringArray & args,int streamFlags)1226 bool ChildProcess::start (const StringArray& args, int streamFlags)
1227 {
1228 if (args.size() == 0)
1229 return false;
1230
1231 activeProcess.reset (new ActiveProcess (args, streamFlags));
1232
1233 if (activeProcess->childPID == 0)
1234 activeProcess.reset();
1235
1236 return activeProcess != nullptr;
1237 }
1238
1239 #endif
1240
1241 //==============================================================================
1242 struct HighResolutionTimer::Pimpl
1243 {
PimplPimpl1244 explicit Pimpl (HighResolutionTimer& t)
1245 : owner (t)
1246 {}
1247
~PimplPimpl1248 ~Pimpl()
1249 {
1250 jassert (periodMs == 0);
1251 stop();
1252 }
1253
startPimpl1254 void start (int newPeriod)
1255 {
1256 if (periodMs == newPeriod)
1257 return;
1258
1259 if (thread.get_id() == std::this_thread::get_id())
1260 {
1261 periodMs = newPeriod;
1262 return;
1263 }
1264
1265 stop();
1266
1267 periodMs = newPeriod;
1268
1269 thread = std::thread ([this, newPeriod]
1270 {
1271 setThisThreadToRealtime ((uint64) newPeriod);
1272
1273 auto lastPeriod = periodMs.load();
1274 Clock clock (lastPeriod);
1275
1276 std::unique_lock<std::mutex> unique_lock (timerMutex);
1277
1278 while (periodMs != 0)
1279 {
1280 clock.next();
1281 while (periodMs != 0 && clock.wait (stopCond, unique_lock));
1282
1283 if (periodMs == 0)
1284 break;
1285
1286 owner.hiResTimerCallback();
1287
1288 auto nextPeriod = periodMs.load();
1289
1290 if (lastPeriod != nextPeriod)
1291 {
1292 lastPeriod = nextPeriod;
1293 clock = Clock (lastPeriod);
1294 }
1295 }
1296
1297 periodMs = 0;
1298 });
1299 }
1300
stopPimpl1301 void stop()
1302 {
1303 periodMs = 0;
1304
1305 const auto thread_id = thread.get_id();
1306
1307 if (thread_id == std::thread::id() || thread_id == std::this_thread::get_id())
1308 return;
1309
1310 {
1311 std::unique_lock<std::mutex> unique_lock (timerMutex);
1312 stopCond.notify_one();
1313 }
1314
1315 thread.join();
1316 }
1317
1318 HighResolutionTimer& owner;
1319 std::atomic<int> periodMs { 0 };
1320
1321 private:
1322 std::thread thread;
1323 std::condition_variable stopCond;
1324 std::mutex timerMutex;
1325
1326 class Clock
1327 {
1328 public:
ClockPimpl1329 explicit Clock (std::chrono::steady_clock::rep millis) noexcept
1330 : time (std::chrono::steady_clock::now()),
1331 delta (std::chrono::milliseconds (millis))
1332 {}
1333
waitPimpl1334 bool wait (std::condition_variable& cond, std::unique_lock<std::mutex>& lock) noexcept
1335 {
1336 return cond.wait_until (lock, time) != std::cv_status::timeout;
1337 }
1338
nextPimpl1339 void next() noexcept
1340 {
1341 time += delta;
1342 }
1343
1344 private:
1345 std::chrono::time_point<std::chrono::steady_clock> time;
1346 std::chrono::steady_clock::duration delta;
1347 };
1348
setThisThreadToRealtimePimpl1349 static bool setThisThreadToRealtime (uint64 periodMs)
1350 {
1351 const auto thread = pthread_self();
1352
1353 #if JUCE_MAC || JUCE_IOS
1354 mach_timebase_info_data_t timebase;
1355 mach_timebase_info (&timebase);
1356
1357 const auto ticksPerMs = ((double) timebase.denom * 1000000.0) / (double) timebase.numer;
1358 const auto periodTicks = (uint32_t) (ticksPerMs * periodMs);
1359
1360 thread_time_constraint_policy_data_t policy;
1361 policy.period = periodTicks;
1362 policy.computation = jmin ((uint32_t) 50000, policy.period);
1363 policy.constraint = policy.period;
1364 policy.preemptible = true;
1365
1366 return thread_policy_set (pthread_mach_thread_np (thread),
1367 THREAD_TIME_CONSTRAINT_POLICY,
1368 (thread_policy_t) &policy,
1369 THREAD_TIME_CONSTRAINT_POLICY_COUNT) == KERN_SUCCESS;
1370
1371 #else
1372 ignoreUnused (periodMs);
1373 struct sched_param param;
1374 param.sched_priority = sched_get_priority_max (SCHED_RR);
1375 return pthread_setschedparam (thread, SCHED_RR, ¶m) == 0;
1376 #endif
1377 }
1378
1379 JUCE_DECLARE_NON_COPYABLE (Pimpl)
1380 };
1381
1382 } // namespace juce
1383