1 /*
   2  * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 #include "Platform.h"
  27 
  28 #include "MacPlatform.h"
  29 #include "Helpers.h"
  30 #include "Package.h"
  31 #include "PropertyFile.h"
  32 #include "IniFile.h"
  33 
  34 #include <sys/sysctl.h>
  35 #include <pthread.h>
  36 #include <vector>
  37 #include <signal.h>
  38 #include <mach-o/dyld.h>
  39 
  40 #import <Foundation/Foundation.h>
  41 #import <AppKit/NSRunningApplication.h>
  42 
  43 #include <CoreFoundation/CoreFoundation.h>
  44 #include <CoreFoundation/CFString.h>
  45 
  46 #ifdef __OBJC__
  47 #import <Cocoa/Cocoa.h>
  48 #endif //__OBJC__
  49 
  50 #define MAC_JPACKAGE_TMP_DIR \
  51         "/Library/Application Support/Java/JPackage/tmp"
  52 
  53 NSString* StringToNSString(TString Value) {
  54     NSString* result = [NSString stringWithCString : Value.c_str()
  55             encoding : [NSString defaultCStringEncoding]];
  56     return result;
  57 }
  58 
  59 FileSystemStringToString::FileSystemStringToString(const TCHAR* value) {
  60     bool release = false;
  61     PlatformString lvalue = PlatformString(value);
  62     Platform& platform = Platform::GetInstance();
  63     TCHAR* buffer = platform.ConvertFileSystemStringToString(lvalue, release);
  64     FData = buffer;
  65 
  66     if (buffer != NULL && release == true) {
  67         delete[] buffer;
  68     }
  69 }
  70 
  71 FileSystemStringToString::operator TString() {
  72     return FData;
  73 }
  74 
  75 StringToFileSystemString::StringToFileSystemString(const TString &value) {
  76     FRelease = false;
  77     PlatformString lvalue = PlatformString(value);
  78     Platform& platform = Platform::GetInstance();
  79     FData = platform.ConvertStringToFileSystemString(lvalue, FRelease);
  80 }
  81 
  82 StringToFileSystemString::~StringToFileSystemString() {
  83     if (FRelease == true) {
  84         delete[] FData;
  85     }
  86 }
  87 
  88 StringToFileSystemString::operator TCHAR* () {
  89     return FData;
  90 }
  91 
  92 MacPlatform::MacPlatform(void) : Platform(), PosixPlatform() {
  93 }
  94 
  95 MacPlatform::~MacPlatform(void) {
  96 }
  97 
  98 TString MacPlatform::GetPackageAppDirectory() {
  99     return FilePath::IncludeTrailingSeparator(
 100             GetPackageRootDirectory()) + _T("Java");
 101 }
 102 
 103 TString MacPlatform::GetPackageLauncherDirectory() {
 104     return FilePath::IncludeTrailingSeparator(
 105             GetPackageRootDirectory()) + _T("MacOS");
 106 }
 107 
 108 TString MacPlatform::GetPackageRuntimeBinDirectory() {
 109     return FilePath::IncludeTrailingSeparator(GetPackageRootDirectory()) +
 110             _T("Plugins/Java.runtime/Contents/Home/bin");
 111 }
 112 
 113 bool MacPlatform::UsePListForConfigFile() {
 114     return FilePath::FileExists(GetConfigFileName()) == false;
 115 }
 116 
 117 void MacPlatform::ShowMessage(TString Title, TString Description) {
 118     NSString *ltitle = StringToNSString(Title);
 119     NSString *ldescription = StringToNSString(Description);
 120 
 121     NSLog(@"%@:%@", ltitle, ldescription);
 122 }
 123 
 124 void MacPlatform::ShowMessage(TString Description) {
 125     TString appname = GetModuleFileName();
 126     appname = FilePath::ExtractFileName(appname);
 127     ShowMessage(appname, Description);
 128 }
 129 
 130 TString MacPlatform::getTmpDirString() {
 131     return TString(MAC_JPACKAGE_TMP_DIR);
 132 }
 133 
 134 TCHAR* MacPlatform::ConvertStringToFileSystemString(TCHAR* Source,
 135         bool &release) {
 136     TCHAR* result = NULL;
 137     release = false;
 138     CFStringRef StringRef = CFStringCreateWithCString(kCFAllocatorDefault,
 139             Source, kCFStringEncodingUTF8);
 140 
 141     if (StringRef != NULL) {
 142         @ try {
 143             CFIndex length =
 144                     CFStringGetMaximumSizeOfFileSystemRepresentation(StringRef);
 145             result = new char[length + 1];
 146             if (result != NULL) {
 147                 if (CFStringGetFileSystemRepresentation(StringRef,
 148                         result, length)) {
 149                     release = true;
 150                 } else {
 151                     delete[] result;
 152                     result = NULL;
 153                 }
 154             }
 155         }
 156         @finally
 157         {
 158             CFRelease(StringRef);
 159         }
 160     }
 161 
 162     return result;
 163 }
 164 
 165 TCHAR* MacPlatform::ConvertFileSystemStringToString(TCHAR* Source,
 166         bool &release) {
 167     TCHAR* result = NULL;
 168     release = false;
 169     CFStringRef StringRef = CFStringCreateWithFileSystemRepresentation(
 170             kCFAllocatorDefault, Source);
 171 
 172     if (StringRef != NULL) {
 173         @ try {
 174             CFIndex length = CFStringGetLength(StringRef);
 175 
 176             if (length > 0) {
 177                 CFIndex maxSize = CFStringGetMaximumSizeForEncoding(
 178                         length, kCFStringEncodingUTF8);
 179 
 180                 result = new char[maxSize + 1];
 181                 if (result != NULL) {
 182                     if (CFStringGetCString(StringRef, result, maxSize,
 183                             kCFStringEncodingUTF8) == true) {
 184                         release = true;
 185                     } else {
 186                         delete[] result;
 187                         result = NULL;
 188                     }
 189                 }
 190             }
 191         }
 192         @finally
 193         {
 194             CFRelease(StringRef);
 195         }
 196     }
 197 
 198     return result;
 199 }
 200 
 201 void MacPlatform::SetCurrentDirectory(TString Value) {
 202     chdir(PlatformString(Value).toPlatformString());
 203 }
 204 
 205 TString MacPlatform::GetPackageRootDirectory() {
 206     NSBundle *mainBundle = [NSBundle mainBundle];
 207     NSString *mainBundlePath = [mainBundle bundlePath];
 208     NSString *contentsPath =
 209             [mainBundlePath stringByAppendingString : @"/Contents"];
 210     TString result = [contentsPath UTF8String];
 211     return result;
 212 }
 213 
 214 TString MacPlatform::GetAppDataDirectory() {
 215     TString result;
 216     NSArray *paths = NSSearchPathForDirectoriesInDomains(
 217             NSApplicationSupportDirectory, NSUserDomainMask, YES);
 218     NSString *applicationSupportDirectory = [paths firstObject];
 219     result = [applicationSupportDirectory UTF8String];
 220     return result;
 221 }
 222 
 223 TString MacPlatform::GetBundledJVMLibraryFileName(TString RuntimePath) {
 224     TString result;
 225 
 226     // first try lib/, then lib/jli
 227     result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 228             _T("Contents/Home/lib/libjli.dylib");
 229 
 230     if (FilePath::FileExists(result) == false) {
 231         result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 232                 _T("Contents/Home/lib/jli/libjli.dylib");
 233 
 234         if (FilePath::FileExists(result) == false) {
 235             // cannot find
 236             NSLog(@"Cannot find libjli.dysym!");
 237             result = _T("");
 238         }
 239     }
 240 
 241     return result;
 242 }
 243 
 244 TString MacPlatform::GetAppName() {
 245     NSString *appName = [[NSProcessInfo processInfo] processName];
 246     TString result = [appName UTF8String];
 247     return result;
 248 }
 249 
 250 void PosixProcess::Cleanup() {
 251     if (FOutputHandle != 0) {
 252         close(FOutputHandle);
 253         FOutputHandle = 0;
 254     }
 255 
 256     if (FInputHandle != 0) {
 257         close(FInputHandle);
 258         FInputHandle = 0;
 259     }
 260 
 261     sigaction(SIGINT, &savintr, (struct sigaction *) 0);
 262     sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
 263     sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *) 0);
 264 }
 265 
 266 #define PIPE_READ 0
 267 #define PIPE_WRITE 1
 268 
 269 bool PosixProcess::Execute(const TString Application,
 270         const std::vector<TString> Arguments, bool AWait) {
 271     bool result = false;
 272 
 273     if (FRunning == false) {
 274         FRunning = true;
 275 
 276         int handles[2];
 277 
 278         if (pipe(handles) == -1) {
 279             return false;
 280         }
 281 
 282         struct sigaction sa;
 283         sa.sa_handler = SIG_IGN;
 284         sigemptyset(&sa.sa_mask);
 285         sa.sa_flags = 0;
 286         sigemptyset(&savintr.sa_mask);
 287         sigemptyset(&savequit.sa_mask);
 288         sigaction(SIGINT, &sa, &savintr);
 289         sigaction(SIGQUIT, &sa, &savequit);
 290         sigaddset(&sa.sa_mask, SIGCHLD);
 291         sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
 292 
 293         FChildPID = fork();
 294 
 295         // PID returned by vfork is 0 for the child process and the
 296         // PID of the child process for the parent.
 297         if (FChildPID == -1) {
 298             // Error
 299             TString message = PlatformString::Format(
 300                     _T("Error: Unable to create process %s"),
 301                     Application.data());
 302             throw Exception(message);
 303         } else if (FChildPID == 0) {
 304             Cleanup();
 305             TString command = Application;
 306 
 307             for (std::vector<TString>::const_iterator iterator =
 308                     Arguments.begin(); iterator != Arguments.end();
 309                     iterator++) {
 310                 command += TString(_T(" ")) + *iterator;
 311             }
 312 #ifdef DEBUG
 313             printf("%s\n", command.data());
 314 #endif // DEBUG
 315 
 316             dup2(handles[PIPE_READ], STDIN_FILENO);
 317             dup2(handles[PIPE_WRITE], STDOUT_FILENO);
 318 
 319             close(handles[PIPE_READ]);
 320             close(handles[PIPE_WRITE]);
 321 
 322             execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
 323 
 324             _exit(127);
 325         } else {
 326             FOutputHandle = handles[PIPE_READ];
 327             FInputHandle = handles[PIPE_WRITE];
 328 
 329             if (AWait == true) {
 330                 ReadOutput();
 331                 Wait();
 332                 Cleanup();
 333                 FRunning = false;
 334                 result = true;
 335             } else {
 336                 result = true;
 337             }
 338         }
 339     }
 340 
 341     return result;
 342 }
 343 
 344 void AppendPListArrayToIniFile(NSDictionary *infoDictionary,
 345         IniFile *result, TString Section) {
 346     NSString *sectionKey =
 347             [NSString stringWithUTF8String : PlatformString(Section).toMultibyte()];
 348     NSDictionary *array = [infoDictionary objectForKey : sectionKey];
 349 
 350     for (id option in array) {
 351         if ([option isKindOfClass : [NSString class]]) {
 352             TString arg = [option UTF8String];
 353 
 354             TString name;
 355             TString value;
 356 
 357             if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
 358                 result->Append(Section, name, value);
 359             }
 360         }
 361     }
 362 }
 363 
 364 void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary,
 365         IniFile *result, TString Section, bool FollowSection = true) {
 366     NSDictionary *dictionary = NULL;
 367 
 368     if (FollowSection == true) {
 369         NSString *sectionKey = [NSString stringWithUTF8String : PlatformString(
 370                 Section).toMultibyte()];
 371         dictionary = [infoDictionary objectForKey : sectionKey];
 372     } else {
 373         dictionary = infoDictionary;
 374     }
 375 
 376     for (id key in dictionary) {
 377         id option = [dictionary valueForKey : key];
 378 
 379         if ([key isKindOfClass : [NSString class]] &&
 380                 [option isKindOfClass : [NSString class]]) {
 381             TString name = [key UTF8String];
 382             TString value = [option UTF8String];
 383             result->Append(Section, name, value);
 384         }
 385     }
 386 }
 387 
 388 // Convert parts of the info.plist to the INI format the rest of the jpackage
 389 // uses unless a jpackage config file exists.
 390 ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
 391     IniFile* result = new IniFile();
 392     if (result == NULL) {
 393         return NULL;
 394     }
 395 
 396     if (UsePListForConfigFile() == false) {
 397         if (result->LoadFromFile(FileName) == false) {
 398             // New property file format was not found,
 399             // attempt to load old property file format.
 400             Helpers::LoadOldConfigFile(FileName, result);
 401         }
 402     } else {
 403         NSBundle *mainBundle = [NSBundle mainBundle];
 404         NSDictionary *infoDictionary = [mainBundle infoDictionary];
 405         std::map<TString, TString> keys = GetKeys();
 406 
 407         // JPackage options.
 408         AppendPListDictionaryToIniFile(infoDictionary, result,
 409                 keys[CONFIG_SECTION_APPLICATION], false);
 410 
 411         // jvmargs
 412         AppendPListArrayToIniFile(infoDictionary, result,
 413                 keys[CONFIG_SECTION_JVMOPTIONS]);
 414 
 415         // Generate AppCDS Cache
 416         AppendPListDictionaryToIniFile(infoDictionary, result,
 417                 keys[CONFIG_SECTION_APPCDSJVMOPTIONS]);
 418         AppendPListDictionaryToIniFile(infoDictionary, result,
 419                 keys[CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS]);
 420 
 421         // args
 422         AppendPListArrayToIniFile(infoDictionary, result,
 423                 keys[CONFIG_SECTION_ARGOPTIONS]);
 424     }
 425 
 426     return result;
 427 }
 428 
 429 TString GetModuleFileNameOSX() {
 430     Dl_info module_info;
 431     if (dladdr(reinterpret_cast<void*> (GetModuleFileNameOSX),
 432             &module_info) == 0) {
 433         // Failed to find the symbol we asked for.
 434         return std::string();
 435     }
 436     return TString(module_info.dli_fname);
 437 }
 438 
 439 TString MacPlatform::GetModuleFileName() {
 440     TString result;
 441     DynamicBuffer<TCHAR> buffer(MAX_PATH);
 442     uint32_t size = buffer.GetSize();
 443 
 444     if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
 445         result = FileSystemStringToString(buffer.GetData());
 446     }
 447 
 448     return result;
 449 }
 450 
 451 bool MacPlatform::IsMainThread() {
 452     bool result = (pthread_main_np() == 1);
 453     return result;
 454 }
 455 
 456 TPlatformNumber MacPlatform::GetMemorySize() {
 457     unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
 458 
 459     // Convert from bytes to megabytes.
 460     TPlatformNumber result = memory / 1048576;
 461 
 462     return result;
 463 }
 464 
 465 std::map<TString, TString> MacPlatform::GetKeys() {
 466     std::map<TString, TString> keys;
 467 
 468     if (UsePListForConfigFile() == false) {
 469         return Platform::GetKeys();
 470     } else {
 471         keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
 472                 _T("app.version")));
 473         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
 474                 _T("JVMMainJarName")));
 475         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINMODULE_KEY,
 476                 _T("JVMMainModuleName")));
 477         keys.insert(std::map<TString, TString>::value_type(
 478                 CONFIG_MAINCLASSNAME_KEY, _T("JVMMainClassName")));
 479         keys.insert(std::map<TString, TString>::value_type(
 480                 CONFIG_CLASSPATH_KEY, _T("JVMAppClasspath")));
 481         keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
 482                 _T("CFBundleName")));
 483         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_ID_KEY,
 484                 _T("JVMPreferencesID")));
 485         keys.insert(std::map<TString, TString>::value_type(JVM_RUNTIME_KEY,
 486                 _T("JVMRuntime")));
 487         keys.insert(std::map<TString, TString>::value_type(JPACKAGE_APP_DATA_DIR,
 488                 _T("CFBundleIdentifier")));
 489 
 490         keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
 491                 _T("app.splash")));
 492         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
 493                 _T("app.memory")));
 494         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
 495                 _T("app.debug")));
 496         keys.insert(std::map<TString, TString>::value_type(
 497                 CONFIG_APPLICATION_INSTANCE, _T("app.application.instance")));
 498 
 499         keys.insert(std::map<TString, TString>::value_type(
 500                 CONFIG_SECTION_APPLICATION, _T("Application")));
 501         keys.insert(std::map<TString, TString>::value_type(
 502                 CONFIG_SECTION_JVMOPTIONS, _T("JVMOptions")));
 503         keys.insert(std::map<TString, TString>::value_type(
 504                 CONFIG_SECTION_APPCDSJVMOPTIONS, _T("AppCDSJVMOptions")));
 505         keys.insert(std::map<TString, TString>::value_type(
 506                 CONFIG_SECTION_APPCDSGENERATECACHEJVMOPTIONS,
 507                 _T("AppCDSGenerateCacheJVMOptions")));
 508         keys.insert(std::map<TString, TString>::value_type(
 509                 CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions")));
 510     }
 511 
 512     return keys;
 513 }