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
21struct 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
32struct 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
41static const file_ops_t tmpfs_noop_file_ops = { .open: 0 };
42
43extern const inode_ops_t tmpfs_inode_dir_ops;
44extern const inode_ops_t tmpfs_inode_symlink_ops;
45extern const inode_cache_ops_t tmpfs_inode_cache_ops;
46extern const file_ops_t tmpfs_file_ops;
47extern const superblock_ops_t tmpfs_sb_op;
48
49static PtrResult<dentry_t> tmpfs_fsop_mount(filesystem_t *fs, const char *dev, const char *options); // forward declaration
50FILESYSTEM_DEFINE(fs_tmpfs, "tmpfs", tmpfs_fsop_mount, NULL);
51FILESYSTEM_AUTOREGISTER(fs_tmpfs);
52
53inode_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
100static const file_perm_t tmpfs_default_mode = PERM_READ | PERM_WRITE | PERM_EXEC; // rwxrwxrwx
101
102static 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
126static 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
134static 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
139static 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
148static 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
160static bool tmpfs_i_unlink(inode_t *dir, dentry_t *dentry)
161{
162 MOS_UNUSED(dir);
163 MOS_UNUSED(dentry);
164 return true;
165}
166
167static 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
172static 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
186static 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
191static 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
200const 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
212static 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
220static 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
232static 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
249const inode_ops_t tmpfs_inode_symlink_ops = {
250 .readlink = tmpfs_i_readlink,
251};
252
253const file_ops_t tmpfs_file_ops = {
254 .read = vfs_generic_read,
255 .write = vfs_generic_write,
256};
257
258const 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
264const superblock_ops_t tmpfs_sb_op = {
265 .drop_inode = tmpfs_sb_drop_inode,
266};
267