1 /* 2 * Copyright (c) 2014, 2017, Oracle and/or its affiliates. 3 * All rights reserved. Use is subject to license terms. 4 * 5 * This file is available and licensed under the following license: 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * - Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * - Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the distribution. 16 * - Neither the name of Oracle Corporation nor the names of its 17 * contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 33 34 #include "Platform.h" 35 36 #ifdef MAC 37 38 #include "MacPlatform.h" 39 #include "Helpers.h" 40 #include "Package.h" 41 #include "PropertyFile.h" 42 #include "IniFile.h" 43 44 #include <sys/sysctl.h> 45 #include <pthread.h> 46 #include <vector> 47 48 #import <Foundation/Foundation.h> 49 #import <AppKit/NSRunningApplication.h> 50 51 #include <CoreFoundation/CoreFoundation.h> 52 #include <CoreFoundation/CFString.h> 53 54 #ifdef __OBJC__ 55 #import <Cocoa/Cocoa.h> 56 #endif //__OBJC__ 57 58 #define MAC_PACKAGER_TMP_DIR "/Library/Application Support/Oracle/Java/Packager/tmp" 59 60 //-------------------------------------------------------------------------------------------------- 61 62 NSString* StringToNSString(TString Value) { 63 NSString* result = [NSString stringWithCString:Value.c_str() 64 encoding:[NSString defaultCStringEncoding]]; 65 return result; 66 } 67 68 //-------------------------------------------------------------------------------------------------- 69 70 MacPlatform::MacPlatform(void) : Platform(), GenericPlatform(), PosixPlatform() { 71 } 72 73 MacPlatform::~MacPlatform(void) { 74 } 75 76 bool MacPlatform::UsePListForConfigFile() { 77 return FilePath::FileExists(GetConfigFileName()) == false; 78 } 79 80 void MacPlatform::ShowMessage(TString Title, TString Description) { 81 NSString *ltitle = StringToNSString(Title); 82 NSString *ldescription = StringToNSString(Description); 83 84 NSLog(@"%@:%@", ltitle, ldescription); 85 } 86 87 void MacPlatform::ShowMessage(TString Description) { 88 TString appname = GetModuleFileName(); 89 appname = FilePath::ExtractFileName(appname); 90 ShowMessage(appname, Description); 91 } 92 93 const char* MacPlatform::getTmpDirString() { 94 return MAC_PACKAGER_TMP_DIR; 95 } 96 97 void MacPlatform::reactivateAnotherInstance() { 98 if (singleInstanceProcessId == 0) { 99 printf("Unable to reactivate another instance, PID is undefined"); 100 return; 101 } 102 NSRunningApplication* app = [NSRunningApplication runningApplicationWithProcessIdentifier: singleInstanceProcessId]; 103 if (app != nil) { 104 [app activateWithOptions: NSApplicationActivateIgnoringOtherApps]; 105 } else { 106 printf("Unable to reactivate another instance PID: %d", singleInstanceProcessId); 107 } 108 } 109 110 TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source, bool &release) { 111 TCHAR* result = NULL; 112 release = false; 113 CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault, Source, kCFStringEncodingUTF8); 114 115 if (StringRef != NULL) { 116 @try { 117 CFIndex length = CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef); 118 result = new char[length + 1]; 119 120 if (CFStringGetFileSystemRepresentation(StringRef, result, length)) { 121 release = true; 122 } 123 else { 124 delete[] result; 125 result = NULL; 126 } 127 } 128 @finally { 129 CFRelease(StringRef); 130 } 131 } 132 133 return result; 134 } 135 136 TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source, bool &release) { 137 TCHAR* result = NULL; 138 release = false; 139 CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(kCFAllocatorDefault, Source); 140 141 if (StringRef != NULL) { 142 @try { 143 CFIndex length = CFStringGetLength(StringRef); 144 145 if (length > 0) { 146 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(length, kCFStringEncodingUTF8); 147 148 result = new char[maxSize + 1]; 149 150 if (CFStringGetCString(StringRef, result, maxSize, kCFStringEncodingUTF8) == true) { 151 release = true; 152 } 153 else { 154 delete[] result; 155 result = NULL; 156 } 157 } 158 } 159 @finally { 160 CFRelease(StringRef); 161 } 162 } 163 164 return result; 165 } 166 167 void MacPlatform::SetCurrentDirectory(TString Value) { 168 chdir(PlatformString(Value).toPlatformString()); 169 } 170 171 TString MacPlatform::GetPackageRootDirectory() { 172 NSBundle *mainBundle = [NSBundle mainBundle]; 173 NSString *mainBundlePath = [mainBundle bundlePath]; 174 NSString *contentsPath = [mainBundlePath stringByAppendingString:@"/Contents"]; 175 TString result = [contentsPath UTF8String]; 176 return result; 177 } 178 179 TString MacPlatform::GetAppDataDirectory() { 180 TString result; 181 NSArray *paths = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES); 182 NSString *applicationSupportDirectory = [paths firstObject]; 183 result = [applicationSupportDirectory UTF8String]; 184 return result; 185 } 186 187 TString MacPlatform::GetBundledJVMLibraryFileName(TString RuntimePath) { 188 TString result; 189 190 result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/jre/lib/jli/libjli.dylib"); 191 192 if (FilePath::FileExists(result) == false) { 193 result = FilePath::IncludeTrailingSeparater(RuntimePath) + _T("Contents/Home/lib/jli/libjli.dylib"); 194 195 if (FilePath::FileExists(result) == false) { 196 result = _T(""); 197 } 198 } 199 200 return result; 201 } 202 203 204 TString MacPlatform::GetSystemJRE() { 205 if (GetAppCDSState() != cdsDisabled) { 206 //TODO throw exception 207 return _T(""); 208 } 209 210 return _T("/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/lib/jli/libjli.dylib"); 211 } 212 213 TString MacPlatform::GetSystemJVMLibraryFileName() { 214 TString result = GetSystemJRE(); 215 216 if (FilePath::FileExists(result) == false) { 217 result = _T(""); 218 } 219 220 return result; 221 } 222 223 TString MacPlatform::GetAppName() { 224 NSString *appName = [[NSProcessInfo processInfo] processName]; 225 TString result = [appName UTF8String]; 226 return result; 227 } 228 229 void AppendPListArrayToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section) { 230 NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()]; 231 NSDictionary *array = [infoDictionary objectForKey:sectionKey]; 232 233 for (id option in array) { 234 if ([option isKindOfClass:[NSString class]]) { 235 TString arg = [option UTF8String]; 236 237 TString name; 238 TString value; 239 240 if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) { 241 result->Append(Section, name, value); 242 } 243 } 244 } 245 } 246 247 void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary, IniFile *result, TString Section, bool FollowSection = true) { 248 NSDictionary *dictionary = NULL; 249 250 if (FollowSection == true) { 251 NSString *sectionKey = [NSString stringWithUTF8String:PlatformString(Section).toMultibyte()]; 252 dictionary = [infoDictionary objectForKey:sectionKey]; 253 } 254 else { 255 dictionary = infoDictionary; 256 } 257 258 for (id key in dictionary) { 259 id option = [dictionary valueForKey:key]; 260 261 if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) { 262 TString name = [key UTF8String]; 263 TString value = [option UTF8String]; 264 result->Append(Section, name, value); 265 } 266 } 267 } 268 269 // Convert parts of the info.plist to the INI format the rest of the packager uses unless 270 // a packager config file exists. 271 ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) { 272 IniFile* result = new IniFile(); 273 274 if (UsePListForConfigFile() == false) { 275 if (result->LoadFromFile(FileName) == false) { 276 // New property file format was not found, attempt to load old property file format. 277 Helpers::LoadOldConfigFile(FileName, result); 278 } 279 } 280 else { 281 NSBundle *mainBundle = [NSBundle mainBundle]; 282 NSDictionary *infoDictionary = [mainBundle infoDictionary]; 283 std::map<TString, TString> keys = GetKeys(); 284 285 // Packager options. 286 AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPLICATION], false); 287 288 // jvmargs 289 AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMOPTIONS]); 290 291 // jvmuserargs 292 AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_JVMUSEROPTIONS]); 293 294 // Generate AppCDS Cache 295 AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSJVMOPTIONS]); 296 AppendPListDictionaryToIniFile(infoDictionary, result, keys[CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS]); 297 298 // args 299 AppendPListArrayToIniFile(infoDictionary, result, keys[CONFIG_SECTION_ARGOPTIONS]); 300 } 301 302 return result; 303 } 304 305 TString GetModuleFileNameOSX() { 306 Dl_info module_info; 307 if (dladdr(reinterpret_cast<void*>(GetModuleFileNameOSX), &module_info) == 0) { 308 // Failed to find the symbol we asked for. 309 return std::string(); 310 } 311 return TString(module_info.dli_fname); 312 } 313 314 #include <mach-o/dyld.h> 315 316 TString MacPlatform::GetModuleFileName() { 317 //return GetModuleFileNameOSX(); 318 319 TString result; 320 DynamicBuffer<TCHAR> buffer(MAX_PATH); 321 uint32_t size = buffer.GetSize(); 322 323 if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) { 324 result = FileSystemStringToString(buffer.GetData()); 325 } 326 327 return result; 328 } 329 330 bool MacPlatform::IsMainThread() { 331 bool result = (pthread_main_np() == 1); 332 return result; 333 } 334 335 TPlatformNumber MacPlatform::GetMemorySize() { 336 unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory]; 337 TPlatformNumber result = memory / 1048576; // Convert from bytes to megabytes. 338 return result; 339 } 340 341 std::map<TString, TString> MacPlatform::GetKeys() { 342 std::map<TString, TString> keys; 343 344 if (UsePListForConfigFile() == false) { 345 return GenericPlatform::GetKeys(); 346 } 347 else { 348 keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION, _T("app.version"))); 349 keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY, _T("JVMMainJarName"))); 350 keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY, _T("JVMMainModuleName"))); 351 keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINCLASSNAME_KEY, _T("JVMMainClassName"))); 352 keys.insert(std::map<TString, TString>::value_type(CONFIG_CLASSPATH_KEY, _T("JVMAppClasspath"))); 353 keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY, _T("CFBundleName"))); 354 keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_ID_KEY, _T("JVMPreferencesID"))); 355 keys.insert(std::map<TString, TString>::value_type(JVM_RUNTIME_KEY, _T("JVMRuntime"))); 356 keys.insert(std::map<TString, TString>::value_type(PACKAGER_APP_DATA_DIR, _T("CFBundleIdentifier"))); 357 358 keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY, _T("app.splash"))); 359 keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY, _T("app.memory"))); 360 keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG, _T("app.debug"))); 361 keys.insert(std::map<TString, TString>::value_type(CONFIG_APPLICATION_INSTANCE, _T("app.application.instance"))); 362 363 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPLICATION, _T("Application"))); 364 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMOPTIONS, _T("JVMOptions"))); 365 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROPTIONS, _T("JVMUserOptions"))); 366 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_JVMUSEROVERRIDESOPTIONS, _T("JVMUserOverrideOptions"))); 367 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSJVMOPTIONS, _T("AppCDSJVMOptions"))); 368 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS, _T("AppCDSGenerateCacheJVMOptions"))); 369 keys.insert(std::map<TString, TString>::value_type(CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions"))); 370 } 371 372 return keys; 373 } 374 375 #ifdef DEBUG 376 bool MacPlatform::IsNativeDebuggerPresent() { 377 int state; 378 int mib[4]; 379 struct kinfo_proc info; 380 size_t size; 381 382 info.kp_proc.p_flag = 0; 383 384 mib[0] = CTL_KERN; 385 mib[1] = KERN_PROC; 386 mib[2] = KERN_PROC_PID; 387 mib[3] = getpid(); 388 389 size = sizeof(info); 390 state = sysctl(mib, sizeof(mib) / sizeof(*mib), &info, &size, NULL, 0); 391 assert(state == 0); 392 return ((info.kp_proc.p_flag & P_TRACED) != 0); 393 } 394 395 int MacPlatform::GetProcessID() { 396 int pid = [[NSProcessInfo processInfo] processIdentifier]; 397 return pid; 398 } 399 #endif //DEBUG 400 401 //-------------------------------------------------------------------------------------------------- 402 403 class UserDefaults { 404 private: 405 OrderedMap<TString, TString> FData; 406 TString FDomainName; 407 408 bool ReadDictionary(NSDictionary *Items, OrderedMap<TString, TString> &Data) { 409 bool result = false; 410 411 for (id key in Items) { 412 id option = [Items valueForKey:key]; 413 414 if ([key isKindOfClass:[NSString class]] && [option isKindOfClass:[NSString class]]) { 415 TString name = [key UTF8String]; 416 TString value = [option UTF8String]; 417 418 if (name.empty() == false) { 419 Data.Append(name, value); 420 } 421 } 422 } 423 424 return result; 425 } 426 427 // Open and read the defaults file specified by domain. 428 bool ReadPreferences(NSDictionary *Defaults, std::list<TString> Keys, OrderedMap<TString, TString> &Data) { 429 bool result = false; 430 431 if (Keys.size() > 0 && Defaults != NULL) { 432 NSDictionary *node = Defaults; 433 434 while (Keys.size() > 0 && node != NULL) { 435 TString key = Keys.front(); 436 Keys.pop_front(); 437 NSString *tempKey = StringToNSString(key); 438 node = [node valueForKey:tempKey]; 439 440 if (Keys.size() == 0) { 441 break; 442 } 443 } 444 445 if (node != NULL) { 446 result = ReadDictionary(node, Data); 447 } 448 } 449 450 return result; 451 } 452 453 NSDictionary* LoadPreferences(TString DomainName) { 454 NSDictionary *result = NULL; 455 456 if (DomainName.empty() == false) { 457 NSUserDefaults *prefs = [[NSUserDefaults alloc] init]; 458 459 if (prefs != NULL) { 460 NSString *lDomainName = StringToNSString(DomainName); 461 result = [prefs persistentDomainForName: lDomainName]; 462 } 463 } 464 465 return result; 466 } 467 468 public: 469 UserDefaults(TString DomainName) { 470 FDomainName = DomainName; 471 } 472 473 bool Read(std::list<TString> Keys) { 474 NSDictionary *defaults = LoadPreferences(FDomainName); 475 return ReadPreferences(defaults, Keys, FData); 476 } 477 478 OrderedMap<TString, TString> GetData() { 479 return FData; 480 } 481 }; 482 483 //-------------------------------------------------------------------------------------------------- 484 485 MacJavaUserPreferences::MacJavaUserPreferences(void) : JavaUserPreferences() { 486 } 487 488 TString toLowerCase(TString Value) { 489 // Use Cocoa's lowercase method because it is better than the ones provided by C/C++. 490 NSString *temp = StringToNSString(Value); 491 temp = [temp lowercaseString]; 492 TString result = [temp UTF8String]; 493 return result; 494 } 495 496 // Split the string Value into using Delimiter. 497 std::list<TString> Split(TString Value, TString Delimiter) { 498 std::list<TString> result; 499 std::vector<char> buffer(Value.c_str(), Value.c_str() + Value.size() + 1); 500 char *p = strtok(&buffer[0], Delimiter.data()); 501 502 while (p != NULL) { 503 TString token = p; 504 result.push_back(token); 505 p = strtok(NULL, Delimiter.data()); 506 } 507 508 return result; 509 } 510 511 // 1. If the path is fewer than three components (Example: one/two/three) then the domain is the 512 // default domain "com.apple.java.util.prefs" stored in the plist file 513 // ~/Library/Preferences/com.apple.java.util.prefs.plist 514 // 515 // For example: If AppID = "hello", the path is "hello/JVMUserOptions and the 516 // plist file is ~/Library/Preferences/com.apple.java.util.prefs.plist containing the contents: 517 // 518 // <?xml version="1.0" encoding="UTF-8"?> 519 // <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 520 // <plist version="1.0"> 521 // <dict> 522 // <key>/</key> 523 // <dict> 524 // <key>hello/</key> 525 // <dict> 526 // <key>JVMUserOptions/</key> 527 // <dict> 528 // <key>-DXmx</key> 529 // <string>512m</string> 530 // </dict> 531 // </dict> 532 // </dict> 533 // </dict> 534 // </plist> 535 // 536 // 2. If the path is three or more, the first three become the domain name (even 537 // if shared across applicaitons) and the remaining become individual keys. 538 // 539 // For example: If AppID = "com/hello/foo", the path is "hello/JVMUserOptions and the 540 // domain is "com.hello.foo" stored in the plist file ~/Library/Preferences/com.hello.foo.plist 541 // containing the contents: 542 // 543 // <?xml version="1.0" encoding="UTF-8"?> 544 // <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 545 // <plist version="1.0"> 546 // <dict> 547 // <key>/com/hello/foo/</key> 548 // <dict> 549 // <key>JVMUserOptions/</key> 550 // <dict> 551 // <key>-DXmx</key> 552 // <string>512m</string> 553 // </dict> 554 // </dict> 555 // </dict> 556 // </plist> 557 // 558 // NOTE: To change these values use the command line utility "defaults": 559 // Example: defaults read com.apple.java.util.prefs / 560 // Since OS 10.9 Mavericks the defaults are cashed so directly modifying the files is not recommended. 561 bool MacJavaUserPreferences::Load(TString Appid) { 562 bool result = false; 563 564 if (Appid.empty() == false) { 565 // This is for backwards compatability. Older packaged applications have an 566 // app.preferences.id that is delimited by period (".") rather than 567 // slash ("/") so convert to newer style. 568 TString path = Helpers::ReplaceString(Appid, _T("."), _T("/")); 569 570 path = path + _T("/JVMUserOptions"); 571 TString domainName; 572 std::list<TString> keys = Split(path, _T("/")); 573 574 // If there are less than three parts to the path then use the default preferences file. 575 if (keys.size() < 3) { 576 domainName = _T("com.apple.java.util.prefs"); 577 578 // Append slash to the end of each key. 579 for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) { 580 TString item = *iterator; 581 item = item + _T("/"); 582 *iterator = item; 583 } 584 585 // The root key is /. 586 keys.push_front(_T("/")); 587 } 588 else { 589 // Remove the first three keys and use them for the root key and the preferencesID. 590 TString one = keys.front(); 591 keys.pop_front(); 592 TString two = keys.front(); 593 keys.pop_front(); 594 TString three = keys.front(); 595 keys.pop_front(); 596 domainName = one + TString(".") + two + TString(".") + three; 597 domainName = toLowerCase(domainName); 598 599 // Append slash to the end of each key. 600 for (std::list<TString>::iterator iterator = keys.begin(); iterator != keys.end(); iterator++) { 601 TString item = *iterator; 602 item = item + _T("/"); 603 *iterator = item; 604 } 605 606 // The root key is /one/two/three/ 607 TString key = TString("/") + one + TString("/") + two + TString("/") + three + TString("/"); 608 keys.push_front(key); 609 } 610 611 UserDefaults userDefaults(domainName); 612 613 if (userDefaults.Read(keys) == true) { 614 result = true; 615 FMap = userDefaults.GetData(); 616 } 617 } 618 619 return result; 620 } 621 622 //-------------------------------------------------------------------------------------------------- 623 624 #endif //MAC