Repository: cbreak-black/ZetaWatch Branch: master Commit: 73baba945cab Files: 70 Total size: 377.9 KB Directory structure: gitextract_u2m5ltbd/ ├── .gitignore ├── .gitmodules ├── CommonAuthorization/ │ ├── CommonAuthorization.h │ ├── CommonAuthorization.m │ └── CommonAuthorization.strings ├── LICENSE.md ├── README.md ├── ZetaAuthorizationHelper/ │ ├── Info.plist │ ├── Launchd.plist │ ├── ZetaAuthorizationHelper.h │ ├── ZetaAuthorizationHelper.mm │ ├── ZetaAuthorizationHelperProtocol.h │ ├── ZetaCPPUtils.hpp │ └── main.m ├── ZetaLoginItemHelper/ │ ├── Info.plist │ └── main.m ├── ZetaWatch/ │ ├── Assets.xcassets/ │ │ ├── AppIcon.appiconset/ │ │ │ └── Contents.json │ │ ├── Contents.json │ │ └── Zeta.imageset/ │ │ └── Contents.json │ ├── Base.lproj/ │ │ ├── Localizable.strings │ │ ├── MainMenu.xib │ │ ├── NewFS.xib │ │ └── NewVol.xib │ ├── Info.plist │ ├── InvariantDisks/ │ │ ├── IDDiskArbitrationDispatcher.cpp │ │ ├── IDDiskArbitrationDispatcher.hpp │ │ ├── IDDiskArbitrationHandler.hpp │ │ ├── IDDiskArbitrationUtils.cpp │ │ └── IDDiskArbitrationUtils.hpp │ ├── PathValueTransformer.m │ ├── SizeTransformer.mm │ ├── ZetaAuthorization.h │ ├── ZetaAuthorization.mm │ ├── ZetaAutoImporter.h │ ├── ZetaAutoImporter.mm │ ├── ZetaBookmarkMenu.h │ ├── ZetaBookmarkMenu.mm │ ├── ZetaCommanderBase.h │ ├── ZetaCommanderBase.mm │ ├── ZetaConfirmDialog.h │ ├── ZetaConfirmDialog.mm │ ├── ZetaDictQueryDialog.h │ ├── ZetaDictQueryDialog.mm │ ├── ZetaFileSystemPropertyMenu.h │ ├── ZetaFileSystemPropertyMenu.mm │ ├── ZetaFormatHelpers.cpp │ ├── ZetaFormatHelpers.hpp │ ├── ZetaImportMenu.h │ ├── ZetaImportMenu.mm │ ├── ZetaKeyLoader.h │ ├── ZetaKeyLoader.mm │ ├── ZetaMainMenu.h │ ├── ZetaMainMenu.mm │ ├── ZetaNotificationCenter.h │ ├── ZetaNotificationCenter.mm │ ├── ZetaPoolPropertyMenu.h │ ├── ZetaPoolPropertyMenu.mm │ ├── ZetaPoolWatcher.h │ ├── ZetaPoolWatcher.mm │ ├── ZetaQueryDialog.h │ ├── ZetaQueryDialog.mm │ ├── ZetaSnapshotMenu.h │ ├── ZetaSnapshotMenu.mm │ ├── ZetaWatch.entitlements │ ├── ZetaWatchDelegate.h │ ├── ZetaWatchDelegate.mm │ └── main.m ├── ZetaWatch.xcodeproj/ │ ├── project.pbxproj │ └── project.xcworkspace/ │ └── contents.xcworkspacedata └── uninstall-helper.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ Archive DerivedData xcuserdata ================================================ FILE: .gitmodules ================================================ [submodule "ThirdParty/Sparkle"] path = ThirdParty/Sparkle url = ../Sparkle.git [submodule "ZFSWrapper"] path = ZFSWrapper url = ../ZFSWrapper ================================================ FILE: CommonAuthorization/CommonAuthorization.h ================================================ // // CommonAuthorization.h // ZetaWatch // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #import @interface CommonAuthorization : NSObject //! For a given command selector, return the associated authorization right name. + (NSString *)authorizationRightForCommand:(SEL)command; //! Set up the default authorization rights in the authorization database. + (void)setupAuthorizationRights:(AuthorizationRef)authRef; @end ================================================ FILE: CommonAuthorization/CommonAuthorization.m ================================================ // // CommonAuthorization.m // ZetaWatch // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #import "CommonAuthorization.h" #import "ZetaAuthorizationHelperProtocol.h" @implementation CommonAuthorization static NSString * kKeyAuthRightName = @"authRightName"; static NSString * kKeyAuthRightDefault = @"authRightDefault"; static NSString * kKeyAuthRightDesc = @"authRightDescription"; /*! +commandInfo returns a dictionary that represents everything we need to know about the authorized commands supported by the app. Each dictionary key is the string form of the command selector. The corresponding object is a dictionary that contains three items: o kKeyAuthRightName is the name of the authorization right itself. This is used by both the app (when creating rights and when pre-authorizing rights) and by the tool (when doing the final authorization check). o kKeyAuthRightDefault is the default right specification, used by the app to when it needs to create the default right specification. This is commonly a string contacting a rule a name, but it can potentially be more complex. See the discussion of the rightDefinition parameter of AuthorizationRightSet. o kKeyAuthRightDesc is a user-visible description of the right. This is used by the app when it needs to create the default right specification. Actually, string is used to look up a localized version of the string in "CommonAuthorization.strings". This file is generated by the "genstrings" cli application. */ + (NSDictionary *)commandInfo { static dispatch_once_t sOnceToken; static NSDictionary * sCommandInfo; dispatch_once(&sOnceToken,^{ NSDictionary * dictStop = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.stop", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying stop its helper.", @"prompt shown when user is required to authorize helper termination" ) }; NSDictionary * dictImport = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.import", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to import a pool.", @"prompt shown when user is required to authorize a zpool import" ) }; NSDictionary * dictExport = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.export", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to export a pool.", @"prompt shown when user is required to authorize a zpool export" ) }; NSDictionary * dictMount = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.mount", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to mount a filesystem.", @"prompt shown when user is required to authorize a zfs mount" ) }; NSDictionary * dictUnmount = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.unmount", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to unmount a filesystem.", @"prompt shown when user is required to authorize a zfs unmount" ) }; NSDictionary * dictSnapshot = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.snapshot", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to snapshot a filesystem.", @"prompt shown when user is required to authorize a zfs snapshot" ) }; NSDictionary * dictRollback = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.rollback", kKeyAuthRightDefault: @kAuthorizationRuleAuthenticateAsAdmin, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to roll back a filesystem.", @"prompt shown when user is required to authorize a zfs rollback" ) }; NSDictionary * dictClone = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.clone", kKeyAuthRightDefault: @kAuthorizationRuleAuthenticateAsAdmin, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to clone a snapshot.", @"prompt shown when user is required to authorize a zfs clone" ) }; NSDictionary * dictCreate = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.create", kKeyAuthRightDefault: @kAuthorizationRuleAuthenticateAsAdmin, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to create a filesystem.", @"prompt shown when user is required to authorize a zfs create" ) }; NSDictionary * dictDestroy = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.destroy", kKeyAuthRightDefault: @kAuthorizationRuleAuthenticateAsAdmin, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to destroy a filesystem.", @"prompt shown when user is required to authorize a zfs destroy" ) }; NSDictionary * dictKey = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.key", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to operate on an encryption key.", @"prompt shown when user is required to authorize a crypto key operation" ) }; NSDictionary * dictScrub = @{ kKeyAuthRightName: @"net.the-color-black.ZetaWatch.scrub", kKeyAuthRightDefault: @kAuthorizationRuleClassAllow, kKeyAuthRightDesc: NSLocalizedString( @"ZetaWatch is trying to scrub a pool.", @"prompt shown when user is required to authorize a zpool scrub" ) }; sCommandInfo = @{ NSStringFromSelector(@selector(stopHelperWithAuthorization:withReply:)): dictStop, NSStringFromSelector(@selector(importPools:authorization:withReply:)): dictImport, NSStringFromSelector(@selector(importablePools:authorization:withReply:)): dictImport, NSStringFromSelector(@selector(exportPools:authorization:withReply:)): dictExport, NSStringFromSelector(@selector(mountFilesystems:authorization:withReply:)): dictMount, NSStringFromSelector(@selector(unmountFilesystems:authorization:withReply:)): dictUnmount, NSStringFromSelector(@selector(snapshotFilesystem:authorization:withReply:)): dictSnapshot, NSStringFromSelector(@selector(rollbackFilesystem:authorization:withReply:)): dictRollback, NSStringFromSelector(@selector(cloneSnapshot:authorization:withReply:)): dictClone, NSStringFromSelector(@selector(createFilesystem:authorization:withReply:)): dictCreate, NSStringFromSelector(@selector(createVolume:authorization:withReply:)): dictCreate, NSStringFromSelector(@selector(destroy:authorization:withReply:)): dictDestroy, NSStringFromSelector(@selector(loadKeyForFilesystem:authorization:withReply:)): dictKey, NSStringFromSelector(@selector(unloadKeyForFilesystem:authorization:withReply:)): dictKey, NSStringFromSelector(@selector(scrubPool:authorization:withReply:)): dictScrub, }; }); return sCommandInfo; } + (NSString *)authorizationRightForCommand:(SEL)command { return [self commandInfo][NSStringFromSelector(command)][kKeyAuthRightName]; } + (void)enumerateRightsUsingBlock:(void (^)(NSString * authRightName, id authRightDefault, NSString * authRightDesc))block { [self.commandInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL * stop) { NSDictionary * commandDict = (NSDictionary *) obj; assert([commandDict isKindOfClass:[NSDictionary class]]); NSString * authRightName = [commandDict objectForKey:kKeyAuthRightName]; assert([authRightName isKindOfClass:[NSString class]]); id authRightDefault = [commandDict objectForKey:kKeyAuthRightDefault]; assert(authRightDefault != nil); NSString * authRightDesc = [commandDict objectForKey:kKeyAuthRightDesc]; assert([authRightDesc isKindOfClass:[NSString class]]); block(authRightName, authRightDefault, authRightDesc); }]; } + (void)setupAuthorizationRights:(AuthorizationRef)authRef // See comment in header. { assert(authRef != NULL); [CommonAuthorization enumerateRightsUsingBlock:^(NSString * authRightName, id authRightDefault, NSString * authRightDesc) { // First get the right. If we get back errAuthorizationDenied that means there's // no current definition, so we add our default one. OSStatus blockErr = AuthorizationRightGet([authRightName UTF8String], NULL); if (blockErr == errAuthorizationDenied) { blockErr = AuthorizationRightSet(authRef, [authRightName UTF8String], (__bridge CFTypeRef)authRightDefault, (__bridge CFStringRef)authRightDesc, NULL, CFSTR("CommonAuthorization") ); assert(blockErr == errAuthorizationSuccess); } }]; } @end ================================================ FILE: LICENSE.md ================================================ Copyright (c) 2014, Gerhard Röthlin All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 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. * Neither the name of the Project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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. ================================================ FILE: README.md ================================================ ZetaWatch ========= ![ZetaWatch displaying pool status and filesystems][ZFSImage] ZetaWatch is a small OS X program that displays the zfs status in the menu bar, similar to what iStat Menus does for other information. It is fairly well tested, but due to the current state of libzfs and libzfs_core, changes will be required until the API stabilizes. ZetaWatch is usually compiled for the latest available [ZFS release for Mac OS](https://openzfsonosx.org/), and might not be compatible with other releases. Currently supported features are: * Show pool, vdev, filesystem stats * Show pool / filesystem properties * Start, stop, pause scrubs, and monitor their progress. * Import and Export pools manually, or auto-import when they become available * Mount / unmount datasets manually or at pool import automatically * Load/Unload encryption keys for encrypted datasets manually or automatically * Optionally store pass phrases in the Mac OS X Keychain * Create, Display, Delete, Clone Snapshots or roll back to them * Report errors in notification center when they are discovered Installation ------------ ZetaWatch Releases can be downloaded from the [GitHub Releases Page](https://github.com/cbreak-black/ZetaWatch/releases). Please verify that the Version of ZetaWatch matches your ZFS Version. Usually, only the newest official Release is supported, since code changes are sometimes required to be fully compatible with the new library version. ZetaWatch does not require manual installation. Simply copy it into /Application or where ever else it fits. The bundled helper tool gets installed automatically the first time the program is started. This requires user-authentication. ZetaWatch supports auto updates, if enabled. For Developers ============== ZFS Interaction --------------- ZetaWatch communicates with zfs using `libzfs.dylib`, `libzfs_core.dylib` , `libzpool.dylib` and `libnvpair.dylib`, just like the command line tools do. This gives it all the flexibility of the command line tools, at the cost of having to reimplement functionality that is found in the tools and not the library. And since the libraries are explicitly not meant to provide a stable ABI, ZetaWatch is also closely coupled to the ZFS version it is built and written for. All the ZFS interaction is wrapped in the ZFSWrapper library. This C++ library isolates the issues mentioned above and provides a more convenient and safe API than the original C interface does. The library is used both by the helper tool and the frontend app. This is the most reusable part of ZetaWatch, and might be split out as separate project later. * *ZFSUtils* contains most of the advanced functionality, such as C++ Wrappers around the library, pool, vdev or file system handles. Those classes also have functionality to query state and iterate over members. * *ZFSNVList* provides a wrapper around the `nvpair_t` / `nvlist_t` data structure that is used in ZFS for a lot of userland / kernel communication. It manages resources in both owning and non-owning fashion, and allows for easier iteration over sequences. * *ZFSStrings* translate ZFS status enums into the user facing emoji or string description, optionally with localization. (Localization is not well tested or supported at the moment.) Helper Tool ----------- The implementation of the helper tool follows apple's [EvenBetterAuthorizationSample]. The helper tool communicates with the user application via `AuthorizationService` and `NSXPCConnection`. The application side of code for this is in `ZetaAuthorization.m`. The RPC protocol can be found in `ZetaAuthorizationHelperProtocol.h`, and is implemented in `ZetaAuthorizationHelper.mm`. The `CommonAuthorization.m` file contains the supported commands and associated default permissions. The helper tool can be uninstalled with the `uninstall-helper.sh` script. This is useful for debugging the installation of the helper, or updating the helper without increasing the bundle version. Authorization ------------- The ZetaWatch helper tool uses the Security framework to authorize users before performing privileged operations. It currently supports the following permissions. * `net.the-color-black.ZetaWatch.import`, allowed by default, required for importing a pool. * `net.the-color-black.ZetaWatch.export`, allowed by default, required for exporting a pool. * `net.the-color-black.ZetaWatch.mount`, allowed by default, required for mounting a dataset. * `net.the-color-black.ZetaWatch.unmount`, allowed by default, required for unmounting a dataset. * `net.the-color-black.ZetaWatch.snapshot`, allowed by default, required for creating a snapshot. * `net.the-color-black.ZetaWatch.rollback`, requires admin authentication by default, required for rolling back a filesystem. * `net.the-color-black.ZetaWatch.clone`, requires admin authentication by default, required for cloning a filesystem. * `net.the-color-black.ZetaWatch.create`, requires admin authentication by default, required for creating a new filesystem. * `net.the-color-black.ZetaWatch.destroy`, requires admin authentication by default, required for destroying a filesystem or snapshot. * `net.the-color-black.ZetaWatch.key`, allowed by default, required for loading or unloading a key for a dataset. This also includes the ability to auto mount / unmount them. * `net.the-color-black.ZetaWatch.scrub`, allowed by default, required for starting, stopping or pausing scrubs. These permissions can be manipulated via the `security` command line program. To inspect the current dataset creation permissions, and switching it to allow this to all users: ``` security authorizationdb read net.the-color-black.ZetaWatch.create security authorizationdb write net.the-color-black.ZetaWatch.create allow ``` Permissions include `allow`, `deny` or `authenticate-admin`. More detailed information about this topic can be found in the article apples documentation about [AuthorizationServices] and [Managing the Authorization Database in OS X Mavericks]. Security & Code Signing ----------------------- Official release builds are signed and notarized, and should run without issues even on newer Mac OS X. But there are still issues with authentication reported with the program not being recognized as signed. To verify security manually, the following commands can be used: ```bash codesign -v -v -d ZetaWatch.app xcrun stapler validate -v ZetaWatch.app ``` Building ZetaWatch requires an apple developer account with DeveloperID signing capabilities, since it uses [`SMJobBless`] to run a helper service as root. This service executes actions on behalf of the user, such as mounting, unmounting or loading a key. [Notarization] is required to create binaries that can be run without without warning on the newest Mac OS X. Self Updating ------------- The self-updating uses [SparkleFramework]. Since the newest released sparkle does not yet support hardened runtime, it needs to be compiled manually. Building the "Distribution" target in the Sparkle submodule in release mode is sufficient. The sparkle submodule contains a version that is slimmed down by removing most languages, which saves space. Since ZetaWatch is not localized, this is not a problem. To create a working fork, adjust the public key and update url in the Info.plist file. ZFS Binary Compatibility ------------------------ Since ZetaWatch directly links to the zfs libraries, it only works if those are compatible. And while Sparkle has built-in support for OS compatibility checking, it doesn't have the same for other dependencies. There is support for custom appcast filtering, to select a suitable version, but since the ZFS version and the ZetaWatch version are kind of orthogonal, this didn't seem fitting. The chosen solution was to have a ZFS version specific appcast URL, and make ZetaWatch query the appropriate appcast. This allows updating ZetaWatch when the used ZFS version changes, but also have several supported parallel builds. Currently, the only supported ZFS version is 1.9. License ======= This program is copyrighted by me, because I wrote it. This program is licensed under the "3-clause BSD" License. See the LICENSE.md file for details. [EvenBetterAuthorizationSample]: https://developer.apple.com/library/content/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html [`SMJobBless`]: https://developer.apple.com/documentation/servicemanagement/1431078-smjobbless?language=objc [Notarization]: https://developer.apple.com/documentation/security/notarizing_your_app_before_distribution?language=objc [ZFSImage]: https://raw.githubusercontent.com/cbreak-black/ZetaWatch/master/doc/ZetaWatch.jpg [SparkleFramework]: https://sparkle-project.org/ [SparkleGithub]: https://github.com/sparkle-project/Sparkle [AuthorizationServices]: https://developer.apple.com/documentation/security/authorization_services?language=objc [Managing the Authorization Database in OS X Mavericks]: https://derflounder.wordpress.com/2014/02/16/managing-the-authorization-database-in-os-x-mavericks/ ================================================ FILE: ZetaAuthorizationHelper/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleIdentifier net.the-color-black.ZetaAuthorizationHelper CFBundleInfoDictionaryVersion 6.0 CFBundleName ZetaAuthorizationHelper CFBundleVersion $(ZETA_VERSION) SMAuthorizedClients anchor apple generic and identifier "net.the-color-black.ZetaWatch" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "8THUW5GT6P") ================================================ FILE: ZetaAuthorizationHelper/Launchd.plist ================================================ Label net.the-color-black.ZetaAuthorizationHelper MachServices net.the-color-black.ZetaAuthorizationHelper ================================================ FILE: ZetaAuthorizationHelper/ZetaAuthorizationHelper.h ================================================ // // ZetaAuthorizationHelper.h // ZetaAuthorizationHelper // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #import @interface ZetaAuthorizationHelper : NSObject - (id)init; - (void)run; @end ================================================ FILE: ZetaAuthorizationHelper/ZetaAuthorizationHelper.mm ================================================ // // ZetaAuthorizationHelper.m // ZetaAuthorizationHelper // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #import "ZetaAuthorizationHelper.h" #import "ZetaAuthorizationHelperProtocol.h" #import "CommonAuthorization.h" #include "ZFSWrapper/ZFSUtils.hpp" #include "ZetaCPPUtils.hpp" @interface ZetaAuthorizationHelper () { bool shouldRun; } @property (atomic, strong, readwrite) NSXPCListener * listener; @end NSMutableArray * toArray(std::vector const & strings) { NSMutableArray * array = [[NSMutableArray alloc] initWithCapacity:strings.size()]; for (auto const & s : strings) { [array addObject:[NSString stringWithUTF8String:s.c_str()]]; } return array; } std::vector fromArray(NSArray * array) { std::vector vec; for (NSString * string in array) { vec.push_back([string UTF8String]); } return vec; } @implementation ZetaAuthorizationHelper - (id)init { self = [super init]; if (self != nil) { shouldRun = true; // Set up our XPC listener to handle requests on our Mach service. self->_listener = [[NSXPCListener alloc] initWithMachServiceName:kHelperToolMachServiceName]; self->_listener.delegate = self; } return self; } - (void)run { // Tell the XPC listener to start processing requests. [self.listener resume]; // Run the run loop until it's time to terminate. bool runLoopSuccess = true; while (shouldRun && runLoopSuccess) { runLoopSuccess = [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } } - (void)stop { shouldRun = false; [self.listener invalidate]; // Stop the run loop CFRunLoopStop(CFRunLoopGetMain()); } - (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection { assert(listener == self.listener); assert(newConnection != nil); newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ZetaAuthorizationHelperProtocol)]; newConnection.exportedObject = self; [newConnection resume]; return YES; } NSError * checkAuthorization(NSData * authData, SEL command) { NSError * error = nil; AuthorizationRef authRef = NULL; assert(command != nil); // First check that authData looks reasonable. error = nil; if ((authData == nil) || ([authData length] != sizeof(AuthorizationExternalForm))) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil]; return error; } // Create an authorization ref from that the external form data contained within. auto extForm = static_cast([authData bytes]); OSStatus err = AuthorizationCreateFromExternalForm(extForm, &authRef); // Authorize the right associated with the command. if (err == errAuthorizationSuccess) { AuthorizationItem oneRight = { NULL, 0, NULL, 0 }; AuthorizationRights rights = { 1, &oneRight }; auto right = [CommonAuthorization authorizationRightForCommand:command]; if (!right) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:paramErr userInfo:nil]; return error; } oneRight.name = [right UTF8String]; err = AuthorizationCopyRights(authRef, &rights, NULL, kAuthorizationFlagExtendRights | kAuthorizationFlagInteractionAllowed, NULL); } if (err != errAuthorizationSuccess) { error = [NSError errorWithDomain:NSOSStatusErrorDomain code:err userInfo:nil]; } if (authRef != NULL) { OSStatus junk = AuthorizationFree(authRef, 0); assert(junk == errAuthorizationSuccess); } return error; } - (void)getVersionWithReply:(void (^)(NSError * error, NSString *))reply { reply(nil, [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]); } // Checks authorization, and handles c++ exceptions by forwarding them to the // caller. template void processWithExceptionForwarding(NSData * authData, SEL command, R reply, C callable) { NSError * error = checkAuthorization(authData, command); if (error) { reply(error); return; } try { callable(); } catch (std::exception const & e) { reply([NSError errorWithDomain:@"ZFSException" code:-1 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:e.what()]}]); } } - (void)stopHelperWithAuthorization:(NSData *)authData withReply:(void (^)(NSError *))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { // Acknowledge receipt of stop request, performed asyncronously reply(nullptr); [self stop]; }); } - (void)importPools:(NSDictionary *)importData authorization:(NSData *)authData withReply:(void (^)(NSError *))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { std::vector failures; NSNumber * pool = [importData objectForKey:@"poolGUID"]; zfs::LibZFSHandle::ImportProps props; if (id ar = [importData objectForKey:@"altroot"]) props.altroot.assign([ar UTF8String]); if (id aidm = [importData objectForKey:@"allowHostIDMismatch"]) props.allowHostIDMismatch = [aidm boolValue]; if (id auh = [importData objectForKey:@"allowUnhealthy"]) props.allowUnhealthy = [auh boolValue]; if (id ro = [importData objectForKey:@"readOnly"]) props.readOnly = [ro boolValue]; if (id spo = [importData objectForKey:@"searchPathOverride"]) props.searchPathOverride = fromArray(spo); std::vector importedPools; zfs::LibZFSHandle zfs; if (pool != nil) { importedPools.emplace_back(zfs.import([pool unsignedLongLongValue], props)); } else { importedPools = zfs.importAllPools(props); } if (failures.empty()) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String: formatForHumans(failures).c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)importablePools:(NSDictionary *)importData authorization:(NSData *)authData withReply:(void (^)(NSError *, NSArray *))reply { NSError * error = checkAuthorization(authData, _cmd); if (error) { reply(error, nullptr); return; } try { zfs::LibZFSHandle zfs; std::vector searchPathOverride; if (id spo = [importData objectForKey:@"searchPathOverride"]) searchPathOverride = fromArray(spo); auto pools = zfs.importablePools(searchPathOverride); NSMutableArray * poolsArray = [[NSMutableArray alloc] initWithCapacity:pools.size()]; for (auto const & pool : pools) { NSString * name = [NSString stringWithUTF8String:pool.name.c_str()]; NSNumber * guid = [NSNumber numberWithUnsignedLongLong:pool.guid]; NSNumber * status = [NSNumber numberWithUnsignedLongLong:pool.status]; NSMutableArray * deviceArray = toArray(pool.devices); NSDictionary * poolDict = @{@"name": name, @"guid": guid, @"status": status, @"devices": deviceArray}; [poolsArray addObject:poolDict]; } reply(nullptr, poolsArray); } catch (std::exception const & e) { reply([NSError errorWithDomain:@"ZFSException" code:-1 userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithUTF8String:e.what()]}], nullptr); } } - (void)exportPools:(NSDictionary *)exportData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * poolName = [exportData objectForKey:@"pool"]; bool force = false; if (id o = [exportData objectForKey:@"force"]) force = [o boolValue]; if (!poolName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto pool = zfs.pool(std::string(poolName.UTF8String)); // Export Pool pool.exportPool(force); reply(nullptr); }); } - (void)mountFilesystems:(NSDictionary *)mountData authorization:(NSData *)authData withReply:(void (^)(NSError *))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [mountData objectForKey:@"filesystem"]; bool recursive = false; if (id o = [mountData objectForKey:@"recursive"]) recursive = [o boolValue]; if (!fsName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); int ret = 0; if (recursive) ret = fs.mountRecursive(); else ret = fs.mount(); if (ret == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String: zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)unmountFilesystems:(NSDictionary *)mountData authorization:(NSData *)authData withReply:(void (^)(NSError *))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [mountData objectForKey:@"filesystem"]; bool force = false; if (id o = [mountData objectForKey:@"force"]) force = [o boolValue]; bool recursive = false; if (id o = [mountData objectForKey:@"recursive"]) recursive = [o boolValue]; if (!fsName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); int ret = 0; if (recursive) ret = fs.unmountRecursive(force); else ret = fs.unmount(); if (ret == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String: zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)snapshotFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [fsData objectForKey:@"filesystem"]; NSString * snapName = [fsData objectForKey:@"snapshot"]; bool recursive = false; if (id o = [fsData objectForKey:@"recursive"]) recursive = [o boolValue]; if (!fsName || !snapName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); auto ret = fs.snapshot([snapName UTF8String], recursive); if (ret == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)rollbackFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * snapName = [fsData objectForKey:@"snapshot"]; bool force = false; if (id o = [fsData objectForKey:@"force"]) force = [o boolValue]; if (!snapName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto snap = zfs.filesystem([snapName UTF8String]); auto res = snap.rollback(force); if (res == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)cloneSnapshot:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * snapName = [fsData objectForKey:@"snapshot"]; NSString * newFSName = [fsData objectForKey:@"newFilesystem"]; if (!snapName || !newFSName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; std::string newFSNameStr = [newFSName UTF8String]; auto snap = zfs.filesystem([snapName UTF8String]); if (snap.clone(newFSNameStr) == 0) { auto newFS = zfs.filesystem(newFSNameStr); if (newFS.mount() == 0) { reply(nullptr); return; } } NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); }); } - (void)createFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * newFSName = [fsData objectForKey:@"filesystem"]; NSString * mountpoint = [fsData objectForKey:@"mountpoint"]; if (!newFSName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; std::string newFSNameStr = [newFSName UTF8String]; std::string mountpointStr = mountpoint ? [mountpoint UTF8String] : ""; if (zfs.createFilesystem(newFSNameStr, mountpointStr) == 0) { auto newFS = zfs.filesystem(newFSNameStr); if (newFS.mount() == 0) { reply(nullptr); return; } } NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); }); } - (void)createVolume:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * newFSName = [fsData objectForKey:@"filesystem"]; NSNumber * size = [fsData objectForKey:@"size"]; NSNumber * blocksize = [fsData objectForKey:@"blocksize"]; if (!newFSName || size == nullptr) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; std::string newFSNameStr = [newFSName UTF8String]; auto s = [size unsignedLongLongValue]; auto bs = blocksize != nullptr ? [blocksize unsignedLongLongValue] : 0; if (zfs.createVolume(newFSNameStr, s, bs) == 0) { reply(nullptr); return; } NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); }); } - (void)destroy:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [fsData objectForKey:@"filesystem"]; bool recursive = false; if (id o = [fsData objectForKey:@"recursive"]) recursive = [o boolValue]; bool force = false; if (id o = [fsData objectForKey:@"force"]) force = [o boolValue]; if (!fsName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); int ret = 0; if (recursive) { ret = fs.destroyRecursive(force); } else { auto dependents = fs.dependents(); if (dependents.empty()) { ret = fs.destroy(force); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: @"Filesystem has Dependents" }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); return; } } if (ret == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)loadKeyForFilesystem:(NSDictionary *)loadData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [loadData objectForKey:@"filesystem"]; NSString * key = [loadData objectForKey:@"key"]; if (!fsName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); int ret = 0; if (key) { ret = fs.loadKey([key UTF8String]); } else if (fs.keyLocation() == zfs::ZFileSystem::KeyLocation::uri) { ret = fs.loadKeyFile(); } else { reply([NSError errorWithDomain:@"ZFSKeyError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Key"}]); return; } if (ret) { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSKeyError" code:-1 userInfo:userInfo]); return; } // Encryption Root Filesystem and contained filesystems recursively ret = fs.automountRecursive(); if (ret) { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String:zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } reply(nullptr); }); } - (void)unloadKeyForFilesystem:(NSDictionary *)unloadData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * fsName = [unloadData objectForKey:@"filesystem"]; if (!fsName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto fs = zfs.filesystem([fsName UTF8String]); auto ret = fs.unloadKey(); if (ret == 0) { reply(nullptr); } else { NSDictionary * userInfo = @{ NSLocalizedDescriptionKey: [NSString stringWithUTF8String: zfs.lastError().c_str()] }; reply([NSError errorWithDomain:@"ZFSError" code:-1 userInfo:userInfo]); } }); } - (void)scrubPool:(NSDictionary *)poolData authorization:(NSData *)authData withReply:(void (^)(NSError *))reply { processWithExceptionForwarding(authData, _cmd, reply, [=]() { NSString * poolName = [poolData objectForKey:@"pool"]; NSString * command = [poolData objectForKey:@"command"]; if (!poolName) { reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Missing Arguments"}]); return; } zfs::LibZFSHandle zfs; auto pool = zfs.pool(std::string(poolName.UTF8String)); if (command) { if ([command isEqualToString:@"stop"]) pool.scrubStop(); else if ([command isEqualToString:@"pause"]) pool.scrubPause(); // No other commands are supported else reply([NSError errorWithDomain:@"ZFSArgError" code:-1 userInfo:@{NSLocalizedDescriptionKey: @"Invalid Scrub Command"}]); } else { pool.scrub(); } reply(nullptr); }); } @end ================================================ FILE: ZetaAuthorizationHelper/ZetaAuthorizationHelperProtocol.h ================================================ // // ZetaAuthorizationHelperProtocol.h // ZetaWatch // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #define kHelperToolMachServiceName @"net.the-color-black.ZetaAuthorizationHelper" @protocol ZetaAuthorizationHelperProtocol @required - (void)getVersionWithReply:(void(^)(NSError * error, NSString * version))reply; - (void)stopHelperWithAuthorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)importPools:(NSDictionary *)importData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)importablePools:(NSDictionary *)importData authorization:(NSData*)authData withReply:(void(^)(NSError * error, NSArray * importablePools))reply; - (void)exportPools:(NSDictionary *)exportData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)mountFilesystems:(NSDictionary *)mountData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)unmountFilesystems:(NSDictionary *)mountData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)snapshotFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)rollbackFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)cloneSnapshot:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)createFilesystem:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)createVolume:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)destroy:(NSDictionary *)fsData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)loadKeyForFilesystem:(NSDictionary *)loadData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)unloadKeyForFilesystem:(NSDictionary *)unloadData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; - (void)scrubPool:(NSDictionary *)poolData authorization:(NSData *)authData withReply:(void(^)(NSError * error))reply; @end ================================================ FILE: ZetaAuthorizationHelper/ZetaCPPUtils.hpp ================================================ // // ZetaCPPUtils.hpp // ZetaWatch // // Created by cbreak on 19.07.28. // Copyright © 2019 the-color-black.net. All rights reserved. // #ifndef ZetaCPPUtils_h #define ZetaCPPUtils_h #include "ZFSWrapper/ZFSUtils.hpp" #include #include template inline std::string formatForHumans(std::vector const & things) { if (things.empty()) return std::string(); std::stringstream ss; ss << things[0]; for (size_t i = 1; i < things.size(); ++i) { ss << ", " << things[i]; } return ss.str(); } #endif /* ZetaCPPUtils_h */ ================================================ FILE: ZetaAuthorizationHelper/main.m ================================================ // // main.m // ZetaAuthorizationHelper // // Created by cbreak on 18.01.01. // Copyright © 2018 the-color-black.net. All rights reserved. // #include "ZetaAuthorizationHelper.h" #import int main(int argc, const char * argv[]) { @autoreleasepool { ZetaAuthorizationHelper * helper = [[ZetaAuthorizationHelper alloc] init]; [helper run]; } return 0; } ================================================ FILE: ZetaLoginItemHelper/Info.plist ================================================ CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) NSHumanReadableCopyright Copyright © 2021 the-color-black.net. All rights reserved. NSPrincipalClass NSApplication ================================================ FILE: ZetaLoginItemHelper/main.m ================================================ // // main.m // ZetaLoginItemHelper // // Created by cbreak on 19.11.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #import int main(int argc, const char * argv[]) { // Start the main ZetaWatch application // This trampoline is needed since SMLoginItemSetEnabled only allows adding // helpers to start at login, not the main bundle. // see ZetaWatch/ZetaWatchDelegate.mm for the login item adding code @autoreleasepool { // Start any ZetaWatch without adding a new instance [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:@"net.the-color-black.ZetaWatch" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:0 launchIdentifier:0]; } // Don't exit since ServiceManagement doesn't like it return NSApplicationMain(argc, argv); } ================================================ FILE: ZetaWatch/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "size" : "16x16", "idiom" : "mac", "filename" : "Zetaicoon-16.png", "scale" : "1x" }, { "size" : "16x16", "idiom" : "mac", "filename" : "Zetaicoon-32.png", "scale" : "2x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Zetaicoon-32.png", "scale" : "1x" }, { "size" : "32x32", "idiom" : "mac", "filename" : "Zetaicoon-64.png", "scale" : "2x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Zetaicoon-128.png", "scale" : "1x" }, { "size" : "128x128", "idiom" : "mac", "filename" : "Zetaicoon-256.png", "scale" : "2x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Zetaicoon-256.png", "scale" : "1x" }, { "size" : "256x256", "idiom" : "mac", "filename" : "Zetaicoon-512.png", "scale" : "2x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Zetaicoon-512.png", "scale" : "1x" }, { "size" : "512x512", "idiom" : "mac", "filename" : "Zetaicoon-1024.png", "scale" : "2x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ZetaWatch/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ZetaWatch/Assets.xcassets/Zeta.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Zetaicoon.pdf", "scale" : "1x" }, { "idiom" : "universal", "scale" : "2x" }, { "idiom" : "universal", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: ZetaWatch/Base.lproj/MainMenu.xib ================================================ Default Left to Right Right to Left Default Left to Right Right to Left NSAllRomanInputSourcesLocaleIdentifier public.folder PathValueTransformer PathValueTransformer ================================================ FILE: ZetaWatch/Base.lproj/NewFS.xib ================================================ ================================================ FILE: ZetaWatch/Base.lproj/NewVol.xib ================================================ SizeTransformer SizeTransformer ================================================ FILE: ZetaWatch/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIconFile CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(ZETA_VERSION) CFBundleSignature ???? CFBundleVersion $(ZETA_VERSION) LSApplicationCategoryType public.app-category.utilities LSBackgroundOnly LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) LSUIElement NSHumanReadableCopyright Copyright © 2021 the-color-black.net. All rights reserved. NSMainNibFile MainMenu NSPrincipalClass NSApplication NSUSerNotificationAlertStyle alert SMPrivilegedExecutables net.the-color-black.ZetaAuthorizationHelper anchor apple generic and identifier "net.the-color-black.ZetaAuthorizationHelper" and (certificate leaf[field.1.2.840.113635.100.6.1.9] /* exists */ or certificate 1[field.1.2.840.113635.100.6.2.6] /* exists */ and certificate leaf[field.1.2.840.113635.100.6.1.13] /* exists */ and certificate leaf[subject.OU] = "8THUW5GT6P") SUFeedURL https://zetawatch.the-color-black.net/download/2.1/appcast.xml SUPublicEDKey Ed/JNtoVf4G8CX7qffgoFFHmb4303SFsmJ3yh2vAhyc= ================================================ FILE: ZetaWatch/InvariantDisks/IDDiskArbitrationDispatcher.cpp ================================================ // // IDDiskArbitrationDispatcher.cpp // InvariantDisks // // Created by Gerhard Röthlin on 2014.04.27. // Copyright (c) 2014 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #include "IDDiskArbitrationDispatcher.hpp" #include "IDDiskArbitrationHandler.hpp" #include "IDDiskArbitrationUtils.hpp" #include #include #include #include namespace ID { struct DiskArbitrationDispatcher::Impl { std::mutex mutex; std::vector handler; DASessionRef session = nullptr; bool scheduled = false; }; DiskArbitrationDispatcher::DiskArbitrationDispatcher() : m_impl(new Impl) { m_impl->session = DASessionCreate(kCFAllocatorDefault); DARegisterDiskAppearedCallback(m_impl->session, nullptr, [](DADiskRef disk, void * ctx) { static_cast(ctx)->diskAppeared(disk); }, this); DARegisterDiskDisappearedCallback(m_impl->session, nullptr, [](DADiskRef disk, void * ctx) { static_cast(ctx)->diskDisappeared(disk); }, this); } DiskArbitrationDispatcher::~DiskArbitrationDispatcher() { stop(); CFRelease(m_impl->session); } void DiskArbitrationDispatcher::addHandler(Handler handler) { std::lock_guard lock(m_impl->mutex); m_impl->handler.push_back(std::move(handler)); } void DiskArbitrationDispatcher::removeHandler(Handler const & handler) { std::lock_guard lock(m_impl->mutex); m_impl->handler.erase(std::find(m_impl->handler.begin(), m_impl->handler.end(), handler), m_impl->handler.end()); } void DiskArbitrationDispatcher::clearHandler() { std::lock_guard lock(m_impl->mutex); m_impl->handler.clear(); } void DiskArbitrationDispatcher::start() { std::lock_guard lock(m_impl->mutex); if (!m_impl->scheduled) { DASessionScheduleWithRunLoop(m_impl->session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); m_impl->scheduled = true; } } void DiskArbitrationDispatcher::stop() { std::lock_guard lock(m_impl->mutex); if (m_impl->scheduled) { DASessionUnscheduleFromRunLoop(m_impl->session, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); m_impl->scheduled = false; } } void DiskArbitrationDispatcher::diskAppeared(DADiskRef disk) const { DiskInformation info = getDiskInformation(disk); std::lock_guard lock(m_impl->mutex); for (auto const & handler: m_impl->handler) handler->diskAppeared(disk, info); } void DiskArbitrationDispatcher::diskDisappeared(DADiskRef disk) const { DiskInformation info = getDiskInformation(disk); std::lock_guard lock(m_impl->mutex); for (auto const & handler: m_impl->handler) handler->diskDisappeared(disk, info); } } ================================================ FILE: ZetaWatch/InvariantDisks/IDDiskArbitrationDispatcher.hpp ================================================ // // IDDiskArbitrationDispatcher.hpp // InvariantDisks // // Created by Gerhard Röthlin on 2014.04.27. // Copyright (c) 2014 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #ifndef ID_DISKARBITRATIONDISPATCHER_HPP #define ID_DISKARBITRATIONDISPATCHER_HPP #include #include namespace ID { class DiskArbitrationHandler; /*! \brief Dispatches DiskArbitration events, wrapper around the DiskArbitration framework */ class DiskArbitrationDispatcher { public: typedef std::shared_ptr Handler; public: DiskArbitrationDispatcher(); ~DiskArbitrationDispatcher(); public: void addHandler(Handler handler); void removeHandler(Handler const & handler); void clearHandler(); public: void start(); void stop(); private: void diskAppeared(DADiskRef disk) const; void diskDisappeared(DADiskRef disk) const; private: struct Impl; std::unique_ptr m_impl; }; } #endif ================================================ FILE: ZetaWatch/InvariantDisks/IDDiskArbitrationHandler.hpp ================================================ // // IDDiskArbitrationHandler.hpp // InvariantDisks // // Created by Gerhard Röthlin on 2014.04.27. // Copyright (c) 2014 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #ifndef ID_DISKARBITRATIONHANDLER_HPP #define ID_DISKARBITRATIONHANDLER_HPP #include namespace ID { class DiskInformation; class DiskArbitrationHandler { public: explicit DiskArbitrationHandler() {} virtual ~DiskArbitrationHandler() = default; public: virtual void diskAppeared(DADiskRef disk, DiskInformation const & info) = 0; virtual void diskDisappeared(DADiskRef disk, DiskInformation const & info) = 0; }; } #endif ================================================ FILE: ZetaWatch/InvariantDisks/IDDiskArbitrationUtils.cpp ================================================ // // IDDiskArbitrationUtils.cpp // InvariantDisks // // Created by Gerhard Röthlin on 2014.04.27. // Copyright (c) 2014 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #include "IDDiskArbitrationUtils.hpp" #include #include namespace ID { std::ostream & operator<<(std::ostream & os, DADiskRef disk) { return os << getDiskInformation(disk); } std::ostream & operator<<(std::ostream & os, DiskInformation const & disk) { return os << "Disk: (\n" << "\tVolumeKind=\"" << disk.volumeKind << "\"\n" << "\tVolumeUUID=\"" << disk.volumeUUID << "\"\n" << "\tVolumeName=\"" << disk.volumeName << "\"\n" << "\tVolumePath=\"" << disk.volumePath << "\"\n" << "\tMediaKind=\"" << disk.mediaKind << "\"\n" << "\tMediaType=\"" << disk.mediaType << "\"\n" << "\tMediaUUID=\"" << disk.mediaUUID << "\"\n" << "\tMediaBSDName=\"" << disk.mediaBSDName << "\"\n" << "\tMediaName=\"" << disk.mediaName << "\"\n" << "\tMediaPath=\"" << disk.mediaPath << "\"\n" << "\tMediaContent=\"" << disk.mediaContent << "\"\n" << "\tMedia(Whole,Leaf,Writable)=(" << disk.mediaWhole << ", " << disk.mediaLeaf << ", " << disk.mediaWritable << ")\n" << "\tDeviceGUID=\"" << disk.deviceGUID << "\"\n" << "\tDevicePath=\"" << disk.devicePath << "\"\n" << "\tDeviceProtocol=\"" << disk.deviceProtocol << "\"\n" << "\tDeviceModel=\"" << disk.deviceModel << "\"\n" << "\tBusName=\"" << disk.busName << "\"\n" << "\tBusPath=\"" << disk.busPath << "\"\n" << "\tIOSerial=\"" << disk.ioSerial << "\"\n" << "\tImagePath=\"" << disk.imagePath << "\"\n" << ")"; } std::string to_string(CFStringRef str) { std::string result; CFRange strRange = CFRangeMake(0, CFStringGetLength(str)); CFIndex strBytes = 0; CFStringGetBytes(str, strRange, kCFStringEncodingUTF8, 0, false, nullptr, 0, &strBytes); if (strBytes > 0) { result.resize(static_cast(strBytes), '\0'); CFStringGetBytes(str, strRange, kCFStringEncodingUTF8, 0, false, reinterpret_cast(&result[0]), strBytes, nullptr); } return result; } std::string to_string(CFURLRef url) { CFStringRef str = CFURLCopyPath(url); std::string result = to_string(str); CFRelease(str); return result; } std::string to_string(CFDataRef data) { char const * bytesBegin = reinterpret_cast(CFDataGetBytePtr(data)); char const * bytesEnd = bytesBegin + CFDataGetLength(data); std::stringstream ss; ss << std::hex; for (char const * byteIt = bytesBegin; byteIt != bytesEnd; ++byteIt) ss << static_cast(*byteIt); return ss.str(); } std::string to_string(CFUUIDRef uuid) { CFStringRef str = CFUUIDCreateString(kCFAllocatorDefault, uuid); std::string result = to_string(str); CFRelease(str); return result; } std::string to_string(CFTypeRef variant) { if (!variant) return std::string(); CFTypeID typeID = CFGetTypeID(variant); if (typeID == CFStringGetTypeID()) return to_string(CFStringRef(variant)); else if (typeID == CFURLGetTypeID()) return to_string(CFURLRef(variant)); else if (typeID == CFDataGetTypeID()) return to_string(CFDataRef(variant)); else if (typeID == CFUUIDGetTypeID()) return to_string(CFUUIDRef(variant)); return std::string(); } std::string interpret_as_string(CFDataRef data) { char const * bytesBegin = reinterpret_cast(CFDataGetBytePtr(data)); char const * bytesEnd = bytesBegin + CFDataGetLength(data); return std::string(bytesBegin, bytesEnd); } template std::string stringFromDictionary(CFDictionaryRef dict, CFStringRef key) { if (T value = static_cast(CFDictionaryGetValue(dict, key))) return to_string(value); return std::string(); } int64_t numberFromDictionary(CFDictionaryRef dict, CFStringRef key) { if (CFNumberRef value = static_cast(CFDictionaryGetValue(dict, key))) { int64_t number = 0; CFNumberGetValue(value, kCFNumberSInt64Type, &number); return number; } return 0; } bool boolFromDictionary(CFDictionaryRef dict, CFStringRef key) { if (CFBooleanRef value = static_cast(CFDictionaryGetValue(dict, key))) { return CFBooleanGetValue(value); } return false; } std::string stringFromIOObjectWithParents(io_object_t ioObject, CFStringRef key) { std::string result; CFTypeRef resultRef = IORegistryEntrySearchCFProperty(ioObject, kIOServicePlane, key, kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); if (resultRef) { result = to_string(resultRef); CFRelease(resultRef); } return result; } std::string serialNumberFromIOObject(io_object_t ioObject) { static CFStringRef const serialStrings[] = { CFSTR("Serial Number"), CFSTR("INQUIRY Unit Serial Number"), CFSTR("USB Serial Number") }; for (CFStringRef serialString: serialStrings) { std::string serial = stringFromIOObjectWithParents(ioObject, serialString); if (!serial.empty()) return serial; } return std::string(); } std::string imagePathFromIOObject(io_object_t ioObject) { std::string path; CFStringRef key = CFSTR("image-path"); CFTypeRef resultRef = IORegistryEntrySearchCFProperty(ioObject, kIOServicePlane, key, kCFAllocatorDefault, kIORegistryIterateRecursively | kIORegistryIterateParents); if (resultRef) { if (CFGetTypeID(resultRef) == CFDataGetTypeID()) { CFDataRef resultDataRef = CFDataRef(resultRef); path = interpret_as_string(resultDataRef); } CFRelease(resultRef); } return path; } static std::string coreStorageMark = "/CoreStoragePhysical/"; DiskInformation getDiskInformation(DADiskRef disk) { DiskInformation info; // DiskArbitration CFDictionaryRef descDict = DADiskCopyDescription(disk); if (!descDict) return info; info.volumeKind = stringFromDictionary(descDict, kDADiskDescriptionVolumeKindKey); info.volumeUUID = stringFromDictionary(descDict, kDADiskDescriptionVolumeUUIDKey); info.volumeName = stringFromDictionary(descDict, kDADiskDescriptionVolumeNameKey); info.volumePath = stringFromDictionary(descDict, kDADiskDescriptionVolumePathKey); info.mediaKind = stringFromDictionary(descDict, kDADiskDescriptionMediaKindKey); info.mediaType = stringFromDictionary(descDict, kDADiskDescriptionMediaTypeKey); info.mediaUUID = stringFromDictionary(descDict, kDADiskDescriptionMediaUUIDKey); info.mediaBSDName = stringFromDictionary(descDict, kDADiskDescriptionMediaBSDNameKey); info.mediaName = stringFromDictionary(descDict, kDADiskDescriptionMediaNameKey); info.mediaPath = stringFromDictionary(descDict, kDADiskDescriptionMediaPathKey); info.mediaContent = stringFromDictionary(descDict, kDADiskDescriptionMediaContentKey); info.mediaWhole = boolFromDictionary(descDict, kDADiskDescriptionMediaWholeKey); info.mediaLeaf = boolFromDictionary(descDict, kDADiskDescriptionMediaLeafKey); info.mediaWritable = boolFromDictionary(descDict, kDADiskDescriptionMediaWritableKey); info.deviceGUID = stringFromDictionary(descDict, kDADiskDescriptionDeviceGUIDKey); info.devicePath = stringFromDictionary(descDict, kDADiskDescriptionDevicePathKey); info.deviceProtocol = stringFromDictionary(descDict, kDADiskDescriptionDeviceProtocolKey); info.deviceModel = stringFromDictionary(descDict, kDADiskDescriptionDeviceModelKey); info.busName = stringFromDictionary(descDict, kDADiskDescriptionBusNameKey); info.busPath = stringFromDictionary(descDict, kDADiskDescriptionBusPathKey); CFRelease(descDict); // IOKit io_service_t io = DADiskCopyIOMedia(disk); info.ioSerial = serialNumberFromIOObject(io); info.imagePath = imagePathFromIOObject(io); CFMutableDictionaryRef ioDict = nullptr; if (IORegistryEntryCreateCFProperties(io, &ioDict, kCFAllocatorDefault, 0) == kIOReturnSuccess) { // TODO: Pick out useful IOKit properties CFRelease(ioDict); } IOObjectRelease(io); // Guess wether this is an actual device bool isCoreStorage = info.mediaPath.find(coreStorageMark) != std::string::npos; bool isVirtual = info.deviceProtocol == kIOPropertyPhysicalInterconnectTypeVirtual; info.isDevice = !isCoreStorage && !isVirtual; return info; } bool isDevice(DiskInformation const & di) { return di.isDevice; } bool isWhole(DiskInformation const & di) { return di.mediaWhole; } std::string partitionSuffix(DiskInformation const & di) { if (!isWhole(di)) { size_t suffixStart = di.mediaBSDName.find_last_not_of("0123456789"); if (suffixStart != std::string::npos && suffixStart+1 < di.mediaBSDName.size() && di.mediaBSDName[suffixStart] == 's') { return ':' + di.mediaBSDName.substr(suffixStart+1); } } return std::string(); } } ================================================ FILE: ZetaWatch/InvariantDisks/IDDiskArbitrationUtils.hpp ================================================ // // IDDiskArbitrationUtils.hpp // InvariantDisks // // Created by Gerhard Röthlin on 2014.04.27. // Copyright (c) 2014 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #ifndef ID_DISKARBITRATIONUTILS_HPP #define ID_DISKARBITRATIONUTILS_HPP #include #include #include namespace ID { struct DiskInformation { std::string volumeKind; std::string volumeUUID; std::string volumeName; std::string volumePath; std::string mediaKind; std::string mediaType; std::string mediaUUID; std::string mediaBSDName; std::string mediaName; std::string mediaPath; std::string mediaContent; bool isDevice; bool mediaWhole; bool mediaLeaf; bool mediaWritable; std::string deviceGUID; std::string devicePath; std::string deviceProtocol; std::string deviceModel; std::string busName; std::string busPath; std::string ioSerial; std::string imagePath; }; DiskInformation getDiskInformation(DADiskRef disk); bool isDevice(DiskInformation const & di); bool isWhole(DiskInformation const & di); std::string partitionSuffix(DiskInformation const & di); std::ostream & operator<<(std::ostream & os, DADiskRef disk); std::ostream & operator<<(std::ostream & os, DiskInformation const & disk); } #endif ================================================ FILE: ZetaWatch/PathValueTransformer.m ================================================ // // PathValueTransformer.m // ZetaWatch // // Created by cbreak on 19.11.08. // Copyright © 2019 the-color-black.net. All rights reserved. // #import /*! \brief Transforms a path URL into a string and back, used for value binding from a xib file. */ @interface PathValueTransformer : NSValueTransformer @end @implementation PathValueTransformer + (Class)transformedValueClass { return [NSString class]; } + (BOOL)allowsReverseTransformation { return YES; } - (id)transformedValue:(id)value { NSString * string = value; return [NSURL URLWithString:string]; } - (id)reverseTransformedValue:(id)value { NSURL * url = value; return [url path]; } - (void)initialize { if (self == [PathValueTransformer self]) { PathValueTransformer * transformer = [[PathValueTransformer alloc] init]; [NSValueTransformer setValueTransformer:transformer forName:@"PathValueTransformer"]; } } @end ================================================ FILE: ZetaWatch/SizeTransformer.mm ================================================ // // SizeTransformer.m // ZetaWatch // // Created by cbreak on 19.11.08. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #include "ZetaFormatHelpers.hpp" /*! \brief Transforms between a string and a byte number, used for value binding from a xib file. */ @interface SizeTransformer : NSValueTransformer @end @implementation SizeTransformer + (Class)transformedValueClass { return [NSString class]; } + (BOOL)allowsReverseTransformation { return YES; } - (id)transformedValue:(id)value { NSNumber * number = value; auto str = formatBytes([number unsignedLongLongValue]); return [NSString stringWithUTF8String:str.c_str()]; } - (id)reverseTransformedValue:(id)value { NSString * string = value; std::size_t bytes; if (parseBytes([string UTF8String], bytes)) { return [NSNumber numberWithUnsignedLongLong:bytes]; } return nullptr; } - (void)initialize { if (self == [SizeTransformer self]) { SizeTransformer * transformer = [[SizeTransformer alloc] init]; [NSValueTransformer setValueTransformer:transformer forName:@"SizeTransformer"]; } } @end ================================================ FILE: ZetaWatch/ZetaAuthorization.h ================================================ // // ZetaAuthoized.h // ZetaWatch // // Created by cbreak on 17.12.31. // Copyright © 2017 the-color-black.net. All rights reserved. // #import @class ZetaNotificationCenter; /*! This class wraps privileged operations that require authorization. This is heavily based on apple's EvenBetterAuthorizationSample https://developer.apple.com/library/content/samplecode/EvenBetterAuthorizationSample/Introduction/Intro.html */ @interface ZetaAuthorization : NSObject //! Call this after the program finished starting -(void)connectToAuthorization; - (void)stopHelper; - (void)importPools:(NSDictionary *)importData withReply:(void(^)(NSError * error))reply; - (void)importablePools:(NSDictionary *)importData withReply:(void(^)(NSError * error, NSArray * importablePools))reply; - (void)exportPools:(NSDictionary *)exportData withReply:(void(^)(NSError * error))reply; - (void)mountFilesystems:(NSDictionary *)mountData withReply:(void(^)(NSError * error))reply; - (void)unmountFilesystems:(NSDictionary *)mountData withReply:(void(^)(NSError * error))reply; - (void)snapshotFilesystem:(NSDictionary *)snapshotData withReply:(void(^)(NSError * error))reply; - (void)rollbackFilesystem:(NSDictionary *)rollbackData withReply:(void(^)(NSError * error))reply; - (void)cloneSnapshot:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply; - (void)createFilesystem:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply; - (void)createVolume:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply; - (void)destroy:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply; - (void)loadKeyForFilesystem:(NSDictionary *)loadData withReply:(void(^)(NSError * error))reply; - (void)unloadKeyForFilesystem:(NSDictionary *)unloadData withReply:(void(^)(NSError * error))reply; - (void)scrubPool:(NSDictionary *)poolData withReply:(void(^)(NSError * error))reply; - (void)executeWhenConnected:(void(^)(id proxy))task onError:(void(^)(NSError * error))handleError; @property (weak) IBOutlet ZetaNotificationCenter * notificationCenter; @end ================================================ FILE: ZetaWatch/ZetaAuthorization.mm ================================================ // // ZetaAuthoized.m // ZetaWatch // // Created by cbreak on 17.12.31. // Copyright © 2017 the-color-black.net. All rights reserved. // #import "ZetaAuthorization.h" #import "ZetaAuthorizationHelperProtocol.h" #import "CommonAuthorization.h" #import "ZetaNotificationCenter.h" #include #include @interface ZetaAuthorization () { AuthorizationRef _authRef; } @property (atomic, copy, readwrite) NSData * authorization; @property (atomic, strong, readwrite) NSXPCConnection * helperToolConnection; @end @implementation ZetaAuthorization - (void)awakeFromNib { [self connectToAuthorization]; [self installIfNeeded]; } -(void)connectToAuthorization { // Create our connection to the authorization system. // // If we can't create an authorization reference then the app is not going // to be able to do anything requiring authorization. Generally this only // happens when you launch the app in some wacky, and typically unsupported, // way. We continue with self->_authRef as NULL, which will cause all // authorized operations to fail. AuthorizationExternalForm extForm = {}; OSStatus err = AuthorizationCreate(NULL, NULL, 0, &self->_authRef); if (err == errAuthorizationSuccess) { err = AuthorizationMakeExternalForm(self->_authRef, &extForm); } if (err == errAuthorizationSuccess) { self.authorization = [[NSData alloc] initWithBytes:&extForm length:sizeof(extForm)]; } // If we successfully connected to Authorization Services, add definitions // for our default rights (unless they're already in the database). if (self->_authRef) { [CommonAuthorization setupAuthorizationRights:self->_authRef]; } } -(void)install { // Install the helper tool into the system location. As of 10.12 this is // /Library/LaunchDaemons/ and /Library/PrivilegedHelperTools/ Boolean success = NO; CFErrorRef error = nil; success = SMJobBless(kSMDomainSystemLaunchd, CFSTR("net.the-color-black.ZetaAuthorizationHelper"), self->_authRef, &error); if (success) { } else { NSLog(@"Error installing helper tool: %@\n", error); CFRelease(error); } } - (void)connectToHelperTool { assert([NSThread isMainThread]); if (self.helperToolConnection == nil) { self.helperToolConnection = [[NSXPCConnection alloc] initWithMachServiceName:kHelperToolMachServiceName options:NSXPCConnectionPrivileged]; self.helperToolConnection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(ZetaAuthorizationHelperProtocol)]; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-retain-cycles" // We can ignore the retain cycle warning because a) the retain taken by the // invalidation handler block is released by us setting it to nil when the block // actually runs, and b) the retain taken by the block passed to -addOperationWithBlock: // will be released when that operation completes and the operation itself is deallocated // (notably self does not have a reference to the NSBlockOperation). self.helperToolConnection.invalidationHandler = ^{ // If the connection gets invalidated then, on the main thread, nil out our // reference to it. This ensures that we attempt to rebuild it the next time around. self.helperToolConnection.invalidationHandler = nil; [[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.helperToolConnection = nil; }]; }; #pragma clang diagnostic pop [self.helperToolConnection resume]; } } - (void)stopHelper { id proxy = [self.helperToolConnection synchronousRemoteObjectProxyWithErrorHandler:^(NSError * error) { // Ignore errors, invalidate connection anyway [self.helperToolConnection invalidate]; self.helperToolConnection = nil; }]; [proxy stopHelperWithAuthorization:self->_authorization withReply:[=](NSError * error) { // Invalidate connection [self.helperToolConnection invalidate]; self.helperToolConnection = nil; }]; } - (void)installIfNeeded { // Ensure that there's a helper tool connection in place. [self connectToHelperTool]; id proxy = [self.helperToolConnection remoteObjectProxyWithErrorHandler: ^(NSError * proxyError) { // Install on proxy error dispatch_async(dispatch_get_main_queue(), ^(){ // Install if there's a proxy error [self install]; }); }]; [proxy getVersionWithReply:^(NSError * error, NSString * helperVersion) { // Install if there's no valid helper version (because of error) if (error) { dispatch_async(dispatch_get_main_queue(), ^(){ [self install]; }); return; } NSString * localVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"]; // Install if the versions don't match exactly if (![localVersion isEqualToString:helperVersion]) { dispatch_async(dispatch_get_main_queue(), ^(){ [self install]; }); return; } }]; } - (void)executeWhenConnected:(void(^)(id proxy))task onError:(void(^)(NSError * error))handleError; { // Ensure that there's a helper tool connection in place. [self connectToHelperTool]; id proxy = [self.helperToolConnection remoteObjectProxyWithErrorHandler:handleError]; task(proxy); } - (void)executeOnProxy:(SEL)selector withData:(NSDictionary *)data withReply:(void(^)(NSError * error))reply withNotification:(ZetaNotification*)notification { [self executeWhenConnected:^(id proxy) { auto block = ^(NSError * error) { [self dispatchReply:^(){ reply(error); [self stopNotification:notification withError:error]; }]; }; auto sig = [proxy methodSignatureForSelector:selector]; auto inv = [NSInvocation invocationWithMethodSignature:sig]; [inv setTarget:proxy]; [inv setSelector:selector]; NSDictionary * dataLoc = data; [inv setArgument:&dataLoc atIndex:2]; [inv setArgument:&self->_authorization atIndex:3]; [inv setArgument:&block atIndex:4]; [inv invoke]; } onError:^(NSError * error) { [self dispatchReply:^(){ reply(error); [self stopNotification:notification withError:error]; }]; }]; } - (void)dispatchReply:(void(^)(void))reply { [self performSelectorOnMainThread:@selector(dispatchReplyMainThread:) withObject:reply waitUntilDone:FALSE]; } - (void)dispatchReplyMainThread:(void(^)(void))reply { reply(); } - (void)getVersionWithReply:(void (^)(NSError * error, NSString *))reply { [self executeWhenConnected:^(id proxy) { [proxy getVersionWithReply:^(NSError * error, NSString * version) { [self dispatchReply:^(){ reply(error, version); }]; }]; } onError:^(NSError * error) { [self dispatchReply:^(){ reply(error, nil); }]; }]; } - (void)importPools:(NSDictionary *)importData withReply:(void(^)(NSError * error))reply { NSString * targetGUID = importData[@"poolGUID"]; NSString * targetName = importData[@"poolName"]; NSString * target; if (targetGUID == nil) target = @"all pools"; else target = [NSString stringWithFormat:@"%@ (%@)", targetName, targetGUID]; ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Importing", @"Importing Action") withTarget:target]; [self executeOnProxy:@selector(importPools:authorization:withReply:) withData:importData withReply:reply withNotification:notification]; } - (void)importablePools:(NSDictionary *)importData withReply:(void(^)(NSError * error, NSArray * importablePools))reply { [self executeWhenConnected:^(id proxy) { [proxy importablePools:importData authorization:self.authorization withReply:^(NSError * error, NSArray * importablePools) { [self dispatchReply:^(){ reply(error, importablePools); }]; }]; } onError:^(NSError * error) { [self dispatchReply:^(){ reply(error, nil); }]; }]; } - (void)exportPools:(NSDictionary *)exportData withReply:(void(^)(NSError * error))reply { NSString * target = exportData[@"pool"]; if (target == nil) target = NSLocalizedString(@"all pools", @"All Pools"); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Exporting", @"Exporting Action") withTarget:target]; [self executeOnProxy:@selector(exportPools:authorization:withReply:) withData:exportData withReply:reply withNotification:notification]; } - (void)mountFilesystems:(NSDictionary *)mountData withReply:(void(^)(NSError * error))reply { NSString * target = mountData[@"filesystem"]; if (target == nil) target = NSLocalizedString(@"all filesystems", @"All Filesystems"); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Mounting", @"Mounting Action") withTarget:target]; [self executeOnProxy:@selector(mountFilesystems:authorization:withReply:) withData:mountData withReply:reply withNotification:notification]; } - (void)unmountFilesystems:(NSDictionary *)mountData withReply:(void(^)(NSError * error))reply { NSString * target = mountData[@"filesystem"]; if (target == nil) target = NSLocalizedString(@"all filesystems", @"All Filesystems"); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Unmounting", @"Unmounting Action") withTarget:target]; [self executeOnProxy:@selector(unmountFilesystems:authorization:withReply:) withData:mountData withReply:reply withNotification:notification]; } - (void)snapshotFilesystem:(NSDictionary *)snapshotData withReply:(void(^)(NSError * error))reply { NSString * filesystem = snapshotData[@"filesystem"]; if (filesystem == nil) std::logic_error("Missing required parameter \"filesystem\""); NSString * snapshot = snapshotData[@"snapshot"]; if (snapshot == nil) std::logic_error("Missing required parameter \"snapshot\""); NSString * target = [NSString stringWithFormat:@"%@@%@", filesystem, snapshot]; ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Snapshotting", @"Snapshot Action") withTarget:target]; [self executeOnProxy:@selector(snapshotFilesystem:authorization:withReply:) withData:snapshotData withReply:reply withNotification:notification]; } - (void)rollbackFilesystem:(NSDictionary *)rollbackData withReply:(void(^)(NSError * error))reply { NSString * target = rollbackData[@"snapshot"]; if (target == nil) std::logic_error("Missing required parameter \"snapshot\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Rolling back", @"Rollback Action") withTarget:target]; [self executeOnProxy:@selector(rollbackFilesystem:authorization:withReply:) withData:rollbackData withReply:reply withNotification:notification]; } - (void)cloneSnapshot:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply { NSString * target = fsData[@"snapshot"]; if (target == nil) std::logic_error("Missing required parameter \"snapshot\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Cloning", @"Cloning Action") withTarget:target]; [self executeOnProxy:@selector(cloneSnapshot:authorization:withReply:) withData:fsData withReply:reply withNotification:notification]; } - (void)createFilesystem:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply { NSString * target = fsData[@"filesystem"]; if (target == nil) std::logic_error("Missing required parameter \"filesystem\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Creating", @"Create Action") withTarget:target]; [self executeOnProxy:@selector(createFilesystem:authorization:withReply:) withData:fsData withReply:reply withNotification:notification]; } - (void)createVolume:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply { NSString * target = fsData[@"filesystem"]; if (target == nil) std::logic_error("Missing required parameter \"filesystem\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Creating", @"Create Action") withTarget:target]; [self executeOnProxy:@selector(createVolume:authorization:withReply:) withData:fsData withReply:reply withNotification:notification]; } - (void)destroy:(NSDictionary *)fsData withReply:(void(^)(NSError * error))reply { NSString * target = fsData[@"filesystem"]; if (target == nil) std::logic_error("Missing required parameter \"filesystem\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Destroying", @"Destroy Action") withTarget:target]; [self executeOnProxy:@selector(destroy:authorization:withReply:) withData:fsData withReply:reply withNotification:notification]; } - (void)loadKeyForFilesystem:(NSDictionary *)data withReply:(void(^)(NSError * error))reply { NSString * target = data[@"filesystem"]; if (target == nil) std::logic_error("Missing required parameter \"filesystem\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Loading Key for", @"LoadKey Action") withTarget:target]; [self executeOnProxy:@selector(loadKeyForFilesystem:authorization:withReply:) withData:data withReply:reply withNotification:notification]; } - (void)unloadKeyForFilesystem:(NSDictionary *)data withReply:(void(^)(NSError * error))reply { NSString * target = data[@"filesystem"]; if (target == nil) std::logic_error("Missing required parameter \"filesystem\""); ZetaNotification * notification = [self startNotificationForAction: NSLocalizedString(@"Unloading Key for", @"UnloadKey Action") withTarget:target]; [self executeOnProxy:@selector(unloadKeyForFilesystem:authorization:withReply:) withData:data withReply:reply withNotification:notification]; } - (void)scrubPool:(NSDictionary *)poolData withReply:(void(^)(NSError * error))reply { [self executeOnProxy:@selector(scrubPool:authorization:withReply:) withData:poolData withReply:reply withNotification:nullptr]; } - (ZetaNotification*)startNotificationForAction:(NSString*)action withTarget:(NSString*)target { NSString * titleFormat = NSLocalizedString(@"%@ %@", @"Helper Status notification Title Format"); NSString * title = [NSString stringWithFormat:titleFormat, action, target]; return [self.notificationCenter startAction:title]; } - (void)stopNotification:(ZetaNotification*)notification withError:(NSError*)error { if (notification) { [self.notificationCenter stopAction:notification withError:error]; } } @end ================================================ FILE: ZetaWatch/ZetaAutoImporter.h ================================================ // // ZetaAutoImporter.h // ZetaWatch // // Created by cbreak on 19.08.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import "ZetaCommanderBase.h" #import "ZetaPoolWatcher.h" #include "ZFSUtils.hpp" #include @interface ZetaAutoImporter : ZetaCommanderBase - (id)init; @property (readonly) std::vector const & importablePools; @end ================================================ FILE: ZetaWatch/ZetaAutoImporter.mm ================================================ // // ZetaIDWatcher.cpp // ZetaWatch // // Created by cbreak on 19.08.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaAutoImporter.h" #include "IDDiskArbitrationDispatcher.hpp" #include "IDDiskArbitrationHandler.hpp" #include "IDDiskArbitrationUtils.hpp" #include #include #include #include /*! If Auto-Import is configured: Discovered importable pools get classified into known and new pools. Pools that have not been seen recently are importable and are imported automatically. This set prevents attempting to import a pool multiple times in case of an error. Pools that were imported previously are added to the knownPools set and are not imported. They are ignored until one of their underlying devices disappears. */ @interface ZetaAutoImporter () { // ID ID::DiskArbitrationDispatcher _idDispatcher; NSTimer * checkTimer; // Management std::vector _importable; std::vector _importedBefore; } - (void)scheduleChecking; - (void)handleDisappearedDevice:(ID::DiskInformation const &)info; @end class ZetaIDHandler : public ID::DiskArbitrationHandler { public: ZetaIDHandler(ZetaAutoImporter * watcher) : watcher(watcher) { scheduleChecking(); } public: virtual void diskAppeared(DADiskRef disk, ID::DiskInformation const & info) { scheduleChecking(); } virtual void diskDisappeared(DADiskRef disk, ID::DiskInformation const & info) { scheduleChecking(); [watcher handleDisappearedDevice:info]; } private: void scheduleChecking() { [watcher scheduleChecking]; } private: ZetaAutoImporter __weak * watcher; }; @implementation ZetaAutoImporter - (id)init { if (self = [super init]) { // ID _idDispatcher.addHandler(std::make_shared(self)); _idDispatcher.start(); // Auto-Import handling [self seedKnownPools]; } return self; } - (void)dealloc { _idDispatcher.stop(); } - (void)scheduleChecking { if (checkTimer && [checkTimer isValid]) { return; } checkTimer = [NSTimer timerWithTimeInterval:4 target:self selector:@selector(checkForImportablePools) userInfo:nil repeats:NO]; checkTimer.tolerance = 2; [[NSRunLoop currentRunLoop] addTimer:checkTimer forMode:NSDefaultRunLoopMode]; } - (void)handleDisappearedDevice:(ID::DiskInformation const &)info { if (info.mediaBSDName.empty()) return; std::string devicePath = "/dev/" + info.mediaBSDName; // Forget pools that were once importable but now are no longer since at // least one device was removed _importedBefore.erase( std::remove_if(_importedBefore.begin(), _importedBefore.end(), [&](zfs::ImportablePool const & pool) { auto const & devices = pool.devices; return std::find(devices.begin(), devices.end(), devicePath) != devices.end(); }), _importedBefore.end()); } - (void)checkForImportablePools { auto defaults = [NSUserDefaults standardUserDefaults]; NSMutableDictionary * importData = [[NSMutableDictionary alloc] init]; if (auto spo = [defaults arrayForKey:@"searchPathOverride"]) { [importData setObject:spo forKey:@"searchPathOverride"]; } [_authorization importablePools:importData withReply: ^(NSError * error, NSArray * importablePools) { if (error) [self notifyErrorFromHelper:error]; else [self handleImportablePools:importablePools]; }]; } std::vector arrayToStringVec(NSArray * stringArray) { std::vector strings; for (NSString * string in stringArray) { strings.push_back([string UTF8String]); } return strings; } std::vector arrayToPoolVec(NSArray * poolsArray) { std::vector pools; for (NSDictionary * poolDict in poolsArray) { pools.push_back({ [poolDict[@"name"] UTF8String], [poolDict[@"guid"] unsignedLongLongValue], [poolDict[@"status"] unsignedLongLongValue], arrayToStringVec(poolDict[@"devices"]), }); } std::sort(pools.begin(), pools.end()); return pools; } - (void)seedKnownPools { zfs::LibZFSHandle lib; std::vector knownPools; for (auto const & pool : lib.pools()) { knownPools.push_back({ pool.name(), pool.guid(), pool.status(), lib.devicesFromPoolConfig(pool.config()), }); } std::sort(knownPools.begin(), knownPools.end()); _importedBefore = std::move(knownPools); } - (void)handleImportablePools:(NSArray*)importablePools { std::vector importableCurrent = arrayToPoolVec(importablePools); // Find the pools that had not been imported before, for auto import std::vector importableNew; std::set_difference(importableCurrent.begin(), importableCurrent.end(), _importedBefore.begin(), _importedBefore.end(), std::back_inserter(importableNew)); auto importedPools = [self handleNewImportablePools:importableNew]; // Aggregate all known pools to prevent double-auto import std::vector importedBefore; std::set_union(_importedBefore.begin(), _importedBefore.end(), importedPools.begin(), importedPools.end(), std::back_inserter(importedBefore)); // Update currently importable pools collection _importable = std::move(importableCurrent); _importedBefore = std::move(importedBefore); } - (std::vector)handleNewImportablePools:(std::vector const &)importableNew { std::vector importedPools; auto defaults = [NSUserDefaults standardUserDefaults]; bool allowHostIDMismatch = [defaults boolForKey:@"allowHostIDMismatch"]; if ([defaults boolForKey:@"autoImport"]) { for (auto const & pool : importableNew) { if (!zfs::healthy(pool.status, allowHostIDMismatch)) continue; // skip pools that aren't healthy NSNumber * guid = [NSNumber numberWithUnsignedLongLong:pool.guid]; NSString * name = [NSString stringWithUTF8String:pool.name.c_str()]; NSString * title = [NSString stringWithFormat: NSLocalizedString(@"Auto-importing %@", @"Pool AutoImport short format"), name]; NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Auto-importing Pool %@ (%@)", @"Pool AutoImport format"), name, guid]; [self notifySuccessWithTitle:title text:text]; NSDictionary * poolDict = @{ @"poolGUID": guid, @"poolName": name}; NSMutableDictionary * mutablePool = [poolDict mutableCopy]; [mutablePool setValue:[NSNumber numberWithBool:allowHostIDMismatch] forKey:@"allowHostIDMismatch"]; if ([defaults boolForKey:@"useAltroot"]) { [mutablePool setObject:[defaults stringForKey:@"defaultAltroot"] forKey:@"altroot"]; } if (auto spo = [defaults arrayForKey:@"searchPathOverride"]) { [mutablePool setObject:spo forKey:@"searchPathOverride"]; } [_authorization importPools:mutablePool withReply:^(NSError * error) { [self handlePoolImportReply:error forPool:poolDict]; }]; importedPools.push_back(pool); } } return importedPools; } - (void)handlePoolImportReply:(NSError*)error forPool:(NSDictionary*)pool { if (error) { [self notifyErrorFromHelper:error]; } else { NSString * title = [NSString stringWithFormat: NSLocalizedString(@"Pool %@ auto-imported", @"Pool AutoImport Success short format"), pool[@"poolName"]]; NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Pool %@ (%@) auto-imported", @"Pool AutoImport Success format"), pool[@"poolName"], pool[@"poolGUID"]]; [self notifySuccessWithTitle:title text:text]; } } - (std::vector const &)importablePools { return _importable; } @end ================================================ FILE: ZetaWatch/ZetaBookmarkMenu.h ================================================ // // ZetaBookmarkMenu.h // ZetaWatch // // Created by cbreak on 19.12.14. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import "ZetaCommanderBase.h" #include "ZFSUtils.hpp" NS_ASSUME_NONNULL_BEGIN @class ZetaMainMenu; @interface ZetaBookmarkMenu : ZetaCommanderBase - (id)initWithFileSystem:(zfs::ZFileSystem)fs delegate:(ZetaMainMenu*)main; - (void)menuNeedsUpdate:(NSMenu*)menu; @end NS_ASSUME_NONNULL_END ================================================ FILE: ZetaWatch/ZetaBookmarkMenu.mm ================================================ // // ZetaBookmarkMenu.m // ZetaWatch // // Created by cbreak on 19.12.14. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaBookmarkMenu.h" #import "ZetaMainMenu.h" @implementation ZetaBookmarkMenu { zfs::ZFileSystem _fs; ZetaMainMenu __weak * _delegate; } - (id)initWithFileSystem:(zfs::ZFileSystem)fs delegate:(ZetaMainMenu*)delegate { if (self = [super init]) { _fs = std::move(fs); _delegate = delegate; } return self; } NSMenuItem * createBookmarkMenu(zfs::ZFileSystem const & bookmark, ZetaMainMenu * delegate) { NSMenu * bMenu = [[NSMenu alloc] init]; [bMenu setAutoenablesItems:NO]; NSString * bName = [NSString stringWithUTF8String:bookmark.name()]; auto addBookmarkCommand = [&](NSString * title, SEL selector) { auto item = [bMenu addItemWithTitle:title action:selector keyEquivalent:@""]; item.representedObject = bName; item.target = delegate; }; addBookmarkCommand(NSLocalizedString(@"Destroy", @"Destroy"), @selector(destroy:)); auto item = [[NSMenuItem alloc] initWithTitle:bName action:nullptr keyEquivalent:@""]; item.representedObject = bName; item.submenu = bMenu; return item; } - (void)menuNeedsUpdate:(NSMenu*)menu { [menu removeAllItems]; auto bookmarks = _fs.bookmarks(); if (!bookmarks.empty()) { for (size_t i = bookmarks.size(); i > 0; --i) { NSMenuItem * item = createBookmarkMenu(bookmarks[i-1], _delegate); [menu addItem:item]; } } else { [menu addItemWithTitle:NSLocalizedString(@"No bookmarks found", @"No Bookmarks") action:NULL keyEquivalent:@""]; } } @end ================================================ FILE: ZetaWatch/ZetaCommanderBase.h ================================================ // // ZetaCommanderBase.h // ZetaWatch // // Created by cbreak on 19.06.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #ifndef ZetaMenuBase_h #define ZetaMenuBase_h #import #import "ZetaAuthorization.h" #include "ZetaFormatHelpers.hpp" @interface ZetaCommanderBase : NSObject { IBOutlet ZetaAuthorization * _authorization; } - (void)notifySuccessWithTitle:(NSString*)title text:(NSString*)text; - (void)notifyErrorFromHelper:(NSError*)error; - (IBAction)copyRepresentedObject:(id)sender; @end // C++ Variadic Templates and Objective-C Vararg functions don't work well together inline NSString * formatNSString(NSString * format) { return format; } template NSString * formatNSString(NSString * format, T const & t) { return [NSString stringWithFormat:format, toFormatable(t)]; } template NSString * formatNSString(NSString * format, T const & t, U const & u) { return [NSString stringWithFormat:format, toFormatable(t), toFormatable(u)]; } template NSString * formatNSString(NSString * format, T const & t, U const & u, V const & v) { return [NSString stringWithFormat:format, toFormatable(t), toFormatable(u), toFormatable(v)]; } template NSMenuItem * addMenuItem(NSMenu * menu, ZetaCommanderBase * delegate, NSString * format, T const & ... t) { auto title = formatNSString(format, t...); auto item = [menu addItemWithTitle:title action:@selector(copyRepresentedObject:) keyEquivalent:@""]; item.representedObject = title; item.target = delegate; return item; } #endif /* ZetaMenuBase_h */ ================================================ FILE: ZetaWatch/ZetaCommanderBase.mm ================================================ // // ZetaCommanderBase.mm // ZetaWatch // // Created by cbreak on 19.06.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaCommanderBase.h" @implementation ZetaCommanderBase - (void)notifySuccessWithTitle:(NSString*)title text:(NSString*)text { NSUserNotification * notification = [[NSUserNotification alloc] init]; notification.title = title; notification.informativeText = text == nil ? title : text; notification.hasActionButton = NO; auto nc = [NSUserNotificationCenter defaultUserNotificationCenter]; [nc deliverNotification:notification]; [nc performSelector:@selector(removeDeliveredNotification:) withObject:notification afterDelay:10]; } - (void)notifyErrorFromHelper:(NSError*)error { NSUserNotification * notification = [[NSUserNotification alloc] init]; notification.title = NSLocalizedString(@"ZetaWatch Error", @"Helper Error notification Title"); NSString * errorFormat = NSLocalizedString(@"%@.", @"Helper Error notification Format"); notification.informativeText = [NSString stringWithFormat:errorFormat, [error localizedDescription]]; notification.hasActionButton = NO; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } - (IBAction)copyRepresentedObject:(id)sender { NSPasteboard * pb = [NSPasteboard generalPasteboard]; [pb clearContents]; [pb writeObjects:@[[sender representedObject]]]; } @end ================================================ FILE: ZetaWatch/ZetaConfirmDialog.h ================================================ // // ZetaConfirmDialog.h // ZetaWatch // // Created by cbreak on 19.10.13. // Copyright © 2019 the-color-black.net. All rights reserved. // #import @interface ZetaConfirmDialog : NSObject @property (weak) NSStatusItem * statusItem; @property (weak) IBOutlet NSPopover * popover; @property (weak) IBOutlet NSTextField * queryField; @property (weak) IBOutlet NSTextField * infoField; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; - (BOOL)popoverShouldDetach:(NSPopover *)popover; - (void)addQuery:(NSString*)query withInformation:(NSString*)info withCallback:(void(^)(bool))callback; @end ================================================ FILE: ZetaWatch/ZetaConfirmDialog.mm ================================================ // // ZetaConfirmDialog.mm // ZetaWatch // // Created by cbreak on 19.10.13. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaConfirmDialog.h" #include #include namespace { typedef void (^QueryCallback)(bool); struct Query { NSString * query; NSString * info; QueryCallback reply; }; } @interface ZetaConfirmDialog () { std::deque queries; } @end @implementation ZetaConfirmDialog - (void)addQuery:(NSString*)query withInformation:(NSString*)info withCallback:(void(^)(bool))callback { queries.push_back({query, info, callback}); if (queries.size() == 1) [self updateQuery]; if (![_popover isShown]) [self show]; } - (void)show { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; NSView * positioningView = [_statusItem button]; [_popover showRelativeToRect:NSMakeRect(0, 0, 0, 0) ofView:positioningView preferredEdge:NSRectEdgeMinY]; } - (IBAction)ok:(id)sender { if (!queries.empty()) queries.front().reply(true); [self advanceQuery]; } - (IBAction)cancel:(id)sender { if (!queries.empty()) queries.front().reply(false); [self advanceQuery]; } - (BOOL)popoverShouldDetach:(NSPopover *)popover { return YES; } - (void)advanceQuery { queries.pop_front(); [self updateQuery]; if (queries.empty()) { [_popover performClose:self]; } } - (void)updateQuery { if (queries.empty()) { [_queryField setStringValue:@""]; [_infoField setStringValue:@""]; } else { [_queryField setStringValue:queries.front().query]; [_infoField setStringValue:queries.front().info]; } } - (BOOL)popoverShouldClose:(NSPopover *)popover { if (!queries.empty()) { queries.front().reply(false); queries.pop_front(); if (queries.empty()) return YES; [self updateQuery]; return NO; } else { return YES; } } - (void)popoverWillShow:(NSNotification *)notification { } - (void)popoverDidClose:(NSNotification *)notification { } @end ================================================ FILE: ZetaWatch/ZetaDictQueryDialog.h ================================================ // // ZetaDictQueryDialog.h // ZetaWatch // // Created by cbreak on 19.10.06. // Copyright © 2019 the-color-black.net. All rights reserved. // #import @interface ZetaDictQueryDialog : NSObject @property (weak) NSStatusItem * statusItem; @property (weak) IBOutlet NSPopover * popover; @property (retain) NSMutableDictionary * queryDict; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; - (BOOL)popoverShouldDetach:(NSPopover *)popover; - (void)addQuery:(NSMutableDictionary*)query withCallback:(void(^)(NSDictionary*))callback; - (id)initWithDialog:(NSString*)dialogName; @end ================================================ FILE: ZetaWatch/ZetaDictQueryDialog.mm ================================================ // // ZetaNewFSDialog.mm // ZetaWatch // // Created by cbreak on 19.10.06. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaDictQueryDialog.h" #include #include namespace { typedef void (^QueryCallback)(NSDictionary *); struct Query { NSMutableDictionary * query; QueryCallback reply; }; } @interface ZetaDictQueryDialog () { std::deque queries; NSArray * topLevelObjects; } @end @implementation ZetaDictQueryDialog - (id)initWithDialog:(NSString*)dialogName { if (self = [super init]) { NSArray * tlo; [[NSBundle mainBundle] loadNibNamed:dialogName owner:self topLevelObjects:&tlo]; topLevelObjects = tlo; } return self; } - (void)addQuery:(NSMutableDictionary*)query withCallback:(void(^)(NSDictionary*))callback { queries.push_back({query, callback}); if (queries.size() == 1) [self updateQuery]; if (![_popover isShown]) [self show]; } - (void)show { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; NSView * positioningView = [_statusItem button]; [_popover showRelativeToRect:NSMakeRect(0, 0, 0, 0) ofView:positioningView preferredEdge:NSRectEdgeMinY]; } - (IBAction)ok:(id)sender { if (!queries.empty()) queries.front().reply(queries.front().query); [self advanceQuery]; } - (IBAction)cancel:(id)sender { [self advanceQuery]; } - (BOOL)popoverShouldDetach:(NSPopover *)popover { return YES; } - (void)advanceQuery { queries.pop_front(); [self updateQuery]; if (queries.empty()) { [_popover performClose:self]; } } - (void)updateQuery { if (!queries.empty()) { self.queryDict = queries.front().query; } else { self.queryDict = nullptr; } } - (BOOL)popoverShouldClose:(NSPopover *)popover { if (!queries.empty()) { queries.pop_front(); if (queries.empty()) return YES; [self updateQuery]; return NO; } else { return YES; } } - (void)popoverWillShow:(NSNotification *)notification { } - (void)popoverDidClose:(NSNotification *)notification { self.queryDict = nullptr; } @end ================================================ FILE: ZetaWatch/ZetaFileSystemPropertyMenu.h ================================================ // // ZetaFileSystemPropertyMenu.h // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import #import "ZetaCommanderBase.h" #include "ZFSUtils.hpp" @interface ZetaFileSystemPropertyMenu : ZetaCommanderBase - (id)initWithFileSystem:(zfs::ZFileSystem)fs; - (void)menuNeedsUpdate:(NSMenu*)menu; @end ================================================ FILE: ZetaWatch/ZetaFileSystemPropertyMenu.mm ================================================ // // ZetaFileSystemPropertyMenu.mm // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaFileSystemPropertyMenu.h" @implementation ZetaFileSystemPropertyMenu { zfs::ZFileSystem _fs; } - (id)initWithFileSystem:(zfs::ZFileSystem)fs { if (self = [super init]) { _fs = std::move(fs); } return self; } - (void)menuNeedsUpdate:(NSMenu*)menu { [menu removeAllItems]; auto props = _fs.properties(); for (auto const & p : props) { if (p.source.size() > 0) { addMenuItem(menu, self, NSLocalizedString(@"%-64s \t %-32s \t (from %s)", @"KeyValueSource"), p.name, p.value, p.source); } else { addMenuItem(menu, self, NSLocalizedString(@"%-64s \t %s", @"KeyValue"), p.name, p.value); } } } @end ================================================ FILE: ZetaWatch/ZetaFormatHelpers.cpp ================================================ // // ZetaFormatHelpers.cpp // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #include "ZetaFormatHelpers.hpp" Prefix const metricPrefixes[] = { { 1000000000000000000, "E" }, { 1000000000000000, "P" }, { 1000000000000, "T" }, { 1000000000, "G" }, { 1000000, "M" }, { 1000, "k" }, }; size_t const metricPrefixCount = std::extent::value; // https://en.wikipedia.org/wiki/Binary_prefix Prefix const binaryPrefixes[] = { { 1ull << 60, "Ei" }, { 1ull << 50, "Pi" }, { 1ull << 40, "Ti" }, { 1ull << 30, "Gi" }, { 1ull << 20, "Mi" }, { 1ull << 10, "ki" }, }; size_t const binaryPrefixCount = std::extent::value; ================================================ FILE: ZetaWatch/ZetaFormatHelpers.hpp ================================================ // // ZetaFormatHelpers.hpp // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #ifndef ZetaFormatHelpers_hpp #define ZetaFormatHelpers_hpp #include #include #include #include struct Prefix { uint64_t factor; char const * prefix; }; extern Prefix const metricPrefixes[]; extern size_t const metricPrefixCount; extern Prefix const binaryPrefixes[]; extern size_t const binaryPrefixCount; template std::string formatPrefixedValue(T size, Prefix const * prefix, size_t prefixCount) { for (size_t p = 0; p < prefixCount; ++p) { if (size >= prefix[p].factor) { double scaledSize = size / double(prefix[p].factor); std::stringstream ss; ss << std::setprecision(2) << std::fixed << scaledSize << " " << prefix[p].prefix; return ss.str(); } } return std::to_string(size) + " "; } template std::string formatInformationValue(T size) { return formatPrefixedValue(size, binaryPrefixes, binaryPrefixCount); } template std::string formatNormalValue(T size) { return formatPrefixedValue(size, metricPrefixes, metricPrefixCount); } template std::string formatBytes(T bytes) { return formatInformationValue(bytes) + "B"; } template bool parseBytes(char const * byteString, T & outBytes) { std::regex byteRegex(R"((\d+\.?\d*)\s*([EPTGMk]?i?)B?)", std::regex::icase); std::cmatch match; if (std::regex_match(byteString, match, byteRegex)) { double bytesFormated = std::stod(std::string(match[1].first, match[1].second)); std::string prefix(match[2].first, match[2].second); std::transform(prefix.begin(), prefix.end(), prefix.begin(), ::tolower); if (prefix.length() == 1) prefix += 'i'; Prefix const * foundPrefix = std::find_if(binaryPrefixes, binaryPrefixes + binaryPrefixCount, [=](Prefix const & p) { std::string pp(p.prefix); std::transform(pp.begin(), pp.end(), pp.begin(), ::tolower); return prefix == pp; }); if (foundPrefix != binaryPrefixes + binaryPrefixCount) { outBytes = static_cast(bytesFormated * foundPrefix->factor); } else { outBytes = static_cast(bytesFormated); } return true; } return false; } inline std::string formatRate(uint64_t bytes, std::chrono::seconds const & time) { return formatBytes(bytes / time.count()) + "/s"; } template T toFormatable(T t) { return t; } inline char const * toFormatable(std::string const & str) { return str.c_str(); } inline std::string trim(std::string const & s) { size_t first = s.find_first_not_of(' '); size_t last = s.find_last_not_of(' '); if (first != std::string::npos) return s.substr(first, last - first + 1); return s; } #endif /* ZetaMenuHelpers_h */ ================================================ FILE: ZetaWatch/ZetaImportMenu.h ================================================ // // ZetaImportMenu.h // ZetaWatch // // Created by cbreak on 19.06.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #ifndef ZetaImportMenu_h #define ZetaImportMenu_h #import #import "ZetaAuthorization.h" #import "ZetaCommanderBase.h" #import "ZetaAutoImporter.h" @class ZetaPoolWatcher; @interface ZetaImportMenu : ZetaCommanderBase @property (weak) IBOutlet NSMenu * importMenu; @property (weak) IBOutlet ZetaPoolWatcher * poolWatcher; @property (weak) IBOutlet ZetaAutoImporter * autoImporter; @end #endif /* ZetaImportMenuDelegate_h */ ================================================ FILE: ZetaWatch/ZetaImportMenu.mm ================================================ // // ZetaImportMenu.m // ZetaWatch // // Created by cbreak on 19.06.02. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaImportMenu.h" #import "ZetaAuthorization.h" #import "ZetaPoolWatcher.h" #include "ZFSStrings.hpp" @implementation ZetaImportMenu - (void)menuNeedsUpdate:(NSMenu*)menu { [menu removeAllItems]; NSMenuItem * importAllItem = [menu addItemWithTitle:@"Import all Pools" action:@selector(importAllPools:) keyEquivalent:@""]; importAllItem.target = self; [menu addItem:[NSMenuItem separatorItem]]; auto importablePools = [self.autoImporter importablePools]; if (importablePools.size() > 0) { auto addPoolImportMenu = [self](NSMenu * menu, auto const & pool) { NSString * title = [NSString stringWithFormat:@"%s %@ (%llu)", pool.name.c_str(), zfs::emojistring_pool_status_t(pool.status), pool.guid]; NSMenuItem * item = [menu addItemWithTitle:title action:@selector(importPool:) keyEquivalent:@""]; [item setAction:@selector(importPool:)]; [item setTarget:self]; // Communicate pool to callback NSNumber * guid = [NSNumber numberWithUnsignedLongLong:pool.guid]; NSString * name = [NSString stringWithUTF8String:pool.name.c_str()]; NSMutableDictionary * poolDict = [NSMutableDictionary dictionary]; poolDict[@"poolGUID"] = guid; poolDict[@"poolName"] = name; [item setRepresentedObject:poolDict]; return poolDict; }; for (auto const & pool : importablePools) { addPoolImportMenu(menu, pool); } [menu addItem:[NSMenuItem separatorItem]]; // Read-Only Menu NSMenuItem * importROItem = [menu addItemWithTitle: NSLocalizedString(@"Import Read Only", @"Import Read Only") action:NULL keyEquivalent:@""]; NSMenu * importROMenu = [[NSMenu alloc] init]; importROItem.submenu = importROMenu; for (auto const & pool : importablePools) { auto dict = addPoolImportMenu(importROMenu, pool); dict[@"readOnly"] = @YES; } // Read-Write Menu NSMenuItem * importRWItem = [menu addItemWithTitle: NSLocalizedString(@"Import Read Write", @"Import Read Write") action:NULL keyEquivalent:@""]; NSMenu * importRWMenu = [[NSMenu alloc] init]; importRWItem.submenu = importRWMenu; for (auto const & pool : importablePools) { auto dict = addPoolImportMenu(importRWMenu, pool); dict[@"readOnly"] = @NO; } } else { [_importMenu addItemWithTitle:NSLocalizedString(@"No importable Pools found", @"No Importable Pools") action:NULL keyEquivalent:@""]; } } - (void)importablePoolsDiscovered:(NSDictionary*)importablePools { } - (void)importablePoolsError:(NSError*)error { NSMenuItem * item = [_importMenu itemAtIndex:2]; [item setTitle:[error localizedDescription]]; } - (IBAction)importPool:(id)sender { NSDictionary * pool = [sender representedObject]; NSMutableDictionary * mutablePool = [pool mutableCopy]; auto defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"useAltroot"]) { [mutablePool setObject:[defaults stringForKey:@"defaultAltroot"] forKey:@"altroot"]; } if (auto spo = [defaults arrayForKey:@"searchPathOverride"]) { [mutablePool setObject:spo forKey:@"searchPathOverride"]; } // Direct imports always allow unhealthy pools with mismatching host IDs. [mutablePool setObject:@YES forKey:@"allowUnhealthy"]; [mutablePool setObject:@YES forKey:@"allowHostIDMismatch"]; [_authorization importPools:mutablePool withReply:^(NSError * error) { if (!error) { NSString * title = [NSString stringWithFormat: NSLocalizedString(@"Pool %@ imported", @"Pool Import Success short format"), pool[@"poolName"]]; NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ (%@) imported", @"Pool Import Success format"), pool[@"poolName"], pool[@"poolGUID"]]; [self notifySuccessWithTitle:title text:text]; } [self handlePoolChangeReply:error]; }]; } - (IBAction)importAllPools:(id)sender { NSMutableDictionary * mutablePool = [NSMutableDictionary dictionary]; auto defaults = [NSUserDefaults standardUserDefaults]; if ([defaults boolForKey:@"allowHostIDMismatch"]) { [mutablePool setObject:@YES forKey:@"allowHostIDMismatch"]; } if ([defaults boolForKey:@"useAltroot"]) { [mutablePool setObject:[defaults stringForKey:@"defaultAltroot"] forKey:@"altroot"]; } if (auto spo = [defaults arrayForKey:@"searchPathOverride"]) { [mutablePool setObject:spo forKey:@"searchPathOverride"]; } [_authorization importPools:mutablePool withReply:^(NSError * error) { if (!error) { NSString * title = [NSString stringWithFormat: NSLocalizedString(@"All pools imported", @"Pool Import Success all")]; [self notifySuccessWithTitle:title text:nil]; } [self handlePoolChangeReply:error]; }]; } - (void)handlePoolChangeReply:(NSError*)error { if (error) { [self notifyErrorFromHelper:error]; } else { [[self poolWatcher] checkForChanges]; } } @end ================================================ FILE: ZetaWatch/ZetaKeyLoader.h ================================================ // // ZetaKeyLoader.h // ZetaWatch // // Created by cbreak on 19.06.16. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import "ZetaCommanderBase.h" #import "ZetaPoolWatcher.h" @interface ZetaKeyLoader : ZetaCommanderBase @property (weak) NSStatusItem * statusItem; @property (weak) IBOutlet NSPopover * popover; @property (weak) IBOutlet NSSecureTextField * passwordField; @property (weak) IBOutlet NSTextField * queryField; @property (weak) IBOutlet NSTextField * statusField; @property (weak) IBOutlet NSButton * useKeychainCheckbox; @property (weak) IBOutlet NSProgressIndicator * progressIndicator; @property (weak) IBOutlet NSButton * loadButton; @property (weak) IBOutlet NSButton * skipButton; @property (weak) IBOutlet ZetaPoolWatcher * poolWatcher; - (void)unlockFileSystem:(NSString*)filesystem; - (IBAction)loadKey:(id)sender; - (IBAction)skipFileSystem:(id)sender; - (BOOL)popoverShouldDetach:(NSPopover *)popover; @end ================================================ FILE: ZetaWatch/ZetaKeyLoader.mm ================================================ // // ZetaKeyLoader.m // ZetaWatch // // Created by cbreak on 19.06.16. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaKeyLoader.h" #import #include "ZFSWrapper/ZFSUtils.hpp" #include #include enum class LoaderState { idle, examineFilesystem, loadKeyfile, loadStoredKey, loadInteractiveGet, loadInteractiveUnlock, loadCompleted, }; enum class LoaderEvent { newFilesystem, unlockSucceeded, unlockFailedPassword, unlockFailedOther, buttonLoad, buttonSkip, }; @interface ZetaKeyLoader () { std::deque filesystems; LoaderState state; zfs::LibZFSHandle libZFS; } @end @implementation ZetaKeyLoader - (void)awakeFromNib { state = LoaderState::idle; if (self.poolWatcher) { [self.poolWatcher.delegates addObject:self]; } } - (IBAction)loadKey:(id)sender { [self handleLoaderEvent:LoaderEvent::buttonLoad]; } - (IBAction)skipFileSystem:(id)sender { [self handleLoaderEvent:LoaderEvent::buttonSkip]; } - (void)unlockFileSystem:(NSString*)filesystem { filesystems.push_back(filesystem); [self handleLoaderEvent:LoaderEvent::newFilesystem]; } - (void)advanceFilesystems { filesystems.pop_front(); if (filesystems.size() > 0) [self transitionToState:LoaderState::examineFilesystem]; else [self transitionToState:LoaderState::idle]; } // State transition function that enters a state, setting up UI, and starting // functions that might trigger further state transitions. Can include direct // transitions to other states. - (void)transitionToState:(LoaderState)nextState { state = nextState; switch (state) { case LoaderState::idle: { [self updateFileSystem]; [_popover performClose:self]; break; } case LoaderState::examineFilesystem: { [self updateFileSystem]; [self show]; [self examineFilesystem:filesystems.front()]; break; } case LoaderState::loadKeyfile: { [self showActionInProgress: NSLocalizedString(@"Loading Keyfile...", @"LoadingKeyfileStatus")]; [self loadKeyFileForFilesystem:filesystems.front()]; break; } case LoaderState::loadStoredKey: { [self showActionInProgress: NSLocalizedString(@"Loading stored Key...", @"LoadingNonInteractiveKeyStatus")]; [self loadStoredPasswordForFilesystem:filesystems.front()]; break; } case LoaderState::loadInteractiveGet: { [self requestPassword]; break; } case LoaderState::loadInteractiveUnlock: { [self showActionInProgress: NSLocalizedString(@"Loading entered Key...", @"LoadingInteractiveKeyStatus")]; [self loadInteractivePasswordForFilesystem:filesystems.front()]; break; } case LoaderState::loadCompleted: { [self advanceFilesystems]; break; } } } // Event state transition function that decides which state to go to based on // events it receives and the current state. - (void)handleLoaderEvent:(LoaderEvent)event { switch (state) { case LoaderState::idle: { switch (event) { case LoaderEvent::newFilesystem: [self transitionToState:LoaderState::examineFilesystem]; return; default: return; } break; } case LoaderState::examineFilesystem: { // Transitional state break; } case LoaderState::loadKeyfile: { switch (event) { case LoaderEvent::unlockSucceeded: [self transitionToState:LoaderState::loadCompleted]; return; case LoaderEvent::unlockFailedPassword: case LoaderEvent::unlockFailedOther: [self transitionToState:LoaderState::loadStoredKey]; return; default: return; } break; } case LoaderState::loadStoredKey: { switch (event) { case LoaderEvent::unlockSucceeded: [self transitionToState:LoaderState::loadCompleted]; return; case LoaderEvent::unlockFailedPassword: case LoaderEvent::unlockFailedOther: [self transitionToState:LoaderState::loadInteractiveGet]; return; default: return; } break; } case LoaderState::loadInteractiveGet: { switch (event) { case LoaderEvent::buttonLoad: [self transitionToState:LoaderState::loadInteractiveUnlock]; return; case LoaderEvent::buttonSkip: [self transitionToState:LoaderState::loadCompleted]; return; default: return; } break; } case LoaderState::loadInteractiveUnlock: { switch (event) { case LoaderEvent::unlockSucceeded: case LoaderEvent::unlockFailedOther: [self transitionToState:LoaderState::loadCompleted]; return; case LoaderEvent::unlockFailedPassword: [self transitionToState:LoaderState::loadInteractiveGet]; return; default: return; } break; } case LoaderState::loadCompleted: { // Transitional state break; } } } - (void)show { // [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; NSView * positioningView = [_statusItem button]; [_popover showRelativeToRect:NSMakeRect(0, 0, 0, 0) ofView:positioningView preferredEdge:NSRectEdgeMinY]; } - (void)loadKey:(NSString*)password forFilesystem:(NSString*)filesystem storeInKeychain:(bool)storeInKeychain { NSDictionary * opts = @{@"filesystem": filesystem, @"key": password}; [_authorization loadKeyForFilesystem:opts withReply:^(NSError * error) { bool success = [self handleLoadKeyReply:error]; if (success && storeInKeychain) { [self storePassword:password forFilesystem:filesystem]; } }]; } - (void)loadKeyFileForFilesystem:(NSString*)filesystem { NSDictionary * opts = @{@"filesystem": filesystem}; [_authorization loadKeyForFilesystem:opts withReply:^(NSError * error) { [self handleLoadKeyReply:error]; }]; } - (bool)handleLoadKeyReply:(NSError*)error { if (error) { if ([error.domain isEqualToString:@"ZFSKeyError"]) { [self showError:[error localizedDescription]]; [self handleLoaderEvent:LoaderEvent::unlockFailedPassword]; } else { [self notifyErrorFromHelper:error]; [self handleLoaderEvent:LoaderEvent::unlockFailedOther]; } return false; } else { [self handleLoaderEvent:LoaderEvent::unlockSucceeded]; return true; } } - (void)showActionInProgress:(NSString*)action { [self showStatus:action]; [_progressIndicator startAnimation:self]; [_loadButton setEnabled:NO]; [_skipButton setEnabled:NO]; [_passwordField setEnabled:NO]; [_useKeychainCheckbox setEnabled:NO]; } - (void)requestPassword { [_progressIndicator stopAnimation:self]; [_loadButton setEnabled:YES]; [_skipButton setEnabled:YES]; [_passwordField setEnabled:YES]; [_useKeychainCheckbox setEnabled:YES]; } - (void)showStatus:(NSString*)error { [_statusField setStringValue:error]; [_statusField setTextColor:[NSColor textColor]]; } - (void)showError:(NSString*)error { [_statusField setStringValue:error]; [_statusField setTextColor:[NSColor systemRedColor]]; } - (void)clearPassword { // The password is copied all over the place by the view, the dictionary // and the IPC, so trying to clear it is probably a waste of time. [_passwordField setStringValue:@""]; } - (void)examineFilesystem:(NSString*)filesystem { auto fs = libZFS.filesystem([filesystem UTF8String]); if (fs.keyLocation() == zfs::ZFileSystem::KeyLocation::uri) { [self transitionToState:LoaderState::loadKeyfile]; } else { [self transitionToState:LoaderState::loadStoredKey]; } } - (void)loadInteractivePasswordForFilesystem:(NSString*)filesystem { NSString * pass = [_passwordField stringValue]; bool storeInKeychain = [_useKeychainCheckbox state] == NSControlStateValueOn; [self clearPassword]; [self loadKey:pass forFilesystem:filesystem storeInKeychain:storeInKeychain]; } - (void)loadStoredPasswordForFilesystem:(NSString*)filesystem { NSString * password = [self retrievePasswordForFilesystem:filesystem]; if (password) { [self loadKey:password forFilesystem:filesystem storeInKeychain:false]; } else { [self showStatus: NSLocalizedString(@"No stored key found", @"NoStoredKeyStatus")]; [self handleLoaderEvent:LoaderEvent::unlockFailedOther]; } } - (NSString*)retrievePasswordForFilesystem:(NSString*)filesystem { void const * keys[] = { kSecClass, kSecAttrService, kSecMatchLimit, kSecReturnData, }; void const * values[] = { kSecClassGenericPassword, (__bridge CFStringRef)filesystem, kSecMatchLimitOne, kCFBooleanTrue, }; static_assert(std::extent_v == std::extent_v); constexpr size_t attributeCount = std::extent_v; CFDictionaryRef attributes = CFDictionaryCreate(nullptr, &keys[0], &values[0], attributeCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); void const * data = nullptr; OSStatus result = SecItemCopyMatching(attributes, &data); CFRelease(attributes); if (result != errSecSuccess) { return nullptr; } NSString * password = [[NSString alloc] initWithData:CFBridgingRelease((CFDataRef)data) encoding:NSUTF8StringEncoding]; return password; } - (bool)deletePasswordForFilesystem:(NSString*)filesystem { void const * keys[] = { kSecClass, kSecAttrService, kSecMatchLimit, }; void const * values[] = { kSecClassGenericPassword, (__bridge CFStringRef)filesystem, kSecMatchLimitAll, }; static_assert(std::extent_v == std::extent_v); constexpr size_t attributeCount = std::extent_v; CFDictionaryRef attributes = CFDictionaryCreate(nullptr, &keys[0], &values[0], attributeCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); OSStatus result = SecItemDelete(attributes); CFRelease(attributes); if (result != errSecSuccess) { return false; } return true; } - (bool)storePassword:(NSString*)password forFilesystem:(NSString*)filesystem { [self deletePasswordForFilesystem:filesystem]; NSData * passwordData = [password dataUsingEncoding:NSUTF8StringEncoding]; NSString * label = [NSString stringWithFormat:@"ZetaWatch Password for ZFS filesystem %@", filesystem]; void const * keys[] = { kSecClass, kSecAttrLabel, kSecAttrService, kSecValueData, }; void const * values[] = { kSecClassGenericPassword, (__bridge CFStringRef)label, (__bridge CFStringRef)filesystem, (__bridge CFDataRef)passwordData, }; static_assert(std::extent_v == std::extent_v); constexpr size_t attributeCount = std::extent_v; CFDictionaryRef attributes = CFDictionaryCreate(nullptr, &keys[0], &values[0], attributeCount, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); OSStatus result = SecItemAdd(attributes, nil); CFRelease(attributes); if (result != errSecSuccess) { CFStringRef errorString = SecCopyErrorMessageString(result, nullptr); NSError * error = [NSError errorWithDomain:NSOSStatusErrorDomain code:result userInfo: @{ NSLocalizedDescriptionKey: CFBridgingRelease(errorString) }]; [self notifyErrorFromHelper:error]; return false; } return true; } - (void)updateFileSystem { if (filesystems.empty()) { [_queryField setStringValue:@""]; } else { [_queryField setStringValue:[NSString stringWithFormat:NSLocalizedString(@"Enter the password for %@", @"Password Query"), filesystems.front()]]; bool useKeychain = [[NSUserDefaults standardUserDefaults] boolForKey:@"useKeychain"]; [_useKeychainCheckbox setState:useKeychain ? NSControlStateValueOn : NSControlStateValueOff]; } } - (BOOL)popoverShouldDetach:(NSPopover *)popover { return YES; } - (BOOL)popoverShouldClose:(NSPopover *)popover { return filesystems.empty(); } - (void)popoverWillShow:(NSNotification *)notification { } - (void)popoverDidClose:(NSNotification *)notification { [self clearPassword]; [_passwordField abortEditing]; } - (void)newPoolDetected:(const zfs::ZPool &)pool { if ([[NSUserDefaults standardUserDefaults] boolForKey:@"autoUnlock"]) { for (auto & fs : pool.allFileSystems()) { auto [encRoot, isRoot] = fs.encryptionRoot(); auto keyStatus = fs.keyStatus(); if (isRoot && keyStatus == zfs::ZFileSystem::KeyStatus::unavailable) { NSString * fsName = [NSString stringWithUTF8String:fs.name()]; [self unlockFileSystem:fsName]; } } } } @end ================================================ FILE: ZetaWatch/ZetaMainMenu.h ================================================ // // ZetaMainMenu.h // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.20. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import #import "ZetaPoolWatcher.h" #import "ZetaCommanderBase.h" #import "ZetaPoolWatcher.h" #import "ZetaAutoImporter.h" #import "ZetaKeyLoader.h" #import "ZetaQueryDialog.h" #import "ZetaDictQueryDialog.h" #import "ZetaConfirmDialog.h" @class ZetaNotificationCenter; enum ZetaMenuTags { ZPoolAnchorMenuTag = 100, ActionAnchorMenuTag = 101 }; @interface ZetaMainMenu : ZetaCommanderBase @property (weak) IBOutlet ZetaPoolWatcher * poolWatcher; @property (weak) IBOutlet ZetaKeyLoader * zetaKeyLoader; @property (weak) IBOutlet ZetaQueryDialog * zetaQueryDialog; @property (weak) IBOutlet ZetaDictQueryDialog * zetaNewFSDialog; @property (weak) IBOutlet ZetaDictQueryDialog * zetaNewVolDialog; @property (weak) IBOutlet ZetaConfirmDialog * zetaConfirmDialog; @property (weak) IBOutlet ZetaNotificationCenter * notificationCenter; - (IBAction)exportPool:(id)sender; - (IBAction)exportPoolForce:(id)sender; - (IBAction)mountFilesystem:(id)sender; - (IBAction)mountFilesystemRecursive:(id)sender; - (IBAction)unmountFilesystem:(id)sender; - (IBAction)unmountFilesystemRecursive:(id)sender; - (IBAction)unmountFilesystemForce:(id)sender; - (IBAction)snapshotFilesystem:(id)sender; - (IBAction)snapshotFilesystemRecursive:(id)sender; - (IBAction)rollbackFilesystem:(id)sender; - (IBAction)rollbackFilesystemForce:(id)sender; - (IBAction)cloneSnapshot:(id)sender; - (IBAction)destroy:(id)sender; - (IBAction)destroyRecursive:(id)sender; - (IBAction)loadKey:(id)sender; - (IBAction)loadAllKeys:(id)sender; - (IBAction)unloadKey:(id)sender; - (IBAction)unloadAllKeys:(id)sender; - (IBAction)scrubPool:(id)sender; - (IBAction)scrubStopPool:(id)sender; @end ================================================ FILE: ZetaWatch/ZetaMainMenu.mm ================================================ // // ZetaMainMenu.mm // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.20. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import "ZetaMainMenu.h" #import "ZetaImportMenu.h" #import "ZetaPoolWatcher.h" #import "ZetaAuthorization.h" #import "ZetaSnapshotMenu.h" #import "ZetaBookmarkMenu.h" #import "ZetaFileSystemPropertyMenu.h" #import "ZetaPoolPropertyMenu.h" #import "ZetaNotificationCenter.h" #include "ZFSUtils.hpp" #include "ZFSStrings.hpp" #include "InvariantDisks/IDDiskArbitrationUtils.hpp" #include #include #include #include @interface ZetaMainMenu () { NSMutableArray * _dynamicMenus; DASessionRef _diskArbitrationSession; zfs::LibZFSHandle _zfs; } @end @implementation ZetaMainMenu - (id)init { if (self = [super init]) { _dynamicMenus = [[NSMutableArray alloc] init]; _diskArbitrationSession = DASessionCreate(nullptr); } return self; } - (void)dealloc { CFRelease(_diskArbitrationSession); } - (void)menuNeedsUpdate:(NSMenu*)menu { [self clearDynamicMenu:menu]; [self resetLibZFS]; [self createNotificationMenu:menu]; [self createPoolMenu:menu]; [self createActionMenu:menu]; } #pragma mark Formating NSString * formatErrorStat(zfs::VDevStat stat, bool emoji) { NSString * status = emoji ? zfs::emojistring_vdev_state_t(stat.state, stat.aux) : zfs::localized_describe_vdev_state_t(stat.state, stat.aux); NSString * errors = nil; if (stat.errorRead == 0 && stat.errorWrite == 0 && stat.errorChecksum == 0) { errors = NSLocalizedString(@"No Errors", @"Format vdev_stat_t"); } else { NSString * format = NSLocalizedString(@"%llu Read Errors, %llu Write Errors, %llu Checksum Errors", @"Format vdev_stat_t"); errors = [NSString stringWithFormat:format, stat.errorRead, stat.errorWrite, stat.errorChecksum]; } return [NSString stringWithFormat:@"%@, %@", status, errors]; } std::chrono::seconds getElapsed(zfs::ScanStat const & scanStat) { auto elapsed = time(0) - scanStat.passStartTime; elapsed -= scanStat.passPausedSeconds; elapsed = (elapsed > 0) ? elapsed : 1; return std::chrono::seconds(elapsed); } inline std::string formatTimeRemaining(zfs::ScanStat const & scanStat, std::chrono::seconds const & time) { auto bytesRemaining = scanStat.total - scanStat.issued; auto issued = scanStat.passIssued; if (issued == 0) issued = 1; auto secondsRemaining = bytesRemaining * time.count() / issued; std::stringstream ss; ss << std::setfill('0'); ss << (secondsRemaining / (60*60*24)) << " days " << std::setw(2) << ((secondsRemaining / (60*60)) % 24) << ":" << std::setw(2) << ((secondsRemaining / 60) % 60) << ":" << std::setw(2) << (secondsRemaining % 60); return ss.str(); } #pragma mark ZFS Inspection NSMenu * createFSMenu(zfs::ZFileSystem && fs, ZetaMainMenu * delegate) { NSMenu * fsMenu = [[NSMenu alloc] init]; [fsMenu setAutoenablesItems:NO]; NSString * fsName = [NSString stringWithUTF8String:fs.name()]; auto addFSCommand = [&](NSString * title, SEL selector) { auto item = [fsMenu addItemWithTitle:title action:selector keyEquivalent:@""]; item.representedObject = fsName; item.target = delegate; }; if (fs.type() == zfs::ZFileSystem::FSType::filesystem) { auto [encRoot, isRoot] = fs.encryptionRoot(); if (isRoot) { if (fs.keyStatus() != zfs::ZFileSystem::KeyStatus::available) { addFSCommand(NSLocalizedString(@"Load Key...", @"Load Key"), @selector(loadKey:)); } else { addFSCommand(NSLocalizedString(@"Unload Key", @"Unload Key"), @selector(unloadKey:)); } [fsMenu addItem:[NSMenuItem separatorItem]]; } addFSCommand(NSLocalizedString(@"Mount Recursively", @"Mount Recursively"), @selector(mountFilesystemRecursive:)); if (!fs.mounted() && fs.mountable()) { addFSCommand(NSLocalizedString(@"Mount", @"Mount"), @selector(mountFilesystem:)); } addFSCommand(NSLocalizedString(@"Unmount Recursively", @"Unmount Recursively"), @selector(unmountFilesystemRecursive:)); if (fs.mounted()) { addFSCommand(NSLocalizedString(@"Unmount", @"Unmount"), @selector(unmountFilesystem:)); addFSCommand(NSLocalizedString(@"Unmount (Force)", @"Unmount (Force)"), @selector(unmountFilesystemForce:)); } } // Snapshots [fsMenu addItem:[NSMenuItem separatorItem]]; addFSCommand(NSLocalizedString(@"Snapshot...", @"Snapshot"), @selector(snapshotFilesystem:)); addFSCommand(NSLocalizedString(@"Snapshot Recursively...", @"Snapshot Recursively"), @selector(snapshotFilesystemRecursive:)); { // Snapshots submenu NSString * snapsTitle = NSLocalizedString(@"Snapshots", @"Snapshots"); NSMenu * snaps = [[NSMenu alloc] initWithTitle:snapsTitle]; ZetaSnapshotMenu * sd = [[ZetaSnapshotMenu alloc] initWithFileSystem:zfs::ZFileSystem(fs) delegate:delegate]; snaps.delegate = sd; NSMenuItem * snapsItem = [[NSMenuItem alloc] initWithTitle:snapsTitle action:nullptr keyEquivalent:@""]; snapsItem.submenu = snaps; snapsItem.representedObject = sd; [fsMenu addItem:snapsItem]; } { // Bookmarks Submenu NSString * bookmarksTitle = NSLocalizedString(@"Bookmarks", @"Bookmarks"); NSMenu * bookmarks = [[NSMenu alloc] initWithTitle:bookmarksTitle]; ZetaBookmarkMenu * bd = [[ZetaBookmarkMenu alloc] initWithFileSystem:zfs::ZFileSystem(fs) delegate:delegate]; bookmarks.delegate = bd; NSMenuItem * bookmarksItem = [[NSMenuItem alloc] initWithTitle:bookmarksTitle action:nullptr keyEquivalent:@""]; bookmarksItem.submenu = bookmarks; bookmarksItem.representedObject = bd; [fsMenu addItem:bookmarksItem]; } // Create [fsMenu addItem:[NSMenuItem separatorItem]]; addFSCommand(NSLocalizedString(@"Create Filesystem...", @"Create Filesystem..."), @selector(createFilesystem:)); addFSCommand(NSLocalizedString(@"Create Volume...", @"Create Volume..."), @selector(createVolume:)); // Destroy [fsMenu addItem:[NSMenuItem separatorItem]]; if (!fs.isRoot()) { addFSCommand(NSLocalizedString(@"Destroy", @"Destroy"), @selector(destroy:)); } addFSCommand(NSLocalizedString(@"Destroy Recursively", @"Destroy Recursively"), @selector(destroyRecursive:)); // Selected Properties [fsMenu addItem:[NSMenuItem separatorItem]]; addMenuItem(fsMenu, delegate, NSLocalizedString(@"Available:          \t %s", @"FS Available Menu Entry"), formatBytes(fs.available())); addMenuItem(fsMenu, delegate, NSLocalizedString(@"Used:               \t %s", @"FS Used Menu Entry"), formatBytes(fs.used())); addMenuItem(fsMenu, delegate, NSLocalizedString(@"Referenced:         \t %s", @"FS Referenced Menu Entry"), formatBytes(fs.referenced())); addMenuItem(fsMenu, delegate, NSLocalizedString(@"Logical Used:       \t %s", @"FS Logically Used Menu Entry"), formatBytes(fs.logicalused())); addMenuItem(fsMenu, delegate, NSLocalizedString(@"Compression Ratio:  \t %1.2fx", @"FS Compression Menu Entry"), fs.compressRatio()); addMenuItem(fsMenu, delegate, NSLocalizedString(@"Mount Point:        \t %s", @"FS Mountpoint Menu Entry"), fs.mountpoint()); // All Properties NSString * allPropsTitle = NSLocalizedString(@"All Properties", @"All Properties"); NSMenu * allProps = [[NSMenu alloc] initWithTitle:allPropsTitle]; ZetaFileSystemPropertyMenu * pd = [[ZetaFileSystemPropertyMenu alloc] initWithFileSystem:std::move(fs)]; allProps.delegate = pd; NSMenuItem * allPropsItem = [[NSMenuItem alloc] initWithTitle:allPropsTitle action:nullptr keyEquivalent:@""]; allPropsItem.submenu = allProps; allPropsItem.representedObject = pd; [fsMenu addItem:allPropsItem]; return fsMenu; } NSString * formatStatus(zfs::ZFileSystem const & fs) { NSString * mountStatus = fs.mounted() ? NSLocalizedString(@"📌", @"mounted status") : NSLocalizedString(@"🕳", @"unmounted status"); NSString * encStatus = nil; switch (fs.keyStatus()) { case zfs::ZFileSystem::KeyStatus::none: encStatus = @""; break; case zfs::ZFileSystem::KeyStatus::unavailable: encStatus = NSLocalizedString(@"🔒", @"locked status"); break; case zfs::ZFileSystem::KeyStatus::available: encStatus = NSLocalizedString(@"🔑", @"unlocked status"); break; } auto [encRoot, isRoot] = fs.encryptionRoot(); NSString * encRootStr = isRoot ? @"🎁" : @""; NSString * fsLine = [NSString stringWithFormat:NSLocalizedString(@"%s (%@%@%@)", @"File System Menu Entry"), fs.name(), mountStatus, encStatus, encRootStr]; return fsLine; } NSMenuItem * addVdev(zfs::ZPool const & pool, zfs::NVList const & device, NSMenu * menu, DASessionRef daSession, ZetaMainMenu * delegate) { // Menu Item auto stat = zfs::vdevStat(device); auto item = addMenuItem(menu, delegate, NSLocalizedString(@"%s (%@)", @"Device Menu Entry"), pool.vdevName(device), formatErrorStat(stat, true)); // Submenu // ZFS Info NSMenu * subMenu = [[NSMenu alloc] init]; addMenuItem(subMenu, delegate, formatErrorStat(stat, false)); addMenuItem(subMenu, delegate, NSLocalizedString(@"Space:          \t %s used / %s total", @"VDev Space Menu Entry"), formatBytes(stat.alloc), formatBytes(stat.space)); addMenuItem(subMenu, delegate, NSLocalizedString(@"Fragmentation:  \t %llu%%", @"VDev Fragmentation Menu Entry"), stat.fragmentation); addMenuItem(subMenu, delegate, NSLocalizedString(@"VDev GUID:      \t %llu", @"VDev GUID Menu Entry"), zfs::vdevGUID(device)); std::string type = zfs::vdevType(device); addMenuItem(subMenu, delegate, NSLocalizedString(@"Device:         \t %s (%s)", @"VDev Device Menu Entry"), pool.vdevDevice(device), type); // Disk Info, only if state is at least 5 or higher, (FAULTED, DEGRADED, HEALTHY) if (type == "disk" && stat.state >= 5) { [subMenu addItem:[NSMenuItem separatorItem]]; auto devicePath = pool.vdevDevice(device); DADiskRef daDisk = DADiskCreateFromBSDName(nullptr, daSession, devicePath.c_str()); auto diskInfo = ID::getDiskInformation(daDisk); addMenuItem(subMenu, delegate, NSLocalizedString(@"UUID:           \t %s", @"VDev MediaUUID Menu Entry"), diskInfo.mediaUUID); addMenuItem(subMenu, delegate, NSLocalizedString(@"Model:          \t %s", @"VDev Model Menu Entry"), trim(diskInfo.deviceModel)); addMenuItem(subMenu, delegate, NSLocalizedString(@"Serial:         \t %s", @"VDev Serial Menu Entry"), trim(diskInfo.ioSerial)); CFRelease(daDisk); } item.submenu = subMenu; return item; } void createScrubMenu(zfs::ZPool & pool, ZetaMainMenu * delegate, NSMenu * vdevMenu) { // Scrub auto scrub = pool.scanStat(); auto startDate = [NSDate dateWithTimeIntervalSince1970:scrub.scanStartTime]; auto endDate = [NSDate dateWithTimeIntervalSince1970:scrub.scanEndTime]; auto startString = [NSDateFormatter localizedStringFromDate:startDate dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]; auto endString = [NSDateFormatter localizedStringFromDate:endDate dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]; NSString * scanLine0; switch (scrub.state) { case zfs::ScanStat::stateNone: { scanLine0 = [NSString stringWithFormat:NSLocalizedString( @"Never scrubbed", @"Scrub None")]; break; } case zfs::ScanStat::scanning: { scanLine0 = [NSString stringWithFormat:NSLocalizedString( @"Scrub started %@ still in progress", @"Scrub Scanning"), startString]; break; } case zfs::ScanStat::finished: { scanLine0 = [NSString stringWithFormat:NSLocalizedString( @"Scrub started %@ finished successfully at %@", @"Scrub Finished"), startString, endString]; break; } case zfs::ScanStat::canceled: { scanLine0 = [NSString stringWithFormat:NSLocalizedString( @"Scrub started %@ was canceled at $@", @"Scrub Canceled"), startString, endString]; break; } } auto scrubItem = [vdevMenu addItemWithTitle:scanLine0 action:nullptr keyEquivalent:@""]; auto scrubMenu = [[NSMenu alloc] init]; scrubItem.submenu = scrubMenu; NSString * poolName = [NSString stringWithUTF8String:pool.name()]; if (scrub.state == zfs::ScanStat::scanning && scrub.passPauseTime == 0) { auto item = [scrubMenu addItemWithTitle: NSLocalizedString(@"Stop Scrub", @"Stop Scrub") action:@selector(scrubStopPool:) keyEquivalent:@""]; item.representedObject = poolName; item.target = delegate; item = [scrubMenu addItemWithTitle: NSLocalizedString(@"Pause Scrub", @"Pause Scrub") action:@selector(scrubPausePool:) keyEquivalent:@""]; item.representedObject = poolName; item.target = delegate; } else { auto item = [scrubMenu addItemWithTitle: NSLocalizedString(@"Start Scrub", @"Start Scrub") action:@selector(scrubPool:) keyEquivalent:@""]; item.representedObject = poolName; item.target = delegate; } if (scrub.state == zfs::ScanStat::scanning) { // Scan Stats auto elapsed = getElapsed(scrub); if (scrub.passPauseTime != 0) { auto pauseDate = [NSDate dateWithTimeIntervalSince1970:scrub.passPauseTime]; auto pauseString = [NSDateFormatter localizedStringFromDate:pauseDate dateStyle:NSDateFormatterMediumStyle timeStyle:NSDateFormatterMediumStyle]; NSString * scanLinePaused = [NSString stringWithFormat:NSLocalizedString( @"Scrub Paused at %@", @"Scrub Paused"), pauseString]; auto m = [vdevMenu addItemWithTitle:scanLinePaused action:nullptr keyEquivalent:@""]; m.indentationLevel = 1; } NSString * scanLine1 = [NSString stringWithFormat:NSLocalizedString( @"%s scanned at %s, %s issued at %s", @"Scrub Menu Entry 1"), formatBytes(scrub.scanned).c_str(), formatRate(scrub.passScanned, elapsed).c_str(), formatBytes(scrub.issued).c_str(), formatRate(scrub.passIssued, elapsed).c_str()]; NSString * scanLine2 = [NSString stringWithFormat:NSLocalizedString( @"%s total, %0.2f %% done, %s remaining, %llu errors", @"Scrub Menu Entry 2"), formatBytes(scrub.total).c_str(), 100.0*scrub.issued/scrub.total, formatTimeRemaining(scrub, elapsed).c_str(), scrub.errors]; auto m1 = [vdevMenu addItemWithTitle:scanLine1 action:nullptr keyEquivalent:@""]; auto m2 = [vdevMenu addItemWithTitle:scanLine2 action:nullptr keyEquivalent:@""]; m1.indentationLevel = 1; m2.indentationLevel = 1; } } NSMenu * createVdevMenu(zfs::ZPool && pool, ZetaMainMenu * delegate, DASessionRef daSession) { NSMenu * vdevMenu = [[NSMenu alloc] init]; [vdevMenu setAutoenablesItems:NO]; try { createScrubMenu(pool, delegate, vdevMenu); [vdevMenu addItem:[NSMenuItem separatorItem]]; // VDevs auto vdevs = pool.vdevs(); for (auto && vdev: vdevs) { // VDev addVdev(pool, vdev, vdevMenu, daSession, delegate); // Children auto devices = zfs::vdevChildren(vdev); for (auto && device: devices) { auto item = addVdev(pool, device, vdevMenu, daSession, delegate); [item setIndentationLevel:1]; } } // Caches auto caches = pool.caches(); if (caches.size() > 0) { [vdevMenu addItemWithTitle:@"cache" action:nullptr keyEquivalent:@""]; for (auto && cache: caches) { auto item = addVdev(pool, cache, vdevMenu, daSession, delegate); [item setIndentationLevel:1]; } } // Filesystems [vdevMenu addItem:[NSMenuItem separatorItem]]; auto childFileSystems = pool.allFileSystems(); if (childFileSystems.empty()) { // This seems to happen when a pool is UNAVAIL NSMenuItem * item = [vdevMenu addItemWithTitle:@"No Filesystems!" action:nil keyEquivalent:@""]; [item setEnabled:NO]; } else { for (auto & fs : childFileSystems) { auto fsLine = formatStatus(fs); NSMenuItem * item = [vdevMenu addItemWithTitle:fsLine action:nullptr keyEquivalent:@""]; item.representedObject = [NSString stringWithUTF8String:fs.name()]; item.submenu = createFSMenu(std::move(fs), delegate); } } // Command helper NSString * poolName = [NSString stringWithUTF8String:pool.name()]; auto addRootFSCommand = [&](NSString * title, SEL selector) { auto item = [vdevMenu addItemWithTitle:title action:selector keyEquivalent:@""]; item.representedObject = poolName; item.target = delegate; }; // Mount Recursively [vdevMenu addItem:[NSMenuItem separatorItem]]; addRootFSCommand(NSLocalizedString(@"Mount Recursively", @"Mount Recursively"), @selector(mountFilesystemRecursive:)); addRootFSCommand(NSLocalizedString(@"Unmount Recursively", @"Unmount Recursively"), @selector(unmountFilesystemRecursive:)); // Snapshot [vdevMenu addItem:[NSMenuItem separatorItem]]; addRootFSCommand(NSLocalizedString(@"Snapshot Recursively...", @"Snapshot Recursively"), @selector(snapshotFilesystemRecursive:)); // Create [vdevMenu addItem:[NSMenuItem separatorItem]]; addRootFSCommand(NSLocalizedString(@"Create Filesystem...", @"Create Filesystem..."), @selector(createFilesystem:)); addRootFSCommand(NSLocalizedString(@"Create Volume...", @"Create Volume..."), @selector(createVolume:)); // Export Actions [vdevMenu addItem:[NSMenuItem separatorItem]]; addRootFSCommand(NSLocalizedString(@"Export", @"Export"), @selector(exportPool:)); addRootFSCommand(NSLocalizedString(@"Export (Force)", @"Export (Force)"), @selector(exportPoolForce:)); // All Properties [vdevMenu addItem:[NSMenuItem separatorItem]]; NSMenu * allProps = [[NSMenu alloc] initWithTitle:@"All Properties"]; ZetaPoolPropertyMenu * pd = [[ZetaPoolPropertyMenu alloc] initWithPool:std::move(pool)]; allProps.delegate = pd; NSMenuItem * allPropsItem = [[NSMenuItem alloc] initWithTitle:@"All Properties" action:nullptr keyEquivalent:@""]; allPropsItem.submenu = allProps; allPropsItem.representedObject = pd; [vdevMenu addItem:allPropsItem]; } catch (std::exception const & e) { [vdevMenu addItemWithTitle:NSLocalizedString(@"Error reading pool configuration", @"Pool Config Error Message") action:nullptr keyEquivalent:@""]; } return vdevMenu; } - (void)createNotificationMenu:(NSMenu*)menu { if ([self.notificationCenter.inProgressActions count] > 0) { NSUInteger notifIdx = 0; for (ZetaNotification * notification in self.notificationCenter.inProgressActions) { NSMenuItem * notifItem = [[NSMenuItem alloc] initWithTitle:notification.title action:nil keyEquivalent:@""]; [menu insertItem:notifItem atIndex:0]; [_dynamicMenus addObject:notifItem]; ++notifIdx; } NSMenuItem * sepItem = [NSMenuItem separatorItem]; [menu insertItem:sepItem atIndex:notifIdx]; [_dynamicMenus addObject:sepItem]; } } - (void)createPoolMenu:(NSMenu*)menu { NSInteger poolMenuIdx = [menu indexOfItemWithTag:ZPoolAnchorMenuTag]; if (poolMenuIdx < 0) return; NSInteger poolItemRootIdx = poolMenuIdx + 1; NSUInteger poolIdx = 0; try { for (auto && pool: _zfs.pools()) { NSString * poolLine = [NSString stringWithFormat:NSLocalizedString(@"%s (%@)", @"Pool Menu Entry"), pool.name(), zfs::emojistring_pool_status_t(pool.status())]; NSMenuItem * poolItem = [[NSMenuItem alloc] initWithTitle:poolLine action:NULL keyEquivalent:@""]; NSMenu * vdevMenu = createVdevMenu(std::move(pool), self, _diskArbitrationSession); [poolItem setSubmenu:vdevMenu]; [menu insertItem:poolItem atIndex:poolItemRootIdx + poolIdx]; [_dynamicMenus addObject:poolItem]; ++poolIdx; } } catch (std::exception const & e) { NSString * error = [NSString stringWithFormat:@"Exception during pool iteration: %s", e.what()]; NSMenuItem * errorItem = [[NSMenuItem alloc] initWithTitle:error action:nullptr keyEquivalent:@""]; [menu insertItem:errorItem atIndex:poolItemRootIdx]; [_dynamicMenus addObject:errorItem]; } } - (void)createActionMenu:(NSMenu*)menu { NSInteger actionMenuIdx = [menu indexOfItemWithTag:ActionAnchorMenuTag]; if (actionMenuIdx < 0) return; // Unlock NSMenuItem * unlockItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Load Keys...", @"Load Key Menu Entry") action:NULL keyEquivalent:@""]; NSMenu * unlockMenu = [[NSMenu alloc] init]; [unlockItem setSubmenu:unlockMenu]; NSMutableArray * lockedEncryptionRoots = [NSMutableArray array]; NSMenuItem * unlockAllItem = [unlockMenu addItemWithTitle:NSLocalizedString(@"Load All Keys...", @"Load All Menu Entry") action:@selector(loadAllKeys:) keyEquivalent:@""]; unlockAllItem.target = self; unlockAllItem.representedObject = lockedEncryptionRoots; [unlockMenu addItem:[NSMenuItem separatorItem]]; // Lock NSMenuItem * lockItem = [[NSMenuItem alloc] initWithTitle:NSLocalizedString(@"Unload Keys", @"Unload Key Menu Entry") action:NULL keyEquivalent:@""]; NSMenu * lockMenu = [[NSMenu alloc] init]; [lockItem setSubmenu:lockMenu]; NSMutableArray * unlockedEncryptionRoots = [NSMutableArray array]; NSMenuItem * lockAllItem = [lockMenu addItemWithTitle:NSLocalizedString(@"Unload All Keys", @"Unload All Menu Entry") action:@selector(unloadAllKeys:) keyEquivalent:@""]; lockAllItem.target = self; lockAllItem.representedObject = unlockedEncryptionRoots; [lockMenu addItem:[NSMenuItem separatorItem]]; // Individual entries try { for (auto && pool: _zfs.pools()) { for (auto & fs : pool.allFileSystems()) { auto [encRoot, isRoot] = fs.encryptionRoot(); auto keyStatus = fs.keyStatus(); if (isRoot) { NSString * fsName = [NSString stringWithUTF8String:fs.name()]; if (keyStatus == zfs::ZFileSystem::KeyStatus::unavailable) { NSMenuItem * item = [unlockMenu addItemWithTitle:fsName action:@selector(loadKey:) keyEquivalent:@""]; item.representedObject = fsName; item.target = self; [lockedEncryptionRoots addObject:fsName]; } else { NSMenuItem * item = [lockMenu addItemWithTitle:fsName action:@selector(unloadKey:) keyEquivalent:@""]; item.representedObject = fsName; item.target = self; [unlockedEncryptionRoots addObject:fsName]; } } } } if ([unlockedEncryptionRoots count] > 0) { [menu insertItem:lockItem atIndex:actionMenuIdx + 1]; [_dynamicMenus addObject:lockItem]; } if ([lockedEncryptionRoots count] > 0) { [menu insertItem:unlockItem atIndex:actionMenuIdx + 1]; [_dynamicMenus addObject:unlockItem]; } } catch (std::exception const & e) { NSString * error = [NSString stringWithFormat:@"Exception during pool iteration: %s", e.what()]; NSMenuItem * errorItem = [[NSMenuItem alloc] initWithTitle:error action:nullptr keyEquivalent:@""]; [menu insertItem:errorItem atIndex:actionMenuIdx + 1]; [_dynamicMenus addObject:lockItem]; } } - (void)resetLibZFS { // Reset library to get fresh property state. This seems to be essential // for getting up-to-date altroot of pools that have been imported after // being known to the previous libzfs state. Calling zfs_refresh_properties // is insufficient for refreshing this state. // This also invalidates all filesystem and pool handles that still exist. _zfs.reset(); } - (void)clearDynamicMenu:(NSMenu*)menu { for (NSMenuItem * m in _dynamicMenus) { [menu removeItem:m]; } [_dynamicMenus removeAllObjects]; } - (void)handlePoolChangeReply:(NSError*)error { if (error) [self notifyErrorFromHelper:error]; else [[self poolWatcher] checkForChanges]; } - (void)handleFileSystemChangeReply:(NSError*)error { if (error) [self notifyErrorFromHelper:error]; } - (void)handleMetaDataChangeReply:(NSError*)error { if (error) [self notifyErrorFromHelper:error]; } #pragma mark ZFS Maintenance - (IBAction)exportPool:(id)sender { NSDictionary * opts = @{@"pool": [sender representedObject]}; [_authorization exportPools:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Export succeeded", @"Export succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ exported", @"Export Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handlePoolChangeReply:error]; }]; } - (IBAction)exportPoolForce:(id)sender { NSDictionary * opts = @{@"pool": [sender representedObject], @"force": @YES}; [_authorization exportPools:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Forced export succeeded", @"Forced export succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ force-exported", @"Force-Export Successful format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handlePoolChangeReply:error]; }]; } - (IBAction)mountFilesystem:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject]}; [_authorization mountFilesystems:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Mount succeeded", @"Mount succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ mounted", @"Mount Successful format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } - (IBAction)mountFilesystemRecursive:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject], @"recursive": @TRUE}; [_authorization mountFilesystems:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Recursive mount succeeded", @"Recursive mount succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ mounted recursively", @"Recursive Mount Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } - (IBAction)unmountFilesystem:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject]}; [_authorization unmountFilesystems:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Unmount succeeded", @"Unmount succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ unmounted", @"FS Unmount Successful format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } - (IBAction)unmountFilesystemRecursive:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject], @"recursive": @TRUE}; [_authorization unmountFilesystems:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Recursive unmount succeeded", @"Recursive unmount succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ unmounted recursively", @"Recursive Unmount Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } - (IBAction)unmountFilesystemForce:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject], @"force": @YES}; [_authorization unmountFilesystems:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Filesystem unmount (forced) succeeded", @"Filesystem unmount (forced) succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ force-unmounted", @"FS ForceUnmount Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } static NSString * defaultSnapshotName() { auto now = [NSDate date]; auto formater = [[NSDateFormatter alloc] init]; formater.dateFormat = @"'ZetaSnap'-yyyy-MM-dd-HH-mm-ss"; auto nowString = [formater stringFromDate:now]; return nowString; } - (IBAction)snapshotFilesystem:(id)sender { NSString * filesystem = [sender representedObject]; [_zetaQueryDialog addQuery:NSLocalizedString(@"Enter snapshot name", @"Snapshot Query") withDefault:defaultSnapshotName() withCallback:^(NSString * snapshot) { NSDictionary * opts = @{@"filesystem": filesystem, @"snapshot": snapshot}; [self->_authorization snapshotFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Snapshot succeeded", @"Snapshot succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@@%@ created", @"Snapshot Success format"), filesystem, snapshot]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; }]; } - (IBAction)snapshotFilesystemRecursive:(id)sender { NSString * filesystem = [sender representedObject]; [_zetaQueryDialog addQuery:NSLocalizedString(@"Enter snapshot name", @"Snapshot Query") withDefault:defaultSnapshotName() withCallback:^(NSString * snapshot) { NSDictionary * opts = @{@"filesystem": filesystem, @"snapshot": snapshot, @"recursive": @YES}; [self->_authorization snapshotFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Recursive snapshot succeeded", @"Recursive snapshot succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@@%@ created recursively", @"RecursiveSnapshot Success format"), filesystem, snapshot]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; }]; } static NSMutableString * appendAsString(NSMutableString * fileSystemString, std::vector const & fileSystems) { for (auto const & d : fileSystems) [fileSystemString appendFormat:@"%s\n", d.name()]; return fileSystemString; } static NSMutableString * toString(std::vector const & fileSystems) { NSMutableString * fileSystemString = [NSMutableString string]; return appendAsString(fileSystemString, fileSystems); } static NSString * formatRollbackDependents( std::vector const & clones, std::vector const & snap, std::vector const & bookmarks) { NSMutableString * depString = [NSMutableString string]; if (!clones.empty()) { [depString appendString:@"# Clones\n"]; appendAsString(depString, clones); } if (!snap.empty()) { [depString appendString:@"# Snapshots\n"]; appendAsString(depString, snap); } if (!bookmarks.empty()) { [depString appendString:@"# Bookmarks\n"]; appendAsString(depString, bookmarks); } return depString; } - (IBAction)rollbackFilesystem:(NSString*)snapNameStr Force:(bool)force { std::string snapName([snapNameStr UTF8String]); std::string baseName = snapName.substr(0, snapName.find_last_of('@')); zfs::LibZFSHandle lib; auto snap = lib.filesystem(snapName); auto fs = lib.filesystem(baseName); std::vector clones; auto snapshots = fs.snapshotsSince(snap); for (auto && snap : snapshots) { if (snap.cloneCount() > 0) { auto dep = snap.dependents(); std::move(dep.begin(), dep.end(), std::back_inserter(clones)); } } auto bookmarks = fs.bookmarksSince(snap); auto rollBackBlock = ^(bool ok) { if (ok) { NSDictionary * opts = @{ @"snapshot": snapNameStr, @"force": [NSNumber numberWithBool:force] }; [self->_authorization rollbackFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Rollback succeeded", @"Rollback succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ rolled back", @"Rollback Success format"), snapNameStr]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } }; if (!snapshots.empty() || !clones.empty() || !bookmarks.empty()) { [_zetaConfirmDialog addQuery:NSLocalizedString(@"The following will be destroyed by the rollback", @"Rollback Snapshot Query") withInformation:formatRollbackDependents(clones, snapshots, bookmarks) withCallback:rollBackBlock]; } else { rollBackBlock(true); } } - (IBAction)rollbackFilesystem:(id)sender { [self rollbackFilesystem:[sender representedObject] Force:false]; } - (IBAction)rollbackFilesystemForce:(id)sender { [self rollbackFilesystem:[sender representedObject] Force:true]; } - (IBAction)cloneSnapshot:(id)sender { NSString * snapshot = [sender representedObject]; NSString * newFileSystem = [snapshot stringByReplacingOccurrencesOfString:@"@" withString:@"-"]; [_zetaQueryDialog addQuery:NSLocalizedString(@"Enter new filesystem name", @"Clone Query") withDefault:newFileSystem withCallback:^(NSString * newFileSystem) { NSDictionary * opts = @{@"snapshot": snapshot, @"newFilesystem": newFileSystem}; [self->_authorization cloneSnapshot:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Clone succeeded", @"Clone succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ cloned to %@", @"Clone Success format"), snapshot, newFileSystem]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; }]; } - (IBAction)createFilesystem:(id)sender { NSString * parentFilesyStem = [sender representedObject]; NSMutableDictionary * query = [NSMutableDictionary dictionary]; query[@"filesystem"] = [NSString stringWithFormat:@"%@/NewFilesystem", parentFilesyStem]; [_zetaNewFSDialog addQuery:query withCallback:^(NSDictionary * opts) { [self->_authorization createFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Filesystem creation succeeded", @"Filesystem creation succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Filesystem %@ created", @"FS Create Success format"), opts[@"filesystem"]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; }]; } - (IBAction)createVolume:(id)sender { NSString * parentFilesyStem = [sender representedObject]; NSMutableDictionary * query = [NSMutableDictionary dictionary]; query[@"filesystem"] = [NSString stringWithFormat:@"%@/NewVolume", parentFilesyStem]; query[@"size"] = [NSNumber numberWithUnsignedLongLong:(1 << 30)]; [_zetaNewVolDialog addQuery:query withCallback:^(NSDictionary * opts) { [self->_authorization createVolume:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Volume creation succeeded", @"Volume creation succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Volume %@ created", @"Volume Create Success format"), opts[@"filesystem"]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; }]; } - (IBAction)destroy:(id)sender { NSString * fsName = [sender representedObject]; zfs::LibZFSHandle lib; auto fs = lib.filesystem([fsName UTF8String]); auto dependents = fs.dependents(); if (!dependents.empty()) { // cannot destroy 'tank/nuts': filesystem has children [_zetaConfirmDialog addQuery:NSLocalizedString(@"Unable to destroy, the filesystem has dependent datasets", @"Destroy Dep Failure") withInformation:toString(dependents) withCallback:^(bool ok) { }]; } else { NSDictionary * opts = @{@"filesystem": fsName}; [_authorization destroy:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Destroy succeeded", @"Destroy Succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ destroyed", @"Destroy Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } } - (IBAction)destroyRecursive:(id)sender { NSString * fsName = [sender representedObject]; zfs::LibZFSHandle lib; auto fs = lib.filesystem([fsName UTF8String]); auto dependents = fs.dependents(); auto destroyBlock = ^(bool ok) { if (ok) { NSDictionary * opts = @{@"filesystem": [sender representedObject], @"recursive": @TRUE}; [self->_authorization destroy:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Recursive destroy succeeded", @"Recursive destroy succeeded"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"%@ destroyed recursively", @"Destroy Recursive Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } }; if (!dependents.empty()) { [_zetaConfirmDialog addQuery:NSLocalizedString(@"The following dependent datasets will be destroyed", @"Destroy Dep Query") withInformation:toString(dependents) withCallback:destroyBlock]; } else { destroyBlock(true); } } - (IBAction)loadKey:(id)sender { NSString * fs = [sender representedObject]; [_zetaKeyLoader unlockFileSystem:fs]; } - (IBAction)loadAllKeys:(id)sender { NSArray * fss = [sender representedObject]; for (NSString * fs in fss) { [_zetaKeyLoader unlockFileSystem:fs]; } } - (IBAction)unloadKey:(id)sender { NSDictionary * opts = @{@"filesystem": [sender representedObject]}; [_authorization unloadKeyForFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Key unloaded successfully", @"Key unloaded successfully"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Key for %@ unloaded", @"Key Unload Success format"), [sender representedObject]]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; }]; } - (void)unloadNextKey:(NSMutableArray*)fileSystems { if ([fileSystems count] > 0) { NSString * fs = [fileSystems lastObject]; [fileSystems removeLastObject]; NSDictionary * opts = @{@"filesystem": fs}; [_authorization unloadKeyForFilesystem:opts withReply:^(NSError * error) { if (!error) { NSString * title = NSLocalizedString(@"Key unloaded successfully", @"Key unloaded successfully"); NSString * text = [NSString stringWithFormat: NSLocalizedString(@"Key for %@ unloaded", @"Key Unload Success format"), fs]; [self notifySuccessWithTitle:title text:text]; } [self handleFileSystemChangeReply:error]; [self unloadNextKey:fileSystems]; }]; } } - (IBAction)unloadAllKeys:(id)sender { NSMutableArray * fileSystems = [sender representedObject]; [self unloadNextKey:fileSystems]; } - (IBAction)scrubPool:(id)sender { NSDictionary * opts = @{@"pool": [sender representedObject]}; [_authorization scrubPool:opts withReply:^(NSError * error) { [self handleMetaDataChangeReply:error]; }]; } - (IBAction)scrubStopPool:(id)sender { NSDictionary * opts = @{@"pool": [sender representedObject], @"command": @"stop"}; [_authorization scrubPool:opts withReply:^(NSError * error) { [self handleMetaDataChangeReply:error]; }]; } - (IBAction)scrubPausePool:(id)sender { NSDictionary * opts = @{@"pool": [sender representedObject], @"command": @"pause"}; [_authorization scrubPool:opts withReply:^(NSError * error) { [self handleMetaDataChangeReply:error]; }]; } @end ================================================ FILE: ZetaWatch/ZetaNotificationCenter.h ================================================ // // ZetaNotificationCenter.h // ZetaWatch // // Created by cbreak on 19.08.25. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaPoolWatcher.h" #import @interface ZetaNotification : NSObject { } @property (readonly) NSString * title; @end @interface ZetaNotificationCenter : NSObject { } - (ZetaNotification*)startAction:(NSString*)title; - (void)stopAction:(ZetaNotification*)notification; - (void)stopAction:(ZetaNotification*)notification withError:(NSError*)error; - (void)errorDetected:(std::string const &)error; - (void)errorDetectedInPool:(std::string const &)pool; @property (readonly) NSArray * inProgressActions; @property (weak) IBOutlet ZetaPoolWatcher * poolWatcher; @end ================================================ FILE: ZetaWatch/ZetaNotificationCenter.mm ================================================ // // ZetaNotificationCenter.m // ZetaWatch // // Created by cbreak on 19.08.25. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaNotificationCenter.h" #import "ZetaPoolWatcher.h" @implementation ZetaNotification { } - (id)initWithTitle:(NSString*)title { if (self = [super init]) { self->title = title; } return self; } @synthesize title; @end @implementation ZetaNotificationCenter { NSMutableArray * inProgressActions; } - (id)init { if (self = [super init]) { inProgressActions = [[NSMutableArray alloc] init]; } return self; } - (void)awakeFromNib { if (self.poolWatcher) { [self.poolWatcher.delegates addObject:self]; } } - (ZetaNotification*)startAction:(NSString*)title { ZetaNotification * notification = [[ZetaNotification alloc] initWithTitle:title]; [inProgressActions addObject:notification]; return notification; } - (void)stopAction:(ZetaNotification*)notification { [inProgressActions removeObject:notification]; } - (void)stopAction:(ZetaNotification*)notification withError:(NSError*)error { [self stopAction:notification]; } - (void)errorDetectedInPool:(std::string const &)pool { NSUserNotification * notification = [[NSUserNotification alloc] init]; notification.title = NSLocalizedString(@"ZFS Pool Error", @"ZFS Pool Error Title"); NSString * errorFormat = NSLocalizedString(@"ZFS detected an error on pool %s.", @"ZFS Pool Error Format"); notification.informativeText = [NSString stringWithFormat:errorFormat, pool.c_str()]; notification.hasActionButton = NO; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } - (void)errorDetected:(std::string const &)error { NSUserNotification * notification = [[NSUserNotification alloc] init]; notification.title = NSLocalizedString(@"ZFS Error", @"ZFS Error Title"); NSString * errorFormat = NSLocalizedString(@"ZFS encountered an error: %s.", @"ZFS Error Format"); notification.informativeText = [NSString stringWithFormat:errorFormat, error.c_str()]; notification.hasActionButton = NO; [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } @synthesize inProgressActions; @end ================================================ FILE: ZetaWatch/ZetaPoolPropertyMenu.h ================================================ // // ZetaPoolPropertyMenu.h // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import #import "ZetaCommanderBase.h" #include "ZFSUtils.hpp" @interface ZetaPoolPropertyMenu : ZetaCommanderBase - (id)initWithPool:(zfs::ZPool)pool; - (void)menuNeedsUpdate:(NSMenu*)menu; @end ================================================ FILE: ZetaWatch/ZetaPoolPropertyMenu.mm ================================================ // // ZetaPoolPropertyMenu.mm // ZetaWatch // // Created by cbreak on 19.06.22. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaPoolPropertyMenu.h" @implementation ZetaPoolPropertyMenu { zfs::ZPool _pool; } - (id)initWithPool:(zfs::ZPool)pool { if (self = [super init]) { _pool = std::move(pool); } return self; } - (void)menuNeedsUpdate:(NSMenu*)menu { [menu removeAllItems]; auto props = _pool.properties(); for (auto const & p : props) { addMenuItem(menu, self, NSLocalizedString(@"%-64s \t %s", @"KeyValue"), p.name, p.value); } } @end ================================================ FILE: ZetaWatch/ZetaPoolWatcher.h ================================================ // // ZetaPoolWatcher.h // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.31. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import #include "ZFSUtils.hpp" #include @protocol ZetaPoolWatcherDelegate @optional - (void)newPoolDetected:(zfs::ZPool const &)pool; - (void)errorDetectedInPool:(std::string const &)pool; - (void)errorDetected:(std::string const &)error; @end @interface ZetaPoolWatcher : NSObject - (id)init; - (void)checkForChanges; - (void)keepAwake; - (void)stopKeepingAwake; @property (strong) NSMutableArray> * delegates; @end ================================================ FILE: ZetaWatch/ZetaPoolWatcher.mm ================================================ // // ZetaPoolWatcher.mm // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.31. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import "ZetaPoolWatcher.h" #import #include CFStringRef powerAssertionName = CFSTR("ZFSScrub"); CFStringRef powerAssertionReason = CFSTR("ZFS Scrub in progress"); @interface ZetaPoolWatcher () { // ZFS std::vector _knownPools; // Statistics std::map _errorStats; // Sleep Prevention IOPMAssertionID assertionID; bool keptAwake; // Timing NSTimer * _autoUpdateTimer; } @end bool containsMoreErrors(zfs::VDevStat const & a, zfs::VDevStat const & b) { return b.errorRead > a.errorRead || b.errorWrite > a.errorWrite || b.errorChecksum > a.errorChecksum; } @implementation ZetaPoolWatcher - (id)init { if (self = [super init]) { _autoUpdateTimer = [NSTimer timerWithTimeInterval:60 target:self selector:@selector(timedUpdate:) userInfo:nil repeats:YES]; _autoUpdateTimer.tolerance = 8; [[NSRunLoop currentRunLoop] addTimer:_autoUpdateTimer forMode:NSDefaultRunLoopMode]; delegates = [[NSMutableArray alloc] init]; } return self; } - (void)dealloc { [_autoUpdateTimer invalidate]; _autoUpdateTimer = nil; [self stopKeepingAwake]; } - (void)timedUpdate:(NSTimer*)timer { [self checkForChanges]; } - (void)checkForChanges { try { zfs::LibZFSHandle zfs; auto p = zfs.pools(); [self checkForNewPools:p]; [self checkForNewErrors:p]; auto scrubCounter = [self countScrubsInProgress:p]; auto sd = [NSUserDefaults standardUserDefaults]; if (scrubCounter > 0 && [sd boolForKey:@"keepAwakeDuringScrub"]) [self keepAwake]; else [self stopKeepingAwake]; } catch (std::exception const & e) { [self notifyError:e.what()]; } } - (bool)checkDev:(zfs::NVList const &)dev { auto guid = vdevGUID(dev); auto newStat = vdevStat(dev); auto & oldStat = _errorStats[guid]; bool error = containsMoreErrors(oldStat, newStat); oldStat = newStat; return error; } - (bool)checkForNewErrors:(std::vector const &)pools { try { for (auto && pool: pools) { auto vdevs = pool.vdevs(); for (auto && vdev: vdevs) { if ([self checkDev:vdev]) { [self notifyErrorInPool:pool.name()]; return true; } auto devices = zfs::vdevChildren(vdev); for (auto && device: devices) { if ([self checkDev:device]) { [self notifyErrorInPool:pool.name()]; return true; } } } } } catch (std::exception const & e) { [self notifyError:e.what()]; } return false; } - (void)notifyNewPoolDetected:(zfs::ZPool const &)pool { for (id d in [self delegates]) { if ([d respondsToSelector:@selector(newPoolDetected:)]) { [d newPoolDetected:pool]; } } } - (void)notifyErrorInPool:(std::string const &)pool { for (id d in [self delegates]) { if ([d respondsToSelector:@selector(errorDetectedInPool:)]) { [d errorDetectedInPool:pool]; } } } - (void)notifyError:(std::string const &)pool { for (id d in [self delegates]) { if ([d respondsToSelector:@selector(errorDetected:)]) { [d errorDetected:pool]; } } } std::vector poolsToGUID(std::vector const & pools) { std::vector guids(pools.size()); std::transform(pools.begin(), pools.end(), guids.begin(), [](auto const & pool) { return pool.guid(); }); std::sort(guids.begin(), guids.end()); return guids; } - (void)checkForNewPools:(std::vector const &)pools { for (auto const & p : pools) { if (std::binary_search(_knownPools.begin(), _knownPools.end(), p.guid())) { // Already known pool } else { // New pool [self notifyNewPoolDetected:p]; } } _knownPools = poolsToGUID(pools); } - (uint64_t)countScrubsInProgress:(std::vector const &)pools { uint64_t scrubsInProgress = 0; try { for (auto && pool: pools) { auto vdevs = pool.vdevs(); auto scan = pool.scanStat(); if (scan.state == zfs::ScanStat::scanning) ++scrubsInProgress; } } catch (std::exception const & e) { [self notifyError:e.what()]; } return scrubsInProgress; } - (void)keepAwake { if (!keptAwake) { IOReturn success = IOPMAssertionCreateWithDescription( kIOPMAssertPreventUserIdleSystemSleep, powerAssertionName, powerAssertionReason, 0, 0, 0, 0, &assertionID); if (success == kIOReturnSuccess) { keptAwake = true; } else { keptAwake = false; assertionID = 0; } } } - (void)stopKeepingAwake { if (keptAwake) { IOReturn success = IOPMAssertionRelease(assertionID); if (success == kIOReturnSuccess) { keptAwake = false; assertionID = 0; } } } @synthesize delegates; @end ================================================ FILE: ZetaWatch/ZetaQueryDialog.h ================================================ // // ZetaQueryDialog.h // ZetaWatch // // Created by cbreak on 19.10.06. // Copyright © 2019 the-color-black.net. All rights reserved. // #import @interface ZetaQueryDialog : NSObject @property (weak) NSStatusItem * statusItem; @property (weak) IBOutlet NSPopover * popover; @property (weak) IBOutlet NSTextField * replyField; @property (weak) IBOutlet NSTextField * queryField; - (IBAction)ok:(id)sender; - (IBAction)cancel:(id)sender; - (BOOL)popoverShouldDetach:(NSPopover *)popover; - (void)addQuery:(NSString*)query withDefault:(NSString*)defaultReply withCallback:(void(^)(NSString*))callback; @end ================================================ FILE: ZetaWatch/ZetaQueryDialog.mm ================================================ // // ZetaQueryDialog.mm // ZetaWatch // // Created by cbreak on 19.10.06. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaQueryDialog.h" #include #include namespace { typedef void (^QueryCallback)(NSString *); struct Query { NSString * query; NSString * defaultReply; QueryCallback reply; }; } @interface ZetaQueryDialog () { std::deque queries; } @end @implementation ZetaQueryDialog - (void)addQuery:(NSString*)query withDefault:(NSString*)defaultReply withCallback:(void(^)(NSString*))callback { queries.push_back({query, defaultReply, callback}); if (queries.size() == 1) [self updateQuery]; if (![_popover isShown]) [self show]; } - (void)show { [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; NSView * positioningView = [_statusItem button]; [_popover showRelativeToRect:NSMakeRect(0, 0, 0, 0) ofView:positioningView preferredEdge:NSRectEdgeMinY]; } - (IBAction)ok:(id)sender { if (!queries.empty()) queries.front().reply([_replyField stringValue]); [self advanceQuery]; } - (IBAction)cancel:(id)sender { [self advanceQuery]; } - (BOOL)popoverShouldDetach:(NSPopover *)popover { return YES; } - (void)advanceQuery { queries.pop_front(); [self updateQuery]; if (queries.empty()) { [_popover performClose:self]; } } - (void)updateQuery { if (queries.empty()) { [_queryField setStringValue:@""]; [_replyField setStringValue:@""]; } else { [_queryField setStringValue:queries.front().query]; [_replyField setStringValue:queries.front().defaultReply]; } } - (BOOL)popoverShouldClose:(NSPopover *)popover { if (!queries.empty()) { queries.pop_front(); if (queries.empty()) return YES; [self updateQuery]; return NO; } else { return YES; } } - (void)popoverWillShow:(NSNotification *)notification { } - (void)popoverDidClose:(NSNotification *)notification { [_replyField abortEditing]; } @end ================================================ FILE: ZetaWatch/ZetaSnapshotMenu.h ================================================ // // ZetaSnapshotMenu.h // ZetaWatch // // Created by cbreak on 19.10.05. // Copyright © 2019 the-color-black.net. All rights reserved. // #import #import "ZetaCommanderBase.h" #include "ZFSUtils.hpp" NS_ASSUME_NONNULL_BEGIN @class ZetaMainMenu; @interface ZetaSnapshotMenu : ZetaCommanderBase - (id)initWithFileSystem:(zfs::ZFileSystem)fs delegate:(ZetaMainMenu*)main; - (void)menuNeedsUpdate:(NSMenu*)menu; @end NS_ASSUME_NONNULL_END ================================================ FILE: ZetaWatch/ZetaSnapshotMenu.mm ================================================ // // ZetaSnapshotMenu.m // ZetaWatch // // Created by cbreak on 19.10.05. // Copyright © 2019 the-color-black.net. All rights reserved. // #import "ZetaSnapshotMenu.h" #import "ZetaMainMenu.h" @implementation ZetaSnapshotMenu { zfs::ZFileSystem _fs; ZetaMainMenu __weak * _delegate; } - (id)initWithFileSystem:(zfs::ZFileSystem)fs delegate:(ZetaMainMenu*)delegate { if (self = [super init]) { _fs = std::move(fs); _delegate = delegate; } return self; } NSMenuItem * createSnapMenu(zfs::ZFileSystem const & snap, ZetaMainMenu * delegate) { NSMenu * sMenu = [[NSMenu alloc] init]; [sMenu setAutoenablesItems:NO]; NSString * sName = [NSString stringWithUTF8String:snap.name()]; auto addSnapCommand = [&](NSString * title, SEL selector) { auto item = [sMenu addItemWithTitle:title action:selector keyEquivalent:@""]; item.representedObject = sName; item.target = delegate; }; addSnapCommand(NSLocalizedString(@"Clone", @"Clone"), @selector(cloneSnapshot:)); addSnapCommand(NSLocalizedString(@"Rollback", @"Rollback"), @selector(rollbackFilesystem:)); addSnapCommand(NSLocalizedString(@"Rollback (Force)", @"Rollback (Force)"), @selector(rollbackFilesystemForce:)); [sMenu addItem:[NSMenuItem separatorItem]]; if (!snap.mounted()) { addSnapCommand(NSLocalizedString(@"Mount", @"Mount"), @selector(mountFilesystem:)); } else { addSnapCommand(NSLocalizedString(@"Unmount", @"Unmount"), @selector(unmountFilesystem:)); addSnapCommand(NSLocalizedString(@"Unmount (Force)", @"Unmount (Force)"), @selector(unmountFilesystemForce:)); } [sMenu addItem:[NSMenuItem separatorItem]]; if (snap.cloneCount() == 0) { addSnapCommand(NSLocalizedString(@"Destroy", @"Destroy"), @selector(destroy:)); } else { addSnapCommand(NSLocalizedString(@"Destroy Recursive", @"Destroy Recursive"), @selector(destroyRecursive:)); } auto item = [[NSMenuItem alloc] initWithTitle:sName action:nullptr keyEquivalent:@""]; item.representedObject = sName; item.submenu = sMenu; return item; } - (void)menuNeedsUpdate:(NSMenu*)menu { [menu removeAllItems]; auto snap = _fs.snapshots(); if (!snap.empty()) { for (size_t i = snap.size(); i > 0; --i) { NSMenuItem * item = createSnapMenu(snap[i-1], _delegate); [menu addItem:item]; } } else { [menu addItemWithTitle:NSLocalizedString(@"No snapshots found", @"No Snapshots") action:NULL keyEquivalent:@""]; } } @end ================================================ FILE: ZetaWatch/ZetaWatch.entitlements ================================================ com.apple.security.cs.disable-library-validation ================================================ FILE: ZetaWatch/ZetaWatchDelegate.h ================================================ // // ZetaWatchDelegate.h // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.20. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import @interface ZetaWatchDelegate : NSObject // NSUserNotificationCenterDelegate - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification; @end ================================================ FILE: ZetaWatch/ZetaWatchDelegate.mm ================================================ // // ZetaWatchDelegate.mm // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.20. // Copyright © 2015 the-color-black.net. All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, are permitted // provided that the conditions of the "3-Clause BSD" license described in the BSD.LICENSE file are met. // Additional licensing options are described in the README file. // #import "ZetaWatchDelegate.h" #import "ZetaAuthorization.h" #import "ZetaMainMenu.h" #import "ZetaKeyLoader.h" #import "ZetaQueryDialog.h" #import "ZetaDictQueryDialog.h" #import "ZetaConfirmDialog.h" #import "ZFSUtils.hpp" #import #import @interface ZetaWatchDelegate () { NSStatusItem * _statusItem; } @property (weak) IBOutlet NSMenu * zetaMenu; @property (weak) IBOutlet ZetaAuthorization * authorization; @property (weak) IBOutlet ZetaMainMenu * zetaMainMenu; @property (weak) IBOutlet ZetaKeyLoader * zetaKeyLoader; @property (weak) IBOutlet ZetaQueryDialog * zetaQueryDialog; @property (weak) IBOutlet ZetaConfirmDialog * zetaConfirmDialog; @property (weak) IBOutlet ZetaPoolWatcher * poolWatcher; @property (weak) IBOutlet SUUpdater * updater; @property (weak) IBOutlet NSPopover * settings; @property (strong) ZetaDictQueryDialog * zetaNewFSDialog; @property (strong) ZetaDictQueryDialog * zetaNewVolDialog; @end @implementation ZetaWatchDelegate - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // Menu Item NSStatusBar * bar = [NSStatusBar systemStatusBar]; _statusItem = [bar statusItemWithLength:NSSquareStatusItemLength]; NSImage * zetaImage = [NSImage imageNamed:@"Zeta"]; [zetaImage setTemplate:YES]; _statusItem.button.image = zetaImage; _statusItem.menu = _zetaMenu; _zetaKeyLoader.statusItem = _statusItem; _zetaQueryDialog.statusItem = _statusItem; _zetaConfirmDialog.statusItem = _statusItem; // Dialogs _zetaNewFSDialog = [[ZetaDictQueryDialog alloc] initWithDialog:@"NewFS"]; _zetaNewFSDialog.statusItem = _statusItem; _zetaMainMenu.zetaNewFSDialog = _zetaNewFSDialog; _zetaNewVolDialog = [[ZetaDictQueryDialog alloc] initWithDialog:@"NewVol"]; _zetaNewVolDialog.statusItem = _statusItem; _zetaMainMenu.zetaNewVolDialog = _zetaNewVolDialog; // Watcher [[self poolWatcher] checkForChanges]; // User Notification Center Delegate [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:self]; // Login Item [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"startAtLogin" options:NSKeyValueObservingOptionInitial context:nullptr]; } - (void)applicationWillFinishLaunching:(NSNotification *)notification { // User Defaults [[NSUserDefaults standardUserDefaults] registerDefaults:@{ @"autoUnlock": @YES, @"autoImport": @YES, @"allowHostIDMismatch": @NO, @"useKeychain": @NO, @"startAtLogin": @YES, @"keepAwakeDuringScrub": @YES, @"defaultAltroot": @"/Volumes", @"useAltroot": @NO, @"searchPathOverride": @[ @"/var/run/disk/by-serial", @"/var/run/disk/by-id", ], }]; try { // Update Feed URL to the one matching the current ZFS version, even if // ZetaWatch was not compiled for it. auto version = zfs::LibZFSHandle::version(); NSString * feedString = [NSString stringWithFormat: @"https://zetawatch.the-color-black.net/download/%i.%i/appcast.xml", version.major, version.minor]; NSURL * feedURL = [NSURL URLWithString:feedString]; [self.updater setFeedURL:feedURL]; // TODO: Check for compatibility } catch (std::exception const & e) { NSLog(@"Error querying ZFS Version: %s", e.what()); // Disable auto update [self.updater setAutomaticallyChecksForUpdates:NO]; [self.updater setAutomaticallyDownloadsUpdates:NO]; } } - (void)applicationWillTerminate:(NSNotification *)aNotification { [[NSUserDefaults standardUserDefaults] removeObserver:self forKeyPath:@"startAtLogin"]; [self.authorization stopHelper]; } - (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center shouldPresentNotification:(NSUserNotification *)notification { return YES; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"startAtLogin"]) { bool startAtLogin = [object boolForKey:@"startAtLogin"]; SMLoginItemSetEnabled(CFSTR("net.the-color-black.ZetaLoginItemHelper"), startAtLogin); } } - (IBAction)showSettings:(id)sender { NSView * positioningView = [_statusItem button]; [_settings showRelativeToRect:NSMakeRect(0, 0, 0, 0) ofView:positioningView preferredEdge:NSRectEdgeMinY]; } @end ================================================ FILE: ZetaWatch/main.m ================================================ // // main.m // ZetaWatch // // Created by Gerhard Röthlin on 2015.12.20. // Copyright © 2015 the-color-black.net. All rights reserved. // #import int main(int argc, const char * argv[]) { return NSApplicationMain(argc, argv); } ================================================ FILE: ZetaWatch.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 7006C4801C26CA1500929DAE /* ZetaWatchDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7006C47F1C26CA1500929DAE /* ZetaWatchDelegate.mm */; }; 7006C4831C26CA1500929DAE /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 7006C4821C26CA1500929DAE /* main.m */; }; 7006C4851C26CA1500929DAE /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7006C4841C26CA1500929DAE /* Assets.xcassets */; }; 7006C4881C26CA1500929DAE /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7006C4861C26CA1500929DAE /* MainMenu.xib */; }; 7006C4AC1C26D3E700929DAE /* ZetaMainMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7006C4AB1C26D3E700929DAE /* ZetaMainMenu.mm */; }; 70168B6622B64E48002C760A /* ZetaKeyLoader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70168B6522B64E48002C760A /* ZetaKeyLoader.mm */; }; 7018764120E7B45900BA39B8 /* ZFSUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FB71C2C076E00929DAE /* ZFSUtils.cpp */; }; 7018764220E7B45B00BA39B8 /* ZFSStrings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FBF1C2D9CB900929DAE /* ZFSStrings.cpp */; }; 7018764320E7B45E00BA39B8 /* ZFSNVList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FC21C2EAE5A00929DAE /* ZFSNVList.cpp */; }; 7018764820E7B4A100BA39B8 /* ZFSNVList.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FC31C2EAE5A00929DAE /* ZFSNVList.hpp */; }; 7018764920E7B4A300BA39B8 /* ZFSStrings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FC11C2D9CC900929DAE /* ZFSStrings.hpp */; }; 7018764A20E7B4A600BA39B8 /* ZFSUtils.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FB81C2C076E00929DAE /* ZFSUtils.hpp */; }; 7018764B20E7B55700BA39B8 /* libZFSWrapperStatic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7018763D20E7B44800BA39B8 /* libZFSWrapperStatic.a */; }; 7018765120E7B6A500BA39B8 /* ZFSUtils.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FB81C2C076E00929DAE /* ZFSUtils.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 7018765220E7B6A700BA39B8 /* ZFSStrings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FC11C2D9CC900929DAE /* ZFSStrings.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 7018765320E7B6B500BA39B8 /* ZFSNVList.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 70D85FC31C2EAE5A00929DAE /* ZFSNVList.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 7018765620E7B79E00BA39B8 /* libZFSWrapperStatic.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7018763D20E7B44800BA39B8 /* libZFSWrapperStatic.a */; }; 7023C1FB26C1C768002C760A /* ZFSStrings.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7023C1FA26C1C768002C760A /* ZFSStrings.mm */; }; 7023C1FC26C1C768002C760A /* ZFSStrings.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7023C1FA26C1C768002C760A /* ZFSStrings.mm */; }; 703811A12312A2CB002C760A /* ZetaNotificationCenter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 703811A02312A2CB002C760A /* ZetaNotificationCenter.mm */; }; 703811A42312C833002C760A /* ServiceManagement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 703811A32312C833002C760A /* ServiceManagement.framework */; }; 703B176C262C6B0F002C760A /* libnvpair.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1752262C6A31002C760A /* libnvpair.3.dylib */; }; 703B176D262C6B0F002C760A /* libzfs_core.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1751262C6A31002C760A /* libzfs_core.3.dylib */; }; 703B176E262C6B0F002C760A /* libzfs.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1750262C6A31002C760A /* libzfs.4.dylib */; }; 70552BC62336AFD8002C760A /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 70552BC42336AEB2002C760A /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 70552BC82336B196002C760A /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70552BC42336AEB2002C760A /* Sparkle.framework */; }; 7068A94623ACFB0A002C760A /* NewVol.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7068A94423ACFB0A002C760A /* NewVol.xib */; }; 7068A94823ACFBB1002C760A /* SizeTransformer.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7068A94723ACFBB1002C760A /* SizeTransformer.mm */; }; 70703F3722AD7633002C760A /* DiskArbitration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 70703F3622AD7633002C760A /* DiskArbitration.framework */; }; 70703F3922AD7A17002C760A /* IDDiskArbitrationUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70703F3822AD7A16002C760A /* IDDiskArbitrationUtils.cpp */; }; 7075BE36236E09DF002C760A /* ZetaLoginItemHelper.app in CopyFiles */ = {isa = PBXBuildFile; fileRef = 70E4A21D236DF7D6002C760A /* ZetaLoginItemHelper.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 708112B726D993F3002C760A /* libnvpair.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1752262C6A31002C760A /* libnvpair.3.dylib */; }; 708112B826D993F4002C760A /* libzfs_core.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1751262C6A31002C760A /* libzfs_core.3.dylib */; }; 708112B926D993F4002C760A /* libzfs.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1750262C6A31002C760A /* libzfs.4.dylib */; }; 708112BB26D993F4002C760A /* libzpool.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 708112BA26D993F4002C760A /* libzpool.5.dylib */; }; 708112C026D99413002C760A /* libnvpair.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1752262C6A31002C760A /* libnvpair.3.dylib */; }; 708112C126D99413002C760A /* libzfs_core.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1751262C6A31002C760A /* libzfs_core.3.dylib */; }; 708112C226D99413002C760A /* libzfs.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1750262C6A31002C760A /* libzfs.4.dylib */; }; 708112C326D99413002C760A /* libzpool.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 708112BA26D993F4002C760A /* libzpool.5.dylib */; }; 708112CC26D99470002C760A /* libzpool.5.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 708112BA26D993F4002C760A /* libzpool.5.dylib */; }; 708112CD26D99473002C760A /* libnvpair.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1752262C6A31002C760A /* libnvpair.3.dylib */; }; 708112CE26D99475002C760A /* libzfs_core.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1751262C6A31002C760A /* libzfs_core.3.dylib */; }; 708112CF26D99479002C760A /* libzfs.4.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 703B1750262C6A31002C760A /* libzfs.4.dylib */; }; 7099FB4523A575F9002C760A /* ZetaBookmarkMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 7099FB4323A575F8002C760A /* ZetaBookmarkMenu.mm */; }; 709C1CAD1C354F8B00929DAE /* ZetaPoolWatcher.mm in Sources */ = {isa = PBXBuildFile; fileRef = 709C1CAC1C354F8B00929DAE /* ZetaPoolWatcher.mm */; }; 70AE5AB222A3DBAA002C760A /* ZetaImportMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70AE5AB122A3DBAA002C760A /* ZetaImportMenu.mm */; }; 70AE5AB522A3F7D3002C760A /* ZetaCommanderBase.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70AE5AB422A3F7D3002C760A /* ZetaCommanderBase.mm */; }; 70B4E4C922F45E7C002C760A /* IDDiskArbitrationDispatcher.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70B4E4C622F45E7C002C760A /* IDDiskArbitrationDispatcher.cpp */; }; 70B4E4CC22F469FC002C760A /* ZetaAutoImporter.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70B4E4CA22F469FC002C760A /* ZetaAutoImporter.mm */; }; 70BF211023539130002C760A /* ZetaConfirmDialog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70BF210F23539130002C760A /* ZetaConfirmDialog.mm */; }; 70C930D822122CBD00BA39B8 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 70C930D622122CBD00BA39B8 /* Localizable.strings */; }; 70CE283D2348B0D4002C760A /* ZetaSnapshotMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70CE283C2348B0D4002C760A /* ZetaSnapshotMenu.mm */; }; 70D118B72375FE28002C760A /* PathValueTransformer.m in Sources */ = {isa = PBXBuildFile; fileRef = 70D118B62375FE28002C760A /* PathValueTransformer.m */; }; 70D85FEF1C316F3100929DAE /* ZFSUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FB71C2C076E00929DAE /* ZFSUtils.cpp */; }; 70D85FF01C316F3100929DAE /* ZFSStrings.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FBF1C2D9CB900929DAE /* ZFSStrings.cpp */; }; 70D85FF11C316F3100929DAE /* ZFSNVList.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70D85FC21C2EAE5A00929DAE /* ZFSNVList.cpp */; }; 70DF66B1234A2884002C760A /* ZetaQueryDialog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70DF66AF234A2884002C760A /* ZetaQueryDialog.mm */; }; 70E4A229236DF7D7002C760A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 70E4A228236DF7D7002C760A /* main.m */; }; 70EA5F5022BEC358002C760A /* ZetaFileSystemPropertyMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70EA5F4F22BEC358002C760A /* ZetaFileSystemPropertyMenu.mm */; }; 70EA5F5322BEC7B1002C760A /* ZetaFormatHelpers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 70EA5F5222BEC7B1002C760A /* ZetaFormatHelpers.cpp */; }; 70EA5F5622BED079002C760A /* ZetaPoolPropertyMenu.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70EA5F5522BED079002C760A /* ZetaPoolPropertyMenu.mm */; }; 70EABDC01FF9992A00BA39B8 /* ZetaAuthorization.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70EABDBF1FF9992A00BA39B8 /* ZetaAuthorization.mm */; }; 70EABDC41FF9A8D100BA39B8 /* CommonAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = 70EABDC31FF9A8D100BA39B8 /* CommonAuthorization.m */; }; 70EABDCD1FF9ACB300BA39B8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 70EABDCC1FF9ACB300BA39B8 /* main.m */; }; 70EABDD41FF9B21C00BA39B8 /* ZetaAuthorizationHelper.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70EABDD31FF9B21C00BA39B8 /* ZetaAuthorizationHelper.mm */; }; 70EABDD91FF9B6C600BA39B8 /* net.the-color-black.ZetaAuthorizationHelper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 70EABDCA1FF9ACB300BA39B8 /* net.the-color-black.ZetaAuthorizationHelper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 70EABDDA1FF9C05700BA39B8 /* CommonAuthorization.m in Sources */ = {isa = PBXBuildFile; fileRef = 70EABDC31FF9A8D100BA39B8 /* CommonAuthorization.m */; }; 70EABDDC1FF9C1F000BA39B8 /* CommonAuthorization.strings in Resources */ = {isa = PBXBuildFile; fileRef = 70EABDDB1FF9C11E00BA39B8 /* CommonAuthorization.strings */; }; 70F307CD23ACD917002C760A /* ZetaDictQueryDialog.mm in Sources */ = {isa = PBXBuildFile; fileRef = 70F307CC23ACD917002C760A /* ZetaDictQueryDialog.mm */; }; 70F307D023ACE415002C760A /* NewFS.xib in Resources */ = {isa = PBXBuildFile; fileRef = 70F307CE23ACE415002C760A /* NewFS.xib */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 7018764F20E7B62400BA39B8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7006C4731C26CA1400929DAE /* Project object */; proxyType = 1; remoteGlobalIDString = 7018763C20E7B44800BA39B8; remoteInfo = ZFSWrapperStatic; }; 7018765420E7B79800BA39B8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7006C4731C26CA1400929DAE /* Project object */; proxyType = 1; remoteGlobalIDString = 7018763C20E7B44800BA39B8; remoteInfo = ZFSWrapperStatic; }; 70E4A22E236DF7FD002C760A /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7006C4731C26CA1400929DAE /* Project object */; proxyType = 1; remoteGlobalIDString = 70E4A21C236DF7D6002C760A; remoteInfo = ZetaLoginItemHelper; }; 70EABDD61FF9B69000BA39B8 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 7006C4731C26CA1400929DAE /* Project object */; proxyType = 1; remoteGlobalIDString = 70EABDC91FF9ACB300BA39B8; remoteInfo = ZetaAuthorizationHelper; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 70D85FEE1C316E8F00929DAE /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 10; files = ( 70552BC62336AFD8002C760A /* Sparkle.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; 70E4A212236DF1F6002C760A /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LoginItems; dstSubfolderSpec = 1; files = ( 7075BE36236E09DF002C760A /* ZetaLoginItemHelper.app in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; 70EABDD81FF9B6A200BA39B8 /* CopyFiles */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = Contents/Library/LaunchServices; dstSubfolderSpec = 1; files = ( 70EABDD91FF9B6C600BA39B8 /* net.the-color-black.ZetaAuthorizationHelper in CopyFiles */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 7006C47B1C26CA1500929DAE /* ZetaWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ZetaWatch.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7006C47E1C26CA1500929DAE /* ZetaWatchDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaWatchDelegate.h; sourceTree = ""; }; 7006C47F1C26CA1500929DAE /* ZetaWatchDelegate.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaWatchDelegate.mm; sourceTree = ""; }; 7006C4821C26CA1500929DAE /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 7006C4841C26CA1500929DAE /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 7006C4871C26CA1500929DAE /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 7006C4891C26CA1500929DAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 7006C4AB1C26D3E700929DAE /* ZetaMainMenu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaMainMenu.mm; sourceTree = ""; }; 7006C4AD1C26D3FA00929DAE /* ZetaMainMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaMainMenu.h; sourceTree = ""; }; 70168B6422B64E48002C760A /* ZetaKeyLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaKeyLoader.h; sourceTree = ""; }; 70168B6522B64E48002C760A /* ZetaKeyLoader.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaKeyLoader.mm; sourceTree = ""; }; 7018763D20E7B44800BA39B8 /* libZFSWrapperStatic.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libZFSWrapperStatic.a; sourceTree = BUILT_PRODUCTS_DIR; }; 7018765A20E7DA8100BA39B8 /* libzpool.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libzpool.1.dylib; sourceTree = ""; }; 7023C1FA26C1C768002C760A /* ZFSStrings.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZFSStrings.mm; sourceTree = ""; }; 703811A02312A2CB002C760A /* ZetaNotificationCenter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaNotificationCenter.mm; sourceTree = ""; }; 703811A22312A2E6002C760A /* ZetaNotificationCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaNotificationCenter.h; sourceTree = ""; }; 703811A32312C833002C760A /* ServiceManagement.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ServiceManagement.framework; path = System/Library/Frameworks/ServiceManagement.framework; sourceTree = SDKROOT; }; 703B1750262C6A31002C760A /* libzfs.4.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libzfs.4.dylib; path = ../../../../usr/local/zfs/lib/libzfs.4.dylib; sourceTree = ""; }; 703B1751262C6A31002C760A /* libzfs_core.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libzfs_core.3.dylib; path = ../../../../usr/local/zfs/lib/libzfs_core.3.dylib; sourceTree = ""; }; 703B1752262C6A31002C760A /* libnvpair.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libnvpair.3.dylib; path = ../../../../usr/local/zfs/lib/libnvpair.3.dylib; sourceTree = ""; }; 70552BC42336AEB2002C760A /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ThirdParty/Sparkle/DerivedData/Sparkle/Build/Products/Release/Sparkle.framework; sourceTree = ""; }; 7068A94523ACFB0A002C760A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/NewVol.xib; sourceTree = ""; }; 7068A94723ACFBB1002C760A /* SizeTransformer.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = SizeTransformer.mm; sourceTree = ""; }; 70703F3622AD7633002C760A /* DiskArbitration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DiskArbitration.framework; path = System/Library/Frameworks/DiskArbitration.framework; sourceTree = SDKROOT; }; 70703F3822AD7A16002C760A /* IDDiskArbitrationUtils.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = IDDiskArbitrationUtils.cpp; path = InvariantDisks/IDDiskArbitrationUtils.cpp; sourceTree = ""; }; 7072697B22EDB2BE002C760A /* ZetaCPPUtils.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZetaCPPUtils.hpp; sourceTree = ""; }; 708112BA26D993F4002C760A /* libzpool.5.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libzpool.5.dylib; path = ../../../../usr/local/zfs/lib/libzpool.5.dylib; sourceTree = ""; }; 7099FB4323A575F8002C760A /* ZetaBookmarkMenu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaBookmarkMenu.mm; sourceTree = ""; }; 7099FB4423A575F8002C760A /* ZetaBookmarkMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZetaBookmarkMenu.h; sourceTree = ""; }; 709C1CAC1C354F8B00929DAE /* ZetaPoolWatcher.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaPoolWatcher.mm; sourceTree = ""; }; 709C1CAE1C354F9B00929DAE /* ZetaPoolWatcher.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaPoolWatcher.h; sourceTree = ""; }; 70A60A4622AD7A5F002C760A /* IDDiskArbitrationUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = IDDiskArbitrationUtils.hpp; path = InvariantDisks/IDDiskArbitrationUtils.hpp; sourceTree = ""; }; 70AE5AB122A3DBAA002C760A /* ZetaImportMenu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaImportMenu.mm; sourceTree = ""; }; 70AE5AB322A3DBBF002C760A /* ZetaImportMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaImportMenu.h; sourceTree = ""; }; 70AE5AB422A3F7D3002C760A /* ZetaCommanderBase.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaCommanderBase.mm; sourceTree = ""; }; 70AE5AB622A3F7E4002C760A /* ZetaCommanderBase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaCommanderBase.h; sourceTree = ""; }; 70B4E4C622F45E7C002C760A /* IDDiskArbitrationDispatcher.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = IDDiskArbitrationDispatcher.cpp; path = InvariantDisks/IDDiskArbitrationDispatcher.cpp; sourceTree = ""; }; 70B4E4C722F45E7C002C760A /* IDDiskArbitrationDispatcher.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = IDDiskArbitrationDispatcher.hpp; path = InvariantDisks/IDDiskArbitrationDispatcher.hpp; sourceTree = ""; }; 70B4E4C822F45E7C002C760A /* IDDiskArbitrationHandler.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; name = IDDiskArbitrationHandler.hpp; path = InvariantDisks/IDDiskArbitrationHandler.hpp; sourceTree = ""; }; 70B4E4CA22F469FC002C760A /* ZetaAutoImporter.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaAutoImporter.mm; sourceTree = ""; }; 70B4E4CB22F469FC002C760A /* ZetaAutoImporter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaAutoImporter.h; sourceTree = ""; }; 70BF210F23539130002C760A /* ZetaConfirmDialog.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaConfirmDialog.mm; sourceTree = ""; }; 70BF21112353914F002C760A /* ZetaConfirmDialog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaConfirmDialog.h; sourceTree = ""; }; 70C930D722122CBD00BA39B8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; 70CE283B2348B0D4002C760A /* ZetaSnapshotMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaSnapshotMenu.h; sourceTree = ""; }; 70CE283C2348B0D4002C760A /* ZetaSnapshotMenu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaSnapshotMenu.mm; sourceTree = ""; }; 70D118B62375FE28002C760A /* PathValueTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PathValueTransformer.m; sourceTree = ""; }; 70D85FB71C2C076E00929DAE /* ZFSUtils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZFSUtils.cpp; sourceTree = ""; }; 70D85FB81C2C076E00929DAE /* ZFSUtils.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZFSUtils.hpp; sourceTree = ""; }; 70D85FBA1C2C18BE00929DAE /* libzfs_core.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libzfs_core.1.dylib; sourceTree = ""; }; 70D85FBB1C2C18BE00929DAE /* libzfs.2.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libzfs.2.dylib; sourceTree = ""; }; 70D85FBF1C2D9CB900929DAE /* ZFSStrings.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZFSStrings.cpp; sourceTree = ""; }; 70D85FC11C2D9CC900929DAE /* ZFSStrings.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZFSStrings.hpp; sourceTree = ""; }; 70D85FC21C2EAE5A00929DAE /* ZFSNVList.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = ZFSNVList.cpp; sourceTree = ""; }; 70D85FC31C2EAE5A00929DAE /* ZFSNVList.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = ZFSNVList.hpp; sourceTree = ""; }; 70D85FC51C2EBC5000929DAE /* libnvpair.1.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; path = libnvpair.1.dylib; sourceTree = ""; }; 70D85FE21C316E8F00929DAE /* ZFSWrapper.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ZFSWrapper.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 70D85FE61C316E8F00929DAE /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70DF66AF234A2884002C760A /* ZetaQueryDialog.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaQueryDialog.mm; sourceTree = ""; }; 70DF66B0234A2884002C760A /* ZetaQueryDialog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZetaQueryDialog.h; sourceTree = ""; }; 70E4A21D236DF7D6002C760A /* ZetaLoginItemHelper.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ZetaLoginItemHelper.app; sourceTree = BUILT_PRODUCTS_DIR; }; 70E4A227236DF7D7002C760A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70E4A228236DF7D7002C760A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 70EA5F4E22BEC358002C760A /* ZetaFileSystemPropertyMenu.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaFileSystemPropertyMenu.h; sourceTree = ""; }; 70EA5F4F22BEC358002C760A /* ZetaFileSystemPropertyMenu.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaFileSystemPropertyMenu.mm; sourceTree = ""; }; 70EA5F5122BEC6BC002C760A /* ZetaFormatHelpers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ZetaFormatHelpers.hpp; sourceTree = ""; }; 70EA5F5222BEC7B1002C760A /* ZetaFormatHelpers.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ZetaFormatHelpers.cpp; sourceTree = ""; }; 70EA5F5422BED079002C760A /* ZetaPoolPropertyMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ZetaPoolPropertyMenu.h; sourceTree = ""; }; 70EA5F5522BED079002C760A /* ZetaPoolPropertyMenu.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaPoolPropertyMenu.mm; sourceTree = ""; }; 70EABDBE1FF9992A00BA39B8 /* ZetaAuthorization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaAuthorization.h; sourceTree = ""; }; 70EABDBF1FF9992A00BA39B8 /* ZetaAuthorization.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaAuthorization.mm; sourceTree = ""; }; 70EABDC21FF9A8D100BA39B8 /* CommonAuthorization.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CommonAuthorization.h; sourceTree = ""; }; 70EABDC31FF9A8D100BA39B8 /* CommonAuthorization.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CommonAuthorization.m; sourceTree = ""; }; 70EABDC51FF9AAB800BA39B8 /* ZetaAuthorizationHelperProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaAuthorizationHelperProtocol.h; sourceTree = ""; }; 70EABDCA1FF9ACB300BA39B8 /* net.the-color-black.ZetaAuthorizationHelper */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "net.the-color-black.ZetaAuthorizationHelper"; sourceTree = BUILT_PRODUCTS_DIR; }; 70EABDCC1FF9ACB300BA39B8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 70EABDD11FF9AE2800BA39B8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 70EABDD21FF9B21C00BA39B8 /* ZetaAuthorizationHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaAuthorizationHelper.h; sourceTree = ""; }; 70EABDD31FF9B21C00BA39B8 /* ZetaAuthorizationHelper.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaAuthorizationHelper.mm; sourceTree = ""; }; 70EABDD51FF9B40F00BA39B8 /* Launchd.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = ""; }; 70EABDDB1FF9C11E00BA39B8 /* CommonAuthorization.strings */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; path = CommonAuthorization.strings; sourceTree = ""; }; 70F307CB23ACD917002C760A /* ZetaDictQueryDialog.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ZetaDictQueryDialog.h; sourceTree = ""; }; 70F307CC23ACD917002C760A /* ZetaDictQueryDialog.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ZetaDictQueryDialog.mm; sourceTree = ""; }; 70F307CF23ACE415002C760A /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/NewFS.xib; sourceTree = ""; }; 70F386AA22906D36002C760A /* ZetaWatch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ZetaWatch.entitlements; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 7006C4781C26CA1400929DAE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 703811A42312C833002C760A /* ServiceManagement.framework in Frameworks */, 70703F3722AD7633002C760A /* DiskArbitration.framework in Frameworks */, 7018764B20E7B55700BA39B8 /* libZFSWrapperStatic.a in Frameworks */, 703B176C262C6B0F002C760A /* libnvpair.3.dylib in Frameworks */, 703B176D262C6B0F002C760A /* libzfs_core.3.dylib in Frameworks */, 703B176E262C6B0F002C760A /* libzfs.4.dylib in Frameworks */, 70552BC82336B196002C760A /* Sparkle.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 7018763A20E7B44800BA39B8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 708112BB26D993F4002C760A /* libzpool.5.dylib in Frameworks */, 708112B726D993F3002C760A /* libnvpair.3.dylib in Frameworks */, 708112B826D993F4002C760A /* libzfs_core.3.dylib in Frameworks */, 708112B926D993F4002C760A /* libzfs.4.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 70D85FDE1C316E8F00929DAE /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 708112C026D99413002C760A /* libnvpair.3.dylib in Frameworks */, 708112C126D99413002C760A /* libzfs_core.3.dylib in Frameworks */, 708112C226D99413002C760A /* libzfs.4.dylib in Frameworks */, 708112C326D99413002C760A /* libzpool.5.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 70E4A21A236DF7D6002C760A /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 70EABDC71FF9ACB300BA39B8 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 7018765620E7B79E00BA39B8 /* libZFSWrapperStatic.a in Frameworks */, 708112CC26D99470002C760A /* libzpool.5.dylib in Frameworks */, 708112CD26D99473002C760A /* libnvpair.3.dylib in Frameworks */, 708112CE26D99475002C760A /* libzfs_core.3.dylib in Frameworks */, 708112CF26D99479002C760A /* libzfs.4.dylib in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 7006C4721C26CA1400929DAE = { isa = PBXGroup; children = ( 7006C47D1C26CA1500929DAE /* ZetaWatch */, 70D85FCC1C316B0900929DAE /* ZFSWrapper */, 70EABDCB1FF9ACB300BA39B8 /* ZetaAuthorizationHelper */, 70EABDC11FF9A81800BA39B8 /* CommonAuthorization */, 70E4A21E236DF7D6002C760A /* ZetaLoginItemHelper */, 7006C47C1C26CA1500929DAE /* Products */, 70DB77B11FFAD8E300BA39B8 /* Frameworks */, ); sourceTree = ""; }; 7006C47C1C26CA1500929DAE /* Products */ = { isa = PBXGroup; children = ( 7006C47B1C26CA1500929DAE /* ZetaWatch.app */, 70D85FE21C316E8F00929DAE /* ZFSWrapper.framework */, 70EABDCA1FF9ACB300BA39B8 /* net.the-color-black.ZetaAuthorizationHelper */, 7018763D20E7B44800BA39B8 /* libZFSWrapperStatic.a */, 70E4A21D236DF7D6002C760A /* ZetaLoginItemHelper.app */, ); name = Products; sourceTree = ""; }; 7006C47D1C26CA1500929DAE /* ZetaWatch */ = { isa = PBXGroup; children = ( 7006C47E1C26CA1500929DAE /* ZetaWatchDelegate.h */, 7006C47F1C26CA1500929DAE /* ZetaWatchDelegate.mm */, 70AE5AB622A3F7E4002C760A /* ZetaCommanderBase.h */, 70AE5AB422A3F7D3002C760A /* ZetaCommanderBase.mm */, 7006C4AD1C26D3FA00929DAE /* ZetaMainMenu.h */, 7006C4AB1C26D3E700929DAE /* ZetaMainMenu.mm */, 70CE283B2348B0D4002C760A /* ZetaSnapshotMenu.h */, 70CE283C2348B0D4002C760A /* ZetaSnapshotMenu.mm */, 70EA5F4E22BEC358002C760A /* ZetaFileSystemPropertyMenu.h */, 70EA5F4F22BEC358002C760A /* ZetaFileSystemPropertyMenu.mm */, 7099FB4423A575F8002C760A /* ZetaBookmarkMenu.h */, 7099FB4323A575F8002C760A /* ZetaBookmarkMenu.mm */, 70EA5F5422BED079002C760A /* ZetaPoolPropertyMenu.h */, 70EA5F5522BED079002C760A /* ZetaPoolPropertyMenu.mm */, 70AE5AB322A3DBBF002C760A /* ZetaImportMenu.h */, 70AE5AB122A3DBAA002C760A /* ZetaImportMenu.mm */, 70168B6422B64E48002C760A /* ZetaKeyLoader.h */, 70168B6522B64E48002C760A /* ZetaKeyLoader.mm */, 70DF66B0234A2884002C760A /* ZetaQueryDialog.h */, 70DF66AF234A2884002C760A /* ZetaQueryDialog.mm */, 70F307CB23ACD917002C760A /* ZetaDictQueryDialog.h */, 70F307CC23ACD917002C760A /* ZetaDictQueryDialog.mm */, 70BF21112353914F002C760A /* ZetaConfirmDialog.h */, 70BF210F23539130002C760A /* ZetaConfirmDialog.mm */, 709C1CAE1C354F9B00929DAE /* ZetaPoolWatcher.h */, 709C1CAC1C354F8B00929DAE /* ZetaPoolWatcher.mm */, 70B4E4CB22F469FC002C760A /* ZetaAutoImporter.h */, 70B4E4CA22F469FC002C760A /* ZetaAutoImporter.mm */, 70EABDBE1FF9992A00BA39B8 /* ZetaAuthorization.h */, 70EABDBF1FF9992A00BA39B8 /* ZetaAuthorization.mm */, 703811A22312A2E6002C760A /* ZetaNotificationCenter.h */, 703811A02312A2CB002C760A /* ZetaNotificationCenter.mm */, 70EA5F5122BEC6BC002C760A /* ZetaFormatHelpers.hpp */, 70EA5F5222BEC7B1002C760A /* ZetaFormatHelpers.cpp */, 70D118B62375FE28002C760A /* PathValueTransformer.m */, 7068A94723ACFBB1002C760A /* SizeTransformer.mm */, 70A60A4622AD7A5F002C760A /* IDDiskArbitrationUtils.hpp */, 70703F3822AD7A16002C760A /* IDDiskArbitrationUtils.cpp */, 70B4E4C722F45E7C002C760A /* IDDiskArbitrationDispatcher.hpp */, 70B4E4C622F45E7C002C760A /* IDDiskArbitrationDispatcher.cpp */, 70B4E4C822F45E7C002C760A /* IDDiskArbitrationHandler.hpp */, 7006C4841C26CA1500929DAE /* Assets.xcassets */, 70C930D622122CBD00BA39B8 /* Localizable.strings */, 7006C4861C26CA1500929DAE /* MainMenu.xib */, 70F307CE23ACE415002C760A /* NewFS.xib */, 7068A94423ACFB0A002C760A /* NewVol.xib */, 7006C4891C26CA1500929DAE /* Info.plist */, 70F386AA22906D36002C760A /* ZetaWatch.entitlements */, 7006C4811C26CA1500929DAE /* Supporting Files */, ); path = ZetaWatch; sourceTree = ""; }; 7006C4811C26CA1500929DAE /* Supporting Files */ = { isa = PBXGroup; children = ( 7006C4821C26CA1500929DAE /* main.m */, ); name = "Supporting Files"; sourceTree = ""; }; 7023C21926C1DF98002C760A /* include */ = { isa = PBXGroup; children = ( 70D85FB81C2C076E00929DAE /* ZFSUtils.hpp */, 70D85FC11C2D9CC900929DAE /* ZFSStrings.hpp */, 70D85FC31C2EAE5A00929DAE /* ZFSNVList.hpp */, ); path = include; sourceTree = ""; }; 7023C21A26C1DFA2002C760A /* src */ = { isa = PBXGroup; children = ( 70D85FB71C2C076E00929DAE /* ZFSUtils.cpp */, 70D85FBF1C2D9CB900929DAE /* ZFSStrings.cpp */, 7023C1FA26C1C768002C760A /* ZFSStrings.mm */, 70D85FC21C2EAE5A00929DAE /* ZFSNVList.cpp */, ); path = src; sourceTree = ""; }; 7023C21B26C1DFB1002C760A /* misc */ = { isa = PBXGroup; children = ( 70D85FE61C316E8F00929DAE /* Info.plist */, ); path = misc; sourceTree = ""; }; 70D85FBE1C2C1D9100929DAE /* lib */ = { isa = PBXGroup; children = ( 70D85FC51C2EBC5000929DAE /* libnvpair.1.dylib */, 70D85FBA1C2C18BE00929DAE /* libzfs_core.1.dylib */, 70D85FBB1C2C18BE00929DAE /* libzfs.2.dylib */, 7018765A20E7DA8100BA39B8 /* libzpool.1.dylib */, ); name = lib; path = /usr/local/lib; sourceTree = ""; }; 70D85FCC1C316B0900929DAE /* ZFSWrapper */ = { isa = PBXGroup; children = ( 7023C21B26C1DFB1002C760A /* misc */, 7023C21A26C1DFA2002C760A /* src */, 7023C21926C1DF98002C760A /* include */, 70D85FBE1C2C1D9100929DAE /* lib */, ); path = ZFSWrapper; sourceTree = ""; }; 70DB77B11FFAD8E300BA39B8 /* Frameworks */ = { isa = PBXGroup; children = ( 708112BA26D993F4002C760A /* libzpool.5.dylib */, 703B1752262C6A31002C760A /* libnvpair.3.dylib */, 703B1751262C6A31002C760A /* libzfs_core.3.dylib */, 703B1750262C6A31002C760A /* libzfs.4.dylib */, 70552BC42336AEB2002C760A /* Sparkle.framework */, 703811A32312C833002C760A /* ServiceManagement.framework */, 70703F3622AD7633002C760A /* DiskArbitration.framework */, ); name = Frameworks; sourceTree = ""; }; 70E4A21E236DF7D6002C760A /* ZetaLoginItemHelper */ = { isa = PBXGroup; children = ( 70E4A227236DF7D7002C760A /* Info.plist */, 70E4A228236DF7D7002C760A /* main.m */, ); path = ZetaLoginItemHelper; sourceTree = ""; }; 70EABDC11FF9A81800BA39B8 /* CommonAuthorization */ = { isa = PBXGroup; children = ( 70EABDC21FF9A8D100BA39B8 /* CommonAuthorization.h */, 70EABDC31FF9A8D100BA39B8 /* CommonAuthorization.m */, 70EABDDB1FF9C11E00BA39B8 /* CommonAuthorization.strings */, ); path = CommonAuthorization; sourceTree = ""; }; 70EABDCB1FF9ACB300BA39B8 /* ZetaAuthorizationHelper */ = { isa = PBXGroup; children = ( 70EABDC51FF9AAB800BA39B8 /* ZetaAuthorizationHelperProtocol.h */, 70EABDD21FF9B21C00BA39B8 /* ZetaAuthorizationHelper.h */, 70EABDD31FF9B21C00BA39B8 /* ZetaAuthorizationHelper.mm */, 7072697B22EDB2BE002C760A /* ZetaCPPUtils.hpp */, 70EABDCC1FF9ACB300BA39B8 /* main.m */, 70EABDD11FF9AE2800BA39B8 /* Info.plist */, 70EABDD51FF9B40F00BA39B8 /* Launchd.plist */, ); path = ZetaAuthorizationHelper; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ 7018763B20E7B44800BA39B8 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 7018764A20E7B4A600BA39B8 /* ZFSUtils.hpp in Headers */, 7018764820E7B4A100BA39B8 /* ZFSNVList.hpp in Headers */, 7018764920E7B4A300BA39B8 /* ZFSStrings.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; 70D85FDF1C316E8F00929DAE /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( 7018765120E7B6A500BA39B8 /* ZFSUtils.hpp in Headers */, 7018765320E7B6B500BA39B8 /* ZFSNVList.hpp in Headers */, 7018765220E7B6A700BA39B8 /* ZFSStrings.hpp in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ 7006C47A1C26CA1400929DAE /* ZetaWatch */ = { isa = PBXNativeTarget; buildConfigurationList = 7006C4A21C26CA1500929DAE /* Build configuration list for PBXNativeTarget "ZetaWatch" */; buildPhases = ( 7006C4771C26CA1400929DAE /* Sources */, 7006C4781C26CA1400929DAE /* Frameworks */, 7006C4791C26CA1400929DAE /* Resources */, 70D85FEE1C316E8F00929DAE /* Embed Frameworks */, 70EABDD81FF9B6A200BA39B8 /* CopyFiles */, 70E4A212236DF1F6002C760A /* CopyFiles */, ); buildRules = ( ); dependencies = ( 70E4A22F236DF7FD002C760A /* PBXTargetDependency */, 7018765020E7B62400BA39B8 /* PBXTargetDependency */, 70EABDD71FF9B69000BA39B8 /* PBXTargetDependency */, ); name = ZetaWatch; productName = ZetaWatch; productReference = 7006C47B1C26CA1500929DAE /* ZetaWatch.app */; productType = "com.apple.product-type.application"; }; 7018763C20E7B44800BA39B8 /* ZFSWrapperStatic */ = { isa = PBXNativeTarget; buildConfigurationList = 7018763E20E7B44800BA39B8 /* Build configuration list for PBXNativeTarget "ZFSWrapperStatic" */; buildPhases = ( 7018763920E7B44800BA39B8 /* Sources */, 7018763A20E7B44800BA39B8 /* Frameworks */, 7018763B20E7B44800BA39B8 /* Headers */, ); buildRules = ( ); dependencies = ( ); name = ZFSWrapperStatic; productName = ZFSWrapperStatic; productReference = 7018763D20E7B44800BA39B8 /* libZFSWrapperStatic.a */; productType = "com.apple.product-type.library.static"; }; 70D85FE11C316E8F00929DAE /* ZFSWrapper */ = { isa = PBXNativeTarget; buildConfigurationList = 70D85FEB1C316E8F00929DAE /* Build configuration list for PBXNativeTarget "ZFSWrapper" */; buildPhases = ( 70D85FDD1C316E8F00929DAE /* Sources */, 70D85FDE1C316E8F00929DAE /* Frameworks */, 70D85FDF1C316E8F00929DAE /* Headers */, 70D85FE01C316E8F00929DAE /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ZFSWrapper; productName = ZFSWrapper; productReference = 70D85FE21C316E8F00929DAE /* ZFSWrapper.framework */; productType = "com.apple.product-type.framework"; }; 70E4A21C236DF7D6002C760A /* ZetaLoginItemHelper */ = { isa = PBXNativeTarget; buildConfigurationList = 70E4A22B236DF7D7002C760A /* Build configuration list for PBXNativeTarget "ZetaLoginItemHelper" */; buildPhases = ( 70E4A219236DF7D6002C760A /* Sources */, 70E4A21A236DF7D6002C760A /* Frameworks */, 70E4A21B236DF7D6002C760A /* Resources */, ); buildRules = ( ); dependencies = ( ); name = ZetaLoginItemHelper; productName = ZetaLoginItemHelper; productReference = 70E4A21D236DF7D6002C760A /* ZetaLoginItemHelper.app */; productType = "com.apple.product-type.application"; }; 70EABDC91FF9ACB300BA39B8 /* ZetaAuthorizationHelper */ = { isa = PBXNativeTarget; buildConfigurationList = 70EABDCE1FF9ACB300BA39B8 /* Build configuration list for PBXNativeTarget "ZetaAuthorizationHelper" */; buildPhases = ( 70EABDC61FF9ACB300BA39B8 /* Sources */, 70EABDC71FF9ACB300BA39B8 /* Frameworks */, ); buildRules = ( ); dependencies = ( 7018765520E7B79800BA39B8 /* PBXTargetDependency */, ); name = ZetaAuthorizationHelper; productName = ZetaAuthorizationHelper; productReference = 70EABDCA1FF9ACB300BA39B8 /* net.the-color-black.ZetaAuthorizationHelper */; productType = "com.apple.product-type.tool"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 7006C4731C26CA1400929DAE /* Project object */ = { isa = PBXProject; attributes = { LastUpgradeCheck = 1130; ORGANIZATIONNAME = "the-color-black.net"; TargetAttributes = { 7006C47A1C26CA1400929DAE = { CreatedOnToolsVersion = 7.2; DevelopmentTeam = 8THUW5GT6P; ProvisioningStyle = Manual; }; 7018763C20E7B44800BA39B8 = { CreatedOnToolsVersion = 9.4.1; DevelopmentTeam = 8THUW5GT6P; ProvisioningStyle = Automatic; }; 70D85FE11C316E8F00929DAE = { CreatedOnToolsVersion = 7.2; DevelopmentTeam = 8THUW5GT6P; ProvisioningStyle = Manual; }; 70E4A21C236DF7D6002C760A = { CreatedOnToolsVersion = 10.1; DevelopmentTeam = 8THUW5GT6P; ProvisioningStyle = Manual; SystemCapabilities = { com.apple.Sandbox = { enabled = 0; }; }; }; 70EABDC91FF9ACB300BA39B8 = { CreatedOnToolsVersion = 9.2; DevelopmentTeam = 8THUW5GT6P; ProvisioningStyle = Manual; }; }; }; buildConfigurationList = 7006C4761C26CA1400929DAE /* Build configuration list for PBXProject "ZetaWatch" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 7006C4721C26CA1400929DAE; productRefGroup = 7006C47C1C26CA1500929DAE /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 7006C47A1C26CA1400929DAE /* ZetaWatch */, 70D85FE11C316E8F00929DAE /* ZFSWrapper */, 7018763C20E7B44800BA39B8 /* ZFSWrapperStatic */, 70EABDC91FF9ACB300BA39B8 /* ZetaAuthorizationHelper */, 70E4A21C236DF7D6002C760A /* ZetaLoginItemHelper */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 7006C4791C26CA1400929DAE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 70EABDDC1FF9C1F000BA39B8 /* CommonAuthorization.strings in Resources */, 70C930D822122CBD00BA39B8 /* Localizable.strings in Resources */, 7068A94623ACFB0A002C760A /* NewVol.xib in Resources */, 70F307D023ACE415002C760A /* NewFS.xib in Resources */, 7006C4851C26CA1500929DAE /* Assets.xcassets in Resources */, 7006C4881C26CA1500929DAE /* MainMenu.xib in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 70D85FE01C316E8F00929DAE /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 70E4A21B236DF7D6002C760A /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 7006C4771C26CA1400929DAE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70B4E4CC22F469FC002C760A /* ZetaAutoImporter.mm in Sources */, 70EA5F5322BEC7B1002C760A /* ZetaFormatHelpers.cpp in Sources */, 70F307CD23ACD917002C760A /* ZetaDictQueryDialog.mm in Sources */, 709C1CAD1C354F8B00929DAE /* ZetaPoolWatcher.mm in Sources */, 70B4E4C922F45E7C002C760A /* IDDiskArbitrationDispatcher.cpp in Sources */, 7068A94823ACFBB1002C760A /* SizeTransformer.mm in Sources */, 7006C4831C26CA1500929DAE /* main.m in Sources */, 70EA5F5622BED079002C760A /* ZetaPoolPropertyMenu.mm in Sources */, 70D118B72375FE28002C760A /* PathValueTransformer.m in Sources */, 7006C4801C26CA1500929DAE /* ZetaWatchDelegate.mm in Sources */, 7006C4AC1C26D3E700929DAE /* ZetaMainMenu.mm in Sources */, 7099FB4523A575F9002C760A /* ZetaBookmarkMenu.mm in Sources */, 70AE5AB222A3DBAA002C760A /* ZetaImportMenu.mm in Sources */, 70EABDC41FF9A8D100BA39B8 /* CommonAuthorization.m in Sources */, 70168B6622B64E48002C760A /* ZetaKeyLoader.mm in Sources */, 70EABDC01FF9992A00BA39B8 /* ZetaAuthorization.mm in Sources */, 70DF66B1234A2884002C760A /* ZetaQueryDialog.mm in Sources */, 70CE283D2348B0D4002C760A /* ZetaSnapshotMenu.mm in Sources */, 70EA5F5022BEC358002C760A /* ZetaFileSystemPropertyMenu.mm in Sources */, 70BF211023539130002C760A /* ZetaConfirmDialog.mm in Sources */, 70AE5AB522A3F7D3002C760A /* ZetaCommanderBase.mm in Sources */, 70703F3922AD7A17002C760A /* IDDiskArbitrationUtils.cpp in Sources */, 703811A12312A2CB002C760A /* ZetaNotificationCenter.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 7018763920E7B44800BA39B8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 7018764120E7B45900BA39B8 /* ZFSUtils.cpp in Sources */, 7018764220E7B45B00BA39B8 /* ZFSStrings.cpp in Sources */, 7023C1FB26C1C768002C760A /* ZFSStrings.mm in Sources */, 7018764320E7B45E00BA39B8 /* ZFSNVList.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 70D85FDD1C316E8F00929DAE /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70D85FF01C316F3100929DAE /* ZFSStrings.cpp in Sources */, 70D85FF11C316F3100929DAE /* ZFSNVList.cpp in Sources */, 7023C1FC26C1C768002C760A /* ZFSStrings.mm in Sources */, 70D85FEF1C316F3100929DAE /* ZFSUtils.cpp in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 70E4A219236DF7D6002C760A /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70E4A229236DF7D7002C760A /* main.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 70EABDC61FF9ACB300BA39B8 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 70EABDCD1FF9ACB300BA39B8 /* main.m in Sources */, 70EABDDA1FF9C05700BA39B8 /* CommonAuthorization.m in Sources */, 70EABDD41FF9B21C00BA39B8 /* ZetaAuthorizationHelper.mm in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 7018765020E7B62400BA39B8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7018763C20E7B44800BA39B8 /* ZFSWrapperStatic */; targetProxy = 7018764F20E7B62400BA39B8 /* PBXContainerItemProxy */; }; 7018765520E7B79800BA39B8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 7018763C20E7B44800BA39B8 /* ZFSWrapperStatic */; targetProxy = 7018765420E7B79800BA39B8 /* PBXContainerItemProxy */; }; 70E4A22F236DF7FD002C760A /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 70E4A21C236DF7D6002C760A /* ZetaLoginItemHelper */; targetProxy = 70E4A22E236DF7FD002C760A /* PBXContainerItemProxy */; }; 70EABDD71FF9B69000BA39B8 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 70EABDC91FF9ACB300BA39B8 /* ZetaAuthorizationHelper */; targetProxy = 70EABDD61FF9B69000BA39B8 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 7006C4861C26CA1500929DAE /* MainMenu.xib */ = { isa = PBXVariantGroup; children = ( 7006C4871C26CA1500929DAE /* Base */, ); name = MainMenu.xib; sourceTree = ""; }; 7068A94423ACFB0A002C760A /* NewVol.xib */ = { isa = PBXVariantGroup; children = ( 7068A94523ACFB0A002C760A /* Base */, ); name = NewVol.xib; sourceTree = ""; }; 70C930D622122CBD00BA39B8 /* Localizable.strings */ = { isa = PBXVariantGroup; children = ( 70C930D722122CBD00BA39B8 /* Base */, ); name = Localizable.strings; sourceTree = ""; }; 70F307CE23ACE415002C760A /* NewFS.xib */ = { isa = PBXVariantGroup; children = ( 70F307CF23ACE415002C760A /* Base */, ); name = NewFS.xib; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 7006C4A01C26CA1500929DAE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = x86_64; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = ZetaWatch/ZetaWatch.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 8THUW5GT6P; ENABLE_HARDENED_RUNTIME = YES; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; MACOSX_DEPLOYMENT_TARGET = 10.12; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; ZETA_VERSION = 50; ZFS_DIR = /usr/local/zfs; }; name = Debug; }; 7006C4A11C26CA1500929DAE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ARCHS = x86_64; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; CODE_SIGN_ENTITLEMENTS = ZetaWatch/ZetaWatch.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 8THUW5GT6P; ENABLE_HARDENED_RUNTIME = YES; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; MACOSX_DEPLOYMENT_TARGET = 10.12; SDKROOT = macosx; ZETA_VERSION = 50; ZFS_DIR = /usr/local/zfs; }; name = Release; }; 7006C4A31C26CA1500929DAE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/ThirdParty/Sparkle/DerivedData/Sparkle/Build/Products/Release/", ); INFOPLIST_FILE = ZetaWatch/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", /usr/local/zfs/lib, ); PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaWatch"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; }; 7006C4A41C26CA1500929DAE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)", "$(PROJECT_DIR)/ThirdParty/Sparkle/DerivedData/Sparkle/Build/Products/Release/", ); INFOPLIST_FILE = ZetaWatch/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; LIBRARY_SEARCH_PATHS = ( "$(inherited)", /usr/local/zfs/lib, ); PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaWatch"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; }; 7018763F20E7B44800BA39B8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; EXECUTABLE_PREFIX = lib; GCC_C_LANGUAGE_STANDARD = gnu11; "GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS[arch=*]" = ( ZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR, ZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( "$(ZFS_DIR)/include", "$(ZFS_DIR)/include/libzfs", "$(ZFS_DIR)/include/libspl", "$(ZFS_DIR)/include/libspl/os/macos", ); LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Debug; }; 7018764020E7B44800BA39B8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; EXECUTABLE_PREFIX = lib; GCC_C_LANGUAGE_STANDARD = gnu11; "GCC_PREPROCESSOR_DEFINITIONS_NOT_USED_IN_PRECOMPS[arch=*]" = ( ZFSW_HAS_ZPOOL_STATUS_COMPATIBILITY_ERR, ZFSW_HAS_ZPOOL_STATUS_INCOMPATIBLE_FEAT, ); GCC_SYMBOLS_PRIVATE_EXTERN = NO; HEADER_SEARCH_PATHS = ( "$(ZFS_DIR)/include", "$(ZFS_DIR)/include/libzfs", "$(ZFS_DIR)/include/libspl", "$(ZFS_DIR)/include/libspl/os/macos", ); LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Release; }; 70D85FEC1C316E8F00929DAE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_SYMBOLS_PRIVATE_EXTERN = YES; HEADER_SEARCH_PATHS = ( "$(ZFS_DIR)/include", "$(ZFS_DIR)/include/libzfs", "$(ZFS_DIR)/include/libspl", "$(ZFS_DIR)/include/libspl/os/macos", ); INFOPLIST_FILE = ZFSWrapper/misc/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZFSWrapper"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; 70D85FED1C316E8F00929DAE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 1; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; GCC_SYMBOLS_PRIVATE_EXTERN = YES; HEADER_SEARCH_PATHS = ( "$(ZFS_DIR)/include", "$(ZFS_DIR)/include/libzfs", "$(ZFS_DIR)/include/libspl", "$(ZFS_DIR)/include/libspl/os/macos", ); INFOPLIST_FILE = ZFSWrapper/misc/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; LIBRARY_SEARCH_PATHS = "$(ZFS_DIR)/lib"; PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZFSWrapper"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; 70E4A22C236DF7D7002C760A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 8THUW5GT6P; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ZetaLoginItemHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaLoginItemHelper"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Debug; }; 70E4A22D236DF7D7002C760A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Developer ID Application"; COMBINE_HIDPI_IMAGES = YES; DEVELOPMENT_TEAM = 8THUW5GT6P; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = ZetaLoginItemHelper/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaLoginItemHelper"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; }; name = Release; }; 70EABDCF1FF9ACB300BA39B8 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Developer ID Application"; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/ZetaAuthorizationHelper/Info.plist"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, __launchd_plist, ZetaAuthorizationHelper/Launchd.plist, ); PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaAuthorizationHelper"; PRODUCT_NAME = "net.the-color-black.ZetaAuthorizationHelper"; SKIP_INSTALL = YES; }; name = Debug; }; 70EABDD01FF9ACB300BA39B8 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_IDENTITY = "Developer ID Application"; CREATE_INFOPLIST_SECTION_IN_BINARY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; INFOPLIST_FILE = "$(SRCROOT)/ZetaAuthorizationHelper/Info.plist"; LIBRARY_SEARCH_PATHS = "$(inherited)"; OTHER_LDFLAGS = ( "-sectcreate", __TEXT, __launchd_plist, ZetaAuthorizationHelper/Launchd.plist, ); PRODUCT_BUNDLE_IDENTIFIER = "net.the-color-black.ZetaAuthorizationHelper"; PRODUCT_NAME = "net.the-color-black.ZetaAuthorizationHelper"; SKIP_INSTALL = YES; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 7006C4761C26CA1400929DAE /* Build configuration list for PBXProject "ZetaWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( 7006C4A01C26CA1500929DAE /* Debug */, 7006C4A11C26CA1500929DAE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7006C4A21C26CA1500929DAE /* Build configuration list for PBXNativeTarget "ZetaWatch" */ = { isa = XCConfigurationList; buildConfigurations = ( 7006C4A31C26CA1500929DAE /* Debug */, 7006C4A41C26CA1500929DAE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 7018763E20E7B44800BA39B8 /* Build configuration list for PBXNativeTarget "ZFSWrapperStatic" */ = { isa = XCConfigurationList; buildConfigurations = ( 7018763F20E7B44800BA39B8 /* Debug */, 7018764020E7B44800BA39B8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 70D85FEB1C316E8F00929DAE /* Build configuration list for PBXNativeTarget "ZFSWrapper" */ = { isa = XCConfigurationList; buildConfigurations = ( 70D85FEC1C316E8F00929DAE /* Debug */, 70D85FED1C316E8F00929DAE /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 70E4A22B236DF7D7002C760A /* Build configuration list for PBXNativeTarget "ZetaLoginItemHelper" */ = { isa = XCConfigurationList; buildConfigurations = ( 70E4A22C236DF7D7002C760A /* Debug */, 70E4A22D236DF7D7002C760A /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 70EABDCE1FF9ACB300BA39B8 /* Build configuration list for PBXNativeTarget "ZetaAuthorizationHelper" */ = { isa = XCConfigurationList; buildConfigurations = ( 70EABDCF1FF9ACB300BA39B8 /* Debug */, 70EABDD01FF9ACB300BA39B8 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 7006C4731C26CA1400929DAE /* Project object */; } ================================================ FILE: ZetaWatch.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: uninstall-helper.sh ================================================ #!/bin/sh launchctl unload /Library/LaunchDaemons/net.the-color-black.ZetaAuthorizationHelper.plist rm /Library/LaunchDaemons/net.the-color-black.ZetaAuthorizationHelper.plist rm /Library/PrivilegedHelperTools/net.the-color-black.ZetaAuthorizationHelper