| // Copyright 2014 The Crashpad Authors |
| // |
| // 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 "util/mac/service_management.h" |
| |
| #import <Foundation/Foundation.h> |
| #include <launch.h> |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/apple/bridging.h" |
| #include "base/apple/scoped_cftyperef.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "gtest/gtest.h" |
| #include "util/misc/clock.h" |
| #include "util/misc/random_string.h" |
| #include "util/posix/process_info.h" |
| #include "util/stdlib/objc.h" |
| |
| namespace crashpad { |
| namespace test { |
| namespace { |
| |
| // Ensures that the process with the specified PID is running, identifying it by |
| // requiring that its argv[argc - 1] compare equal to last_arg. |
| void ExpectProcessIsRunning(pid_t pid, std::string& last_arg) { |
| ProcessInfo process_info; |
| ASSERT_TRUE(process_info.InitializeWithPid(pid)); |
| |
| // The process may not have called exec yet, so loop with a small delay while |
| // looking for the cookie. |
| int outer_tries = 10; |
| std::vector<std::string> job_argv; |
| while (outer_tries--) { |
| // If the process is in the middle of calling exec, process_info.Arguments() |
| // may fail. Loop with a small retry delay while waiting for the expected |
| // successful call. |
| int inner_tries = 10; |
| bool success; |
| do { |
| success = process_info.Arguments(&job_argv); |
| if (success) { |
| break; |
| } |
| if (inner_tries > 0) { |
| SleepNanoseconds(1E6); // 1 millisecond |
| } |
| } while (inner_tries--); |
| ASSERT_TRUE(success); |
| |
| ASSERT_FALSE(job_argv.empty()); |
| if (job_argv.back() == last_arg) { |
| break; |
| } |
| |
| if (outer_tries > 0) { |
| SleepNanoseconds(1E6); // 1 millisecond |
| } |
| } |
| |
| ASSERT_FALSE(job_argv.empty()); |
| EXPECT_EQ(job_argv.back(), last_arg); |
| } |
| |
| // Ensures that the process with the specified PID is not running. Because the |
| // PID may be reused for another process, a process is only treated as running |
| // if its argv[argc - 1] compares equal to last_arg. |
| void ExpectProcessIsNotRunning(pid_t pid, std::string& last_arg) { |
| // The process may not have exited yet, so loop with a small delay while |
| // checking that it has exited. |
| int tries = 10; |
| std::vector<std::string> job_argv; |
| while (tries--) { |
| ProcessInfo process_info; |
| if (!process_info.InitializeWithPid(pid) || |
| !process_info.Arguments(&job_argv)) { |
| // The PID was not found. |
| return; |
| } |
| |
| // The PID was found. It may have been recycled for another process. Make |
| // sure that the cookie isn’t found. |
| ASSERT_FALSE(job_argv.empty()); |
| if (job_argv.back() != last_arg) { |
| break; |
| } |
| |
| if (tries > 0) { |
| SleepNanoseconds(1E6); // 1 millisecond |
| } |
| } |
| |
| ASSERT_FALSE(job_argv.empty()); |
| EXPECT_NE(job_argv.back(), last_arg); |
| } |
| |
| TEST(ServiceManagement, SubmitRemoveJob) { |
| @autoreleasepool { |
| const std::string cookie = RandomString(); |
| |
| std::string shell_script = |
| base::StringPrintf("sleep 10; echo %s", cookie.c_str()); |
| NSString* shell_script_ns = base::SysUTF8ToNSString(shell_script); |
| |
| static constexpr char kJobLabel[] = |
| "org.chromium.crashpad.test.service_management"; |
| NSDictionary* job_dictionary_ns = @{ |
| @LAUNCH_JOBKEY_LABEL : @"org.chromium.crashpad.test.service_management", |
| @LAUNCH_JOBKEY_RUNATLOAD : @YES, |
| @LAUNCH_JOBKEY_PROGRAMARGUMENTS : |
| @[ @"/bin/sh", @"-c", shell_script_ns, ], |
| }; |
| CFDictionaryRef job_dictionary_cf = |
| base::apple::NSToCFPtrCast(job_dictionary_ns); |
| |
| // The job may be left over from a failed previous run. |
| if (ServiceManagementIsJobLoaded(kJobLabel)) { |
| EXPECT_TRUE(ServiceManagementRemoveJob(kJobLabel, true)); |
| } |
| |
| EXPECT_FALSE(ServiceManagementIsJobLoaded(kJobLabel)); |
| ASSERT_FALSE(ServiceManagementIsJobRunning(kJobLabel)); |
| |
| // Submit the job. |
| ASSERT_TRUE(ServiceManagementSubmitJob(job_dictionary_cf)); |
| EXPECT_TRUE(ServiceManagementIsJobLoaded(kJobLabel)); |
| |
| // launchd started the job because RunAtLoad is true. |
| pid_t job_pid = ServiceManagementIsJobRunning(kJobLabel); |
| ASSERT_GT(job_pid, 0); |
| |
| ExpectProcessIsRunning(job_pid, shell_script); |
| |
| // Remove the job. |
| ASSERT_TRUE(ServiceManagementRemoveJob(kJobLabel, true)); |
| EXPECT_FALSE(ServiceManagementIsJobLoaded(kJobLabel)); |
| EXPECT_EQ(ServiceManagementIsJobRunning(kJobLabel), 0); |
| |
| // Now that the job is unloaded, a subsequent attempt to unload it should be |
| // an error. |
| EXPECT_FALSE(ServiceManagementRemoveJob(kJobLabel, false)); |
| |
| ExpectProcessIsNotRunning(job_pid, shell_script); |
| } |
| } |
| |
| } // namespace |
| } // namespace test |
| } // namespace crashpad |