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
20typedef 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
31typedef 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
40static const file_ops_t tmpfs_noop_file_ops = { 0 };
41
42static const inode_ops_t tmpfs_inode_dir_ops;
43static const inode_ops_t tmpfs_inode_symlink_ops;
44static const inode_cache_ops_t tmpfs_inode_cache_ops;
45static const file_ops_t tmpfs_file_ops;
46static const superblock_ops_t tmpfs_sb_op;
47
48static slab_t *tmpfs_inode_cache = NULL;
49SLAB_AUTOINIT("tmpfs_i", tmpfs_inode_cache, tmpfs_inode_t);
50
51static slab_t *tmpfs_superblock_cache = NULL;
52SLAB_AUTOINIT("tmpfs_sb", tmpfs_superblock_cache, tmpfs_sb_t);
53
54static dentry_t *tmpfs_fsop_mount(filesystem_t *fs, const char *dev, const char *options); // forward declaration
55FILESYSTEM_DEFINE(fs_tmpfs, "tmpfs", tmpfs_fsop_mount, NULL);
56FILESYSTEM_AUTOREGISTER(fs_tmpfs);
57
58inode_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
105static const file_perm_t tmpfs_default_mode = PERM_READ | PERM_WRITE | PERM_EXEC; // rwxrwxrwx
106
107static 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
131static 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
139static 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
144static 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
153static 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
165static bool tmpfs_i_unlink(inode_t *dir, dentry_t *dentry)
166{
167 MOS_UNUSED(dir);
168 MOS_UNUSED(dentry);
169 return true;
170}
171
172static 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
177static 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
191static 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
196static 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
205static 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
217static 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
225static 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
237static 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
254static const inode_ops_t tmpfs_inode_symlink_ops = {
255 .readlink = tmpfs_i_readlink,
256};
257
258static const file_ops_t tmpfs_file_ops = {
259 .read = vfs_generic_read,
260 .write = vfs_generic_write,
261};
262
263static 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
269static const superblock_ops_t tmpfs_sb_op = {
270 .drop_inode = tmpfs_sb_drop_inode,
271};
272