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("app");
 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("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 TString MacPlatform::GetPackageRootDirectory() {
 202     NSBundle *mainBundle = [NSBundle mainBundle];
 203     NSString *mainBundlePath = [mainBundle bundlePath];
 204     NSString *contentsPath =
 205             [mainBundlePath stringByAppendingString : @"/Contents"];
 206     TString result = [contentsPath UTF8String];
 207     return result;
 208 }
 209 
 210 TString MacPlatform::GetAppDataDirectory() {
 211     TString result;
 212     NSArray *paths = NSSearchPathForDirectoriesInDomains(
 213             NSApplicationSupportDirectory, NSUserDomainMask, YES);
 214     NSString *applicationSupportDirectory = [paths firstObject];
 215     result = [applicationSupportDirectory UTF8String];
 216     return result;
 217 }
 218 
 219 TString MacPlatform::GetBundledJavaLibraryFileName(TString RuntimePath) {
 220     TString result;
 221 
 222     // first try lib/, then lib/jli
 223     result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 224             _T("Contents/Home/lib/libjli.dylib");
 225 
 226     if (FilePath::FileExists(result) == false) {
 227         result = FilePath::IncludeTrailingSeparator(RuntimePath) +
 228                 _T("Contents/Home/lib/jli/libjli.dylib");
 229 
 230         if (FilePath::FileExists(result) == false) {
 231             // cannot find
 232             NSLog(@"Cannot find libjli.dysym!");
 233             result = _T("");
 234         }
 235     }
 236 
 237     return result;
 238 }
 239 
 240 TString MacPlatform::GetAppName() {
 241     NSString *appName = [[NSProcessInfo processInfo] processName];
 242     TString result = [appName UTF8String];
 243     return result;
 244 }
 245 
 246 void PosixProcess::Cleanup() {
 247     if (FOutputHandle != 0) {
 248         close(FOutputHandle);
 249         FOutputHandle = 0;
 250     }
 251 
 252     if (FInputHandle != 0) {
 253         close(FInputHandle);
 254         FInputHandle = 0;
 255     }
 256 
 257     sigaction(SIGINT, &savintr, (struct sigaction *) 0);
 258     sigaction(SIGQUIT, &savequit, (struct sigaction *) 0);
 259     sigprocmask(SIG_SETMASK, &saveblock, (sigset_t *) 0);
 260 }
 261 
 262 #define PIPE_READ 0
 263 #define PIPE_WRITE 1
 264 
 265 bool PosixProcess::Execute(const TString Application,
 266         const std::vector<TString> Arguments, bool AWait) {
 267     bool result = false;
 268 
 269     if (FRunning == false) {
 270         FRunning = true;
 271 
 272         int handles[2];
 273 
 274         if (pipe(handles) == -1) {
 275             return false;
 276         }
 277 
 278         struct sigaction sa;
 279         sa.sa_handler = SIG_IGN;
 280         sigemptyset(&sa.sa_mask);
 281         sa.sa_flags = 0;
 282         sigemptyset(&savintr.sa_mask);
 283         sigemptyset(&savequit.sa_mask);
 284         sigaction(SIGINT, &sa, &savintr);
 285         sigaction(SIGQUIT, &sa, &savequit);
 286         sigaddset(&sa.sa_mask, SIGCHLD);
 287         sigprocmask(SIG_BLOCK, &sa.sa_mask, &saveblock);
 288 
 289         FChildPID = fork();
 290 
 291         // PID returned by vfork is 0 for the child process and the
 292         // PID of the child process for the parent.
 293         if (FChildPID == -1) {
 294             // Error
 295             TString message = PlatformString::Format(
 296                     _T("Error: Unable to create process %s"),
 297                     Application.data());
 298             throw Exception(message);
 299         } else if (FChildPID == 0) {
 300             Cleanup();
 301             TString command = Application;
 302 
 303             for (std::vector<TString>::const_iterator iterator =
 304                     Arguments.begin(); iterator != Arguments.end();
 305                     iterator++) {
 306                 command += TString(_T(" ")) + *iterator;
 307             }
 308 #ifdef DEBUG
 309             printf("%s\n", command.data());
 310 #endif // DEBUG
 311 
 312             dup2(handles[PIPE_READ], STDIN_FILENO);
 313             dup2(handles[PIPE_WRITE], STDOUT_FILENO);
 314 
 315             close(handles[PIPE_READ]);
 316             close(handles[PIPE_WRITE]);
 317 
 318             execl("/bin/sh", "sh", "-c", command.data(), (char *) 0);
 319 
 320             _exit(127);
 321         } else {
 322             FOutputHandle = handles[PIPE_READ];
 323             FInputHandle = handles[PIPE_WRITE];
 324 
 325             if (AWait == true) {
 326                 ReadOutput();
 327                 Wait();
 328                 Cleanup();
 329                 FRunning = false;
 330                 result = true;
 331             } else {
 332                 result = true;
 333             }
 334         }
 335     }
 336 
 337     return result;
 338 }
 339 
 340 void AppendPListArrayToIniFile(NSDictionary *infoDictionary,
 341         IniFile *result, TString Section) {
 342     NSString *sectionKey =
 343             [NSString stringWithUTF8String : PlatformString(Section).toMultibyte()];
 344     NSDictionary *array = [infoDictionary objectForKey : sectionKey];
 345 
 346     for (id option in array) {
 347         if ([option isKindOfClass : [NSString class]]) {
 348             TString arg = [option UTF8String];
 349 
 350             TString name;
 351             TString value;
 352 
 353             if (Helpers::SplitOptionIntoNameValue(arg, name, value) == true) {
 354                 result->Append(Section, name, value);
 355             }
 356         }
 357     }
 358 }
 359 
 360 void AppendPListDictionaryToIniFile(NSDictionary *infoDictionary,
 361         IniFile *result, TString Section, bool FollowSection = true) {
 362     NSDictionary *dictionary = NULL;
 363 
 364     if (FollowSection == true) {
 365         NSString *sectionKey = [NSString stringWithUTF8String : PlatformString(
 366                 Section).toMultibyte()];
 367         dictionary = [infoDictionary objectForKey : sectionKey];
 368     } else {
 369         dictionary = infoDictionary;
 370     }
 371 
 372     for (id key in dictionary) {
 373         id option = [dictionary valueForKey : key];
 374 
 375         if ([key isKindOfClass : [NSString class]] &&
 376                 [option isKindOfClass : [NSString class]]) {
 377             TString name = [key UTF8String];
 378             TString value = [option UTF8String];
 379             result->Append(Section, name, value);
 380         }
 381     }
 382 }
 383 
 384 // Convert parts of the info.plist to the INI format the rest of the jpackage
 385 // uses unless a jpackage config file exists.
 386 ISectionalPropertyContainer* MacPlatform::GetConfigFile(TString FileName) {
 387     IniFile* result = new IniFile();
 388     if (result == NULL) {
 389         return NULL;
 390     }
 391 
 392     if (UsePListForConfigFile() == false) {
 393         result->LoadFromFile(FileName);
 394     } else {
 395         NSBundle *mainBundle = [NSBundle mainBundle];
 396         NSDictionary *infoDictionary = [mainBundle infoDictionary];
 397         std::map<TString, TString> keys = GetKeys();
 398 
 399         // JPackage options.
 400         AppendPListDictionaryToIniFile(infoDictionary, result,
 401                 keys[CONFIG_SECTION_APPLICATION], false);
 402 
 403         // jvmargs
 404         AppendPListArrayToIniFile(infoDictionary, result,
 405                 keys[CONFIG_SECTION_JAVAOPTIONS]);
 406 
 407         // Generate AppCDS Cache
 408         AppendPListDictionaryToIniFile(infoDictionary, result,
 409                 keys[CONFIG_SECTION_APPCDSJAVAOPTIONS]);
 410         AppendPListDictionaryToIniFile(infoDictionary, result,
 411                 keys[CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS]);
 412 
 413         // args
 414         AppendPListArrayToIniFile(infoDictionary, result,
 415                 keys[CONFIG_SECTION_ARGOPTIONS]);
 416     }
 417 
 418     return result;
 419 }
 420 
 421 TString GetModuleFileNameOSX() {
 422     Dl_info module_info;
 423     if (dladdr(reinterpret_cast<void*> (GetModuleFileNameOSX),
 424             &module_info) == 0) {
 425         // Failed to find the symbol we asked for.
 426         return std::string();
 427     }
 428     return TString(module_info.dli_fname);
 429 }
 430 
 431 TString MacPlatform::GetModuleFileName() {
 432     TString result;
 433     DynamicBuffer<TCHAR> buffer(MAX_PATH);
 434     uint32_t size = buffer.GetSize();
 435 
 436     if (_NSGetExecutablePath(buffer.GetData(), &size) == 0) {
 437         result = FileSystemStringToString(buffer.GetData());
 438     }
 439 
 440     return result;
 441 }
 442 
 443 bool MacPlatform::IsMainThread() {
 444     bool result = (pthread_main_np() == 1);
 445     return result;
 446 }
 447 
 448 TPlatformNumber MacPlatform::GetMemorySize() {
 449     unsigned long long memory = [[NSProcessInfo processInfo] physicalMemory];
 450 
 451     // Convert from bytes to megabytes.
 452     TPlatformNumber result = memory / 1048576;
 453 
 454     return result;
 455 }
 456 
 457 std::map<TString, TString> MacPlatform::GetKeys() {
 458     std::map<TString, TString> keys;
 459 
 460     if (UsePListForConfigFile() == false) {
 461         return Platform::GetKeys();
 462     } else {
 463         keys.insert(std::map<TString, TString>::value_type(CONFIG_VERSION,
 464                 _T("app.version")));
 465         keys.insert(std::map<TString, TString>::value_type(CONFIG_MAINJAR_KEY,
 466                 _T("JavaMainJarName")));
 467         keys.insert(std::map<TString,
 468                 TString>::value_type(CONFIG_MAINMODULE_KEY,
 469                 _T("JavaMainModuleName")));
 470         keys.insert(std::map<TString, TString>::value_type(
 471                 CONFIG_MAINCLASSNAME_KEY, _T("JavaMainClassName")));
 472         keys.insert(std::map<TString, TString>::value_type(
 473                 CONFIG_CLASSPATH_KEY, _T("JavaAppClasspath")));
 474         keys.insert(std::map<TString, TString>::value_type(APP_NAME_KEY,
 475                 _T("CFBundleName")));
 476         keys.insert(std::map<TString, TString>::value_type(JAVA_RUNTIME_KEY,
 477                 _T("JavaRuntime")));
 478         keys.insert(std::map<TString,
 479                 TString>::value_type(JPACKAGE_APP_DATA_DIR,
 480                 _T("CFBundleIdentifier")));
 481 
 482         keys.insert(std::map<TString, TString>::value_type(CONFIG_SPLASH_KEY,
 483                 _T("app.splash")));
 484         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_MEMORY,
 485                 _T("app.memory")));
 486         keys.insert(std::map<TString, TString>::value_type(CONFIG_APP_DEBUG,
 487                 _T("app.debug")));
 488         keys.insert(std::map<TString, TString>::value_type(
 489                 CONFIG_APPLICATION_INSTANCE, _T("app.application.instance")));
 490 
 491         keys.insert(std::map<TString, TString>::value_type(
 492                 CONFIG_SECTION_APPLICATION, _T("Application")));
 493         keys.insert(std::map<TString, TString>::value_type(
 494                 CONFIG_SECTION_JAVAOPTIONS, _T("JavaOptions")));
 495         keys.insert(std::map<TString, TString>::value_type(
 496                 CONFIG_SECTION_APPCDSJAVAOPTIONS, _T("AppCDSJavaOptions")));
 497         keys.insert(std::map<TString, TString>::value_type(
 498                 CONFIG_SECTION_APPCDSGENERATECACHEJAVAOPTIONS,
 499                 _T("AppCDSGenerateCacheJavaOptions")));
 500         keys.insert(std::map<TString, TString>::value_type(
 501                 CONFIG_SECTION_ARGOPTIONS, _T("ArgOptions")));
 502     }
 503 
 504     return keys;
 505 }