├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── kext ├── Info.plist.in ├── Makefile ├── Makefile.inc └── src │ ├── emptyfs.c │ ├── emptyfs.h │ ├── emptyfs_vfsops.c │ ├── emptyfs_vfsops.h │ ├── emptyfs_vnops.c │ ├── emptyfs_vnops.h │ ├── utils.c │ └── utils.h └── mount_emptyfs ├── Makefile ├── emptyfs_mnt_args.h └── mount_emptyfs.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *~ 3 | *.swp 4 | *.o 5 | *.out 6 | *.plist 7 | *.kext 8 | *.dSYM 9 | *.xcodeproj/ 10 | *.xcworkspace/ 11 | DerivedData/ 12 | bin/ 13 | 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, lynnl 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # All-in-one Makefile 3 | # 4 | 5 | MAKE=make 6 | MV=mv 7 | RM=rm 8 | MKDIR=mkdir 9 | OUT=bin 10 | 11 | all: debug 12 | 13 | debug: 14 | $(RM) -rf $(OUT)/emptyfs.kext* $(OUT)/mount_emptyfs* 15 | $(MAKE) -C kext $(TARGET) 16 | $(MAKE) -C mount_emptyfs $(TARGET) 17 | $(MKDIR) -p $(OUT) 18 | $(MV) kext/emptyfs.kext kext/emptyfs.kext.dSYM $(OUT) 19 | $(MV) mount_emptyfs/mount_emptyfs $(OUT) 20 | $(MV) mount_emptyfs/mount_emptyfs.dSYM $(OUT) 2> /dev/null || true 21 | 22 | release: TARGET=release 23 | release: debug 24 | 25 | clean: 26 | $(RM) -rf $(OUT)/emptyfs.kext* $(OUT)/mount_emptyfs 27 | $(MAKE) -C kext clean 28 | $(MAKE) -C mount_emptyfs clean 29 | 30 | .PHONY: all debug release clean 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## `emptyfs` - A trivial macOS VFS plug-in implementation 2 | 3 | ### Summary 4 | 5 | `emptyfs` is a trivial VFS plug-in(kernel extension), its volumes are completely empty(except for the root directory). 6 | 7 | This simple VFS plug-in is a minimal implementation, you can use it to explore VFS behaviours, or as a template as your own VFS plug-in. 8 | 9 | ### Build 10 | 11 | You must install Apple's [Command Line Tools](https://developer.apple.com/download/more) as a minimal build environment, or Xcode as a full build environment in App Store. 12 | 13 | ```shell 14 | $ make # Debug build 15 | $ make release # Release build 16 | ``` 17 | 18 | Theoretically, this kext can be compile and run in macOS 10.4+, yet currently only from 10.10 up to 10.14 are tested. 19 | 20 | If you compile in macOS >= 10.13 and wants the kext to stay compatible with macOS <= 10.12, you should specify `-DKASAN` or `-D_FORTIFY_SOURCE=0` to `CPPFLAGS`, for more info, please refer to [issues#1](https://github.com/lynnlx/emptyfs/issues/1) 21 | 22 | ### Debugging 23 | 24 | ``` 25 | # Under kext/ directory 26 | $ make load # Load emptyfs kext 27 | $ make stat # Print status of emptyfs kext 28 | $ make unload # Unload emptyfs kext 29 | ``` 30 | 31 | **TODO**: add info about kext logging 32 | 33 | ### Install 34 | 35 | ```shell 36 | # Under kext/ directory 37 | $ make install 38 | $ make uninstall # In case you want to uninstall 39 | ``` 40 | 41 | You can specify `PREFIX` variable to make for install location, the default is `/Library/Extensions` 42 | 43 | **TODO:** add docs about HOWTO load at boot-up time 44 | 45 | After installation, the kext will be loaded automatically in each boot. 46 | 47 | ### Use of `emptyfs` 48 | 49 | To test the VFS plug-in, you must specify what device node to mounted on. Simplest approach is to create a disk image: 50 | 51 | ```shell 52 | # Create a disk image (name: test.dmg size: 1MB type: EMPTYFS) 53 | $ hdiutil create -size 1m -partitionType EMPTYFS test 54 | created: /Users/lynnl/test.dmg 55 | 56 | $ file test.dmg 57 | test.dmg: Apple Driver Map, blocksize 512, blockcount 2048, devtype 0, devid 0, driver count 0, contains[@0x200]: Apple Partition Map, map block count 2, start block 1, block count 63, name Apple, type Apple_partition_map, valid, allocated, contains[@0x400]: Apple Partition Map, map block count 2, start block 64, block count 1984, name disk image, type EMPTYFS, valid, allocated, readable, writable, mount at startup 58 | ``` 59 | 60 | Then attach the disk image with `-nomount` flag(.: the system won't automatically mount the volumes on the image): 61 | 62 | ```shell 63 | # The first two volumes are image scheme and map partition 64 | # We cares about the last one(i.e. EMPTYFS) 65 | $ hdiutil attach -nomount test.dmg 66 | /dev/disk2 Apple_partition_scheme 67 | /dev/disk2s1 Apple_partition_map 68 | /dev/disk2s2 EMPTYFS 69 | ``` 70 | 71 | After you load emptyfs kext, you can create a mount point and mount the file system: 72 | 73 | ```shell 74 | $ sudo kextload emptyfs.kext 75 | $ mkdir emptyfs_mp 76 | $ ./mount_emptyfs /dev/disk2s2 emptyfs_mp 77 | ``` 78 | 79 | Use [mount(8)](x-man-page://8/mount) to check mount info and explore the file system: 80 | 81 | ```shell 82 | $ mount | grep emptyfs 83 | /dev/disk2s2 on /Users/lynnl/emptyfs_mp (emptyfs, local, nodev, noexec, nosuid, read-only, noowners, mounted by lynnl) 84 | 85 | $ ls -la emptyfs_mp 86 | total 8 87 | dr-xr-xr-x 2 lynnl staff 528 Dec 27 22:00 . 88 | drwxr-xr-x+ 19 lynnl staff 608 Dec 27 21:59 .. 89 | 90 | $ stat emptyfs_mp 91 | 16777226 2 dr-xr-xr-x 2 lynnl staff 0 528 "Dec 27 22:00:25 2018" "Dec 27 22:00:25 2018" "Dec 27 22:00:25 2018" "Dec 27 22:00:25 2018" 4096 8 0 emptyfs_mp 92 | ``` 93 | 94 | When you explore the file system thoroughly, you can first [umount(8)](x-man-page://8/umount) the file system and then unload the kext: 95 | 96 | ```shell 97 | $ unmount emptyfs_mp 98 | $ sudo kextunload emptyfs.kext 99 | ``` 100 | 101 | --- 102 | 103 | ### Unranked references 104 | 105 | [EmptyFS - A very simple VFS plug-in that mounts a volume that is completely empty](https://developer.apple.com/library/archive/samplecode/EmptyFS/Introduction/Intro.html) 106 | 107 | [MFSLives - Sample VFS plug-in for the Macintosh File System (MFS) volume format, as used on 400KB floppies](https://developer.apple.com/library/archive/samplecode/MFSLives/Introduction/Intro.html) 108 | 109 | [mac9p - 9P for Mac](https://github.com/benavento/mac9p) 110 | 111 | [antekone's cross-reference search site](http://xr.anadoxin.org/source/xref) 112 | 113 | [FreeBSD and Linux Kernel Cross-Reference](http://fxr.watson.org) 114 | 115 | [Darwin kernel miscfs](https://opensource.apple.com/source/xnu/xnu-4570.71.2/bsd/miscfs) 116 | 117 | [Darwin kernel NFS](https://opensource.apple.com/source/xnu/xnu-4570.71.2/bsd/nfs) 118 | 119 | [Darwin kernel NTFS](https://opensource.apple.com/source/ntfs) 120 | 121 | [Darwin kernel msdosfs](https://opensource.apple.com/source/msdosfs) 122 | 123 | [Darwin kernel autofs](https://opensource.apple.com/source/autofs) 124 | 125 | [Darwin kernel cddafs](https://opensource.apple.com/source/cddafs/cddafs) 126 | 127 | [Darwin kernel hfs](https://opensource.apple.com/source/hfs) 128 | 129 | [Darwin kernel webdavfs](https://opensource.apple.com/source/webdavfs/webdavfs) 130 | 131 | -------------------------------------------------------------------------------- /kext/Info.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BuildMachineOSBuild 6 | __OSBUILD__ 7 | BuildMachineClangVersion 8 | __CLANGVER__ 9 | CFBundleExecutable 10 | __KEXTMACHO__ 11 | CFBundleIdentifier 12 | __BUNDLEID__ 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | __KEXTNAME__ 17 | CFBundlePackageType 18 | KEXT 19 | CFBundleVersion 20 | __KEXTVERSION__ 21 | CFBundleShortVersionString 22 | __KEXTBUILD__ 23 | CFBundleSupportedPlatforms 24 | 25 | MacOSX 26 | 27 | __KEXTLIBS__ 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /kext/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # macOS generic kernel extension Makefile 3 | # 4 | 5 | include Makefile.inc 6 | 7 | # 8 | # Check mandatory vars 9 | # 10 | ifndef KEXTNAME 11 | $(error KEXTNAME not defined) 12 | endif 13 | 14 | ifndef KEXTVERSION 15 | $(error KEXTVERSION not defined) 16 | endif 17 | 18 | ifndef KEXTBUILD 19 | # [assume] zero indicates no build number 20 | KEXTBUILD:= 0 21 | endif 22 | 23 | ifndef BUNDLEDOMAIN 24 | $(error BUNDLEDOMAIN not defined) 25 | endif 26 | 27 | 28 | # defaults 29 | BUNDLEID?= $(BUNDLEDOMAIN).kext.$(KEXTNAME) 30 | KEXTBUNDLE?= $(KEXTNAME).kext 31 | KEXTMACHO?= $(KEXTNAME).out 32 | ARCHFLAGS?= -arch x86_64 33 | #ARCHFLAGS?= -arch x86_64 -arch i386 34 | PREFIX?= /Library/Extensions 35 | 36 | # 37 | # Set default macOS SDK 38 | # You may use 39 | # sudo xcode-select -s /Applications/Xcode.app/Contents/Developer 40 | # to switch to Xcode from Command Line Tools if cannot find any SDK 41 | # 42 | SDKROOT?= $(shell xcrun --sdk macosx --show-sdk-path) 43 | 44 | SDKFLAGS= -isysroot $(SDKROOT) 45 | CC= $(shell xcrun -find -sdk $(SDKROOT) cc) 46 | #CXX= $(shell xcrun -find -sdk $(SDKROOT) c++) 47 | CODESIGN= $(shell xcrun -find -sdk $(SDKROOT) codesign) 48 | 49 | # 50 | # Standard defines and includes for kernel extensions 51 | # 52 | # The __kext_makefile__ macro used to compatible with XCode 53 | # Since XCode use intermediate objects which causes symbol duplicated 54 | # 55 | CPPFLAGS+= -DKERNEL \ 56 | -DKERNEL_PRIVATE \ 57 | -DDRIVER_PRIVATE \ 58 | -DAPPLE \ 59 | -DNeXT \ 60 | -I$(SDKROOT)/System/Library/Frameworks/Kernel.framework/Headers \ 61 | -I$(SDKROOT)/System/Library/Frameworks/Kernel.framework/PrivateHeaders \ 62 | -D__kext_makefile__ 63 | 64 | # 65 | # Convenience defines 66 | # BUNDLEID macro will be used in KMOD_EXPLICIT_DECL 67 | # 68 | CPPFLAGS+= -DKEXTNAME_S=\"$(KEXTNAME)\" \ 69 | -DKEXTVERSION_S=\"$(KEXTVERSION)\" \ 70 | -DKEXTBUILD_S=\"$(KEXTBUILD)\" \ 71 | -DBUNDLEID_S=\"$(BUNDLEID)\" \ 72 | -DBUNDLEID=$(BUNDLEID) 73 | 74 | TIME_STAMP:= $(shell date +'%Y/%m/%d\ %H:%M:%S%z') 75 | CPPFLAGS+= -D__TS__=\"$(TIME_STAMP)\" 76 | 77 | # 78 | # C compiler flags 79 | # 80 | ifdef MACOSX_VERSION_MIN 81 | CFLAGS+= -mmacosx-version-min=$(MACOSX_VERSION_MIN) 82 | else 83 | CFLAGS+= -mmacosx-version-min=10.4 84 | endif 85 | CFLAGS+= $(SDKFLAGS) \ 86 | $(ARCHFLAGS) \ 87 | -x c \ 88 | -std=c99 \ 89 | -nostdinc \ 90 | -fno-builtin \ 91 | -fno-common \ 92 | -mkernel 93 | 94 | # warnings 95 | CFLAGS+= -Wall -Wextra 96 | 97 | # linker flags 98 | ifdef MACOSX_VERSION_MIN 99 | LDFLAGS+= -mmacosx-version-min=$(MACOSX_VERSION_MIN) 100 | else 101 | LDFLAGS+= -mmacosx-version-min=10.4 102 | endif 103 | LDFLAGS+= $(SDKFLAGS) \ 104 | $(ARCHFLAGS) \ 105 | -nostdlib \ 106 | -Xlinker -kext \ 107 | -Xlinker -object_path_lto \ 108 | -Xlinker -export_dynamic 109 | 110 | # libraries 111 | LIBS+= -lkmod 112 | #LIBS+= -lkmodc++ 113 | LIBS+= -lcc_kext 114 | 115 | # kextlibs flags 116 | KLFLAGS+= -xml -c -unsupported -undef-symbols 117 | 118 | # source, object files 119 | SRCS:= $(wildcard src/*.c) 120 | OBJS:= $(SRCS:.c=.o) 121 | 122 | # targets 123 | 124 | all: debug 125 | 126 | %.o: %.c $(HDRS) 127 | $(CC) $(CPPFLAGS) $(CFLAGS) -c -o $@ $< 128 | 129 | $(KEXTMACHO): $(OBJS) 130 | $(CC) $(LDFLAGS) $(LIBS) -o $@ $^ 131 | otool -h $@ 132 | otool -l $@ | grep uuid 133 | 134 | Info.plist~: Info.plist.in 135 | sed \ 136 | -e 's/__KEXTNAME__/$(KEXTNAME)/g' \ 137 | -e 's/__KEXTMACHO__/$(KEXTNAME)/g' \ 138 | -e 's/__KEXTVERSION__/$(KEXTVERSION)/g' \ 139 | -e 's/__KEXTBUILD__/$(KEXTBUILD)/g' \ 140 | -e 's/__BUNDLEID__/$(BUNDLEID)/g' \ 141 | -e 's/__OSBUILD__/$(shell /usr/bin/sw_vers -buildVersion)/g' \ 142 | -e 's/__CLANGVER__/$(shell $(CC) -v 2>&1 | grep version)/g' \ 143 | $^ > $@ 144 | 145 | $(KEXTBUNDLE): $(KEXTMACHO) Info.plist~ 146 | mkdir -p $@/Contents/MacOS 147 | mv $< $@/Contents/MacOS/$(KEXTNAME) 148 | 149 | # Clear placeholders(o.w. kextlibs cannot parse) 150 | sed 's/__KEXTLIBS__//g' Info.plist~ > $@/Contents/Info.plist 151 | awk '/__KEXTLIBS__/{system("kextlibs $(KLFLAGS) $@");next};1' Info.plist~ > $@/Contents/Info.plist~ 152 | mv $@/Contents/Info.plist~ $@/Contents/Info.plist 153 | 154 | ifdef COMPATIBLE_VERSION 155 | /usr/libexec/PlistBuddy -c 'Add :OSBundleCompatibleVersion string "$(COMPATIBLE_VERSION)"' $@/Contents/Info.plist 156 | endif 157 | 158 | ifdef COPYRIGHT 159 | /usr/libexec/PlistBuddy -c 'Add :NSHumanReadableCopyright string "$(COPYRIGHT)"' $@/Contents/Info.plist 160 | endif 161 | 162 | ifdef SIGNCERT 163 | $(CODESIGN) --force --timestamp=none --sign $(SIGNCERT) $@ 164 | /usr/libexec/PlistBuddy -c 'Add :CFBundleSignature string ????' $@/Contents/Info.plist 165 | endif 166 | 167 | # Empty-dependency kext cannot be load so we add one if necessary 168 | /usr/libexec/PlistBuddy -c 'Print OSBundleLibraries' $@/Contents/Info.plist &> /dev/null || \ 169 | /usr/libexec/PlistBuddy -c 'Add :OSBundleLibraries:com.apple.kpi.bsd string "8.0b1"' $@/Contents/Info.plist 170 | 171 | touch $@ 172 | 173 | dsymutil $(ARCHFLAGS) -o $(KEXTNAME).kext.dSYM $@/Contents/MacOS/$(KEXTNAME) 174 | 175 | # see: https://www.gnu.org/software/make/manual/html_node/Target_002dspecific.html 176 | # Those two flags must present at the same time o.w. debug symbol cannot be generated 177 | debug: CPPFLAGS += -g -DDEBUG 178 | debug: CFLAGS += -O0 179 | debug: $(KEXTBUNDLE) 180 | 181 | # see: https://stackoverflow.com/questions/15548023/clang-optimization-levels 182 | release: CFLAGS += -O2 183 | release: $(KEXTBUNDLE) 184 | 185 | load: $(KEXTBUNDLE) 186 | sudo chown -R root:wheel $< 187 | sudo sync 188 | sudo kextutil $< 189 | # restore original owner:group 190 | sudo chown -R '$(USER):$(shell id -gn)' $< 191 | sudo dmesg | grep $(KEXTNAME) | tail -1 192 | 193 | stat: 194 | kextstat | grep $(KEXTNAME) 195 | 196 | unload: 197 | sudo kextunload $(KEXTBUNDLE) 198 | sudo dmesg | grep $(KEXTNAME) | tail -2 199 | 200 | install: $(KEXTBUNDLE) uninstall 201 | test -d "$(PREFIX)" 202 | sudo cp -r $< "$(PREFIX)/$<" 203 | sudo chown -R root:wheel "$(PREFIX)/$<" 204 | 205 | uninstall: 206 | test -d "$(PREFIX)" 207 | test -e "$(PREFIX)/$(KEXTBUNDLE)" && \ 208 | sudo rm -rf "$(PREFIX)/$(KEXTBUNDLE)" || true 209 | 210 | clean: 211 | rm -rf $(KEXTBUNDLE) $(KEXTBUNDLE).dSYM Info.plist~ $(OBJS) $(KEXTMACHO) 212 | 213 | .PHONY: all debug release load stat unload intall uninstall clean 214 | 215 | -------------------------------------------------------------------------------- /kext/Makefile.inc: -------------------------------------------------------------------------------- 1 | # 2 | # Put user-defined variables here 3 | # 4 | 5 | KEXTNAME=emptyfs 6 | KEXTVERSION=0000.00.04 7 | KEXTBUILD=1 8 | BUNDLEDOMAIN=cn.junkman 9 | 10 | -------------------------------------------------------------------------------- /kext/src/emptyfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181110 lynnl 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "emptyfs.h" 10 | #include "utils.h" 11 | #include "emptyfs_vfsops.h" 12 | #include "emptyfs_vnops.h" 13 | 14 | /* 15 | * this struct describe overall VFS plugin 16 | * passed as a parameter to vfs_fsadd to register this file system 17 | */ 18 | static struct vfs_fsentry emptyfs_vfsentry = { 19 | &emptyfs_vfsops, 20 | ARRAY_SIZE(emptyfs_vnopv_desc_list), 21 | emptyfs_vnopv_desc_list, 22 | EMPTYFS_NOTYPENUM, 23 | EMPTYFS_NAME, 24 | EMPTYFS_VFS_FLAGS, 25 | {NULL, NULL}, /* Reserved fields */ 26 | }; 27 | 28 | /* 29 | * global locking group which is used by all types of locks 30 | */ 31 | lck_grp_t *lckgrp = NULL; 32 | 33 | /* 34 | * an opaque vfs handle which will be passed to vfs_fsremove() 35 | */ 36 | static vfstable_t emptyfs_vfstbl_ref = NULL; 37 | 38 | kern_return_t emptyfs_start(kmod_info_t *ki, void *d __unused) 39 | { 40 | kern_return_t e; 41 | uuid_string_t uuid; 42 | 43 | LOG("built with Apple LLVM version %s", __clang_version__); 44 | 45 | e = util_vma_uuid(ki->address, uuid); 46 | kassert(e == 0); 47 | LOG("kext executable uuid %s", uuid); 48 | 49 | lckgrp = lck_grp_alloc_init(LCKGRP_NAME, LCK_GRP_ATTR_NULL); 50 | if (lckgrp == NULL) { 51 | LOG_ERR("lck_grp_alloc_init() fail"); 52 | goto out_lckgrp; 53 | } 54 | LOG_DBG("lock group(%s) allocated", LCKGRP_NAME); 55 | 56 | e = vfs_fsadd(&emptyfs_vfsentry, &emptyfs_vfstbl_ref); 57 | if (e != 0) { 58 | LOG_ERR("vfs_fsadd() failure errno: %d", e); 59 | goto out_vfsadd; 60 | } 61 | LOG_DBG("%s file system registered", emptyfs_vfsentry.vfe_fsname); 62 | 63 | LOG("loaded %s version %s build %s (%s)", 64 | BUNDLEID_S, KEXTVERSION_S, KEXTBUILD_S, __TS__); 65 | 66 | out_exit: 67 | return e; 68 | 69 | out_vfsadd: 70 | lck_grp_free(lckgrp); 71 | 72 | out_lckgrp: 73 | e = KERN_FAILURE; 74 | goto out_exit; 75 | } 76 | 77 | kern_return_t emptyfs_stop(kmod_info_t *ki __unused, void *d __unused) 78 | { 79 | kern_return_t e; 80 | 81 | /* will fail if any of our volumes(i.e. emptyfs) mounted */ 82 | e = vfs_fsremove(emptyfs_vfstbl_ref); 83 | if (e) { 84 | LOG_ERR("vfs_fsremove() failure errno: %d", e); 85 | goto out_vfs_rm; 86 | } 87 | 88 | lck_grp_free(lckgrp); 89 | 90 | util_massert(); 91 | 92 | LOG("unloaded %s version %s build %s (%s)", 93 | BUNDLEID_S, KEXTVERSION_S, KEXTBUILD_S, __TS__); 94 | 95 | out_exit: 96 | return e; 97 | 98 | out_vfs_rm: 99 | e = KERN_FAILURE; 100 | goto out_exit; 101 | } 102 | 103 | #ifdef __kext_makefile__ 104 | extern kern_return_t _start(kmod_info_t *, void *); 105 | extern kern_return_t _stop(kmod_info_t *, void *); 106 | 107 | /* will expand name if it's a macro */ 108 | #define KMOD_EXPLICIT_DECL2(name, ver, start, stop) \ 109 | __attribute__((visibility("default"))) \ 110 | KMOD_EXPLICIT_DECL(name, ver, start, stop) 111 | 112 | KMOD_EXPLICIT_DECL2(BUNDLEID, KEXTBUILD_S, _start, _stop) 113 | 114 | /* if you intended to write a kext library NULLify _realmain and _antimain */ 115 | __private_extern__ kmod_start_func_t *_realmain = emptyfs_start; 116 | __private_extern__ kmod_stop_func_t *_antimain = emptyfs_stop; 117 | 118 | __private_extern__ int _kext_apple_cc = __APPLE_CC__; 119 | #endif 120 | 121 | -------------------------------------------------------------------------------- /kext/src/emptyfs.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181110 lynnl 3 | */ 4 | 5 | #ifndef __EMPTYFS_H 6 | #define __EMPTYFS_H 7 | 8 | #include 9 | #include "utils.h" 10 | 11 | #ifndef __kext_makefile__ 12 | #define __TZ__ "+0000" 13 | #define BUNDLEID_S "cn.junkman.kext.emptyfs" 14 | #define KEXTVERSION_S "0000.00.04" 15 | #define KEXTBUILD_S "1" 16 | #endif 17 | 18 | #define LCKGRP_NAME BUNDLEID_S ".lckgrp" 19 | 20 | #define EMPTYFS_NOTYPENUM 0 /* will assign one dynamically */ 21 | #define EMPTYFS_NAME "emptyfs" 22 | 23 | /* 24 | * VFS_TBLTHREADSAFE, VFS_TBLFSNODELOCK: 25 | * we do our own internal locking and thus don't need funnel protection 26 | * 27 | * VFS_TBLNOTYPENUM: 28 | * we don't have a pre-defined file system type 29 | * (the VT_XXX constants in ) 30 | * VFS should dynamically assign us a type 31 | * 32 | * VFS_TBLLOCALVOL: 33 | * our file system is local; causes MNT_LOCAL to be set and indicates 34 | * that the first field of our file system specific mount arguments 35 | * is a path to a block device 36 | * 37 | * VFS_TBL64BITREADY: 38 | * we are 64-bit aware; our mount, ioctl and sysctl entry points can be 39 | * called by both 32-bit and 64-bit processes; we'll use the type of 40 | * process to interpret our arguments(if they're not 32/64-bit invariant) 41 | */ 42 | #define EMPTYFS_VFS_FLAGS ( \ 43 | VFS_TBLTHREADSAFE | \ 44 | VFS_TBLFSNODELOCK | \ 45 | VFS_TBLNOTYPENUM | \ 46 | VFS_TBLLOCALVOL | \ 47 | VFS_TBL64BITREADY | \ 48 | 0 \ 49 | ) 50 | 51 | readonly_extern lck_grp_t *lckgrp; 52 | 53 | /* The largest 32-bit De Bruijn constant */ 54 | #define EMPTYFS_MNTARG_MAGIC 0x0fb9ac52 55 | 56 | /* 57 | * This structure is passed from userspace mount(2) 58 | * tells the kernel and our VFS plugin what and how to mount 59 | * part of the fields interpreted by the kernel(enclosed by KERNEL) 60 | * the rest is only interpreted by our VFS mount entry point 61 | * 62 | * XXX: 63 | * this structure should be invariant between 32/64-bits(except the KERNEL part) 64 | * o.w. a mount from a 64-bit process will fail when it try to mount 65 | * for this reason you must use arch-independent data type 66 | * for example, long int may 32-bit or 64-bit(which it's arch-dependent) 67 | * 68 | * see: emptyfs_vfsops.c#emptyfs_vfsop_mount 69 | */ 70 | struct emptyfs_mnt_args { 71 | #ifndef KERNEL 72 | /* path to the block device node to mount example: /dev/disk0s1 */ 73 | const char *dev_node_path; 74 | #endif 75 | uint32_t magic; /* must be EMPTYFS_MNTARG_MAGIC */ 76 | uint32_t dbg_mode; /* enable debug for verbose output */ 77 | uint32_t force_fail; /* if non-zero mount(2) will always fail */ 78 | }; 79 | 80 | #endif /* __EMPTYFS_H */ 81 | 82 | -------------------------------------------------------------------------------- /kext/src/emptyfs_vfsops.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181117 lynnl 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "emptyfs_vfsops.h" 13 | #include "emptyfs_vnops.h" 14 | #include "emptyfs.h" 15 | #include "utils.h" 16 | 17 | static int emptyfs_vfsop_mount(struct mount *, vnode_t, user_addr_t, vfs_context_t); 18 | static int emptyfs_vfsop_start(struct mount *, int, vfs_context_t); 19 | static int emptyfs_vfsop_unmount(struct mount *, int, vfs_context_t); 20 | static int emptyfs_vfsop_root(struct mount *, struct vnode **, vfs_context_t); 21 | static int emptyfs_vfsop_getattr(struct mount *, struct vfs_attr *, vfs_context_t); 22 | 23 | /* 24 | * a structure that stores function pointers to all VFS routines 25 | * these functions operates on the instances of the file system itself 26 | * (rather NOT file system vnodes) 27 | */ 28 | struct vfsops emptyfs_vfsops = { 29 | .vfs_mount = emptyfs_vfsop_mount, 30 | .vfs_start = emptyfs_vfsop_start, 31 | .vfs_unmount = emptyfs_vfsop_unmount, 32 | .vfs_root = emptyfs_vfsop_root, 33 | .vfs_getattr = emptyfs_vfsop_getattr, 34 | }; 35 | 36 | /* 37 | * Get filesystem-specific mount structure 38 | */ 39 | struct emptyfs_mount *emptyfs_mount_from_mp(mount_t __nonnull mp) 40 | { 41 | struct emptyfs_mount *mntp; 42 | kassert_nonnull(mp); 43 | mntp = vfs_fsprivate(mp); 44 | kassert_nonnull(mntp); 45 | kassert(mntp->magic == EMPTYFS_MNT_MAGIC); 46 | kassert(mntp->mp == mp); 47 | return mntp; 48 | } 49 | 50 | #define VFS_ATTR_BLKSZ 4096 51 | 52 | /* 53 | * Initialize `stuct vfs_attr''s f_capabilities and f_attributes 54 | * TODO: put those attrlist into a solo header 55 | * see: xnu/bsd/hfs/hfs_attrlist.h 56 | */ 57 | static void emptyfs_init_volattrs(struct emptyfs_mount * __nonnull mntp) 58 | { 59 | vol_capabilities_attr_t *cap; 60 | vol_attributes_attr_t *attr; 61 | 62 | kassert_nonnull(mntp); 63 | 64 | cap = &mntp->attr.f_capabilities; 65 | attr = &mntp->attr.f_attributes; 66 | 67 | cap->capabilities[VOL_CAPABILITIES_FORMAT] = 0 68 | | VOL_CAP_FMT_NO_ROOT_TIMES 69 | | VOL_CAP_FMT_CASE_SENSITIVE 70 | | VOL_CAP_FMT_CASE_PRESERVING 71 | | VOL_CAP_FMT_FAST_STATFS 72 | | VOL_CAP_FMT_2TB_FILESIZE 73 | #if defined(OS_VER_MIN_REQ) && OS_VER_MIN_REQ >= __MAC_10_12 74 | | VOL_CAP_FMT_NO_PERMISSIONS 75 | #elif defined(OS_VER_MIN_REQ) 76 | #warning Some volume capabilities may unavailable under macOS target <= 10.12 77 | #else 78 | #warning OS_VER_MIN_REQ undefined 79 | #endif 80 | ; 81 | 82 | /* XXX: forcibly mark all capabilities as valid? */ 83 | cap->valid[VOL_CAPABILITIES_FORMAT] = (__typeof(*(cap->valid))) -1; 84 | 85 | cap->capabilities[VOL_CAPABILITIES_INTERFACES] = VOL_CAP_INT_ATTRLIST; 86 | cap->valid[VOL_CAPABILITIES_INTERFACES] = (__typeof(*(cap->valid))) -1; 87 | 88 | attr->validattr.commonattr = 0 89 | | ATTR_CMN_NAME 90 | | ATTR_CMN_DEVID 91 | | ATTR_CMN_FSID 92 | | ATTR_CMN_OBJTYPE 93 | | ATTR_CMN_OBJID 94 | | ATTR_CMN_PAROBJID /* Q: Parent object ID? */ 95 | | ATTR_CMN_CRTIME 96 | | ATTR_CMN_MODTIME 97 | | ATTR_CMN_CHGTIME /* Same as ATTR_CMN_MODTIME */ 98 | | ATTR_CMN_ACCTIME 99 | | ATTR_CMN_OWNERID 100 | | ATTR_CMN_GRPID 101 | | ATTR_CMN_ACCESSMASK 102 | | ATTR_CMN_FLAGS; 103 | 104 | attr->validattr.dirattr = 0; 105 | 106 | attr->validattr.fileattr = 0 107 | | ATTR_FILE_TOTALSIZE 108 | | ATTR_FILE_IOBLOCKSIZE 109 | | ATTR_FILE_DATALENGTH 110 | | ATTR_FILE_DATAALLOCSIZE; 111 | 112 | attr->validattr.forkattr = 0; 113 | 114 | attr->validattr.volattr = 0 115 | | ATTR_VOL_FSTYPE 116 | | ATTR_VOL_SIZE 117 | | ATTR_VOL_SPACEFREE 118 | | ATTR_VOL_SPACEAVAIL 119 | | ATTR_VOL_IOBLOCKSIZE 120 | | ATTR_VOL_OBJCOUNT 121 | | ATTR_VOL_FILECOUNT 122 | | ATTR_VOL_DIRCOUNT 123 | | ATTR_VOL_MAXOBJCOUNT 124 | | ATTR_VOL_MOUNTPOINT 125 | | ATTR_VOL_NAME 126 | | ATTR_VOL_MOUNTFLAGS 127 | | ATTR_VOL_MOUNTEDDEVICE 128 | | ATTR_VOL_CAPABILITIES 129 | | ATTR_VOL_UUID 130 | | ATTR_VOL_ATTRIBUTES; 131 | 132 | /* All attributes that we do support we support natively */ 133 | bcopy(&attr->validattr, &attr->nativeattr, sizeof(attr->validattr)); 134 | } 135 | 136 | /* 137 | * Initialize VFS attributes in `struct emptyfs_mount' with appropriate 138 | * static values (this done in initialization time .: it's thread-safe) 139 | */ 140 | static void emptyfs_init_attrs( 141 | struct emptyfs_mount * __nonnull mntp, 142 | vfs_context_t __nonnull ctx) 143 | { 144 | kauth_cred_t cred; 145 | uid_t uid; 146 | struct timespec ts; 147 | uuid_string_t uuid; 148 | 149 | kassert_nonnull(mntp); 150 | kassert_nonnull(ctx); 151 | 152 | cred = vfs_context_ucred(ctx); 153 | kassert_nonnull(cred); 154 | uid = kauth_cred_getuid(cred); 155 | 156 | mntp->attr.f_objcount = 1; 157 | mntp->attr.f_filecount = 0; 158 | mntp->attr.f_dircount = 1; 159 | mntp->attr.f_maxobjcount = 1; 160 | 161 | mntp->attr.f_bsize = VFS_ATTR_BLKSZ; 162 | mntp->attr.f_iosize = VFS_ATTR_BLKSZ; 163 | mntp->attr.f_blocks = 1; 164 | mntp->attr.f_bfree = 0; 165 | mntp->attr.f_bavail = 0; 166 | mntp->attr.f_bused = 1; 167 | mntp->attr.f_files = 1; 168 | mntp->attr.f_ffree = 0; 169 | 170 | mntp->attr.f_fsid.val[0] = mntp->devid; 171 | mntp->attr.f_fsid.val[1] = vfs_typenum(mntp->mp); 172 | mntp->attr.f_owner = uid; 173 | 174 | emptyfs_init_volattrs(mntp); 175 | 176 | nanotime(&ts); 177 | LOG_DBG("fs ctime mtim atime: %ld", ts.tv_sec + (ts.tv_nsec / 1000000000L)); 178 | bcopy(&ts, &mntp->attr.f_create_time, sizeof(ts)); 179 | bcopy(&ts, &mntp->attr.f_modify_time, sizeof(ts)); 180 | bcopy(&ts, &mntp->attr.f_access_time, sizeof(ts)); 181 | 182 | mntp->attr.f_fssubtype = 0; 183 | 184 | mntp->attr.f_vol_name = mntp->volname; 185 | 186 | uuid_generate_random(mntp->attr.f_uuid); 187 | format_uuid_string(mntp->attr.f_uuid, uuid); 188 | LOG_DBG("file system UUID: %s", uuid); 189 | 190 | /* remaining not supported implicitly */ 191 | } 192 | 193 | /* 194 | * Called by VFS to mount an instance of our file system 195 | * 196 | * @mp mount structure for the newly mounted filesystem 197 | * @devvp either an open vnode for the backing block device which 198 | * going to be mounted or NULL 199 | * in the former case the first field of our filesystem-specific 200 | * mount argument must be a pointer to C-style string holding 201 | * an UTF-8 encoded path to the block device node 202 | * @data filesystem-specific data passed down from userspace via mount(2) 203 | * :. VFS_TBLLOCALVOL is set the first field of this structure 204 | * must be a pointer to the path of the backing block device node 205 | * kernel'll interpret this argument opening up the node for us 206 | * @ctx context to authenticate for mount 207 | * @return 0 if success errno o.w. 208 | * if a mount call fails the filesystem must clean up any state 209 | * it has constructed :. vfs-level mount code will not clean it up 210 | * 211 | * XXX: 212 | * if VFS_TBLLOCALVOL is set first field of the filesystem-specific mount 213 | * argument is interpreted by kernel and kernel'll ADVANCE data pointer to 214 | * the field straight after the path 215 | * we handle this by defining filesystem-specific argument in two ways 216 | * 1) in userspace first field is a pointer to the block device node path 217 | * 2) in kernel we omit the field aforementioned 218 | * 219 | * XXX: 220 | * if VFS_TBL64BITREADY is set(i.e. claim your fs to be 64-bit ready) 221 | * you must be prepared to handle mount requests from both 32/64-bit proc. 222 | * .: your filesystem-specific mount arguments must be either 32/64-bit invariant 223 | * (just as this fs) or you must interpret them differently depends on the 224 | * type of process you're calling by (see: @proc_is64bit) 225 | */ 226 | static int emptyfs_vfsop_mount( 227 | struct mount *mp, 228 | vnode_t devvp, 229 | user_addr_t udata, 230 | vfs_context_t ctx) 231 | { 232 | int e, e2; 233 | struct emptyfs_mnt_args args; 234 | struct emptyfs_mount *mntp; 235 | struct vfsstatfs *st; 236 | 237 | kassert_nonnull(mp); 238 | kassert_nonnull(devvp); 239 | kassert_nonnull(udata); /* Q: what if user passed NULL mount(3) arg. */ 240 | kassert_nonnull(ctx); 241 | 242 | LOG_DBG("mp: %p devvp: %p(%d) %#x data: %#llx", 243 | mp, devvp, vnode_vtype(devvp), vnode_vid(devvp), udata); 244 | 245 | /* 246 | * this fs doesn't support over update a volume's state 247 | * for example: upgrade it from readonly to readwrite 248 | */ 249 | if (vfs_isupdate(mp) || vfs_iswriteupgrade(mp)) { 250 | e = ENOTSUP; 251 | LOG_ERR("mount %p is updating we doesn't support it", mp); 252 | goto out_exit; 253 | } 254 | 255 | e = copyin(udata, &args, sizeof(args)); 256 | if (e) { 257 | LOG_ERR("copyin() fail errno: %d", e); 258 | goto out_exit; 259 | } 260 | 261 | if (args.magic != EMPTYFS_MNTARG_MAGIC) { 262 | e = EINVAL; 263 | LOG_ERR("bad mount arguments from mount(2) magic: %#x", args.magic); 264 | goto out_exit; 265 | } 266 | 267 | mntp = util_malloc(sizeof(*mntp), M_ZERO); 268 | if (mntp == NULL) { 269 | e = ENOMEM; 270 | LOG_ERR("util_malloc() fail errno: %d", e); 271 | goto out_exit; 272 | } 273 | 274 | vfs_setfsprivate(mp, mntp); 275 | 276 | /* fill out fields in our mount point */ 277 | 278 | e = vnode_ref(devvp); 279 | if (e) { 280 | LOG_ERR("vnode_ref() fail errno: %d", e); 281 | goto out_exit; 282 | } 283 | mntp->devvp = devvp; 284 | mntp->devid = vnode_specrdev(devvp); 285 | 286 | mntp->mtx_root = lck_mtx_alloc_init(lckgrp, NULL); 287 | if (mntp->mtx_root == NULL) { 288 | e = ENOMEM; 289 | LOG_ERR("lck_mtx_alloc_init() fail errno: %d", e); 290 | goto out_exit; 291 | } 292 | 293 | mntp->magic = EMPTYFS_MNT_MAGIC; 294 | mntp->mp = mp; 295 | mntp->dbg_mode = args.dbg_mode; 296 | kassert(strlen(EMPTYFS_NAME) < sizeof(mntp->volname)); 297 | (void) strlcpy(mntp->volname, EMPTYFS_NAME, sizeof(mntp->volname)); 298 | /* 299 | * make sure that mntp->devid initialized 300 | * :. emptyfs_init_attrs() reads it 301 | */ 302 | emptyfs_init_attrs(mntp, ctx); 303 | 304 | kassert(!mntp->is_root_attaching); 305 | kassert(!mntp->is_root_waiting); 306 | kassert(mntp->rootvp == NULL); 307 | 308 | st = vfs_statfs(mp); 309 | kassert_nonnull(st); 310 | kassert(!strcmp(st->f_fstypename, EMPTYFS_NAME)); 311 | 312 | st->f_bsize = mntp->attr.f_bsize; 313 | st->f_iosize = mntp->attr.f_iosize; 314 | st->f_blocks = mntp->attr.f_blocks; 315 | st->f_bfree = mntp->attr.f_bfree; 316 | st->f_bavail = mntp->attr.f_bavail; 317 | st->f_bused = mntp->attr.f_bused; 318 | st->f_files = mntp->attr.f_files; 319 | st->f_ffree = mntp->attr.f_ffree; 320 | st->f_fsid = mntp->attr.f_fsid; 321 | st->f_owner = mntp->attr.f_owner; 322 | 323 | vfs_setflags(mp, MNT_RDONLY | MNT_NOEXEC | MNT_NOSUID | 324 | MNT_NODEV | MNT_IGNORE_OWNERSHIP); 325 | 326 | /* no need to call vnode_setmountedon() :. the system already done that */ 327 | 328 | if (args.force_fail) { 329 | /* force failure allows us to test unmount path */ 330 | e = ECANCELED; 331 | LOG_ERR("mount emptyfs success yet force failure errno: %d", e); 332 | goto out_exit; 333 | } else { 334 | LOG_INF("mount emptyfs success rdev: %#x dbg: %d", 335 | mntp->devid, mntp->dbg_mode); 336 | } 337 | 338 | out_exit: 339 | if (e) { 340 | e2 = emptyfs_vfsop_unmount(mp, MNT_FORCE, ctx); 341 | kassertf(e2 == 0, "why force unmount fail? errno: %d", e2); 342 | } 343 | 344 | return e; 345 | } 346 | 347 | /* 348 | * Called by VFS to mark a mount as ready to be used 349 | * 350 | * after receiving this calldown 351 | * a filesystem will be hooked into the mount list 352 | * and should expect calls down from the VFS layer 353 | * 354 | * this entry point isn't particularly useful to avoid concurrency problems 355 | * you should do all mount initialization in VFS mount entry point 356 | * moreover it's unnecessary to implement this :. in the VFS_START call 357 | * if you glue a NULL to vfs_start field it returns ENOTSUP 358 | * and the caller ignores the result 359 | * 360 | * @mp the mount structure reference 361 | * @flags unused 362 | * @ctx context to authenticate for mount 363 | * @return return value is ignored 364 | * 365 | * see: 366 | * xnu/bsd/vfs/kpi_vfs.c#VFS_START 367 | * xnu/bsd/vfs/vfs_subr.c#vfs_mountroot 368 | * xnu/bsd/vfs/vfs_syscalls.c#mount_common 369 | */ 370 | static int emptyfs_vfsop_start( 371 | struct mount *mp, 372 | int flags, 373 | vfs_context_t ctx) 374 | { 375 | kassert_nonnull(mp); 376 | kassert_known_flags(flags, 0); 377 | kassert_nonnull(ctx); 378 | 379 | LOG_DBG("mp: %p flags: %#x", mp, flags); 380 | 381 | return 0; 382 | } 383 | 384 | /* 385 | * Called by VFS to unmount a volume 386 | * @mp mount reference 387 | * @flags unmount flags 388 | * @ctx identity of the calling process 389 | * @return 0 if success errno o.w. 390 | */ 391 | static int emptyfs_vfsop_unmount( 392 | struct mount *mp, 393 | int flags, 394 | vfs_context_t ctx) 395 | { 396 | int e; 397 | int flush_flags; 398 | struct emptyfs_mount *mntp; 399 | 400 | kassert_nonnull(mp); 401 | kassert_known_flags(flags, MNT_FORCE); 402 | kassert_nonnull(ctx); 403 | 404 | LOG_DBG("mp: %p flags: %#x", mp, flags); 405 | 406 | flush_flags = (flags & MNT_FORCE) ? FORCECLOSE : 0; 407 | 408 | e = vflush(mp, NULL, flush_flags); 409 | if (e) { 410 | LOG_ERR("vflush() fail errno: %d", e); 411 | goto out_exit; 412 | } 413 | 414 | mntp = vfs_fsprivate(mp); 415 | if (mntp == NULL) goto out_exit; 416 | 417 | if (mntp->devvp != NULL) { 418 | vnode_rele(mntp->devvp); 419 | mntp->devvp = NULL; 420 | mntp->devid = 0; 421 | } 422 | 423 | kassert(!mntp->is_root_attaching); 424 | kassert(!mntp->is_root_waiting); 425 | 426 | /* 427 | * vflush() call above forces VFS to reclaim any vnode in our volume 428 | * in such case the root vnode should be NULL 429 | */ 430 | kassert(mntp->rootvp == NULL); 431 | 432 | if (mntp->mtx_root != NULL) lck_mtx_free(mntp->mtx_root, lckgrp); 433 | 434 | mntp->magic = 0; /* our mount invalidated reset the magic */ 435 | 436 | util_mfree(mntp); 437 | 438 | out_exit: 439 | return e; 440 | } 441 | 442 | /** 443 | * @return root vnode of the volume(will create if necessary) 444 | * resulting vnode has an io refcnt. whcih the caller is 445 | * responsible to release it via vnode_put() 446 | */ 447 | static int get_root_vnode( 448 | struct emptyfs_mount * __nonnull mntp, 449 | vnode_t * __nonnull vpp) 450 | { 451 | int e; 452 | int e2; 453 | vnode_t vn = NULL; 454 | uint32_t vid; 455 | struct vnode_fsparam param; 456 | 457 | kassert_nonnull(mntp); 458 | kassert_nonnull(vpp); 459 | kassert_null(*vpp); 460 | 461 | lck_mtx_lock(mntp->mtx_root); 462 | 463 | do { 464 | kassert_null(vn); 465 | lck_mtx_assert(mntp->mtx_root, LCK_MTX_ASSERT_OWNED); 466 | 467 | if (mntp->is_root_attaching) { 468 | mntp->is_root_waiting = 1; 469 | (void) msleep(&mntp->rootvp, mntp->mtx_root, PINOD, NULL, NULL); 470 | kassert(mntp->is_root_waiting == 0); 471 | e = EAGAIN; 472 | } else if (mntp->rootvp == NULLVP) { 473 | mntp->is_root_attaching = 1; 474 | lck_mtx_unlock(mntp->mtx_root); 475 | 476 | param.vnfs_mp = mntp->mp; 477 | param.vnfs_vtype = VDIR; 478 | param.vnfs_str = NULL; 479 | param.vnfs_dvp = NULL; 480 | param.vnfs_fsnode = NULL; 481 | param.vnfs_vops = emptyfs_vnop_p; 482 | param.vnfs_markroot = 1; 483 | param.vnfs_marksystem = 0; 484 | param.vnfs_rdev = 0; /* we don't support VBLK and VCHR */ 485 | param.vnfs_filesize = 0; /* nonsense for VDIR */ 486 | param.vnfs_cnp = NULL; 487 | param.vnfs_flags = VNFS_NOCACHE | VNFS_CANTCACHE; /* no namecache */ 488 | 489 | e = vnode_create(VNCREATE_FLAVOR, sizeof(param), ¶m, &vn); 490 | if (e == 0) { 491 | kassert_nonnull(vn); 492 | LOG_DBG("vnode_create() ok vn: %p vid: %#x", vn, vnode_vid(vn)); 493 | } else { 494 | kassert_null(vn); 495 | LOG_ERR("vnode_create() fail errno: %d", e); 496 | } 497 | 498 | lck_mtx_lock(mntp->mtx_root); 499 | if (e == 0) { 500 | kassert_null(mntp->rootvp); 501 | mntp->rootvp = vn; 502 | e2 = vnode_addfsref(vn); 503 | kassertf(e2 == 0, "vnode_addfsref() fail errno: %d", e2); 504 | 505 | kassert(mntp->is_root_attaching); 506 | mntp->is_root_attaching = 0; 507 | if (mntp->is_root_waiting) { 508 | mntp->is_root_waiting = 0; /* reset beforehand */ 509 | wakeup(&mntp->rootvp); 510 | } 511 | } 512 | } else { 513 | /* we already have a root vnode try get with vnode vid */ 514 | vn = mntp->rootvp; 515 | kassert_nonnull(vn); 516 | vid = vnode_vid(vn); 517 | lck_mtx_unlock(mntp->mtx_root); 518 | 519 | e = vnode_getwithvid(vn, vid); 520 | if (e == 0) { 521 | /* 522 | * ok vnode_getwithvid() has taken an IO refcnt. to the vnode 523 | * this reference prevent from being reclaimed in the interim 524 | */ 525 | } else { 526 | /* 527 | * the vnode has been relaimed likely between dropping the lock 528 | * and calling the vnode_getwithvid() 529 | * .: we loop again to get updated vnode root(hopefully) 530 | */ 531 | LOG_ERR("vnode_getwithvid() fail errno: %d", e); 532 | vn = NULL; /* loop invariant */ 533 | e = EAGAIN; 534 | } 535 | 536 | lck_mtx_lock(mntp->mtx_root); /* loop invariant */ 537 | } 538 | } while (e == EAGAIN); 539 | 540 | lck_mtx_unlock(mntp->mtx_root); 541 | 542 | if (e == 0) { 543 | kassert_nonnull(vn); 544 | *vpp = vn; 545 | } else { 546 | kassert_null(vn); 547 | } 548 | 549 | return e; 550 | } 551 | 552 | /* 553 | * Called by VFS to get root vnode of this file system 554 | * @mp the mount structure reference 555 | * @vpp pointer to a vnode reference 556 | * on success we must update it and have an io refcnt on it 557 | * and it's the caller's responsibility to release it 558 | * @ctx context to authenticate for getting the root 559 | */ 560 | static int emptyfs_vfsop_root( 561 | struct mount *mp, 562 | struct vnode **vpp, 563 | vfs_context_t ctx) 564 | { 565 | int e; 566 | vnode_t vn = NULL; 567 | struct emptyfs_mount *mntp; 568 | 569 | kassert_nonnull(mp); 570 | kassert_nonnull(vpp); 571 | kassert_nonnull(ctx); 572 | 573 | LOG_DBG("mp: %p vpp: %p %p", mp, vpp, *vpp); 574 | 575 | mntp = emptyfs_mount_from_mp(mp); 576 | e = get_root_vnode(mntp, &vn); 577 | /* under all circumstances we should set *vpp to maintain post-conditions */ 578 | *vpp = vn; 579 | 580 | if (e == 0) { 581 | kassert_nonnull(*vpp); 582 | } else { 583 | kassert_null(*vpp); 584 | } 585 | 586 | LOG_DBG("vpp: %p %p", vpp, *vpp); 587 | 588 | return e; 589 | } 590 | 591 | /* 592 | * Called by VFS to get information about this file system 593 | * 594 | * @mp the mount structure reference 595 | * @attr describe the attributes requested and the place to stores the result 596 | * @ctx context to authenticate for mount 597 | * @return 0 :. always success 598 | * 599 | * this implementation is trivial :. our file system attributes are static 600 | */ 601 | static int emptyfs_vfsop_getattr( 602 | struct mount *mp, 603 | struct vfs_attr *attr, 604 | vfs_context_t ctx) 605 | { 606 | struct emptyfs_mount *mntp; 607 | 608 | kassert_nonnull(mp); 609 | kassert_nonnull(attr); 610 | kassert_nonnull(ctx); 611 | 612 | LOG_DBG("mp: %p attr: %p f_active: %#llx f_supported: %#llx", 613 | mp, attr, attr->f_active, attr->f_supported); 614 | 615 | mntp = emptyfs_mount_from_mp(mp); 616 | 617 | VFSATTR_RETURN(attr, f_objcount, mntp->attr.f_objcount); 618 | VFSATTR_RETURN(attr, f_filecount, mntp->attr.f_filecount); 619 | VFSATTR_RETURN(attr, f_dircount, mntp->attr.f_dircount); 620 | VFSATTR_RETURN(attr, f_maxobjcount, mntp->attr.f_maxobjcount); 621 | 622 | VFSATTR_RETURN(attr, f_bsize, mntp->attr.f_bsize); 623 | VFSATTR_RETURN(attr, f_iosize, mntp->attr.f_iosize); 624 | VFSATTR_RETURN(attr, f_blocks, mntp->attr.f_blocks); 625 | VFSATTR_RETURN(attr, f_bfree, mntp->attr.f_bfree); 626 | VFSATTR_RETURN(attr, f_bavail, mntp->attr.f_bavail); 627 | VFSATTR_RETURN(attr, f_bused, mntp->attr.f_bused); 628 | VFSATTR_RETURN(attr, f_files, mntp->attr.f_files); 629 | VFSATTR_RETURN(attr, f_ffree, mntp->attr.f_ffree); 630 | VFSATTR_RETURN(attr, f_fsid, mntp->attr.f_fsid); 631 | VFSATTR_RETURN(attr, f_owner, mntp->attr.f_owner); 632 | 633 | VFSATTR_RETURN(attr, f_capabilities, mntp->attr.f_capabilities); 634 | VFSATTR_RETURN(attr, f_attributes, mntp->attr.f_attributes); 635 | 636 | VFSATTR_RETURN(attr, f_create_time, mntp->attr.f_create_time); 637 | VFSATTR_RETURN(attr, f_modify_time, mntp->attr.f_modify_time); 638 | VFSATTR_RETURN(attr, f_access_time, mntp->attr.f_access_time); 639 | /* no support f_backup_time */ 640 | 641 | VFSATTR_RETURN(attr, f_fssubtype, mntp->attr.f_fssubtype); 642 | 643 | if (VFSATTR_IS_ACTIVE(attr, f_vol_name)) { 644 | strncpy(attr->f_vol_name, mntp->attr.f_vol_name, EMPTYFS_VOLNAME_MAXLEN); 645 | attr->f_vol_name[EMPTYFS_VOLNAME_MAXLEN-1] = '\0'; 646 | VFSATTR_SET_SUPPORTED(attr, f_vol_name); 647 | } 648 | 649 | /* no support f_signature */ 650 | /* no support f_carbon_fsid */ 651 | 652 | if (VFSATTR_IS_ACTIVE(attr, f_uuid)) { 653 | kassert(sizeof(mntp->attr.f_uuid) == sizeof(attr->f_uuid)); 654 | bcopy(mntp->attr.f_uuid, attr->f_uuid, sizeof(attr->f_uuid)); 655 | VFSATTR_SET_SUPPORTED(attr, f_uuid); 656 | } 657 | 658 | /* no support f_quota */ 659 | /* no support f_reserved */ 660 | 661 | LOG_DBG("f_active: %#llx f_supported: %#llx", 662 | attr->f_active, attr->f_supported); 663 | 664 | return 0; 665 | } 666 | 667 | -------------------------------------------------------------------------------- /kext/src/emptyfs_vfsops.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181117 lynnl 3 | */ 4 | 5 | #ifndef __EMPTYFS_VFSOPS_H 6 | #define __EMPTYFS_VFSOPS_H 7 | 8 | #include 9 | #include 10 | #include "utils.h" 11 | 12 | readonly_extern struct vfsops emptyfs_vfsops; 13 | 14 | /* The second largest 32-bit De Bruijn constant */ 15 | #define EMPTYFS_MNT_MAGIC 0x0fb9ac4a 16 | 17 | #define EMPTYFS_VOLNAME_MAXLEN 32 18 | 19 | struct emptyfs_mount { 20 | /* must be EMPTYFS_MNT_MAGIC */ 21 | uint32_t magic; 22 | /* backing pointer to the mount_t */ 23 | mount_t mp; 24 | /* debug mode passed from mount arguments */ 25 | uint32_t dbg_mode; 26 | /* raw dev_t of the device we're mounted on */ 27 | dev_t devid; 28 | /* backing device vnode of above; we use a refcnt. on it */ 29 | vnode_t devvp; 30 | /* volume name(UTF8 encoded) */ 31 | char volname[EMPTYFS_VOLNAME_MAXLEN]; 32 | /* pre-calculated volume attributes */ 33 | struct vfs_attr attr; 34 | 35 | /* mutex lock used to protect following fields */ 36 | lck_mtx_t *mtx_root; 37 | 38 | /* true if someone is attaching a root vnode */ 39 | uint8_t is_root_attaching; 40 | /* 41 | * true if someone is waiting for such an attach to complete 42 | */ 43 | uint8_t is_root_waiting; 44 | /* 45 | * the root vnode 46 | * we hold NO reference to this you must reconfirm its existence each time 47 | */ 48 | vnode_t rootvp; 49 | }; 50 | 51 | struct emptyfs_mount *emptyfs_mount_from_mp(mount_t); 52 | 53 | #endif /* __EMPTYFS_VFSOPS_H */ 54 | 55 | -------------------------------------------------------------------------------- /kext/src/emptyfs_vnops.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181117 lynnl 3 | */ 4 | 5 | /* 6 | * user*_addr_t definition used in 7 | * o.w. os <= 10.11(El Capitan) may unable to compile 8 | */ 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "emptyfs_vnops.h" 15 | #include "emptyfs_vfsops.h" 16 | 17 | /* 18 | * this variable will be set when we register VFS plugin via vfs_fsadd() 19 | * it holds a pointer to the array of vnop functions for this VFS plugin 20 | * we declare it early :. it's referenced by the code that creates the vnodes 21 | */ 22 | int (**emptyfs_vnop_p)(void *); 23 | 24 | #define VNOP_FUNC int (*)(void *) 25 | 26 | /* 27 | * Q: default vnode operation if a specific vnop not yet implemented? 28 | * i.e. a fallback generic operation if NYI 29 | * 30 | * NOTE: 31 | * you may use builtin #vn_default_error() 32 | * it merely return ENOTSUP like what we do 33 | * the KPI resides in com.apple.kpi.bsd 34 | */ 35 | static inline int emptyfs_vnop_default(struct vnop_generic_args *arg) 36 | { 37 | kassert_nonnull(arg); 38 | LOG_DBG("desc: %p", arg->a_desc); 39 | return ENOTSUP; 40 | } 41 | 42 | static int emptyfs_vnop_lookup(struct vnop_lookup_args *); 43 | static int emptyfs_vnop_open(struct vnop_open_args *); 44 | static int emptyfs_vnop_close(struct vnop_close_args *); 45 | static int emptyfs_vnop_getattr(struct vnop_getattr_args *); 46 | static int emptyfs_vnop_readdir(struct vnop_readdir_args *); 47 | static int emptyfs_vnop_reclaim(struct vnop_reclaim_args *); 48 | 49 | 50 | /* 51 | * describes all vnode operations supported by vnodes created by our VFS plugin 52 | */ 53 | static struct vnodeopv_entry_desc emptyfs_vnopv_entry_desc_list[] = { 54 | {&vnop_default_desc, (VNOP_FUNC) emptyfs_vnop_default}, 55 | {&vnop_lookup_desc, (VNOP_FUNC) emptyfs_vnop_lookup}, 56 | {&vnop_open_desc, (VNOP_FUNC) emptyfs_vnop_open}, 57 | {&vnop_close_desc, (VNOP_FUNC) emptyfs_vnop_close}, 58 | {&vnop_getattr_desc, (VNOP_FUNC) emptyfs_vnop_getattr}, 59 | {&vnop_readdir_desc, (VNOP_FUNC) emptyfs_vnop_readdir}, 60 | {&vnop_reclaim_desc, (VNOP_FUNC) emptyfs_vnop_reclaim}, 61 | {NULL, NULL}, 62 | }; 63 | 64 | /* 65 | * this stores our vnode operations array as an input and 66 | * stores a final vnode array(output) that's used to create vnodes 67 | * when register VFS plugin on success with vfs_fsadd() 68 | */ 69 | static struct vnodeopv_desc emptyfs_vnopv_desc = { 70 | &emptyfs_vnop_p, 71 | emptyfs_vnopv_entry_desc_list, 72 | }; 73 | 74 | /* 75 | * [sic] 76 | * this list is an array vnodeopv_desc that allows us to 77 | * register multiple vnode operations arrays at the same time 78 | * 79 | * a full-featured file system would use this to register different arrays for 80 | * standard vnodes, device vnodes(VBLK and VCHR), and FIFO vnodes(VFIFO), etc. 81 | * 82 | * we only support standard vnodes .: our array only has one entry 83 | */ 84 | struct vnodeopv_desc *emptyfs_vnopv_desc_list[__EFS_VNOPV_SZ] = { 85 | &emptyfs_vnopv_desc, 86 | }; 87 | 88 | /** 89 | * Check if a given vnode is valid in our filesystem 90 | * in this fs the only valid vnode is the root vnode 91 | * .: it's a trivial implementation 92 | */ 93 | static void assert_valid_vnode(vnode_t vp) 94 | { 95 | #ifdef DEBUG 96 | int valid; 97 | struct emptyfs_mount *mntp; 98 | 99 | kassert_nonnull(vp); 100 | 101 | mntp = emptyfs_mount_from_mp(vnode_mount(vp)); 102 | 103 | lck_mtx_lock(mntp->mtx_root); 104 | valid = (vp == mntp->rootvp); 105 | lck_mtx_unlock(mntp->mtx_root); 106 | 107 | kassertf(valid, "invalid vnode %p vid: %#x type: %d", 108 | vp, vnode_vid(vp), vnode_vtype(vp)); 109 | #else 110 | kassert_nonnull(vp); 111 | #endif 112 | } 113 | 114 | /** 115 | * Called by VFS to do a directory lookup 116 | * @desc (unused) identity which vnode operation(lookup in such case) 117 | * @dvp the directory to search 118 | * @vpp pointer to a vnode where we return the found item 119 | * the resulting vnode must have an io refcnt. 120 | * and the caller is responsible to RELEASE it 121 | * @cnp describes the name to search for more see 122 | * @ctx identity of the calling process 123 | * @return 0 if found errno o.w. 124 | * 125 | * see: 126 | * search 'macos dot underscore files' 127 | * https://www.dropboxforum.com/t5/Syncing-and-uploads/What-can-we-do-with-files/td-p/186881 128 | * https://apple.stackexchange.com/questions/14980/why-are-dot-underscore-files-created-and-how-can-i-avoid-them 129 | */ 130 | static int emptyfs_vnop_lookup(struct vnop_lookup_args *ap) 131 | { 132 | int e; 133 | struct vnodeop_desc *desc; 134 | vnode_t dvp; 135 | vnode_t *vpp; 136 | struct componentname *cnp; 137 | vfs_context_t ctx; 138 | vnode_t vp = NULL; 139 | 140 | kassert_nonnull(ap); 141 | desc = ap->a_desc; 142 | dvp = ap->a_dvp; 143 | vpp = ap->a_vpp; 144 | cnp = ap->a_cnp; 145 | ctx = ap->a_context; 146 | kassert_nonnull(desc); 147 | kassert_nonnull(dvp); 148 | kassertf(vnode_isdir(dvp), "vnop_lookup() dvp type: %d", vnode_vtype(dvp)); 149 | assert_valid_vnode(dvp); 150 | kassert_nonnull(vpp); 151 | kassert_nonnull(cnp); 152 | kassert_nonnull(ctx); 153 | 154 | LOG_DBG("desc: %p dvp: %p %#x vpp: %p %p cnp: %u %#x %s %s", 155 | desc, dvp, vnode_vid(dvp), vpp, *vpp, 156 | cnp->cn_nameiop, cnp->cn_flags, cnp->cn_pnbuf, cnp->cn_nameptr); 157 | 158 | /* Trivial implementation */ 159 | 160 | if (cnp->cn_flags & ISDOTDOT) { 161 | /* 162 | * Implement lookup for ".."(i.e. parent directory) 163 | * Currently this fs only has one vnode(i.e. the root vnode) 164 | * and parent of root vnode is always itself 165 | * it's equals to "." in such case 166 | * The implementation is trivial 167 | */ 168 | e = vnode_get(dvp); 169 | if (e == 0) vp = dvp; 170 | } else if (!strcmp(cnp->cn_nameptr, ".")) { 171 | /* Ditto */ 172 | e = vnode_get(dvp); 173 | if (e == 0) vp = dvp; 174 | } else { 175 | LOG_DBG("vnop_lookup() ENOENT op: %#x flags: %#x name: %s pn: %s", 176 | cnp->cn_nameiop, cnp->cn_flags, cnp->cn_nameptr, cnp->cn_pnbuf); 177 | e = ENOENT; 178 | } 179 | 180 | /* 181 | * under all circumstances we should update *vpp 182 | * .: we can maintain post-condition 183 | */ 184 | *vpp = vp; 185 | 186 | if (e == 0) { 187 | kassert_nonnull(*vpp); 188 | } else { 189 | kassert_null(*vpp); 190 | } 191 | 192 | return e; 193 | } 194 | 195 | /** 196 | * Called by VFS to open a file for access 197 | * @vp the vnode being opened 198 | * @mode open flags 199 | * @ctx identity of the calling process 200 | * @return always 0 201 | * 202 | * [sic] 203 | * this entry is rarely useful :. VFS can read a file vnode without ever open it 204 | * .: any work that you'd usually do here you have to do lazily in your 205 | * read/write entry points 206 | * 207 | * see: man open(2) 208 | */ 209 | static int emptyfs_vnop_open(struct vnop_open_args *ap) 210 | { 211 | struct vnodeop_desc *desc; 212 | vnode_t vp; 213 | int mode; 214 | vfs_context_t ctx; 215 | 216 | kassert_nonnull(ap); 217 | desc = ap->a_desc; 218 | vp = ap->a_vp; 219 | mode = ap->a_mode; 220 | ctx = ap->a_context; 221 | kassert_nonnull(desc); 222 | kassert(vnode_isdir(vp)); 223 | assert_valid_vnode(vp); 224 | /* NOTE: there seems too many open flags */ 225 | kassert_known_flags(mode, O_CLOEXEC | O_DIRECTORY | O_EVTONLY | 226 | O_NONBLOCK | O_APPEND | FREAD | FWRITE); 227 | kassert_nonnull(ctx); 228 | 229 | LOG_DBG("desc: %p vp: %p %#x mode: %#x", desc, vp, vnode_vid(vp), mode); 230 | 231 | /* Empty implementation */ 232 | 233 | return 0; 234 | } 235 | 236 | /** 237 | * Caller by VFS to close a file from access 238 | * 239 | * [sic] 240 | * this entry is not as useful as you might think :. a vnode can be accessed 241 | * after the last close(for example, it has been mmaped) 242 | * in most cases the work you might think do here 243 | * you end up doing in vnop_inactive 244 | */ 245 | static int emptyfs_vnop_close(struct vnop_close_args *ap) 246 | { 247 | struct vnodeop_desc *desc; 248 | vnode_t vp; 249 | int fflag; 250 | vfs_context_t ctx; 251 | 252 | kassert_nonnull(ap); 253 | desc = ap->a_desc; 254 | vp = ap->a_vp; 255 | fflag = ap->a_fflag; 256 | ctx = ap->a_context; 257 | kassert_nonnull(desc); 258 | kassert(vnode_isdir(vp)); 259 | assert_valid_vnode(vp); 260 | /* NOTE: there seems too many open flags */ 261 | kassert_known_flags(fflag, O_EVTONLY | O_NONBLOCK | O_APPEND | FREAD | FWRITE); 262 | kassert_nonnull(ctx); 263 | 264 | LOG_DBG("desc: %p vp: %p %#x fflag: %#x", desc, vp, vnode_vid(vp), fflag); 265 | 266 | /* Empty implementation */ 267 | 268 | return 0; 269 | } 270 | 271 | /* 272 | * Called by VFS to get attributes about a vnode 273 | * (i.e. backing support of stat getattrlist syscalls) 274 | * 275 | * @vp the vnode whose attributes to retrieve 276 | * @vap describes the attributes requested upon return 277 | * @ctx identity of the calling process 278 | * @return 0 if found errno o.w. 279 | * 280 | * [sic] XXX: 281 | * for attributes whose values readily available use VATTR_RETURN 282 | * macro to unilaterally return the value 283 | * 284 | * for attributes whose values hard to calculate use VATTR_IS_ACTIVE to see 285 | * if the caller requested that attribute 286 | * copy the value into the appropriate field if it's 287 | */ 288 | static int emptyfs_vnop_getattr(struct vnop_getattr_args *ap) 289 | { 290 | struct vnodeop_desc *desc; 291 | vnode_t vp; 292 | struct vnode_attr *vap; 293 | vfs_context_t ctx; 294 | struct emptyfs_mount *mntp; 295 | 296 | kassert_nonnull(ap); 297 | desc = ap->a_desc; 298 | vp = ap->a_vp; 299 | vap = ap->a_vap; 300 | ctx = ap->a_context; 301 | kassert_nonnull(desc); 302 | /* We have only one vnode(i.e. the root vnode) */ 303 | kassert(vnode_isdir(vp)); 304 | assert_valid_vnode(vp); 305 | kassert_nonnull(vap); 306 | kassert_nonnull(ctx); 307 | 308 | LOG_DBG("desc: %p vp: %p %#x va_active: %#llx va_supported: %#llx", 309 | desc, vp, vnode_vid(vp), vap->va_active, vap->va_supported); 310 | 311 | mntp = emptyfs_mount_from_mp(vnode_mount(vp)); 312 | 313 | /* Trivial implementation */ 314 | 315 | /* 316 | * [sic] 317 | * Implementation of stat(2) requires that we support va_rdev 318 | * even on vnodes that aren't device vnode 319 | */ 320 | VATTR_RETURN(vap, va_rdev, 0); 321 | VATTR_RETURN(vap, va_nlink, 2); 322 | VATTR_RETURN(vap, va_data_size, sizeof(struct dirent) << 1); 323 | 324 | /* umask 0555 */ 325 | VATTR_RETURN(vap, va_mode, 326 | S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH); 327 | VATTR_RETURN(vap, va_create_time, mntp->attr.f_create_time); 328 | VATTR_RETURN(vap, va_access_time, mntp->attr.f_access_time); 329 | VATTR_RETURN(vap, va_modify_time, mntp->attr.f_modify_time); 330 | VATTR_RETURN(vap, va_change_time, mntp->attr.f_modify_time); 331 | 332 | VATTR_RETURN(vap, va_fileid, 2); 333 | VATTR_RETURN(vap, va_fsid, mntp->devid); 334 | 335 | #if 0 336 | VATTR_RETURN(vap, va_type, XXX); /* Handled by VFS */ 337 | /* 338 | * Let VFS get from f_mntonname 339 | * see: emptyfs_vfsops.c#emptyfs_vfsop_mount() 340 | */ 341 | VATTR_RETURN(vap, va_name, XXX); 342 | #endif 343 | 344 | LOG_DBG("va_active: %#llx va_supported: %#llx", 345 | vap->va_active, vap->va_supported); 346 | 347 | return 0; 348 | } 349 | 350 | /** 351 | * Safe wrapper of uiomove() 352 | * @addr source address 353 | * @size source address buffer size 354 | * @uio "move" destination 355 | * @return 0 if success errno o.w. 356 | */ 357 | static int uiomove_atomic( 358 | void * __nonnull addr, 359 | size_t size, 360 | uio_t __nonnull uio) 361 | { 362 | int e; 363 | 364 | kassert_nonnull(addr); 365 | kassert_nonnull(uio); 366 | 367 | if (unlikely(size > INT_MAX)) { 368 | e = ERANGE; 369 | } else if (unlikely(size > (user_size_t) uio_resid(uio))) { 370 | e = ENOBUFS; 371 | } else { 372 | e = uiomove(addr, (int) size, uio); 373 | } 374 | 375 | if (e) { 376 | LOG_ERR("uiomove_atomic() fail size: %zu resid: %lld errno: %d", 377 | size, uio_resid(uio), e); 378 | } 379 | 380 | return e; 381 | } 382 | 383 | /* 384 | * Called by VFS to iterate contents of a directory 385 | * (i.e. backing support of getdirentries syscall) 386 | * 387 | * @vp the directory we're iterating 388 | * @uio destination information for resulting direntries 389 | * @flags iteration options 390 | * currently there're 4 options neither of which we support 391 | * needed if the file system is to be NFS exported 392 | * @eofflag return a flag to indicate if we reached the last directory entry 393 | * should be set to 1 if the end of the directory has been reached 394 | * @numdirent return a count of number of directory entries that we've read 395 | * should be set to number of entries written into buffer 396 | * @ctx identity of the calling process 397 | * 398 | * [sic] 399 | * The hardest thing to understand about this entry point is the UIO management 400 | * there are two tricky aspects 401 | * For more info you should check sample code func docs 402 | */ 403 | static int emptyfs_vnop_readdir(struct vnop_readdir_args *ap) 404 | { 405 | int e = 0; 406 | struct vnodeop_desc *desc; 407 | vnode_t vp; 408 | struct uio *uio; 409 | int flags; 410 | int *eofflag; 411 | int *numdirent; 412 | vfs_context_t ctx; 413 | 414 | int eof = 0; 415 | int num = 0; 416 | struct dirent di; 417 | off_t index; 418 | 419 | static int known_flags = VNODE_READDIR_EXTENDED | VNODE_READDIR_REQSEEKOFF | 420 | VNODE_READDIR_SEEKOFF32 | VNODE_READDIR_NAMEMAX; 421 | 422 | kassert_nonnull(ap); 423 | desc = ap->a_desc; 424 | vp = ap->a_vp; 425 | uio = ap->a_uio; 426 | flags = ap->a_flags; 427 | eofflag = ap->a_eofflag; 428 | numdirent = ap->a_numdirent; 429 | ctx = ap->a_context; 430 | kassert_nonnull(desc); 431 | kassert(vnode_isdir(vp)); 432 | assert_valid_vnode(vp); 433 | kassert_nonnull(uio); 434 | kassert_known_flags(flags, known_flags); 435 | /* eofflag and numdirent can be NULL */ 436 | kassert_nonnull(ctx); 437 | 438 | LOG_DBG("desc: %p vp: %p %#x flags: %#x uio: " 439 | "(isuserspace: %d resid: %lld " 440 | "iovcnt: %d offset: %lld " 441 | "curriovbase: %llu curriovlen: %llu)", 442 | desc, vp, vnode_vid(vp), flags, 443 | uio_isuserspace(uio), uio_resid(uio), 444 | uio_iovcnt(uio), uio_offset(uio), 445 | uio_curriovbase(uio), uio_curriovlen(uio)); 446 | 447 | /* Trivial implementation */ 448 | 449 | if (flags & known_flags) { 450 | /* only make sense if backing file system is NFS exported */ 451 | e = EINVAL; 452 | LOG_DBG("Met NFS-exported readdir flags %#x? errno: %d", 453 | flags & known_flags, e); 454 | goto out_exit; 455 | } 456 | 457 | di.d_fileno = 2; /* Q: why NOT set dynamically? */ 458 | di.d_reclen = sizeof(di); 459 | di.d_type = DT_DIR; 460 | 461 | kassert(uio_offset(uio) % 7 == 0); 462 | index = uio_offset(uio) / 7; 463 | 464 | if (index == 0) { 465 | di.d_namlen = (uint8_t) strlen("."); 466 | strlcpy(di.d_name, ".", sizeof(di.d_name)); 467 | e = uiomove_atomic(&di, sizeof(di), uio); 468 | if (e == 0) { 469 | num++; 470 | index++; 471 | } 472 | } 473 | 474 | if (e == 0 && index == 1) { 475 | di.d_namlen = (uint8_t) strlen(".."); 476 | strlcpy(di.d_name, "..", sizeof(di.d_name)); 477 | e = uiomove_atomic(&di, sizeof(di), uio); 478 | if (e == 0) { 479 | num++; 480 | index++; 481 | } 482 | } 483 | 484 | /* 485 | * If we failed :. there wasn't enough space in user space buffer 486 | * just swallow the error this will resulting getdirentries(2) returning 487 | * less than the buffer size(possibly even zero) 488 | * the caller is expected to cope with that 489 | */ 490 | if (e == ENOBUFS) { 491 | e = 0; 492 | } else if (e) { 493 | goto out_exit; 494 | } 495 | 496 | /* Update uio offset and set EOF flag */ 497 | uio_setoffset(uio, index * 7); 498 | eof = index > 1; 499 | 500 | /* Copy out any info requested by caller */ 501 | if (eofflag != NULL) *eofflag = eof; 502 | if (numdirent != NULL) *numdirent = num; 503 | 504 | LOG_DBG("eofflag: %p %d numdirent: %p %d", eofflag, eof, numdirent, num); 505 | 506 | out_exit: 507 | return e; 508 | } 509 | 510 | /* 511 | * High-level function to relacim a vnode 512 | */ 513 | static void detach_root_vnode(struct emptyfs_mount *mntp, vnode_t vp) 514 | { 515 | int e; 516 | 517 | kassert_nonnull(mntp); 518 | kassert_nonnull(vp); 519 | 520 | lck_mtx_lock(mntp->mtx_root); 521 | 522 | /* 523 | * [sic] 524 | * if `is_root_attaching' is set rootvp will and must be NULL 525 | * if in such case we just return 526 | * that's expected behaviour if the system tries to reclaim the vnode 527 | * while other thread is in process of attaching it 528 | */ 529 | if (mntp->is_root_attaching) { 530 | kassert_null(mntp->rootvp); 531 | } 532 | 533 | if (mntp->rootvp != NULL) { 534 | /* The only possible vnode is the root vnode */ 535 | kassert(mntp->rootvp == vp); 536 | 537 | e = vnode_removefsref(mntp->rootvp); 538 | kassertf(e == 0, "vnode_removefsref() fail errno: %d", e); 539 | 540 | mntp->rootvp = NULL; 541 | } else { 542 | /* Do nothing someone else beat this reclaim */ 543 | } 544 | 545 | lck_mtx_unlock(mntp->mtx_root); 546 | } 547 | 548 | /** 549 | * Called by VFS to disassociate a vnode from underlying fsnode 550 | * [sic] Release filesystem-internal resources for a vnode 551 | * 552 | * @vp the vnode to reclaim 553 | * @ctx identity of the calling process 554 | * @return always 0(the kernel will panic if fail) 555 | * 556 | * trivial reclaim implementation 557 | * it's NOT the point where for example you write the fsnode back to disk 558 | * rather you should do this in vnop_inactive entry point 559 | * in a proper file system this entry would have to coordinate 560 | * with fsnode hash layer 561 | */ 562 | static int emptyfs_vnop_reclaim(struct vnop_reclaim_args *ap) 563 | { 564 | struct vnodeop_desc *desc; 565 | vnode_t vp; 566 | vfs_context_t ctx; 567 | struct emptyfs_mount *mntp; 568 | 569 | kassert_nonnull(ap); 570 | desc = ap->a_desc; 571 | vp = ap->a_vp; 572 | ctx = ap->a_context; 573 | kassert_nonnull(desc); 574 | assert_valid_vnode(vp); 575 | kassert_nonnull(ctx); 576 | 577 | LOG_DBG("desc: %p vp: %p %#x", desc, vp, vnode_vid(vp)); 578 | 579 | /* do reclaim as if we have a fsnoe hash layer */ 580 | mntp = emptyfs_mount_from_mp(vnode_mount(vp)); 581 | detach_root_vnode(mntp, vp); 582 | 583 | return 0; 584 | } 585 | 586 | -------------------------------------------------------------------------------- /kext/src/emptyfs_vnops.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181117 lynnl 3 | */ 4 | 5 | #ifndef __EMPTYFS_VNOPS_H 6 | #define __EMPTYFS_VNOPS_H 7 | 8 | #include 9 | #include 10 | #include "utils.h" 11 | 12 | #define __EFS_VNOPV_SZ 1 /* used in this header only */ 13 | readonly_extern struct vnodeopv_desc *emptyfs_vnopv_desc_list[__EFS_VNOPV_SZ]; 14 | 15 | extern int (**emptyfs_vnop_p)(void *); 16 | 17 | #endif /* __EMPTYFS_VNOPS_H */ 18 | 19 | -------------------------------------------------------------------------------- /kext/src/utils.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181110 lynnl 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "utils.h" 11 | 12 | static void util_mstat(int opt) 13 | { 14 | static volatile SInt64 cnt = 0; 15 | switch (opt) { 16 | case 0: 17 | if (OSDecrementAtomic64(&cnt) > 0) return; 18 | break; 19 | case 1: 20 | if (OSIncrementAtomic64(&cnt) >= 0) return; 21 | break; 22 | default: 23 | if (cnt == 0) return; 24 | break; 25 | } 26 | #ifdef DEBUG 27 | panicf("FIXME: potential memleak opt: %d cnt: %lld", opt, cnt); 28 | #else 29 | LOG_BUG("FIXME: potential memleak opt: %d cnt: %lld", opt, cnt); 30 | #endif 31 | } 32 | 33 | void *util_malloc(size_t size, int flags) 34 | { 35 | /* _MALLOC `type' parameter is a joke */ 36 | void *addr = _MALLOC(size, M_TEMP, flags); 37 | if (likely(addr != NULL)) util_mstat(1); 38 | return addr; 39 | } 40 | 41 | /** 42 | * Poor replica of _REALLOC() in XNU 43 | * 44 | * /System/Library/Frameworks/Kernel.framework/Resources/SupportedKPIs-all-archs.txt 45 | * Listed all supported KPI as it revealed 46 | * _MALLOC and _FREE both supported where _REALLOC not exported by Apple 47 | * 48 | * @param addr0 Address needed to reallocation 49 | * @param sz0 Original size of the buffer 50 | * @param sz1 New size 51 | * @param flags Flags to malloc 52 | * @return Return NULL on fail O.w. new allocated buffer 53 | * 54 | * NOTE: 55 | * You should generally avoid allocate zero-length(new buffer size) 56 | * the behaviour is implementation-defined(_MALLOC return NULL in such case) 57 | * 58 | * See: 59 | * xnu/bsd/kern/kern_malloc.c@_REALLOC 60 | * wiki.sei.cmu.edu/confluence/display/c/MEM04-C.+Beware+of+zero-length+allocations 61 | */ 62 | static void *util_realloc2(void *addr0, size_t sz0, size_t sz1, int flags) 63 | { 64 | void *addr1; 65 | 66 | /* 67 | * [sic] _REALLOC(NULL, ...) is equivalent to _MALLOC(...) 68 | * XXX in such case, we require its original size must be zero 69 | */ 70 | if (addr0 == NULL) { 71 | kassert(sz0 == 0); 72 | addr1 = _MALLOC(sz1, M_TEMP, flags); 73 | goto out_exit; 74 | } 75 | 76 | if (unlikely(sz1 == sz0)) { 77 | addr1 = addr0; 78 | goto out_exit; 79 | } 80 | 81 | addr1 = _MALLOC(sz1, M_TEMP, flags); 82 | if (unlikely(addr1 == NULL)) 83 | goto out_exit; 84 | 85 | memcpy(addr1, addr0, MIN(sz0, sz1)); 86 | _FREE(addr0, M_TEMP); 87 | 88 | out_exit: 89 | return addr1; 90 | } 91 | 92 | void *util_realloc(void *addr0, size_t sz0, size_t sz1, int flags) 93 | { 94 | void *addr1 = util_realloc2(addr0, sz0, sz1, flags); 95 | /* 96 | * If addr0 is notnull yet addr1 null the reference shouldn't change 97 | * since addr0 won't be free in such case 98 | */ 99 | if (!addr0 && addr1) util_mstat(1); 100 | return addr1; 101 | } 102 | 103 | void util_mfree(void *addr) 104 | { 105 | if (addr != NULL) util_mstat(0); 106 | _FREE(addr, M_TEMP); 107 | } 108 | 109 | /* XXX: call when all memory freed */ 110 | void util_massert(void) 111 | { 112 | util_mstat(2); 113 | } 114 | 115 | /* 116 | * kcb stands for kernel callbacks a global refcnt used in kext 117 | */ 118 | static int kcb(int opt) 119 | { 120 | static volatile SInt i = 0; 121 | static struct timespec ts = {0, 1e+6}; /* 100ms */ 122 | SInt rd; 123 | 124 | switch (opt) { 125 | case 0: 126 | do { 127 | if ((rd = i) < 0) break; 128 | } while (!OSCompareAndSwap(rd, rd + 1, &i)); 129 | return rd; 130 | 131 | case 1: 132 | rd = OSDecrementAtomic(&i); 133 | kassert(rd > 0); 134 | return rd; 135 | 136 | case 2: 137 | do { 138 | while (i > 0) msleep((void *) &i, NULL, PWAIT, NULL, &ts); 139 | } while (!OSCompareAndSwap(0, (UInt32) -1, &i)); 140 | break; 141 | 142 | default: 143 | panicf("invalid option opt: %d", i); 144 | } 145 | 146 | return i; 147 | } 148 | 149 | /** 150 | * Increase refcnt of activated kext callbacks 151 | * @return refcnt before get -1 if failed to get(must check) 152 | */ 153 | int util_get_kcb(void) 154 | { 155 | return kcb(0); 156 | } 157 | 158 | /** 159 | * Decrease refcnt of activated kext callbacks 160 | * @return refcnt before put 161 | */ 162 | int util_put_kcb(void) 163 | { 164 | return kcb(1); 165 | } 166 | 167 | /** 168 | * Invalidate further kcb operations(should call only once) 169 | */ 170 | void util_invalidate_kcb(void) 171 | { 172 | (void) kcb(2); 173 | } 174 | 175 | #define UUID_STR_BUFSZ sizeof(uuid_string_t) 176 | 177 | /** 178 | * Extract UUID load command from a Mach-O address 179 | * 180 | * @addr Mach-O starting address 181 | * @output UUID string output 182 | * @return 0 if success errno o.w. 183 | */ 184 | int util_vma_uuid(const vm_address_t addr, uuid_string_t output) 185 | { 186 | int e = 0; 187 | uint8_t *p = (void *) addr; 188 | struct mach_header *h = (struct mach_header *) addr; 189 | struct load_command *lc; 190 | uint32_t i; 191 | uint8_t *u; 192 | 193 | kassert_nonnull(addr); 194 | 195 | if (h->magic == MH_MAGIC || h->magic == MH_CIGAM) { 196 | p += sizeof(struct mach_header); 197 | } else if (h->magic == MH_MAGIC_64 || h->magic == MH_CIGAM_64) { 198 | p += sizeof(struct mach_header_64); 199 | } else { 200 | e = EBADMACHO; 201 | goto out_bad; 202 | } 203 | 204 | for (i = 0; i < h->ncmds; i++, p += lc->cmdsize) { 205 | lc = (struct load_command *) p; 206 | if (lc->cmd == LC_UUID) { 207 | u = p + sizeof(*lc); 208 | 209 | (void) snprintf(output, UUID_STR_BUFSZ, 210 | "%02x%02x%02x%02x-%02x%02x-%02x%02x-" 211 | "%02x%02x-%02x%02x%02x%02x%02x%02x", 212 | u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], 213 | u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); 214 | goto out_bad; 215 | } 216 | } 217 | 218 | e = ENOENT; 219 | out_bad: 220 | return e; 221 | } 222 | 223 | void format_uuid_string(const uuid_t u, uuid_string_t output) 224 | { 225 | (void) 226 | snprintf(output, UUID_STR_BUFSZ, 227 | "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", 228 | u[0], u[1], u[2], u[3], u[4], u[5], u[6], u[7], 229 | u[8], u[9], u[10], u[11], u[12], u[13], u[14], u[15]); 230 | } 231 | 232 | -------------------------------------------------------------------------------- /kext/src/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181110 lynnl 3 | * 4 | * Help functions used in kernel extension 5 | */ 6 | 7 | #ifndef __EMPTYFS_UTILS_H 8 | #define __EMPTYFS_UTILS_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifndef __kext_makefile__ 18 | #define KEXTNAME_S "emptyfs" 19 | #endif 20 | 21 | /* 22 | * __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ is a compiler-predefined macro 23 | */ 24 | #define OS_VER_MIN_REQ __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ 25 | #ifndef __MAC_10_12 26 | #define __MAC_10_12 101200 27 | #endif 28 | 29 | /* 30 | * Used to indicate unused function parameters 31 | * see: #__unused 32 | */ 33 | #define UNUSED(arg0, ...) (void) ((void) arg0, ##__VA_ARGS__) 34 | 35 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) 36 | 37 | /* 38 | * literal-aware alias of extern modifier 39 | */ 40 | #define readonly_extern extern 41 | 42 | /* 43 | * G for GCC-specific 44 | * see: 45 | * #MIN, MAX 46 | * #min, max 47 | */ 48 | #define GMIN(a, b) ({ \ 49 | __typeof(a) _a = (a); \ 50 | __typeof(b) _b = (b); \ 51 | _a < _b ? _a : _b; \ 52 | }) 53 | 54 | #define GMAX(a, b) ({ \ 55 | __typeof(a) _a = (a); \ 56 | __typeof(b) _b = (b); \ 57 | _a > _b ? _a : _b; \ 58 | }) 59 | 60 | #define panicf(fmt, ...) \ 61 | panic("\n" fmt "\n%s@%s#L%d\n\n", \ 62 | ##__VA_ARGS__, __BASE_FILE__, __FUNCTION__, __LINE__) 63 | 64 | #ifdef DEBUG 65 | /* 66 | * NOTE: Do NOT use any multi-nary conditional/logical operator inside assertion 67 | * like operators && || ?: it's extremely EVIL 68 | * Separate them each statement per line 69 | */ 70 | #define kassert(ex) (ex) ? (void) 0 : panicf("Assert `%s' failed", #ex) 71 | /** 72 | * @ex the expression 73 | * @fmt panic message format 74 | * 75 | * Example: kassertf(sz > 0, "Why size %zd nonpositive?", sz); 76 | */ 77 | #define kassertf(ex, fmt, ...) \ 78 | (ex) ? (void) 0 : panicf("Assert `%s' failed: " fmt, #ex, ##__VA_ARGS__) 79 | #else 80 | #define kassert(ex) (void) ((void) (ex)) 81 | #define kassertf(ex, fmt, ...) (void) ((void) (ex), ##__VA_ARGS__) 82 | #endif 83 | 84 | #define kassert_null(ptr) kassert(((void *) ptr) == NULL) 85 | #define kassert_nonnull(ptr) kassert(((void *) ptr) != NULL) 86 | 87 | /** 88 | * Branch predictions 89 | * see: linux/include/linux/compiler.h 90 | */ 91 | #define likely(x) __builtin_expect(!!(x), 1) 92 | #define unlikely(x) __builtin_expect(!!(x), 0) 93 | 94 | /** 95 | * os_log() is only available on macOS 10.12 or newer 96 | * thus os_log do have compatibility issue use printf instead 97 | * 98 | * XNU kernel version of printf() don't recognize some rarely used specifiers 99 | * like h, i, j, t use unrecognized spcifier may raise kernel panic 100 | * 101 | * Feel free to print NULL as %s it checked explicitly by kernel-printf 102 | * 103 | * see: xnu/osfmk/kern/printf.c#printf 104 | */ 105 | #define LOG(fmt, ...) printf(KEXTNAME_S ": " fmt "\n", ##__VA_ARGS__) 106 | 107 | #define LOG_INF(fmt, ...) LOG("[INF] " fmt, ##__VA_ARGS__) 108 | #define LOG_WAR(fmt, ...) LOG("[WAR] " fmt, ##__VA_ARGS__) 109 | #define LOG_ERR(fmt, ...) LOG("[ERR] " fmt, ##__VA_ARGS__) 110 | #define LOG_BUG(fmt, ...) LOG("[BUG] " fmt, ##__VA_ARGS__) 111 | #define LOG_OFF(fmt, ...) (void) ((void) 0, ##__VA_ARGS__) 112 | #ifdef DEBUG 113 | #define LOG_DBG(fmt, ...) LOG("[DBG %s#L%d] " fmt, __func__, __LINE__, ##__VA_ARGS__) 114 | #else 115 | #define LOG_DBG(fmt, ...) LOG_OFF(fmt, ##__VA_ARGS__) 116 | #endif 117 | 118 | #ifdef DEBUG 119 | /** 120 | * Assert over a known flags 121 | * @param flags the flags need to assert 122 | * @param known the known flags 123 | * If met any unknown flags it'll print a warning message instead of panic 124 | */ 125 | #define kassert_known_flags(flags, known) do { \ 126 | uint64_t __a = (flags); \ 127 | uint64_t __b = (known); \ 128 | uint64_t __c = __a & ~__b; \ 129 | if (__c) { \ 130 | LOG("[WAR %s#L%d] Met unknown flags %#llx (%#llx vs %#llx)", \ 131 | __func__, __LINE__, __c, __a, __b); \ 132 | } \ 133 | } while (0) 134 | #else 135 | #define kassert_known_flags(flags, known) UNUSED(flags, known) 136 | #endif 137 | 138 | /* 139 | * Capital Q stands for quick 140 | * XXX: should only used for `char[]' NEVER `char *' 141 | */ 142 | #define QSTRLEN(s) (sizeof(s) - 1) 143 | 144 | void *util_malloc(size_t, int); 145 | void *util_realloc(void *, size_t, size_t, int); 146 | void util_mfree(void *); 147 | void util_massert(void); 148 | 149 | int util_get_kcb(void); 150 | int util_put_kcb(void); 151 | int util_read_kcb(void); 152 | void util_invalidate_kcb(void); 153 | 154 | int util_vma_uuid(const vm_address_t, uuid_string_t); 155 | 156 | void format_uuid_string(const uuid_t, uuid_string_t); 157 | 158 | /** 159 | * kern_os_* family provides zero-out memory allocation 160 | * see: xnu/libkern/c++/OSRuntime.cpp 161 | */ 162 | extern void *kern_os_malloc(size_t); 163 | extern void kern_os_free(void *); 164 | extern void *kern_os_realloc(void *, size_t); 165 | 166 | #endif /* __EMPTYFS_UTILS_H */ 167 | 168 | -------------------------------------------------------------------------------- /mount_emptyfs/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Makefile for mount_emptyfs 3 | # 4 | 5 | CC=gcc 6 | CFLAGS=-std=c99 -Wall -Wextra 7 | SOURCES=$(wildcard *.c) 8 | EXECUTABLE=mount_emptyfs 9 | RM=rm 10 | 11 | all: debug 12 | 13 | release: $(EXECUTABLE) 14 | 15 | debug: CFLAGS += -g -DDEBUG 16 | debug: release 17 | 18 | $(EXECUTABLE): $(SOURCES) 19 | $(CC) $(CFLAGS) $< -o $@ 20 | 21 | clean: 22 | $(RM) -rf *.o $(EXECUTABLE) *.dSYM 23 | 24 | .PHONY: all debug release clean 25 | 26 | -------------------------------------------------------------------------------- /mount_emptyfs/emptyfs_mnt_args.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181208 3 | */ 4 | #ifndef __EMPTYFS_MNT_ARGS_H 5 | #define __EMPTYFS_MNT_ARGS_H 6 | 7 | #define EMPTYFS_NAME "emptyfs" 8 | 9 | /* The largest 32-bit De Bruijn constant */ 10 | #define EMPTYFS_MNTARG_MAGIC 0x0fb9ac52 11 | 12 | struct emptyfs_mnt_args { 13 | #ifndef KERNEL 14 | /* block special device to mount example: /dev/disk0s1 */ 15 | const char *fspec; 16 | #endif 17 | uint32_t magic; /* must be EMPTYFS_MNTARG_MAGIC */ 18 | uint32_t dbg_mode; /* enable debug for verbose output */ 19 | uint32_t force_fail; /* if non-zero mount(2) will always fail */ 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /mount_emptyfs/mount_emptyfs.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Created 181208 3 | */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include "emptyfs_mnt_args.h" 16 | 17 | #define MOUNT_EMPTYFS_VERSION "0.1" 18 | 19 | #define LOG(fmt, ...) printf(EMPTYFS_NAME ": " fmt "\n", ##__VA_ARGS__) 20 | #ifdef DEBUG 21 | #define LOG_DBG(fmt, ...) LOG("[DBG] " fmt, ##__VA_ARGS__) 22 | #else 23 | #define LOG_DBG(fmt, ...) (void) (0, ##__VA_ARGS__) 24 | #endif 25 | #define LOG_ERR(fmt, ...) LOG("[ERR] " fmt, ##__VA_ARGS__) 26 | 27 | #define ASSERT_NONNULL(p) assert(p != NULL) 28 | 29 | static __dead2 void usage(char * __nonnull argv0) 30 | { 31 | ASSERT_NONNULL(argv0); 32 | fprintf(stderr, 33 | "usage:\n\t" 34 | "%s [-d | -f] specrdev fsnode\n\t" 35 | "%s -v\n\n\t" 36 | "-d, --debug-mode mount in debug mode(verbose output)\n\t" 37 | "-f, --force-fail force mount failure\n\t" 38 | "-v, --version print version\n\t" 39 | "-h, --help print this help\n\t" 40 | "specrdev special raw device\n\t" 41 | "fsnode file-system node\n\n", 42 | basename(argv0), basename(argv0)); 43 | exit(1); 44 | } 45 | 46 | static __dead2 void version(char * __nonnull argv0) 47 | { 48 | ASSERT_NONNULL(argv0); 49 | fprintf(stderr, 50 | "%s version %s\n" 51 | "built date %s %s\n" 52 | "built with Apple LLVM version %s\n\n", 53 | basename(argv0), MOUNT_EMPTYFS_VERSION, 54 | __DATE__, __TIME__, 55 | __clang_version__); 56 | exit(0); 57 | } 58 | 59 | static int do_mount( 60 | const char * __nonnull fspec, 61 | const char * __nonnull mp, 62 | uint32_t dbg_mode, 63 | uint32_t force_fail) 64 | { 65 | int e; 66 | struct emptyfs_mnt_args mnt_args; 67 | char realmp[MAXPATHLEN]; 68 | 69 | ASSERT_NONNULL(fspec); 70 | ASSERT_NONNULL(mp); 71 | 72 | /* 73 | * [sic] 74 | * We have to canonicalise the mount point :. o.w. 75 | * umount(8) cannot unmount it by name 76 | */ 77 | if (realpath(mp, realmp) == NULL) { 78 | e = -1; 79 | LOG_ERR("realpath(3) fail fspec: %s mp: %s errno: %d", 80 | fspec, mp, errno); 81 | goto out_exit; 82 | } 83 | 84 | #ifndef KERNEL 85 | mnt_args.fspec = fspec; 86 | #endif 87 | mnt_args.magic = EMPTYFS_MNTARG_MAGIC; 88 | mnt_args.dbg_mode = dbg_mode; 89 | mnt_args.force_fail = force_fail; 90 | 91 | e = mount(EMPTYFS_NAME, realmp, 0, &mnt_args); 92 | if (e == -1) { 93 | LOG_ERR("mount(2) fail fspec: %s mp: %s errno: %d", 94 | fspec, realmp, errno); 95 | } 96 | 97 | out_exit: 98 | return e; 99 | } 100 | 101 | int main(int argc, char *argv[]) 102 | { 103 | int ch; 104 | int idx; 105 | int dbg_mode = 0; 106 | int force_fail = 0; 107 | struct option opt[] = { 108 | {"debug-mode", no_argument, &dbg_mode, 1}, 109 | {"force-fail", no_argument, &force_fail, 1}, 110 | {"version", no_argument, NULL, 'v'}, 111 | {"help", no_argument, NULL, 'h'}, 112 | {NULL, no_argument, NULL, 0}, 113 | }; 114 | char *fspec; 115 | char *mp; 116 | 117 | while ((ch = getopt_long(argc, argv, "dfvh", opt, &idx)) != -1) { 118 | switch (ch) { 119 | case 'd': 120 | dbg_mode = 1; 121 | break; 122 | case 'f': 123 | force_fail = 1; 124 | break; 125 | case 'v': 126 | version(argv[0]); 127 | case 'h': 128 | case '?': 129 | default: 130 | usage(argv[0]); 131 | } 132 | } 133 | 134 | if (argc - optind != 2) usage(argv[0]); 135 | fspec = argv[optind]; 136 | mp = argv[optind+1]; 137 | 138 | LOG_DBG("dbg_mode: %d force_fail: %d fspec: %s mp: %s", 139 | dbg_mode, force_fail, fspec, mp); 140 | 141 | return do_mount(fspec, mp, dbg_mode, force_fail); 142 | } 143 | 144 | --------------------------------------------------------------------------------