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