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