| /* |
| * Copyright (C) 2021 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #import "config.h" |
| #import "DaemonTestUtilities.h" |
| |
| #if PLATFORM(MAC) || PLATFORM(IOS) |
| |
| #import <mach-o/dyld.h> |
| #import <wtf/Vector.h> |
| |
| NS_ASSUME_NONNULL_BEGIN |
| |
| #if PLATFORM(IOS) |
| @interface NSTask : NSObject |
| - (instancetype)init; |
| - (void)launch; |
| - (void)waitUntilExit; |
| |
| @property (nullable, copy) NSString *launchPath; |
| @property (nullable, copy) NSArray<NSString *> *arguments; |
| @property (nullable, retain) id standardOutput; |
| @property (readonly, getter=isRunning) BOOL running; |
| @end |
| #endif |
| |
| namespace TestWebKitAPI { |
| |
| static RetainPtr<NSURL> currentExecutableLocation() |
| { |
| uint32_t size { 0 }; |
| _NSGetExecutablePath(nullptr, &size); |
| Vector<char> buffer; |
| buffer.resize(size + 1); |
| _NSGetExecutablePath(buffer.data(), &size); |
| buffer[size] = '\0'; |
| auto pathString = adoptNS([[NSString alloc] initWithUTF8String:buffer.data()]); |
| return adoptNS([[NSURL alloc] initFileURLWithPath:pathString.get() isDirectory:NO]); |
| } |
| |
| RetainPtr<NSURL> currentExecutableDirectory() |
| { |
| return [currentExecutableLocation() URLByDeletingLastPathComponent]; |
| } |
| |
| #if PLATFORM(IOS) |
| static RetainPtr<xpc_object_t> convertArrayToXPC(NSArray *array) |
| { |
| auto xpc = adoptNS(xpc_array_create(nullptr, 0)); |
| for (id value in array) { |
| if ([value isKindOfClass:NSString.class]) |
| xpc_array_set_string(xpc.get(), XPC_ARRAY_APPEND, [value UTF8String]); |
| else |
| ASSERT_NOT_REACHED(); |
| } |
| return xpc; |
| } |
| |
| static RetainPtr<xpc_object_t> convertDictionaryToXPC(NSDictionary<NSString *, id> *dictionary) |
| { |
| auto xpc = adoptNS(xpc_dictionary_create(nullptr, nullptr, 0)); |
| for (NSString *key in dictionary) { |
| ASSERT([key isKindOfClass:NSString.class]); |
| const char* keyUTF8 = key.UTF8String; |
| id value = dictionary[key]; |
| if ([value isKindOfClass:NSString.class]) |
| xpc_dictionary_set_string(xpc.get(), keyUTF8, [value UTF8String]); |
| else if ([value isKindOfClass:NSNumber.class]) { |
| uint64_t uint64Value = [value unsignedLongLongValue]; |
| if (uint64Value == 1) |
| xpc_dictionary_set_bool(xpc.get(), keyUTF8, uint64Value); |
| else { |
| ASSERT([value doubleValue] == uint64Value); |
| xpc_dictionary_set_uint64(xpc.get(), keyUTF8, uint64Value); |
| } |
| } else if ([value isKindOfClass:NSArray.class]) |
| xpc_dictionary_set_value(xpc.get(), keyUTF8, convertArrayToXPC(value).get()); |
| else if ([value isKindOfClass:NSDictionary.class]) |
| xpc_dictionary_set_value(xpc.get(), keyUTF8, convertDictionaryToXPC(value).get()); |
| else |
| ASSERT_NOT_REACHED(); |
| } |
| return xpc; |
| } |
| #endif |
| |
| void registerPlistWithLaunchD(NSDictionary<NSString *, id> *plist, NSURL *tempDir) |
| { |
| NSError *error = nil; |
| #if PLATFORM(IOS) |
| auto xpcPlist = convertDictionaryToXPC(plist); |
| xpc_dictionary_set_string(xpcPlist.get(), "_ManagedBy", "TestWebKitAPI"); |
| xpc_dictionary_set_bool(xpcPlist.get(), "RootedSimulatorPath", true); |
| auto launchDJob = adoptNS([[OSLaunchdJob alloc] initWithPlist:xpcPlist.get()]); |
| [launchDJob submit:&error]; |
| #else |
| NSURL *plistLocation = [tempDir URLByAppendingPathComponent:@"DaemonInfo.plist"]; |
| BOOL success = [[NSFileManager defaultManager] createDirectoryAtURL:tempDir withIntermediateDirectories:YES attributes:nil error:&error]; |
| EXPECT_TRUE(success); |
| EXPECT_FALSE(error); |
| success = [plist writeToURL:plistLocation error:&error]; |
| EXPECT_TRUE(success); |
| system([NSString stringWithFormat:@"launchctl unload %@ 2> /dev/null", plistLocation.path].fileSystemRepresentation); |
| system([NSString stringWithFormat:@"launchctl load %@", plistLocation.path].fileSystemRepresentation); |
| EXPECT_FALSE(error); |
| #endif |
| } |
| |
| static int pidOfFirstDaemonInstance(NSString *daemonExecutableName) |
| { |
| auto task = adoptNS([[NSTask alloc] init]); |
| task.get().launchPath = @"/bin/ps"; |
| task.get().arguments = @[ |
| @"-ax", |
| @"-o", |
| @"pid,comm" |
| ]; |
| |
| auto taskPipe = adoptNS([[NSPipe alloc] init]); |
| [task setStandardOutput:taskPipe.get()]; |
| [task launch]; |
| |
| auto data = adoptNS([[NSMutableData alloc] init]); |
| while ([task isRunning]) |
| [data appendData:[[taskPipe fileHandleForReading] readDataToEndOfFile]]; |
| [data appendData:[[taskPipe fileHandleForReading] readDataToEndOfFile]]; |
| |
| auto psString = adoptNS([[NSString alloc] initWithData:data.get() encoding:NSUTF8StringEncoding]); |
| NSArray<NSString *> *psEntries = [psString componentsSeparatedByString:@"\n"]; |
| |
| for (NSString* entry in psEntries) { |
| if (![entry hasSuffix:daemonExecutableName]) |
| continue; |
| NSArray<NSString *> *components = [entry componentsSeparatedByString:@" "]; |
| EXPECT_GE([components count], 2u); |
| return [[components firstObject] integerValue]; |
| } |
| |
| return 0; |
| } |
| |
| void killFirstInstanceOfDaemon(NSString *daemonExecutableName) |
| { |
| auto pid = pidOfFirstDaemonInstance(daemonExecutableName); |
| if (!pid) |
| return; |
| |
| auto task = adoptNS([[NSTask alloc] init]); |
| task.get().launchPath = @"/bin/kill"; |
| task.get().arguments = @[ |
| @"-9", |
| [@(pid) stringValue] |
| ]; |
| |
| [task launch]; |
| [task waitUntilExit]; |
| } |
| |
| #if PLATFORM(IOS) |
| |
| BOOL restartService(NSString *, NSString *daemonExecutableName) |
| { |
| killFirstInstanceOfDaemon(daemonExecutableName); |
| sleep(1); |
| return YES; |
| } |
| |
| #else |
| |
| BOOL restartService(NSString *serviceName, NSString *) |
| { |
| auto task = adoptNS([[NSTask alloc] init]); |
| [task setLaunchPath:@"/bin/launchctl"]; |
| [task setArguments:@[@"kickstart", @"-k", @"-p", [NSString stringWithFormat:@"gui/%u/%@", geteuid(), serviceName]]]; |
| [task launch]; |
| [task waitUntilExit]; |
| return [task terminationStatus] == EXIT_SUCCESS; |
| } |
| |
| #endif |
| |
| } // namespace TestWebKitAPI |
| |
| NS_ASSUME_NONNULL_END |
| |
| #endif // PLATFORM(MAC) || PLATFORM(IOS) |