chromium/chrome/updater/mac/client_lib/CRURegistration-Private.h

// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// CRURegistration-Private contains declarations of CRURegistration
// implementation details that need unit testing.

#ifndef CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_PRIVATE_H_
#define CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_PRIVATE_H_

#import <Foundation/Foundation.h>

#import "CRURegistration.h"

NS_ASSUME_NONNULL_BEGIN

extern NSString* const CRUReturnCodeErrorDomain;

typedef NS_ERROR_ENUM(CRURegistrationInternalErrorDomain,
                      CRURegistrationInternalError){
    CRURegistrationInternalErrorTaskAlreadyLaunched = 1,

    // An underlying API that can fail returned an error that we did not
    // specifically anticipate.
    CRURegistrationInternalErrorUnrecognized = 9999,
};

/***
 * CRUTaskResultCallback is a block receiving the result of an NSTask
 * invocation.
 *
 * Parameters:
 *  NSString* -- all stdout content, nil if the process never launched.
 *  NSString* -- all stderr content. nil if the process never launched.
 *  NSError* -- return value of the process.
 *      * nil: the process ran and returned zero
 *      * error domain is CRUReturnCodeErrorDomain: process ran and returned
 *      nonzero; error code
 *          is the return value. NSData* arguments will be nonnil.
 *      * any other error domain: the task could not be launched; this is the
 *      error from NSTask or
 *          is in CRURegistrationErrorDomain. NSData* elements will be nil.
 */
typedef void (^CRUTaskResultCallback)(NSString* _Nullable,
                                      NSString* _Nullable,
                                      NSError* _Nullable);

/**
 * CRUAsyncTaskRunner runs an NSTask and asynchronously accumulates its stdout
 * and stderr streams into NSMutableData buffers.
 */
@interface CRUAsyncTaskRunner : NSObject

- (instancetype)initWithTask:(NSTask*)task
                 targetQueue:(dispatch_queue_t)targetQueue
    NS_DESIGNATED_INITIALIZER;

- (instancetype)init NS_UNAVAILABLE;

/**
 * launchWithReply launches the task and buffers its output. It calls `reply`
 * with the results of the task when the task completes. If the task cannot
 * be launched, it invokes `reply` with nil NSString* args and the NSError* from
 * NSTask's launch failure.
 */
- (void)launchWithReply:(CRUTaskResultCallback)reply;

@end

/**
 * CRURegistrationWorkItem represents a task to be constructed and invoked.
 *
 * It is plain data represented as an Objective-C class (instead of a struct)
 * so it can be contained in an NSMutableArray.
 */
@interface CRURegistrationWorkItem : NSObject

/**
 * Callback returning the path of the binary to run. This is invoked immediately
 * before the path is needed to construct an NSTask.
 *
 * This is a callback because some work items -- notably, installing the updater
 * itself -- may affect where future work items should look for the binaries
 * they intend to run, so searching for them needs to be deferred until
 * prior tasks have completed.
 */
@property(nonatomic, copy) NSURL* (^binPathCallback)();

/**
 * Arguments to invoke the NSTask with.
 */
@property(nonatomic, copy) NSArray<NSString*>* args;

/**
 * Handler to asynchronously invoke with task results. This handler is
 * _not_ responsible for cycling the task queue.
 */
@property(nonatomic, copy) CRUTaskResultCallback resultCallback;

@end

@interface CRURegistration (VisibleForTesting)

/**
 * Asynchronously add work items and, if the work queue is not currently being
 * processed, starts processing them. (If work is already in progress, these
 * items will be picked up by its continued execution.)
 */
- (void)addWorkItems:(NSArray<CRURegistrationWorkItem*>*)item;

/**
 * Synchronously finds the path to an installed KSAdmin binary. If a systemwide
 * ksadmin is available, it prefers it; otherwise, if a user ksadmin is
 * available, it returns that; if neither can be found, it returns nil.
 *
 * This does not depend on, or mutate, any protected state inside
 * CRURegistration itself, but does check filesystem state. If the updater is
 * concurrently being installed, it might not find it. This is intended for
 * use while CRURegistration is not concurrently running a task.
 */
- (nullable NSURL*)syncFindBestKSAdmin;

/**
 * Wrap an NSError from a failed attempt to run a task with a semantically
 * appropriate domain and code. If the error is an intended part of the library
 * API, it will be in CRURegistrationErrorDomain; otherwise, it represents a
 * library bug that the user should, ideally, not rely on and will be wrapped
 * under CRURegistrationInternalErrorDomain.
 *
 * Errors already in one of these two error domains are returned unchanged.
 * Nil is also returned unchanged. Otherwise:
 * - errors representing nonzero return codes from tasks are converted to
 *   CRURegistrationErrorTaskFailed
 * - "file not found" errors from Apple APIs are assumed to be NSTask failing
 *   to find a binary and are converted to CRURegistrationErrorHelperNotFound
 * - other errors are unexpected
 *
 * If the returned error is not the same as the input error, the result error's
 * `userInfo` dictionary contains a value under `NSUnderlyingErrorKey` with the
 * original error unchanged. A wrapped error's `userInfo` also contains the
 * values of `gotStdout` and `gotStderr` under `CRUStdoutKey` and `CRUStderrKey`
 * respectively. Values in `userInfo` are intended to assist with debugging, but
 * production code should not rely on these values for identifying and handling
 * the disposition of an error; file bugs against this library if more detail
 * is required than is available, or if any internal error is encountered.
 */
- (nullable NSError*)wrapError:(nullable NSError*)error
                    withStdout:(nullable NSString*)gotStdout
                     andStderr:(nullable NSString*)gotStderr;

@end

NS_ASSUME_NONNULL_END

#endif  // CHROME_UPDATER_MAC_CLIENT_LIB_CRUREGISTRATION_PRIVATE_H_