163 lines
5.1 KiB
Plaintext
163 lines
5.1 KiB
Plaintext
// 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
|