├── .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 |
--------------------------------------------------------------------------------