1#include <compiler.h>
2#include <console/panic.h>
3#include <console/printf.h>
4#include <fs/tmpfs.h>
5#include <fs/vfs.h>
6#include <mem/alloc.h>
7#include <mem/page.h>
8#include <stdbool.h>
9#include <stdint.h>
10#include <string.h>
11
12struct vfs_node *tmpfs_create_vfs_node(struct tmpfs_node *tnode);
13
14struct vfs_node *tmpfs_mkroot(const char *mount_point) {
15 struct tmpfs_fs *fs = kmalloc(sizeof(struct tmpfs_fs), ALLOC_FLAGS_ZERO);
16
17 struct tmpfs_node *root =
18 kmalloc(sizeof(struct tmpfs_node), ALLOC_FLAGS_ZERO);
19 if (!fs || !root)
20 return false;
21
22 root->type = TMPFS_DIR;
23 root->name = strdup(str: mount_point);
24 root->mode = 0755;
25
26 fs->root = root;
27
28 struct vfs_node *vnode = tmpfs_create_vfs_node(tnode: root);
29 return vnode;
30}
31
32static uint16_t tmpfs_to_vfs_mode(enum tmpfs_type mode) {
33 switch (mode) {
34 case TMPFS_DIR: return VFS_MODE_DIR;
35 case TMPFS_FILE: return VFS_MODE_FILE;
36 case TMPFS_SYMLINK: return VFS_MODE_SYMLINK;
37 }
38 return -1;
39}
40
41static enum errno tmpfs_mount(struct vfs_node *mountpoint, struct vfs_node *out,
42 const char *name) {
43 (void) mountpoint, (void) out, (void) name;
44 return ERR_NOT_IMPL;
45}
46
47static enum errno tmpfs_read(struct vfs_node *node, void *buf, uint64_t size,
48 uint64_t offset) {
49 struct tmpfs_node *tn = node->fs_node_data;
50
51 if (tn->type != TMPFS_FILE)
52 return ERR_IS_DIR;
53
54 if (offset >= tn->size)
55 return 0;
56
57 if (offset + size > tn->size)
58 size = tn->size - offset;
59
60 uint8_t *out = buf;
61 while (size > 0) {
62 uint64_t page_idx = offset / PAGE_SIZE;
63 uint64_t page_offset = offset & TMPFS_PAGE_MASK;
64 uint64_t to_copy = PAGE_SIZE - page_offset;
65 if (to_copy > size)
66 to_copy = size;
67
68 if (page_idx >= tn->num_pages || !tn->pages[page_idx]) {
69 memset(out, 0, to_copy);
70 } else {
71 memcpy(out, (uint8_t *) tn->pages[page_idx] + page_offset, to_copy);
72 }
73
74 offset += to_copy;
75 out += to_copy;
76 size -= to_copy;
77 }
78
79 return ERR_OK;
80}
81
82static enum errno realloc_page_array(struct tmpfs_node *tn,
83 size_t required_pages) {
84 if (required_pages > tn->num_pages) {
85 void **new_pages = krealloc(tn->pages, required_pages * sizeof(void *));
86 if (!new_pages)
87 return ERR_NO_MEM;
88 memset(new_pages + tn->num_pages, 0,
89 (required_pages - tn->num_pages) * sizeof(void *));
90 tn->pages = new_pages;
91 tn->num_pages = required_pages;
92 }
93 return ERR_OK;
94}
95
96static enum errno tmpfs_write(struct vfs_node *node, const void *buf,
97 uint64_t size, uint64_t offset) {
98 struct tmpfs_node *tn = node->fs_node_data;
99 if (tn->type != TMPFS_FILE)
100 return ERR_IS_DIR;
101
102 uint64_t end = offset + size;
103 size_t required_pages = (end + PAGE_SIZE - 1) / PAGE_SIZE;
104
105 enum errno e = realloc_page_array(tn, required_pages);
106 if (e != ERR_OK)
107 return e;
108
109 const uint8_t *in = buf;
110 while (size > 0) {
111 uint64_t page_idx = offset / PAGE_SIZE;
112 uint64_t page_offset = offset & TMPFS_PAGE_MASK;
113 uint64_t to_copy = PAGE_SIZE - page_offset;
114 if (to_copy > size)
115 to_copy = size;
116
117 if (!tn->pages[page_idx]) {
118 tn->pages[page_idx] = kmalloc_aligned(PAGE_SIZE, PAGE_SIZE);
119 if (!tn->pages[page_idx])
120 return ERR_NO_MEM;
121 memset(tn->pages[page_idx], 0, PAGE_SIZE);
122 }
123
124 memcpy((uint8_t *) tn->pages[page_idx] + page_offset, in, to_copy);
125
126 offset += to_copy;
127 in += to_copy;
128 size -= to_copy;
129 }
130
131 if (end > tn->size) {
132 tn->size = end;
133 node->size = end;
134 }
135
136 return ERR_OK;
137}
138
139static enum errno tmpfs_open(struct vfs_node *node, uint32_t flags) {
140 (void) node, (void) flags;
141 return ERR_NOT_IMPL; // no-op
142}
143
144static enum errno tmpfs_close(struct vfs_node *node) {
145 (void) node;
146 return ERR_NOT_IMPL; // no-op
147}
148
149static struct tmpfs_node *tmpfs_find_child(struct tmpfs_node *dir,
150 const char *name) {
151 for (uint64_t i = 0; i < dir->child_count; i++)
152 if (strcmp(str1: dir->children[i]->name, str2: name) == 0)
153 return dir->children[i];
154 return NULL;
155}
156
157static enum errno tmpfs_add_child(struct tmpfs_node *parent,
158 struct tmpfs_node *child) {
159 uint64_t needed_size = sizeof(void *) * (parent->child_count + 1);
160
161 parent->children = krealloc(parent->children, needed_size);
162
163 parent->children[parent->child_count++] = child;
164 child->parent = parent;
165 return ERR_OK;
166}
167
168static enum errno tmpfs_create_common(struct vfs_node *parent, const char *name,
169 mode_t mode, enum tmpfs_type type,
170 struct tmpfs_node **out) {
171 struct tmpfs_node *pt = parent->fs_node_data;
172 if (pt->type != TMPFS_DIR)
173 return ERR_NOT_DIR;
174
175 if (tmpfs_find_child(dir: pt, name))
176 return ERR_EXIST;
177
178 struct tmpfs_node *node = kmalloc(sizeof(*node), ALLOC_FLAGS_ZERO);
179 if (unlikely(!node))
180 return ERR_NO_MEM;
181
182 node->type = type;
183 node->name = strdup(str: name);
184 node->mode = mode;
185
186 if (type == TMPFS_FILE) {
187 node->pages = NULL;
188 node->num_pages = 0;
189 node->size = 0;
190 }
191
192 tmpfs_add_child(parent: pt, child: node);
193 if (out)
194 *out = node;
195 return ERR_OK;
196}
197
198static enum errno tmpfs_create(struct vfs_node *parent, const char *name,
199 mode_t mode) {
200 struct tmpfs_node *node;
201 enum tmpfs_type type = (mode & VFS_MODE_DIR) ? TMPFS_DIR : TMPFS_FILE;
202 return tmpfs_create_common(parent, name, mode, type, out: &node);
203}
204
205static enum errno tmpfs_mknod(struct vfs_node *parent, const char *name,
206 mode_t mode, uint32_t dev) {
207 (void) parent, (void) name, (void) mode, (void) dev;
208 return ERR_NOT_IMPL;
209}
210
211static enum errno tmpfs_symlink(struct vfs_node *parent, const char *target,
212 const char *link_name) {
213 struct tmpfs_node *link;
214 enum errno err =
215 tmpfs_create_common(parent, name: link_name, mode: 0777, type: TMPFS_SYMLINK, out: &link);
216
217 if (err != ERR_OK)
218 return err;
219
220 link->symlink_target = strdup(str: target);
221 return ERR_OK;
222}
223
224static enum errno tmpfs_unmount(struct vfs_mount *mountpoint) {
225 (void) mountpoint;
226 return ERR_NOT_IMPL;
227}
228
229static enum errno tmpfs_stat(struct vfs_node *node, struct vfs_stat *out) {
230 struct tmpfs_node *tn = node->fs_node_data;
231 out->mode = tn->mode;
232 out->size = tn->size;
233 // could add uid/gid/mtime/etc.
234 return ERR_OK;
235}
236
237static enum errno tmpfs_readdir(struct vfs_node *node, struct vfs_dirent *out,
238 uint64_t index) {
239 struct tmpfs_node *tn = node->fs_node_data;
240 if (tn->type != TMPFS_DIR)
241 return ERR_NOT_DIR;
242
243 if (index >= tn->child_count)
244 return ERR_NO_ENT;
245
246 struct tmpfs_node *child = tn->children[index];
247 strncpy(dest: out->name, src: child->name, n: sizeof(out->name));
248 out->mode = tmpfs_to_vfs_mode(mode: child->type);
249 return ERR_OK;
250}
251
252static enum errno tmpfs_mkdir(struct vfs_node *parent, const char *name,
253 mode_t mode) {
254 return tmpfs_create_common(parent, name, mode, type: TMPFS_DIR, NULL);
255}
256
257static enum errno tmpfs_rmdir(struct vfs_node *parent, const char *name) {
258 struct tmpfs_node *pt = parent->fs_node_data;
259 for (uint64_t i = 0; i < pt->child_count; i++) {
260 struct tmpfs_node *c = pt->children[i];
261 if (strcmp(str1: c->name, str2: name) == 0 && c->type == TMPFS_DIR) {
262 // remove
263 memmove(&pt->children[i], &pt->children[i + 1],
264 (pt->child_count - i - 1) * sizeof(void *));
265 pt->child_count--;
266 kfree(c->name);
267 kfree(c->children);
268 kfree(c);
269 return ERR_OK;
270 }
271 }
272 return ERR_NO_ENT;
273}
274
275static void tmpfs_free_node(struct tmpfs_node *tn) {
276 for (uint64_t i = 0; i < tn->num_pages; i++) {
277 if (tn->pages[i]) {
278 kfree_aligned(tn->pages[i]);
279 tn->pages[i] = NULL;
280 }
281 }
282 kfree(tn->pages);
283}
284
285static enum errno tmpfs_unlink(struct vfs_node *parent, const char *name) {
286 struct tmpfs_node *pt = parent->fs_node_data;
287 for (uint64_t i = 0; i < pt->child_count; i++) {
288 struct tmpfs_node *c = pt->children[i];
289 if (strcmp(str1: c->name, str2: name) == 0 && c->type != TMPFS_DIR) {
290 memmove(&pt->children[i], &pt->children[i + 1],
291 (pt->child_count - i - 1) * sizeof(void *));
292 pt->child_count--;
293 kfree(c->name);
294 tmpfs_free_node(tn: c);
295 kfree(c);
296 return ERR_OK;
297 }
298 }
299 return ERR_NO_ENT;
300}
301
302static enum errno tmpfs_rename(struct vfs_node *old_parent,
303 const char *old_name,
304 struct vfs_node *new_parent,
305 const char *new_name) {
306 struct tmpfs_node *old_pt = old_parent->fs_node_data;
307 struct tmpfs_node *new_pt = new_parent->fs_node_data;
308 struct tmpfs_node *node = tmpfs_find_child(dir: old_pt, name: old_name);
309 if (!node)
310 return ERR_NO_ENT;
311
312 for (uint64_t i = 0; i < old_pt->child_count; i++) {
313 if (old_pt->children[i] == node) {
314 memmove(&old_pt->children[i], &old_pt->children[i + 1],
315 (old_pt->child_count - i - 1) * sizeof(void *));
316 old_pt->child_count--;
317 break;
318 }
319 }
320
321 kfree(node->name);
322 node->name = strdup(str: new_name);
323 tmpfs_add_child(parent: new_pt, child: node);
324 return ERR_OK;
325}
326
327static enum errno tmpfs_truncate(struct vfs_node *node, uint64_t length) {
328 struct tmpfs_node *tn = node->fs_node_data;
329 if (tn->type != TMPFS_FILE)
330 return ERR_IS_DIR;
331
332 uint64_t old_size = tn->size;
333 uint64_t new_pages = (length + PAGE_SIZE - 1) / PAGE_SIZE;
334
335 if (new_pages < tn->num_pages) {
336 for (uint64_t i = new_pages; i < tn->num_pages; i++) {
337 if (tn->pages[i]) {
338 kfree_aligned(tn->pages[i]);
339 tn->pages[i] = NULL;
340 }
341 }
342 }
343
344 if (new_pages != tn->num_pages) {
345 void **new_array = krealloc(tn->pages, new_pages * sizeof(void *));
346 if (!new_array && new_pages > 0)
347 return ERR_NO_MEM;
348
349 if (new_pages > tn->num_pages)
350 memset(new_array + tn->num_pages, 0,
351 (new_pages - tn->num_pages) * sizeof(void *));
352
353 tn->pages = new_array;
354 tn->num_pages = new_pages;
355 }
356
357 if (length > old_size) {
358 uint64_t last_page_idx = old_size / PAGE_SIZE;
359 uint64_t last_offset = old_size & TMPFS_PAGE_MASK;
360
361 if (last_page_idx < new_pages) {
362 if (!tn->pages[last_page_idx]) {
363 tn->pages[last_page_idx] =
364 kmalloc_aligned(PAGE_SIZE, PAGE_SIZE);
365 if (!tn->pages[last_page_idx])
366 return ERR_NO_MEM;
367 memset(tn->pages[last_page_idx], 0, PAGE_SIZE);
368 }
369
370 uint64_t to_zero = PAGE_SIZE - last_offset;
371 if (length - old_size < to_zero)
372 to_zero = length - old_size;
373
374 memset((uint8_t *) tn->pages[last_page_idx] + last_offset, 0,
375 to_zero);
376 }
377 }
378
379 tn->size = length;
380 node->size = length;
381 return ERR_OK;
382}
383
384static enum errno tmpfs_readlink(struct vfs_node *node, char *buf,
385 uint64_t size) {
386 struct tmpfs_node *tn = node->fs_node_data;
387
388 if (tn->type != TMPFS_SYMLINK)
389 return ERR_IS_DIR;
390
391 strncpy(dest: buf, src: tn->symlink_target, n: size);
392 return ERR_OK;
393}
394
395static enum errno tmpfs_link(struct vfs_node *parent, struct vfs_node *target,
396 const char *link_name) {
397 (void) parent, (void) target, (void) link_name;
398 // tmpfs doesn't support hard links
399 return ERR_NOT_IMPL;
400}
401
402static enum errno tmpfs_chmod(struct vfs_node *node, mode_t mode) {
403 struct tmpfs_node *tn = node->fs_node_data;
404 tn->mode = mode;
405 node->mode = mode;
406 return ERR_OK;
407}
408
409static enum errno tmpfs_chown(struct vfs_node *node, uid_t uid, gid_t gid) {
410 struct tmpfs_node *n = node->fs_node_data;
411 node->uid = uid;
412 node->gid = gid;
413 n->uid = uid;
414 n->gid = gid;
415 return ERR_OK;
416}
417
418static enum errno tmpfs_utime(struct vfs_node *node, uint64_t atime,
419 uint64_t mtime) {
420 struct tmpfs_node *n = node->fs_node_data;
421 node->atime = atime;
422 node->mtime = mtime;
423 n->atime = atime;
424 n->mtime = mtime;
425 return ERR_OK;
426}
427
428static enum errno tmpfs_destroy(struct vfs_node *node) {
429 (void) node;
430 struct tmpfs_node *n = node->fs_node_data;
431
432 if (!n)
433 return ERR_INVAL;
434
435 if (n->children)
436 kfree(n->children);
437
438 tmpfs_free_node(tn: n);
439
440 if (n->symlink_target)
441 kfree(n->symlink_target);
442
443 kfree(n);
444
445 n->children = NULL;
446 n->symlink_target = NULL;
447 n = NULL;
448 return ERR_OK;
449}
450
451static enum errno tmpfs_finddir(struct vfs_node *node, const char *name,
452 struct vfs_dirent *out) {
453 struct tmpfs_node *tn = node->fs_node_data;
454
455 if (tn->type != TMPFS_DIR)
456 return ERR_NOT_DIR;
457
458 struct tmpfs_node *child = tmpfs_find_child(dir: tn, name);
459 if (!child)
460 return ERR_NO_ENT;
461
462 struct vfs_node *n = tmpfs_create_vfs_node(tnode: child);
463 struct vfs_dirent ent;
464 ent.node = n;
465 memcpy(ent.name, name, strlen(name));
466 ent.mode = n->mode;
467 ent.dirent_data = tn;
468
469 memcpy(out, &ent, sizeof(struct vfs_dirent));
470
471 return ERR_OK;
472}
473
474static const struct vfs_ops tmpfs_ops = {.read = tmpfs_read,
475 .write = tmpfs_write,
476 .open = tmpfs_open,
477 .close = tmpfs_close,
478 .create = tmpfs_create,
479 .mknod = tmpfs_mknod,
480 .symlink = tmpfs_symlink,
481 .mount = tmpfs_mount,
482 .unmount = tmpfs_unmount,
483 .stat = tmpfs_stat,
484 .readdir = tmpfs_readdir,
485 .mkdir = tmpfs_mkdir,
486 .rmdir = tmpfs_rmdir,
487 .unlink = tmpfs_unlink,
488 .rename = tmpfs_rename,
489 .truncate = tmpfs_truncate,
490 .readlink = tmpfs_readlink,
491 .link = tmpfs_link,
492 .chmod = tmpfs_chmod,
493 .chown = tmpfs_chown,
494 .utime = tmpfs_utime,
495 .destroy = tmpfs_destroy,
496 .finddir = tmpfs_finddir};
497
498struct vfs_node *tmpfs_create_vfs_node(struct tmpfs_node *tnode) {
499 struct vfs_node *vnode = kmalloc(sizeof(struct vfs_node), ALLOC_FLAGS_ZERO);
500 if (!vnode || !tnode)
501 return NULL;
502
503 vnode->mode = tnode->mode;
504 vnode->size = tnode->size;
505
506 vnode->fs_data = NULL; // could be fs pointer if needed
507 vnode->fs_node_data = tnode;
508 vnode->ops = &tmpfs_ops;
509 vnode->fs_type = FS_TMPFS;
510 return vnode;
511}
512