1 /*
2 * Copyright (c) 2023 Institute of Parallel And Distributed Systems (IPADS), Shanghai Jiao Tong University (SJTU)
3 * Licensed under the Mulan PSL v2.
4 * You can use this software according to the terms and conditions of the Mulan PSL v2.
5 * You may obtain a copy of Mulan PSL v2 at:
6 * http://license.coscl.org.cn/MulanPSL2
7 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
8 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
9 * PURPOSE.
10 * See the Mulan PSL v2 for more details.
11 */
12 #include "chcore/error.h"
13 #include "chcore/ipc.h"
14 #include "fcntl.h"
15 #include "fs_vnode.h"
16 #include "pthread.h"
17 #include "sys/stat.h"
18 #include "sys/mman.h"
19 #include <chcore/container/radix.h>
20 #include <fs_wrapper_defs.h>
21 #include <chcore/falloc.h>
22 #include "defs.h"
23 #include "namei.h"
24 #include <limits.h>
25 #include <stddef.h>
26 #include <string.h>
27 #include <stdint.h>
28
29 /*
30 * This file contains interfaces that fs_base will invoke
31 */
32
33 /* File operations */
34
35 /*
36 * POSIX says:
37 * The creat() function shall behave as if it is implemented as follows:
38 *
39 * int creat(const char *path, mode_t mode)
40 * {
41 * return open(path, O_WRONLY|O_CREAT|O_TRUNC, mode);
42 * }
43 *
44 * however, this should be done at the fs_base level,
45 * when dealing with creat() syscall
46 * we still implement a separate creat() function, and try to
47 * obey POSIX in tmpfs
48 */
tmpfs_creat(ipc_msg_t * ipc_msg,struct fs_request * fr)49 int tmpfs_creat(ipc_msg_t *ipc_msg, struct fs_request *fr)
50 {
51 int err;
52 struct nameidata nd;
53 struct dentry *dentry;
54
55 char *pathname = fr->creat.pathname;
56
57 err = path_openat(&nd, pathname, O_CREAT, 0, &dentry);
58 if (err) {
59 goto error;
60 }
61
62 /* dentry->inode must be a regular file at this point */
63 dentry->inode->f_ops->truncate(dentry->inode, 0);
64
65 error:
66 return err;
67 }
68
tmpfs_open(char * path,int o_flags,int mode,ino_t * vnode_id,off_t * vnode_size,int * vnode_type,void ** vnode_private)69 int tmpfs_open(char *path, int o_flags, int mode, ino_t *vnode_id,
70 off_t *vnode_size, int *vnode_type, void **vnode_private)
71 {
72 /*
73 * For monitoring memory usage in tmpfs
74 *
75 * The current naive implementation use open() to
76 * trigger memory usage collecting and show usage info.
77 * Details can be found in defs.h
78 */
79 if (!strcmp(path, "/tmpfs_mem_usage")) {
80 #if DEBUG_MEM_USAGE
81 tmpfs_get_mem_usage();
82 #endif
83 return -ENOENT; /* this should fail */
84 }
85
86 int err;
87 struct nameidata nd;
88 struct dentry *dentry;
89
90 err = path_openat(&nd, path, o_flags, 0, &dentry);
91
92 if (!err) {
93 struct inode *inode = dentry->inode;
94
95 *vnode_id = (ino_t)(uintptr_t)inode;
96 *vnode_size = inode->size;
97 *vnode_private = inode;
98
99 /* vnode type can not be symlink? */
100 switch (inode->type) {
101 case FS_REG:
102 *vnode_type = FS_NODE_REG;
103 break;
104 case FS_DIR:
105 *vnode_type = FS_NODE_DIR;
106 break;
107 }
108
109 inode->base_ops->open(inode);
110 }
111
112 return err;
113 }
114
tmpfs_close(void * operator,bool is_dir,bool do_close)115 int tmpfs_close(void *operator, bool is_dir /* not handled */, bool do_close)
116 {
117 struct inode *inode = (struct inode *)operator;
118 if (do_close) {
119 inode->base_ops->close(inode);
120 }
121
122 return 0;
123 }
124
tmpfs_read(void * operator,off_t offset,size_t size,char * buf)125 ssize_t tmpfs_read(void *operator, off_t offset, size_t size, char * buf)
126 {
127 struct inode *inode = (struct inode *)operator;
128 BUG_ON(inode->type != FS_REG);
129
130 return inode->f_ops->read(inode, buf, size, offset);
131 }
132
tmpfs_write(void * operator,off_t offset,size_t size,const char * buf)133 ssize_t tmpfs_write(void *operator, off_t offset, size_t size, const char * buf)
134 {
135 struct inode *inode = (struct inode *)operator;
136 BUG_ON(inode->type != FS_REG);
137 return inode->f_ops->write(inode, buf, size, offset);
138 }
139
tmpfs_ftruncate(void * operator,off_t len)140 int tmpfs_ftruncate(void *operator, off_t len)
141 {
142 struct inode *inode = (struct inode *)operator;
143
144 if (inode->type == FS_DIR)
145 return -EISDIR;
146
147 if (inode->type != FS_REG)
148 return -EINVAL;
149
150 return inode->f_ops->truncate(inode, len);
151 }
152
tmpfs_fallocate(ipc_msg_t * ipc_msg,struct fs_request * fr)153 int tmpfs_fallocate(ipc_msg_t *ipc_msg, struct fs_request *fr)
154 {
155 int fd = fr->fallocate.fd;
156 mode_t mode = fr->fallocate.mode;
157 off_t offset = fr->fallocate.offset;
158 off_t len = fr->fallocate.len;
159 int keep_size, ret;
160 struct inode *inode;
161 struct fs_vnode *vnode;
162
163 vnode = server_entrys[fd]->vnode;
164 inode = (struct inode *)vnode->private;
165
166 if (offset < 0 || len <= 0)
167 return -EINVAL;
168
169 if (inode->type != FS_REG) {
170 return -EBADF;
171 }
172
173 pthread_rwlock_wrlock(&vnode->rwlock);
174
175 /* return error if mode is not supported */
176 if (mode
177 & ~(FALLOC_FL_KEEP_SIZE | FALLOC_FL_PUNCH_HOLE
178 | FALLOC_FL_COLLAPSE_RANGE | FALLOC_FL_ZERO_RANGE
179 | FALLOC_FL_INSERT_RANGE)) {
180 ret = -EOPNOTSUPP;
181 goto out;
182 }
183
184 if (mode & FALLOC_FL_PUNCH_HOLE) {
185 ret = inode->f_ops->punch_hole(inode, offset, len);
186 } else if (mode & FALLOC_FL_COLLAPSE_RANGE) {
187 ret = inode->f_ops->collapse_range(inode, offset, len);
188 } else if (mode & FALLOC_FL_ZERO_RANGE) {
189 ret = inode->f_ops->zero_range(inode, offset, len, mode);
190 } else if (mode & FALLOC_FL_INSERT_RANGE) {
191 ret = inode->f_ops->insert_range(inode, offset, len);
192 } else {
193 keep_size = mode & FALLOC_FL_KEEP_SIZE ? 1 : 0;
194
195 ret = inode->f_ops->allocate(inode, offset, len, keep_size);
196 }
197
198 /**
199 * FALLOC_FL_KEEP_SIZE is handled in handlers above, i.e., if size
200 * should be kept, the inode->size won't change, vice versa.
201 */
202 vnode->size = inode->size;
203 out:
204 pthread_rwlock_unlock(&vnode->rwlock);
205 return ret;
206 }
207
208 /* Directory operations */
tmpfs_mkdir(const char * path,mode_t mode)209 int tmpfs_mkdir(const char *path, mode_t mode)
210 {
211 int err;
212 struct nameidata nd;
213 struct dentry *d_parent, *d_new_dir;
214 struct inode *i_parent;
215
216 err = path_parentat(&nd, path, 0, &d_parent);
217 if (err) {
218 goto error;
219 }
220 /* only slashes */
221 if (nd.last.str == NULL) {
222 return -EBUSY;
223 }
224
225 /* creating the new dir */
226 i_parent = d_parent->inode;
227 d_new_dir = i_parent->d_ops->dirlookup(i_parent, nd.last.str, nd.last.len);
228 if (d_new_dir) {
229 err = -EEXIST;
230 goto error;
231 }
232
233 d_new_dir = i_parent->d_ops->alloc_dentry();
234 if (CHCORE_IS_ERR(d_new_dir)) {
235 err = CHCORE_PTR_ERR(d_new_dir);
236 goto error;
237 }
238
239 err = i_parent->d_ops->add_dentry(
240 i_parent, d_new_dir, nd.last.str, nd.last.len);
241 if (err) {
242 goto free_dent;
243 }
244
245 err = i_parent->d_ops->mkdir(i_parent, d_new_dir, mode);
246 if (err) {
247 goto remove_dent;
248 }
249
250 free_string(&nd.last);
251 return 0;
252
253 remove_dent:
254 i_parent->d_ops->remove_dentry(i_parent, d_new_dir);
255 free_dent:
256 i_parent->d_ops->free_dentry(d_new_dir);
257 error:
258 free_string(&nd.last);
259 return err;
260 }
261
tmpfs_rmdir(const char * path,int flags)262 int tmpfs_rmdir(const char *path, int flags)
263 {
264 int err;
265 struct nameidata nd;
266 struct dentry *d_parent, *d_to_remove;
267 struct inode *i_parent;
268
269 err = path_parentat(&nd, path, 0, &d_parent);
270 if (err) {
271 goto error;
272 }
273 /* only slashes */
274 if (nd.last.str == NULL) {
275 return -EBUSY;
276 }
277 i_parent = d_parent->inode;
278
279 d_to_remove =
280 i_parent->d_ops->dirlookup(i_parent, nd.last.str, nd.last.len);
281 if (!d_to_remove) {
282 err = -ENOENT;
283 goto error;
284 }
285
286 if (d_to_remove->inode->type != FS_DIR) {
287 err = -ENOTDIR;
288 goto error;
289 }
290
291 err = i_parent->d_ops->rmdir(i_parent, d_to_remove);
292
293 error:
294 free_string(&nd.last);
295 return err;
296 }
297
tmpfs_getdents(ipc_msg_t * ipc_msg,struct fs_request * fr)298 int tmpfs_getdents(ipc_msg_t *ipc_msg, struct fs_request *fr)
299 {
300 int fd = fr->getdents64.fd;
301 unsigned int count = fr->getdents64.count;
302 char *buff = ipc_get_msg_data(ipc_msg);
303
304 struct inode *inode = (struct inode *)server_entrys[fd]->vnode->private;
305 int ret = 0, read_bytes;
306 if (inode) {
307 if (inode->type == FS_DIR) {
308 ret = inode->d_ops->scan(inode,
309 server_entrys[fd]->offset,
310 buff,
311 buff + count,
312 &read_bytes);
313
314 server_entrys[fd]->offset += ret;
315 ret = read_bytes;
316 } else {
317 ret = -ENOTDIR;
318 }
319 } else {
320 ret = -ENOENT;
321 }
322 return ret;
323 }
324
325 /* Symbolic link operations */
326
tmpfs_symlinkat(ipc_msg_t * ipc_msg,struct fs_request * fr)327 int tmpfs_symlinkat(ipc_msg_t *ipc_msg, struct fs_request *fr)
328 {
329 int err;
330 struct nameidata nd;
331 struct dentry *d_parent, *d_symlink;
332 struct inode *i_parent, *i_symlink;
333 char *target, *path;
334
335 target = fr->symlinkat.target;
336 path = fr->symlinkat.linkpath;
337
338 err = path_parentat(&nd, path, 0, &d_parent);
339 if (err) {
340 goto error;
341 }
342 /* only slashes */
343 if (nd.last.str == NULL) {
344 return -EBUSY;
345 }
346 i_parent = d_parent->inode;
347
348 d_symlink = i_parent->d_ops->dirlookup(i_parent, nd.last.str, nd.last.len);
349 if (d_symlink) {
350 err = -EEXIST;
351 goto error;
352 }
353
354 /* the path contains trailing slashes, return ENOTDIR per POSIX */
355 if (nd.last.str[nd.last.len]) {
356 err = -ENOTDIR;
357 goto error;
358 }
359
360 d_symlink = i_parent->d_ops->alloc_dentry();
361 if (CHCORE_IS_ERR(d_symlink)) {
362 err = CHCORE_PTR_ERR(d_symlink);
363 goto error;
364 }
365
366 err = i_parent->d_ops->add_dentry(
367 i_parent, d_symlink, nd.last.str, nd.last.len);
368 if (err) {
369 goto free_dent;
370 }
371
372 err = i_parent->d_ops->mknod(i_parent, d_symlink, 0, FS_SYM);
373 if (err) {
374 goto remove_dent;
375 }
376 i_symlink = d_symlink->inode;
377
378 ssize_t len =
379 i_symlink->sym_ops->write_link(i_symlink, target, strlen(target));
380 if (len != strlen(target)) {
381 err = (int)len;
382 goto free_node;
383 }
384
385 free_string(&nd.last);
386 return 0;
387
388 free_node:
389 i_symlink->base_ops->free(i_symlink);
390 remove_dent:
391 i_parent->d_ops->remove_dentry(i_parent, d_symlink);
392 free_dent:
393 i_parent->d_ops->free_dentry(d_symlink);
394 error:
395 free_string(&nd.last);
396 return err;
397 }
398
tmpfs_readlinkat(ipc_msg_t * ipc_msg,struct fs_request * fr)399 ssize_t tmpfs_readlinkat(ipc_msg_t *ipc_msg, struct fs_request *fr)
400 {
401 int err;
402 struct nameidata nd;
403 struct dentry *d_symlink;
404 struct inode *i_symlink;
405
406 const char *path;
407 char *buff;
408 ssize_t len;
409
410 path = fr->readlinkat.pathname;
411 buff = fr->readlinkat.buf;
412 len = (ssize_t)fr->readlinkat.bufsiz;
413
414 err = path_lookupat(&nd, path, 0, &d_symlink);
415 if (err) {
416 return err;
417 }
418
419 i_symlink = d_symlink->inode;
420 if (i_symlink->type != FS_SYM) {
421 return -EINVAL;
422 }
423 len = i_symlink->sym_ops->read_link(i_symlink, buff, len);
424
425 return len;
426 }
427
428 /* File and directory operations */
tmpfs_unlink(const char * path,int flags)429 int tmpfs_unlink(const char *path, int flags)
430 {
431 int err;
432 struct nameidata nd;
433 struct dentry *d_parent, *d_unlink;
434 struct inode *i_parent;
435
436 if (flags & (~AT_SYMLINK_NOFOLLOW)) {
437 return -EINVAL;
438 }
439
440 err = path_parentat(&nd, path, 0, &d_parent);
441 if (err) {
442 goto error;
443 }
444 /* only slashes */
445 if (nd.last.str == NULL) {
446 return -EBUSY;
447 }
448
449 i_parent = d_parent->inode;
450
451 d_unlink = i_parent->d_ops->dirlookup(i_parent, nd.last.str, nd.last.len);
452 if (!d_unlink) {
453 err = -ENOENT;
454 goto error;
455 }
456
457 if (d_unlink->inode->type == FS_DIR) {
458 err = -EISDIR;
459 goto error;
460 }
461
462 i_parent->d_ops->unlink(i_parent, d_unlink);
463
464 error:
465 free_string(&nd.last);
466 return err;
467 }
468
469 /*
470 * Some facts is ensured when tmpfs_rename is called by fs_base:
471 * 1. the two paths is not ended with dots
472 * 2. oldpath actually exists
473 * 3. oldpath is not an ancestor of new
474 * 4. newpath has a valid, existed prefix and it's a dir
475 * 5. if newpath exists, the two paths' types match
476 * 6. if newpath exists, the newpath is removed.
477 *
478 * NOTE: dependency on fs_base
479 * The implementation relies on these facts so it can be simple,
480 * but if the implementation of fs_base changed, the implementation
481 * here may also need modification
482 */
tmpfs_rename(const char * oldpath,const char * newpath)483 int tmpfs_rename(const char *oldpath, const char *newpath)
484 {
485 int err;
486 struct nameidata nd;
487 struct dentry *d_old_parent, *d_new_parent, *d_old, *d_new;
488 struct inode *i_old_parent, *i_new_parent;
489
490 err = path_parentat(&nd, oldpath, 0, &d_old_parent);
491 if (err) {
492 error("oldpath should exist!\n");
493 goto error;
494 }
495 /* only slashes */
496 if (nd.last.str == NULL) {
497 return -EBUSY;
498 }
499
500 i_old_parent = d_old_parent->inode;
501
502 d_old =
503 i_old_parent->d_ops->dirlookup(i_old_parent, nd.last.str, nd.last.len);
504
505 free_string(&nd.last);
506
507 err = path_parentat(&nd, newpath, 0, &d_new_parent);
508 if (err) {
509 error("newpath prefix should exist!\n");
510 goto error;
511 }
512 /* only slashes */
513 if (nd.last.str == NULL) {
514 return -EBUSY;
515 }
516
517 i_new_parent = d_new_parent->inode;
518
519 d_new = i_new_parent->d_ops->alloc_dentry();
520 if (CHCORE_IS_ERR(d_new)) {
521 err = CHCORE_PTR_ERR(d_new);
522 goto error;
523 }
524
525 err = i_new_parent->d_ops->add_dentry(
526 i_new_parent, d_new, nd.last.str, nd.last.len);
527 if (err) {
528 goto free_dent;
529 }
530
531 i_old_parent->d_ops->rename(i_old_parent, d_old, i_new_parent, d_new);
532
533 free_string(&nd.last);
534 return 0;
535
536 free_dent:
537 i_new_parent->d_ops->free_dentry(d_new);
538 error:
539 free_string(&nd.last);
540 return err;
541 }
542
543 /* File metadata operations */
544
tmpfs_fstatat(const char * path,struct stat * st,int flags)545 int tmpfs_fstatat(const char *path, struct stat *st, int flags)
546 {
547 int err;
548 struct nameidata nd;
549 struct dentry *dentry;
550 struct inode *inode;
551
552 /*
553 * POSIX says we should follow the trailing symlink,
554 * except AT_SYMLINK_NOFOLLOW is set in @flags
555 */
556 err = path_lookupat(
557 &nd, path, flags & AT_SYMLINK_NOFOLLOW ? 0 : ND_FOLLOW, &dentry);
558 if (err) {
559 return err;
560 }
561
562 inode = dentry->inode;
563 inode->base_ops->stat(inode, st);
564 return 0;
565 }
566
tmpfs_fstat(ipc_msg_t * ipc_msg,struct fs_request * fr)567 int tmpfs_fstat(ipc_msg_t *ipc_msg, struct fs_request *fr)
568 {
569 struct inode *inode;
570
571 int fd = fr->stat.fd;
572 struct stat *stat = (struct stat *)ipc_get_msg_data(ipc_msg);
573
574 BUG_ON(!server_entrys[fd]);
575 inode = (struct inode *)server_entrys[fd]->vnode->private;
576
577 inode->base_ops->stat(inode, stat);
578
579 return 0;
580 }
581
tmpfs_faccessat(ipc_msg_t * ipc_msg,struct fs_request * fr)582 int tmpfs_faccessat(ipc_msg_t *ipc_msg, struct fs_request *fr)
583 {
584 int err;
585 struct nameidata nd;
586 struct dentry *dentry;
587
588 const char *path = fr->faccessat.pathname;
589 /* mode_t mode = fr->faccessat.mode; // again, mode is not handled */
590 int flags = fr->faccessat.flags;
591
592 if (flags & (~AT_EACCESS)) {
593 return -EINVAL;
594 }
595
596 /* look the path up does all the job */
597 err = path_lookupat(&nd, path, 0, &dentry);
598
599 return err;
600 }
601
602 /* File system metadata operations */
603
tmpfs_statfs(ipc_msg_t * ipc_msg,struct fs_request * fr)604 int tmpfs_statfs(ipc_msg_t *ipc_msg, struct fs_request *fr)
605 {
606 int err;
607 struct nameidata nd;
608 struct dentry *dentry;
609
610 /*
611 * dirfd is actually guaranteed to be root dir,
612 * so we are not using it in tmpfs
613 */
614 const char *path = fr->stat.pathname;
615 struct statfs *stat = (struct statfs *)ipc_get_msg_data(ipc_msg);
616
617 err = path_lookupat(&nd, path, 0, &dentry);
618 if (!err) {
619 tmpfs_fs_stat(stat);
620 }
621
622 return err;
623 }
624
tmpfs_fstatfs(ipc_msg_t * ipc_msg,struct fs_request * fr)625 int tmpfs_fstatfs(ipc_msg_t *ipc_msg, struct fs_request *fr)
626 {
627 int fd = fr->stat.fd;
628 struct statfs *stat = (struct statfs *)ipc_get_msg_data(ipc_msg);
629
630 BUG_ON(!server_entrys[fd]);
631
632 tmpfs_fs_stat(stat);
633
634 return 0;
635 }
636
637 /* Additional functionalities */
tmpfs_fcntl(void * operator,int fd,int fcntl_cmd,int fcntl_arg)638 int tmpfs_fcntl(void *operator, int fd, int fcntl_cmd, int fcntl_arg)
639 {
640 struct server_entry *entry;
641 int ret = 0;
642
643 if ((entry = server_entrys[fd]) == NULL)
644 return -EBADF;
645
646 switch (fcntl_cmd) {
647 case F_GETFL:
648 ret = entry->flags;
649 break;
650 case F_SETFL: {
651 // file access mode and the file creation flags
652 // should be ignored, per POSIX
653 int effective_bits =
654 (~O_ACCMODE & ~O_CLOEXEC & ~O_CREAT & ~O_DIRECTORY & ~O_EXCL
655 & ~O_NOCTTY & ~O_NOFOLLOW & ~O_TRUNC & ~O_TTY_INIT);
656
657 entry->flags = (fcntl_arg & effective_bits)
658 | (entry->flags & ~effective_bits);
659 break;
660 }
661 case F_DUPFD:
662 break;
663
664 case F_GETOWN:
665 /* Fall through */
666 case F_SETOWN:
667 case F_GETLK:
668 case F_SETLK:
669 case F_SETLKW:
670 default:
671 error("unsupported fcntl cmd\n");
672 ret = -1;
673 break;
674 }
675
676 return ret;
677 }
678
679 #ifdef CHCORE_ENABLE_FMAP
tmpfs_get_page_addr(void * operator,off_t offset)680 vaddr_t tmpfs_get_page_addr(void *operator, off_t offset)
681 {
682 struct inode *inode;
683 void *page;
684 off_t page_no;
685
686 inode = (struct inode *)operator;
687 page_no = offset / PAGE_SIZE;
688 page = radix_get(&inode->data, page_no);
689
690 /* the case of truncated to a larger size but not yet allocated */
691 if (!page && offset < inode->size) {
692 page = mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
693 if (!page) {
694 return -ENOMEM;
695 }
696 #if DEBUG_MEM_USAGE
697 tmpfs_record_mem_usage(page, PAGE_SIZE, DATA_PAGE);
698 #endif
699
700 /* set page to 0 and cause page to be actually mapped */
701 memset(page, 0, PAGE_SIZE);
702 radix_add(&inode->data, page_no, page);
703 }
704
705 return (vaddr_t)page;
706 }
707 #endif
708
709 struct fs_server_ops server_ops = {
710 /* Unimplemented */
711 .mount = default_server_operation,
712 .umount = default_server_operation,
713
714 /* File Operations */
715 .creat = tmpfs_creat,
716 .open = tmpfs_open,
717 .close = tmpfs_close,
718 .read = tmpfs_read,
719 .write = tmpfs_write,
720 .ftruncate = tmpfs_ftruncate,
721 .fallocate = tmpfs_fallocate,
722
723 /* Directory Operations */
724 .mkdir = tmpfs_mkdir,
725 .rmdir = tmpfs_rmdir,
726 .getdents64 = tmpfs_getdents,
727
728 /* Symbolic Link Operations */
729 .symlinkat = tmpfs_symlinkat,
730 .readlinkat = tmpfs_readlinkat,
731
732 /* File and Directory Operations */
733 .unlink = tmpfs_unlink,
734 .rename = tmpfs_rename,
735
736 /* File Metadata Operations */
737 .fstatat = tmpfs_fstatat,
738 .fstat = tmpfs_fstat,
739 .faccessat = tmpfs_faccessat,
740
741 /* File System Metadata Operations */
742 .statfs = tmpfs_statfs,
743 .fstatfs = tmpfs_fstatfs,
744
745 /* Additional Functionalities */
746 .fcntl = tmpfs_fcntl,
747 #ifdef CHCORE_ENABLE_FMAP
748 .fmap_get_page_addr = tmpfs_get_page_addr,
749 #endif
750 };
751