1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "mos/filesystem/vfs_types.hpp" |
4 | #include "mos/filesystem/vfs_utils.hpp" |
5 | #include "mos/mm/mm.hpp" |
6 | #include "mos/mm/physical/pmm.hpp" |
7 | |
8 | #include <algorithm> |
9 | #include <mos/allocator.hpp> |
10 | #include <mos/filesystem/dentry.hpp> |
11 | #include <mos/filesystem/fs_types.h> |
12 | #include <mos/filesystem/vfs.hpp> |
13 | #include <mos/lib/structures/list.hpp> |
14 | #include <mos/misc/setup.hpp> |
15 | #include <mos/mos_global.h> |
16 | #include <mos/syslog/printk.hpp> |
17 | #include <mos/types.hpp> |
18 | #include <mos_stdlib.hpp> |
19 | #include <mos_string.hpp> |
20 | |
21 | struct tmpfs_inode_t : mos::NamedType<"TmpFS.Inode" > |
22 | { |
23 | inode_t real_inode; |
24 | |
25 | union |
26 | { |
27 | char *symlink_target; |
28 | dev_t dev; |
29 | }; |
30 | }; |
31 | |
32 | struct tmpfs_sb_t : mos::NamedType<"TmpFS.Superblock" > |
33 | { |
34 | superblock_t sb; |
35 | atomic_t ino; |
36 | }; |
37 | |
38 | #define TMPFS_INODE(inode) container_of(inode, tmpfs_inode_t, real_inode) |
39 | #define TMPFS_SB(var) container_of(var, tmpfs_sb_t, sb) |
40 | |
41 | static const file_ops_t tmpfs_noop_file_ops = { .open: 0 }; |
42 | |
43 | extern const inode_ops_t tmpfs_inode_dir_ops; |
44 | extern const inode_ops_t tmpfs_inode_symlink_ops; |
45 | extern const inode_cache_ops_t tmpfs_inode_cache_ops; |
46 | extern const file_ops_t tmpfs_file_ops; |
47 | extern const superblock_ops_t tmpfs_sb_op; |
48 | |
49 | static PtrResult<dentry_t> tmpfs_fsop_mount(filesystem_t *fs, const char *dev, const char *options); // forward declaration |
50 | FILESYSTEM_DEFINE(fs_tmpfs, "tmpfs" , tmpfs_fsop_mount, NULL); |
51 | FILESYSTEM_AUTOREGISTER(fs_tmpfs); |
52 | |
53 | inode_t *tmpfs_create_inode(tmpfs_sb_t *sb, file_type_t type, file_perm_t perm) |
54 | { |
55 | tmpfs_inode_t *inode = mos::create<tmpfs_inode_t>(); |
56 | |
57 | inode_init(inode: &inode->real_inode, sb: &sb->sb, ino: ++sb->ino, type); |
58 | inode->real_inode.perm = perm; |
59 | inode->real_inode.cache.ops = &tmpfs_inode_cache_ops; |
60 | |
61 | switch (type) |
62 | { |
63 | case FILE_TYPE_DIRECTORY: |
64 | // directories |
65 | pr_dinfo2(tmpfs, "tmpfs: creating a directory inode" ); |
66 | inode->real_inode.ops = &tmpfs_inode_dir_ops; |
67 | inode->real_inode.file_ops = &tmpfs_noop_file_ops; |
68 | break; |
69 | |
70 | case FILE_TYPE_SYMLINK: |
71 | // symbolic links |
72 | pr_dinfo2(tmpfs, "tmpfs: creating a symlink inode" ); |
73 | inode->real_inode.ops = &tmpfs_inode_symlink_ops; |
74 | inode->real_inode.file_ops = &tmpfs_noop_file_ops; |
75 | break; |
76 | |
77 | case FILE_TYPE_REGULAR: |
78 | // regular files, char devices, block devices, named pipes and sockets |
79 | pr_dinfo2(tmpfs, "tmpfs: creating a file inode" ); |
80 | inode->real_inode.file_ops = &tmpfs_file_ops; |
81 | break; |
82 | |
83 | case FILE_TYPE_CHAR_DEVICE: |
84 | case FILE_TYPE_BLOCK_DEVICE: |
85 | case FILE_TYPE_NAMED_PIPE: |
86 | case FILE_TYPE_SOCKET: |
87 | mos_warn("TODO: tmpfs: create inode for file type %d" , type); |
88 | mos_panic("tmpfs: unsupported file type" ); |
89 | break; |
90 | |
91 | case FILE_TYPE_UNKNOWN: |
92 | // unknown file type |
93 | mos_panic("tmpfs: unknown file type" ); |
94 | break; |
95 | } |
96 | |
97 | return &inode->real_inode; |
98 | } |
99 | |
100 | static const file_perm_t tmpfs_default_mode = PERM_READ | PERM_WRITE | PERM_EXEC; // rwxrwxrwx |
101 | |
102 | static PtrResult<dentry_t> tmpfs_fsop_mount(filesystem_t *fs, const char *dev, const char *options) |
103 | { |
104 | MOS_ASSERT(fs == &fs_tmpfs); |
105 | if (strcmp(str1: dev, str2: "none" ) != 0) |
106 | { |
107 | mos_warn("tmpfs: device not supported" ); |
108 | return -EINVAL; |
109 | } |
110 | |
111 | if (options && strlen(str: options) != 0 && strcmp(str1: options, str2: "defaults" ) != 0) |
112 | { |
113 | mos_warn("tmpfs: options '%s' not supported" , options); |
114 | return -EINVAL; |
115 | } |
116 | |
117 | tmpfs_sb_t *tmpfs_sb = mos::create<tmpfs_sb_t>(); |
118 | tmpfs_sb->sb.fs = fs; |
119 | tmpfs_sb->sb.ops = &tmpfs_sb_op; |
120 | tmpfs_sb->sb.root = dentry_get_from_parent(sb: &tmpfs_sb->sb, NULL, name: "" ); |
121 | dentry_attach(d: tmpfs_sb->sb.root, inode: tmpfs_create_inode(sb: tmpfs_sb, type: FILE_TYPE_DIRECTORY, perm: tmpfs_default_mode)); |
122 | return tmpfs_sb->sb.root; |
123 | } |
124 | |
125 | // create a new node in the directory |
126 | static bool tmpfs_mknod_impl(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm, dev_t dev) |
127 | { |
128 | inode_t *inode = tmpfs_create_inode(TMPFS_SB(dir->superblock), type, perm); |
129 | TMPFS_INODE(inode)->dev = dev; |
130 | dentry_attach(d: dentry, inode); |
131 | return true; |
132 | } |
133 | |
134 | static bool tmpfs_i_create(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm) |
135 | { |
136 | return tmpfs_mknod_impl(dir, dentry, type, perm, dev: 0); |
137 | } |
138 | |
139 | static bool tmpfs_i_hardlink(dentry_t *old_dentry, inode_t *dir, dentry_t *new_dentry) |
140 | { |
141 | MOS_UNUSED(dir); |
142 | MOS_ASSERT_X(old_dentry->inode->type != FILE_TYPE_DIRECTORY, "hard links to directories are insane" ); |
143 | old_dentry->inode->nlinks++; |
144 | dentry_attach(d: new_dentry, inode: old_dentry->inode); |
145 | return true; |
146 | } |
147 | |
148 | static bool tmpfs_i_symlink(inode_t *dir, dentry_t *dentry, const char *symname) |
149 | { |
150 | bool created = tmpfs_mknod_impl(dir, dentry, type: FILE_TYPE_SYMLINK, perm: tmpfs_default_mode, dev: 0); |
151 | if (created) |
152 | { |
153 | tmpfs_inode_t *inode = TMPFS_INODE(dentry->inode); |
154 | inode->symlink_target = strdup(src: symname); |
155 | } |
156 | |
157 | return created; |
158 | } |
159 | |
160 | static bool tmpfs_i_unlink(inode_t *dir, dentry_t *dentry) |
161 | { |
162 | MOS_UNUSED(dir); |
163 | MOS_UNUSED(dentry); |
164 | return true; |
165 | } |
166 | |
167 | static bool tmpfs_i_mkdir(inode_t *dir, dentry_t *dentry, file_perm_t perm) |
168 | { |
169 | return tmpfs_mknod_impl(dir, dentry, type: FILE_TYPE_DIRECTORY, perm, dev: 0); |
170 | } |
171 | |
172 | static bool tmpfs_i_rmdir(inode_t *dir, dentry_t *subdir_to_remove) |
173 | { |
174 | // VFS will ensure that the directory is empty |
175 | MOS_UNUSED(dir); |
176 | MOS_ASSERT(subdir_to_remove->inode->type == FILE_TYPE_DIRECTORY); |
177 | MOS_ASSERT(subdir_to_remove->inode->nlinks == 1); // should be the only link to the directory |
178 | |
179 | dentry_detach(dentry: subdir_to_remove); |
180 | |
181 | tmpfs_inode_t *inode = TMPFS_INODE(subdir_to_remove->inode); |
182 | delete inode; |
183 | return true; |
184 | } |
185 | |
186 | static bool tmpfs_i_mknod(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm, dev_t dev) |
187 | { |
188 | return tmpfs_mknod_impl(dir, dentry, type, perm, dev); |
189 | } |
190 | |
191 | static bool tmpfs_i_rename(inode_t *old_dir, dentry_t *old_dentry, inode_t *new_dir, dentry_t *new_dentry) |
192 | { |
193 | MOS_UNUSED(old_dir); |
194 | MOS_UNUSED(new_dir); |
195 | dentry_attach(d: new_dentry, inode: old_dentry->inode); |
196 | dentry_detach(dentry: old_dentry); |
197 | return true; |
198 | } |
199 | |
200 | const inode_ops_t tmpfs_inode_dir_ops = { |
201 | .hardlink = tmpfs_i_hardlink, |
202 | .lookup = NULL, // use kernel's default in-memory lookup |
203 | .mkdir = tmpfs_i_mkdir, |
204 | .mknode = tmpfs_i_mknod, |
205 | .newfile = tmpfs_i_create, |
206 | .rename = tmpfs_i_rename, |
207 | .rmdir = tmpfs_i_rmdir, |
208 | .symlink = tmpfs_i_symlink, |
209 | .unlink = tmpfs_i_unlink, |
210 | }; |
211 | |
212 | static size_t tmpfs_i_readlink(dentry_t *dentry, char *buffer, size_t buflen) |
213 | { |
214 | tmpfs_inode_t *inode = TMPFS_INODE(dentry->inode); |
215 | const size_t bytes_to_copy = std::min(a: buflen, b: strlen(str: inode->symlink_target)); |
216 | memcpy(dest: buffer, src: inode->symlink_target, n: bytes_to_copy); |
217 | return bytes_to_copy; |
218 | } |
219 | |
220 | static PtrResult<phyframe_t> tmpfs_fill_cache(inode_cache_t *cache, uint64_t pgoff) |
221 | { |
222 | // if VFS ever calls this function, it means that a file has been |
223 | // written to a new page, but that page has not been allocated yet |
224 | // (i.e. the file has been extended) |
225 | // we don't need to do anything but allocate the page |
226 | MOS_UNUSED(cache); |
227 | MOS_UNUSED(pgoff); |
228 | |
229 | return pmm_ref_one(mm_get_free_page()); |
230 | } |
231 | |
232 | static bool tmpfs_sb_drop_inode(inode_t *inode) |
233 | { |
234 | tmpfs_inode_t *tmpfs_inode = TMPFS_INODE(inode); |
235 | if (inode->nlinks == 0) |
236 | { |
237 | if (inode->type == FILE_TYPE_DIRECTORY) |
238 | return false; |
239 | |
240 | if (inode->type == FILE_TYPE_SYMLINK) |
241 | if (tmpfs_inode->symlink_target != NULL) |
242 | kfree(ptr: tmpfs_inode->symlink_target); |
243 | delete tmpfs_inode; |
244 | } |
245 | |
246 | return true; |
247 | } |
248 | |
249 | const inode_ops_t tmpfs_inode_symlink_ops = { |
250 | .readlink = tmpfs_i_readlink, |
251 | }; |
252 | |
253 | const file_ops_t tmpfs_file_ops = { |
254 | .read = vfs_generic_read, |
255 | .write = vfs_generic_write, |
256 | }; |
257 | |
258 | const inode_cache_ops_t tmpfs_inode_cache_ops = { |
259 | .fill_cache = tmpfs_fill_cache, |
260 | .page_write_begin = simple_page_write_begin, |
261 | .page_write_end = simple_page_write_end, |
262 | }; |
263 | |
264 | const superblock_ops_t tmpfs_sb_op = { |
265 | .drop_inode = tmpfs_sb_drop_inode, |
266 | }; |
267 | |