├── .gitignore ├── Makefile ├── autozfs.plist ├── LICENSE ├── README.md └── autozfs.c /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | .DS_Store 3 | autozfs 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=clang 2 | CFLAGS=-W -Wall -Wextra -std=c11 3 | LDFLAGS=-framework CoreFoundation -framework DiskArbitration 4 | 5 | autozfs: autozfs.c 6 | 7 | clean: 8 | rm -f autozfs 9 | 10 | .PHONY: clean 11 | -------------------------------------------------------------------------------- /autozfs.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Label 6 | autozfs 7 | Program 8 | /usr/local/bin/autozfs 9 | KeepAlive 10 | 11 | RunAtLoad 12 | 13 | StandardErrorPath 14 | /private/var/log/autozfs.err 15 | StandardOutPath 16 | /private/var/log/autozfs.log 17 | 18 | 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Michael Sproul 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Anarcho Software Collective I Guess nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # autozfs 2 | 3 | Daemon for OS X that listens for external drives being plugged in and automatically imports 4 | ZFS pools. 5 | 6 | If you use or want to use ZFS on an external hard drive, you might find this useful :) 7 | 8 | ## Installation 9 | 10 | You'll need [OpenZFSOnOSX][] or similar installed. 11 | 12 | To build: 13 | 14 | ```bash 15 | $ git clone https://github.com/michaelsproul/autozfs.git 16 | $ cd autozfs 17 | $ make 18 | ``` 19 | 20 | To install: 21 | 22 | ```bash 23 | $ sudo cp autozfs /usr/local/bin/ 24 | $ sudo cp autozfs.plist /Library/LaunchDaemons/ 25 | ``` 26 | 27 | Then either reboot, or run: 28 | 29 | ```bash 30 | $ sudo launchctl load /Library/LaunchDaemons/autozfs.plist 31 | ``` 32 | 33 | ## How does it work? 34 | 35 | A background daemon started via `launchd` and running as root listens for "Disk Arbitration" 36 | events, and each time it detects a disk connection it runs `zpool import -a`. Simple! 37 | 38 | ## Debugging 39 | 40 | * You should be able to see a process called `autozfs` running, check Activity Monitor, `ps`, 41 | `htop` or whatever. 42 | * The daemon logs inane messages to `/private/var/log/autozfs.{log,err}`. 43 | 44 | ## Caveats 45 | 46 | * OS X only (for now). 47 | * IMPORTS ALL POOLS. This is hacky. 48 | * Assumes `zpool` is installed in `/usr/local/bin`. This may not be true of all systems... 49 | * Only auto-mounts USB devices, but it's an artificial restriction -- delete the USB stuff 50 | in the source and it should do Firewire/Thunderbolt/whatever just fine. 51 | 52 | ## License 53 | 54 | Copyright (c) 2017, Michael Sproul. 55 | 56 | Licensed under the terms of the 3-clause BSD license. 57 | 58 | [OpenZFSOnOSX]: https://openzfsonosx.org/wiki/Downloads 59 | -------------------------------------------------------------------------------- /autozfs.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define UNUSED(x) UNUSED_ ## x __attribute__((__unused__)) 8 | 9 | // Based on: 10 | // https://developer.apple.com/library/content/documentation/DriversKernelHardware/Conceptual/DiskArbitrationProgGuide/ArbitrationBasics/ArbitrationBasics.html#//apple_ref/doc/uid/TP40009310-CH2-SW2 11 | 12 | void zfsImportAll(DADiskRef UNUSED(disk), void * UNUSED(ctxt)) { 13 | printf("Wubba lubba dub dub! Importing your disks!\n"); 14 | fflush(stdout); 15 | int exitCode = system("/usr/local/bin/zpool import -a"); 16 | if (exitCode == 0) { 17 | printf("Done! Run `zpool list` to see if anything was imported.\n"); 18 | } else { 19 | printf("Oh shit, that failed hard! Exit code for `zpool import -a`: %d\n", exitCode); 20 | } 21 | fflush(stdout); 22 | } 23 | 24 | int main() { 25 | DASessionRef sesh = DASessionCreate(kCFAllocatorDefault); 26 | 27 | CFMutableDictionaryRef matchingDict = CFDictionaryCreateMutable( 28 | kCFAllocatorDefault, 29 | 0, 30 | &kCFTypeDictionaryKeyCallBacks, 31 | &kCFTypeDictionaryValueCallBacks); 32 | 33 | CFDictionaryAddValue(matchingDict, 34 | kDADiskDescriptionDeviceProtocolKey, 35 | CFSTR(kIOPropertyPhysicalInterconnectTypeUSB)); 36 | 37 | CFDictionaryAddValue(matchingDict, 38 | kDADiskDescriptionMediaWholeKey, 39 | kCFBooleanTrue); 40 | 41 | DARegisterDiskPeekCallback(sesh, matchingDict, 0, zfsImportAll, NULL); 42 | 43 | DASessionScheduleWithRunLoop(sesh, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 44 | CFRunLoopRun(); 45 | 46 | // TODO: maybe run these on Ctrl-C? 47 | DASessionUnscheduleFromRunLoop(sesh, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); 48 | CFRelease(sesh); 49 | } 50 | --------------------------------------------------------------------------------