| // Copyright 2014 The Bazel Authors. All rights reserved. | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //    http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | #include "src/main/native/unix_jni.h" | 
 |  | 
 | #include <assert.h> | 
 | #include <errno.h> | 
 | #include <fcntl.h> | 
 | #include <IOKit/IOMessage.h> | 
 | #include <IOKit/pwr_mgt/IOPMLib.h> | 
 | #include <os/log.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <sys/stat.h> | 
 | #include <sys/sysctl.h> | 
 | #include <sys/syslimits.h> | 
 | #include <sys/types.h> | 
 | #include <sys/xattr.h> | 
 |  | 
 | #include <atomic> | 
 | // Linting disabled for this line because for google code we could use | 
 | // absl::Mutex but we cannot yet because Bazel doesn't depend on absl. | 
 | #include <mutex>  // NOLINT | 
 | #include <string> | 
 |  | 
 | namespace blaze_jni { | 
 |  | 
 | const int PATH_MAX2 = PATH_MAX * 2; | 
 |  | 
 | using std::string; | 
 |  | 
 | // See unix_jni.h. | 
 | string ErrorMessage(int error_number) { | 
 |   char buf[1024] = ""; | 
 |   if (strerror_r(error_number, buf, sizeof buf) < 0) { | 
 |     snprintf(buf, sizeof buf, "strerror_r(%d): errno %d", error_number, errno); | 
 |   } | 
 |  | 
 |   return string(buf); | 
 | } | 
 |  | 
 |  | 
 | int portable_fstatat( | 
 |     int dirfd, char *name, portable_stat_struct *statbuf, int flags) { | 
 |   char dirPath[PATH_MAX2];  // Have enough room for relative path | 
 |  | 
 |   // No fstatat under darwin, simulate it | 
 |   if (flags != 0) { | 
 |     // We don't support any flags | 
 |     errno = ENOSYS; | 
 |     return -1; | 
 |   } | 
 |   if (strlen(name) == 0 || name[0] == '/') { | 
 |     // Absolute path, simply stat | 
 |     return portable_stat(name, statbuf); | 
 |   } | 
 |   // Relative path, construct an absolute path | 
 |   if (fcntl(dirfd, F_GETPATH, dirPath) == -1) { | 
 |     return -1; | 
 |   } | 
 |   int l = strlen(dirPath); | 
 |   if (dirPath[l-1] != '/') { | 
 |     // dirPath is twice the PATH_MAX size, we always have room for the extra / | 
 |     dirPath[l] = '/'; | 
 |     dirPath[l+1] = 0; | 
 |     l++; | 
 |   } | 
 |   strncat(dirPath, name, PATH_MAX2-l-1); | 
 |   char *newpath = realpath(dirPath, NULL);  // this resolve the relative path | 
 |   if (newpath == NULL) { | 
 |     return -1; | 
 |   } | 
 |   int r = portable_stat(newpath, statbuf); | 
 |   free(newpath); | 
 |   return r; | 
 | } | 
 |  | 
 | int StatSeconds(const portable_stat_struct &statbuf, StatTimes t) { | 
 |   switch (t) { | 
 |     case STAT_ATIME: | 
 |       return statbuf.st_atime; | 
 |     case STAT_CTIME: | 
 |       return statbuf.st_ctime; | 
 |     case STAT_MTIME: | 
 |       return statbuf.st_mtime; | 
 |     default: | 
 |       CHECK(false); | 
 |   } | 
 | } | 
 |  | 
 | int StatNanoSeconds(const portable_stat_struct &statbuf, StatTimes t) { | 
 |   switch (t) { | 
 |     case STAT_ATIME: | 
 |       return statbuf.st_atimespec.tv_nsec; | 
 |     case STAT_CTIME: | 
 |       return statbuf.st_ctimespec.tv_nsec; | 
 |     case STAT_MTIME: | 
 |       return statbuf.st_mtimespec.tv_nsec; | 
 |     default: | 
 |       CHECK(false); | 
 |   } | 
 | } | 
 |  | 
 | ssize_t portable_getxattr(const char *path, const char *name, void *value, | 
 |                           size_t size, bool *attr_not_found) { | 
 |   ssize_t result = getxattr(path, name, value, size, 0, 0); | 
 |   *attr_not_found = (errno == ENOATTR); | 
 |   return result; | 
 | } | 
 |  | 
 | ssize_t portable_lgetxattr(const char *path, const char *name, void *value, | 
 |                            size_t size, bool *attr_not_found) { | 
 |   ssize_t result = getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); | 
 |   *attr_not_found = (errno == ENOATTR); | 
 |   return result; | 
 | } | 
 |  | 
 | int portable_sysctlbyname(const char *name_chars, long *mibp, size_t *sizep) { | 
 |   return sysctlbyname(name_chars, mibp, sizep, NULL, 0); | 
 | } | 
 |  | 
 | // Queue used for all of our anomaly tracking. | 
 | static dispatch_queue_t JniDispatchQueue() { | 
 |   static dispatch_once_t once_token; | 
 |   static dispatch_queue_t queue; | 
 |   dispatch_once(&once_token, ^{ | 
 |     queue = dispatch_queue_create("build.bazel.jni", DISPATCH_QUEUE_SERIAL); | 
 |     CHECK(queue); | 
 |   }); | 
 |   return queue; | 
 | } | 
 |  | 
 | // Log used for all of our anomaly logging. | 
 | // Logging can be traced using: | 
 | // `log stream -level debug --predicate '(subsystem == "build.bazel")'` | 
 | // | 
 | // This may return NULL if `os_log_create` is not supported on this version of | 
 | // macOS. Use `log_if_possible` to log when supported. | 
 | static os_log_t JniOSLog() { | 
 |   static dispatch_once_t once_token; | 
 |   static os_log_t log = NULL; | 
 |   // On macOS < 10.12, os_log_create is not available. Since we target 10.10, | 
 |   // this will be weakly linked and can be checked for availability at run | 
 |   // time. | 
 |   if (&os_log_create != NULL) { | 
 |     dispatch_once(&once_token, ^{ | 
 |       log = os_log_create("build.bazel", "jni"); | 
 |       CHECK(log); | 
 |     }); | 
 |   } | 
 |   return log; | 
 | } | 
 |  | 
 | // The macOS implementation asserts that `msg` be a string literal (not just a | 
 | // const char*), so we cannot use a function. | 
 | #define log_if_possible(msg)   \ | 
 |   do {                         \ | 
 |     os_log_t log = JniOSLog(); \ | 
 |     if (log != NULL) {         \ | 
 |       os_log_debug(log, msg);  \ | 
 |     }                          \ | 
 |   } while (0); | 
 |  | 
 | // Protects all of the g_sleep_state_* statics. | 
 | // value is "leaked" intentionally because std::mutex is not trivially | 
 | // destructible at this time, g_sleep_state_mutex is a singleton, and | 
 | // leaking it has no consequences. | 
 | static std::mutex *g_sleep_state_mutex = new std::mutex; | 
 |  | 
 | // Keep track of our pushes and pops of sleep state. | 
 | static int g_sleep_state_stack = 0; | 
 |  | 
 | // Our assertion for disabling sleep. | 
 | static IOPMAssertionID g_sleep_state_assertion = kIOPMNullAssertionID; | 
 |  | 
 | int portable_push_disable_sleep() { | 
 |   std::lock_guard<std::mutex> lock(*g_sleep_state_mutex); | 
 |   assert(g_sleep_state_stack >= 0); | 
 |   if (g_sleep_state_stack == 0) { | 
 |     assert(g_sleep_state_assertion == kIOPMNullAssertionID); | 
 |     CFStringRef reasonForActivity = CFSTR("build.bazel"); | 
 |  | 
 |     IOReturn success = IOPMAssertionCreateWithName( | 
 |         kIOPMAssertionTypeNoIdleSleep, kIOPMAssertionLevelOn, reasonForActivity, | 
 |         &g_sleep_state_assertion); | 
 |     assert(success == kIOReturnSuccess); | 
 |     log_if_possible("sleep assertion created"); | 
 |   } | 
 |   g_sleep_state_stack += 1; | 
 |   return 0; | 
 | } | 
 |  | 
 | int portable_pop_disable_sleep() { | 
 |   std::lock_guard<std::mutex> lock(*g_sleep_state_mutex); | 
 |   assert(g_sleep_state_stack > 0); | 
 |   g_sleep_state_stack -= 1; | 
 |   if (g_sleep_state_stack == 0) { | 
 |     assert(g_sleep_state_assertion != kIOPMNullAssertionID); | 
 |     IOReturn success = IOPMAssertionRelease(g_sleep_state_assertion); | 
 |     assert(success == kIOReturnSuccess); | 
 |     g_sleep_state_assertion = kIOPMNullAssertionID; | 
 |     log_if_possible("sleep assertion released"); | 
 |   } | 
 |   return 0; | 
 | } | 
 |  | 
 | typedef struct { | 
 |   // Port used to relay sleep call back messages. | 
 |   io_connect_t connect_port; | 
 |  | 
 |   // Count of suspensions. Atomic because it can be read from any java thread | 
 |   // and is written to from a dispatch_queue thread. | 
 |   std::atomic_int suspend_count; | 
 | } SuspendState; | 
 |  | 
 | static void SleepCallBack(void *refcon, io_service_t service, | 
 |                           natural_t message_type, void *message_argument) { | 
 |   SuspendState *state = (SuspendState *)refcon; | 
 |   switch (message_type) { | 
 |     case kIOMessageCanSystemSleep: | 
 |       // This needs to be handled to allow sleep. | 
 |       IOAllowPowerChange(state->connect_port, (intptr_t)message_argument); | 
 |       break; | 
 |  | 
 |     case kIOMessageSystemWillSleep: | 
 |       log_if_possible("suspend anomaly due to kIOMessageSystemWillSleep"); | 
 |       ++state->suspend_count; | 
 |       // This needs to be acknowledged to allow sleep. | 
 |       IOAllowPowerChange(state->connect_port, (intptr_t)message_argument); | 
 |       break; | 
 |  | 
 |     case kIOMessageSystemWillNotSleep: | 
 |       log_if_possible( | 
 |           "suspend anomaly cancelled due to kIOMessageSystemWillNotSleep"); | 
 |       --state->suspend_count; | 
 |       break; | 
 |  | 
 |     case kIOMessageSystemWillPowerOn: | 
 |     case kIOMessageSystemHasPoweredOn: | 
 |       // We increment g_suspend_count when we are alerted to the sleep as | 
 |       // opposed to when we wake up, because Macs have a "Dark Wake" mode (also | 
 |       // known as PowerNap) which is when the processors (and disk and network) | 
 |       // turn on for brief periods of time | 
 |       // (https://support.apple.com/en-us/HT204032). Dark Wake does NOT trigger | 
 |       // PowerOn messages through our sleep callbacks, but can allow | 
 |       // builds to proceed for a considerable amount of time (for example if | 
 |       // Time Machine is performing a back up). | 
 |       // There is currently a race condition where a build may finish | 
 |       // between the time we receive the kIOMessageSystemWillSleep and the | 
 |       // machine actually goes to sleep (roughly 20 seconds in my experiments) | 
 |       // or between the time we receive the kIOMessageSystemWillSleep and | 
 |       // kIOMessageSystemWillNotSleep. This will result in us reporting that the | 
 |       // build was suspended when it wasn't. I haven't come up with an smart way | 
 |       // of avoiding this issue, but I don't think we really care. Over | 
 |       // reporting "suspensions" is better than under reporting them. | 
 |     default: | 
 |       break; | 
 |   } | 
 | } | 
 |  | 
 | int portable_suspend_count() { | 
 |   static dispatch_once_t once_token; | 
 |   static SuspendState suspend_state; | 
 |   dispatch_once(&once_token, ^{ | 
 |     dispatch_queue_t queue = JniDispatchQueue(); | 
 |     IONotificationPortRef notifyPortRef; | 
 |     io_object_t notifierObject; | 
 |  | 
 |     // Register to receive system sleep notifications. | 
 |     // Testing needs to be done manually. Use the logging to verify | 
 |     // that sleeps are being caught here. | 
 |     // `log stream -level debug --predicate '(subsystem == "build.bazel")'` | 
 |     suspend_state.connect_port = IORegisterForSystemPower( | 
 |         &suspend_state, ¬ifyPortRef, SleepCallBack, ¬ifierObject); | 
 |     CHECK(suspend_state.connect_port != MACH_PORT_NULL); | 
 |     IONotificationPortSetDispatchQueue(notifyPortRef, queue); | 
 |  | 
 |     // Register to deal with SIGCONT. | 
 |     // We register for SIGCONT because we can't catch SIGSTOP and we can't | 
 |     // distinguish a SIGCONT after a SIGSTOP from a SIGCONT after SIGTSTP. | 
 |     // We do have the potential of "over counting" suspensions if you send | 
 |     // multiple SIGCONTs to a process without a previous SIGSTOP/SIGTSTP, | 
 |     // but there is no reason to send a SIGCONT without a SIGSTOP/SIGTSTP, and | 
 |     // having this functionality gives us some ability to unit test suspension | 
 |     // counts. | 
 |     sig_t signal_val = signal(SIGCONT, SIG_IGN); | 
 |     CHECK(signal_val != SIG_ERR); | 
 |     dispatch_source_t signal_source = | 
 |         dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGCONT, 0, queue); | 
 |     CHECK(signal_source != NULL); | 
 |     dispatch_source_set_event_handler(signal_source, ^{ | 
 |       log_if_possible("suspend anomaly due to SIGCONT"); | 
 |       ++suspend_state.suspend_count; | 
 |     }); | 
 |     dispatch_resume(signal_source); | 
 |   }); | 
 |   return suspend_state.suspend_count; | 
 | } | 
 |  | 
 | int portable_memory_pressure_warning_count() { | 
 |   // To test use: | 
 |   // log stream -level debug --predicate '(subsystem == "build.bazel")' | 
 |   // sudo memory_pressure -S -l warn | 
 |   static dispatch_once_t once_token; | 
 |   static std::atomic_int warning_count; | 
 |   dispatch_once(&once_token, ^{ | 
 |     dispatch_source_t source = dispatch_source_create( | 
 |         DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_WARN, | 
 |         JniDispatchQueue()); | 
 |     CHECK(source != NULL); | 
 |     dispatch_source_set_event_handler(source, ^{ | 
 |       dispatch_source_memorypressure_flags_t pressureLevel = | 
 |           dispatch_source_get_data(source); | 
 |       if (pressureLevel == DISPATCH_MEMORYPRESSURE_WARN) { | 
 |         log_if_possible("memory pressure warning anomaly"); | 
 |         ++warning_count; | 
 |       } | 
 |     }); | 
 |     dispatch_resume(source); | 
 |   }); | 
 |   return warning_count; | 
 | } | 
 |  | 
 | int portable_memory_pressure_critical_count() { | 
 |   // To test use: | 
 |   // log stream -level debug --predicate '(subsystem == "build.bazel")' | 
 |   // sudo memory_pressure -S -l critical | 
 |   static dispatch_once_t once_token; | 
 |   static std::atomic_int critical_count; | 
 |   dispatch_once(&once_token, ^{ | 
 |     dispatch_source_t source = dispatch_source_create( | 
 |         DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, | 
 |         DISPATCH_MEMORYPRESSURE_CRITICAL, JniDispatchQueue()); | 
 |     CHECK(source != NULL); | 
 |     dispatch_source_set_event_handler(source, ^{ | 
 |       dispatch_source_memorypressure_flags_t pressureLevel = | 
 |           dispatch_source_get_data(source); | 
 |       if (pressureLevel == DISPATCH_MEMORYPRESSURE_CRITICAL) { | 
 |         log_if_possible("memory pressure critical anomaly"); | 
 |         ++critical_count; | 
 |       } | 
 |     }); | 
 |     dispatch_resume(source); | 
 |   }); | 
 |   return critical_count; | 
 | } | 
 |  | 
 | }  // namespace blaze_jni |