• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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